diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml new file mode 100644 index 0000000..d4cb39b --- /dev/null +++ b/.github/workflows/develop.yml @@ -0,0 +1,40 @@ +name: .NET Core + +on: + push: + branches: [ develop ] + +jobs: + develop: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'true' + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + + - name: Add GitHub Nuget Source + run: dotnet nuget add source https://nuget.pkg.github.com/osmsharp/index.json -n osmsharp -u xivk -p ${{secrets.PACKAGES_SECRET }} --store-password-in-clear-text + + - name: Restore packages. + run: dotnet restore + - name: Build all projects. + run: dotnet build --configuration Release --no-restore + - name: Unittests. + run: dotnet test + working-directory: ./test/OsmSharp.IO.Binary.Test/ + - name: Functional tests. + run: dotnet run -c release + working-directory: ./test/OsmSharp.IO.Binary.Test.Functional/ + - name: Nuget Pack + run: dotnet pack -c release + working-directory: ./src/OsmSharp.IO.Binary/ + - name: Nuget push + run: dotnet nuget push **/*.nupkg --skip-duplicate -k ${{ secrets.GITHUB_TOKEN }} -s https://nuget.pkg.github.com/osmsharp/index.json + working-directory: ./src/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 38d2362..6505d84 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ TestResults*/ *.vspx *.patch *.osm.pbf -*.osm.bin TestResult.xml .vs*/ .idea*/ @@ -50,9 +49,13 @@ src/OsmSharp.IO.Binary.CLI/**/*.osm src/OsmSharp.IO.Binary.CLI/**/*.db src/OsmSharp.IO.Binary.CLI/**/*.hash src/OsmSharp.IO.Binary.CLI/**/*.osc +test/OsmSharp.IO.Binary.Test.Functional/**/*.osm +test/OsmSharp.IO.Binary.Test.Functional/**/*.osm.bin +test/OsmSharp.IO.Binary.Test.Functional/**/*.osm.pbf .idea*/ **/logs/* -**/*.bin.zip \ No newline at end of file +**/*.bin.zip +test*.osm \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index c7b4a4a..c4ec9be 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ // The MIT License (MIT) -// Copyright (c) 2017 Ben Abelshausen +// Copyright (c) 2021 Ben Abelshausen // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c231e7d..7837779 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An IO module on top of OsmSharp that reads/writes OSM data in a **custom binary We built this because it more efficient compared to OSM-XML and has some advantages over OSM-PBF. This can be used to: -- Read/write indivdual objects. +- Read/write individual objects. - Read/write objects with negative ids - Read/write objects missing data (even missing ids, versions, etc.). - Can be streamed. diff --git a/src/OsmSharp.IO.Binary.CLI/OsmSharp.IO.Binary.CLI.csproj b/src/OsmSharp.IO.Binary.CLI/OsmSharp.IO.Binary.CLI.csproj index a61b52e..c268e6a 100644 --- a/src/OsmSharp.IO.Binary.CLI/OsmSharp.IO.Binary.CLI.csproj +++ b/src/OsmSharp.IO.Binary.CLI/OsmSharp.IO.Binary.CLI.csproj @@ -1,7 +1,7 @@  Exe - netcoreapp3.1 + net5.0 diff --git a/src/OsmSharp.IO.Binary/BinarySerializer.cs b/src/OsmSharp.IO.Binary/BinarySerializer.cs index d3411bb..59e3291 100644 --- a/src/OsmSharp.IO.Binary/BinarySerializer.cs +++ b/src/OsmSharp.IO.Binary/BinarySerializer.cs @@ -37,7 +37,7 @@ public static class BinarySerializer /// /// Appends the header byte(s). /// - private static void AppendHeader(this Stream stream, OsmGeo osmGeo) + public static void AppendHeader(this Stream stream, OsmGeo osmGeo) { // build header containing type and nullable flags. byte header = 1; // a node. diff --git a/src/OsmSharp.IO.Binary/OsmSharp.IO.Binary.csproj b/src/OsmSharp.IO.Binary/OsmSharp.IO.Binary.csproj index f1055a4..45a9bcb 100644 --- a/src/OsmSharp.IO.Binary/OsmSharp.IO.Binary.csproj +++ b/src/OsmSharp.IO.Binary/OsmSharp.IO.Binary.csproj @@ -3,7 +3,7 @@ netstandard2.0 OsmSharp.IO.Binary OsmSharp.IO.Binary - 0.2.8-alpha + 0.3.1-pre001 OsmSharp.IO.Binary Ben Abelshausen A binary IO package for OsmSharp. @@ -15,6 +15,6 @@ default - + \ No newline at end of file diff --git a/src/OsmSharp.IO.Binary/v0_1/BinaryOsmStreamSource.cs b/src/OsmSharp.IO.Binary/v0_1/BinaryOsmStreamSource.cs new file mode 100644 index 0000000..0dff2bb --- /dev/null +++ b/src/OsmSharp.IO.Binary/v0_1/BinaryOsmStreamSource.cs @@ -0,0 +1,143 @@ +using System; +using System.IO; +using OsmSharp.Streams; + +namespace OsmSharp.IO.Binary.v0_1 +{ + + /// + /// A stream source that just reads objects in binary format. + /// + public class BinaryOsmStreamSource : OsmStreamSource + { + private readonly Stream _stream; + private readonly byte[] _buffer; + private readonly long? _initialPosition; + + /// + /// Creates a new binary stream source. + /// + public BinaryOsmStreamSource(Stream stream) + { + _stream = stream; + if (_stream.CanSeek) + { + _initialPosition = _stream.Position; + } + _buffer = new byte[1024]; + } + + /// + /// Returns true if this source can be reset. + /// + public override bool CanReset => _stream.CanSeek; + + /// + /// Returns the current object. + /// + /// + public override OsmGeo Current() + { + return _current; + } + + private OsmGeo _current; + private long? _firstWayPosition; + private long? _firstRelationPosition; + + /// + /// Move to the next object in this stream source. + /// + public override bool MoveNext(bool ignoreNodes, bool ignoreWays, bool ignoreRelations) + { + if (_stream.CanSeek) + { + if (_stream.Length == _stream.Position + 1) + { + return false; + } + + // move to first way/relation position if they are known and nodes and or ways are to be skipped. + if (_firstWayPosition != null && ignoreNodes && !ignoreWays) + { + // if nodes have to be ignored, there was already a first pass and ways are not to be ignored jump to the first way. + if (_stream.Position <= _firstWayPosition) + { + // only just to the first way if that hasn't happened yet. + _stream.Seek(_firstWayPosition.Value, SeekOrigin.Begin); + } + } + + if (_firstRelationPosition != null && ignoreNodes && ignoreWays && !ignoreRelations) + { + // if nodes and ways have to be ignored, there was already a first pass and ways are not be ignored jump to the first relation. + if (_stream.Position < _firstRelationPosition) + { + // only just to the first relation if that hasn't happened yet. + _stream.Seek(_firstRelationPosition.Value, SeekOrigin.Begin); + } + } + } + + long? positionBefore = null; + if (_stream.CanSeek) positionBefore = _stream.Position; + var osmGeo = this.DoMoveNext(); + while (osmGeo != null) + { + switch (osmGeo.Type) + { + case OsmGeoType.Node: + if (!ignoreNodes) + { + _current = osmGeo; + return true; + } + + break; + case OsmGeoType.Way: + if (_firstWayPosition == null && positionBefore != null) _firstWayPosition = positionBefore; + if (!ignoreWays) + { + _current = osmGeo; + return true; + } + + break; + case OsmGeoType.Relation: + if (_firstRelationPosition == null && positionBefore != null) + _firstRelationPosition = positionBefore; + if (!ignoreRelations) + { + _current = osmGeo; + return true; + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + + osmGeo = this.DoMoveNext(); + } + + return false; + } + + private OsmGeo DoMoveNext() + { + return BinarySerializer.ReadOsmGeo(_stream, _buffer); + } + + /// + /// Resets this stream. + /// + public override void Reset() + { + if (_initialPosition == null) throw new NotSupportedException( + $"Cannot reset this stream, source stream is not seekable, check {nameof(this.CanReset)} before calling {nameof(this.Reset)}"); + + _current = null; + _stream.Seek(_initialPosition.Value, SeekOrigin.Begin); + } + } +} \ No newline at end of file diff --git a/src/OsmSharp.IO.Binary/v0_1/BinaryOsmStreamTarget.cs b/src/OsmSharp.IO.Binary/v0_1/BinaryOsmStreamTarget.cs new file mode 100644 index 0000000..9510b22 --- /dev/null +++ b/src/OsmSharp.IO.Binary/v0_1/BinaryOsmStreamTarget.cs @@ -0,0 +1,55 @@ +using System.IO; + +namespace OsmSharp.IO.Binary.v0_1 +{ + /// + /// A stream target that just writes objects in binary format. + /// + public class BinaryOsmStreamTarget : OsmSharp.Streams.OsmStreamTarget + { + private readonly Stream _stream; + + /// + /// Creates a new stream target. + /// + public BinaryOsmStreamTarget(Stream stream) + { + _stream = stream; + } + + /// + /// Adds a node. + /// + /// + public override void AddNode(Node node) + { + _stream.Append(node); + } + + /// + /// Adds a relation. + /// + /// + public override void AddRelation(Relation relation) + { + _stream.Append(relation); + } + + /// + /// Adds a way. + /// + /// + public override void AddWay(Way way) + { + _stream.Append(way); + } + + /// + /// Initializes this target. + /// + public override void Initialize() + { + + } + } +} \ No newline at end of file diff --git a/src/OsmSharp.IO.Binary/v0_1/BinarySerializer.cs b/src/OsmSharp.IO.Binary/v0_1/BinarySerializer.cs new file mode 100644 index 0000000..532ebc4 --- /dev/null +++ b/src/OsmSharp.IO.Binary/v0_1/BinarySerializer.cs @@ -0,0 +1,483 @@ +using System; +using System.IO; +using OsmSharp.Tags; + +namespace OsmSharp.IO.Binary.v0_1 +{ + /// + /// Contains all binary formatting code. + /// + public static class BinarySerializer + { + private static System.Text.Encoder _encoder = (new System.Text.UnicodeEncoding()).GetEncoder(); + + /// + /// Appends the header byte(s). + /// + public static int AppendHeader(this Stream stream, OsmGeo osmGeo) + { + // build header containing type and nullable flags. + byte header = 1; // a node. + if(osmGeo.Type == OsmGeoType.Way) + { + header = 2; + } + else if(osmGeo.Type == OsmGeoType.Relation) + { + header = 3; + } + if (!osmGeo.Id.HasValue) { header = (byte)(header | 4); } + if (!osmGeo.ChangeSetId.HasValue) { header = (byte)(header | 8); } + if (!osmGeo.TimeStamp.HasValue) { header = (byte)(header | 16); } + if (!osmGeo.UserId.HasValue) { header = (byte)(header | 32); } + if (!osmGeo.Version.HasValue) { header = (byte)(header | 64); } + if (!osmGeo.Visible.HasValue) { header = (byte)(header | 128); } + stream.WriteByte(header); + + return 1; + } + + /// + /// Writes the given node starting at the stream's current position. + /// + public static int Append(this Stream stream, Node node) + { + if (node == null) { throw new ArgumentNullException(nameof(node)); } + + // appends the header. + var size = stream.AppendHeader(node); + + // write osm geo data. + size += stream.AppendOsmGeo(node); + + // write lat/lon with nullable flags. + byte header = 0; + if (!node.Latitude.HasValue) { header = (byte)(header | 1); } + if (!node.Longitude.HasValue) { header = (byte)(header | 2); } + size += 1; + stream.WriteByte(header); + if (node.Latitude.HasValue) { size += stream.Write(node.Latitude.Value); } + if (node.Longitude.HasValue) { size += stream.Write(node.Longitude.Value); } + + return size; + } + + /// + /// Writes the given way starting at the stream's current position. + /// + public static int Append(this Stream stream, Way way) + { + if (way == null) { throw new ArgumentNullException(nameof(way)); } + + // appends the header. + var size = stream.AppendHeader(way); + + // write data. + size += stream.AppendOsmGeo(way); + + if (way.Nodes == null || + way.Nodes.Length == 0) + { + size += stream.Write(0); + } + else + { + size += stream.Write(way.Nodes.Length); + for (var i = 0; i < way.Nodes.Length; i++) + { + size += stream.Write(way.Nodes[i]); + } + } + + return size; + } + + /// + /// Writes the given relation starting at the stream's current position. + /// + public static int Append(this Stream stream, Relation relation) + { + if (relation == null) { throw new ArgumentNullException(nameof(relation)); } + + // appends the header. + var size = stream.AppendHeader(relation); + + // write data. + size += stream.AppendOsmGeo(relation); + + if (relation.Members == null || + relation.Members.Length == 0) + { + size += stream.Write(0); + } + else + { + size += stream.Write(relation.Members.Length); + for (var i = 0; i < relation.Members.Length; i++) + { + size += stream.Write(relation.Members[i].Id); + size += stream.WriteWithSize(relation.Members[i].Role); + switch (relation.Members[i].Type) + { + case OsmGeoType.Node: + stream.WriteByte((byte)1); + break; + case OsmGeoType.Way: + stream.WriteByte((byte)2); + break; + case OsmGeoType.Relation: + stream.WriteByte((byte)3); + break; + } + size += 1; + } + } + + return size; + } + + private static int AppendOsmGeo(this Stream stream, OsmGeo osmGeo) + { + var size = 0; + + if (osmGeo.Id.HasValue) { size += stream.Write(osmGeo.Id.Value); } + if (osmGeo.ChangeSetId.HasValue) { size += stream.Write(osmGeo.ChangeSetId.Value); } + if (osmGeo.TimeStamp.HasValue) { size += stream.Write(osmGeo.TimeStamp.Value); } + if (osmGeo.UserId.HasValue) { size += stream.Write(osmGeo.UserId.Value); } + size += stream.WriteWithSize(osmGeo.UserName); + if (osmGeo.Version.HasValue) { size += stream.Write((int)osmGeo.Version.Value); } + if (osmGeo.Visible.HasValue) { size += stream.Write(osmGeo.Visible.Value); } + + if (osmGeo.Tags == null || + osmGeo.Tags.Count == 0) + { + size += stream.Write(0); + } + else + { + size += stream.Write(osmGeo.Tags.Count); + foreach (var t in osmGeo.Tags) + { + size += stream.WriteWithSize(t.Key); + size += stream.WriteWithSize(t.Value); + } + } + + return size; + } + + /// + /// Reads the header, returns the type, and outputs the flags. + /// + public static OsmGeoType? ReadOsmGeoHeader(this Stream stream, out bool hasId, out bool hasChangesetId, out bool hasTimestamp, + out bool hasUserId, out bool hasVersion, out bool hasVisible) + { + var header = stream.ReadByte(); + if (header == -1) + { + hasId = false; + hasChangesetId = false; + hasTimestamp = false; + hasUserId = false; + hasVersion = false; + hasVisible = false; + return null; + } + + hasId = (header & 4) == 0; + hasChangesetId = (header & 8) == 0; + hasTimestamp = (header & 16) == 0; + hasUserId = (header & 32) == 0; + hasVersion = (header & 64) == 0; + hasVisible = (header & 128) == 0; + + var type = header & 3; + switch (type) + { + case 1: + return OsmGeoType.Node; + case 2: + return OsmGeoType.Way; + case 3: + return OsmGeoType.Relation; + } + throw new Exception("Invalid header: cannot detect OsmGeoType."); + } + + /// + /// Reads an OSM object starting at the stream's current position. + /// + public static OsmGeo ReadOsmGeo(this Stream stream, byte[] buffer) + { + bool hasId, hasChangesetId, hasTimestamp, hasUserId, hasVersion, hasVisible; + var type = stream.ReadOsmGeoHeader(out hasId, out hasChangesetId, out hasTimestamp, + out hasUserId, out hasVersion, out hasVisible); + if (type == null) return null; + + // read the basics. + long? id = null; + if (hasId) { id = stream.ReadInt64(buffer); } + long? changesetId = null; + if (hasChangesetId) { changesetId = stream.ReadInt64(buffer); } + DateTime? timestamp = null; + if (hasTimestamp) { timestamp = stream.ReadDateTime(buffer); } + long? userId = null; + if (hasUserId) { userId = stream.ReadInt64(buffer); } + var username = stream.ReadWithSizeString(buffer); + int? version = null; + if (hasVersion) { version = stream.ReadInt32(buffer); } + bool? visible = null; + if (hasVisible) { visible = stream.ReadBool(); } + + // read tags. + var tagsCount = stream.ReadInt32(buffer); + TagsCollection tags = null; + if (tagsCount > 0) + { + tags = new TagsCollection(tagsCount); + for (var i = 0; i < tagsCount; i++) + { + var key = stream.ReadWithSizeString(buffer); + var value = stream.ReadWithSizeString(buffer); + tags.AddOrReplace(key, value); + } + } + + OsmGeo osmGeo = null; + switch (type) + { + case OsmGeoType.Node: + osmGeo = stream.ReadNode(buffer); + break; + case OsmGeoType.Way: + osmGeo = stream.ReadWay(buffer); + break; + case OsmGeoType.Relation: + osmGeo = stream.ReadRelation(buffer); + break; + } + + osmGeo.Id = id; + osmGeo.ChangeSetId = changesetId; + osmGeo.TimeStamp = timestamp; + osmGeo.UserId = userId; + osmGeo.UserName = username; + osmGeo.Version = version; + osmGeo.Visible = visible; + osmGeo.Tags = tags; + + return osmGeo; + } + + private static Node ReadNode(this Stream stream, byte[] buffer) + { + var node = new Node(); + + var header = stream.ReadByte(); + var hasLatitude = (header & 1) == 0; + var hasLongitude = (header & 2) == 0; + + if (hasLatitude) { node.Latitude = stream.ReadDouble(buffer); } + if (hasLongitude) { node.Longitude = stream.ReadDouble(buffer); } + + return node; + } + + private static Way ReadWay(this Stream stream, byte[] buffer) + { + var way = new Way(); + + var nodeCount = stream.ReadInt32(buffer); + if (nodeCount > 0) + { + var nodes = new long[nodeCount]; + for (var i = 0; i < nodeCount; i++) + { + nodes[i] = stream.ReadInt64(buffer); + } + way.Nodes = nodes; + } + + return way; + } + + private static Relation ReadRelation(this Stream stream, byte[] buffer) + { + var relation = new Relation(); + + var memberCount = stream.ReadInt32(buffer); + if (memberCount > 0) + { + var members = new RelationMember[memberCount]; + for(var i = 0; i< memberCount; i++) + { + var id = stream.ReadInt64(buffer); + var role = stream.ReadWithSizeString(buffer); + var typeId = stream.ReadByte(); + var type = OsmGeoType.Node; + switch(typeId) + { + case 2: + type = OsmGeoType.Way; + break; + case 3: + type = OsmGeoType.Relation; + break; + } + members[i] = new RelationMember() + { + Id = id, + Role = role, + Type = type + }; + } + relation.Members = members; + } + + return relation; + } + + /// + /// Writes the given value to the stream. + /// + public static int Write(this Stream stream, int value) + { + stream.Write(BitConverter.GetBytes(value), 0, 4); + return 4; + } + + private static int Write(this Stream stream, float value) + { + stream.Write(BitConverter.GetBytes(value), 0, 4); + return 4; + } + + private static int Write(this Stream stream, double value) + { + stream.Write(BitConverter.GetBytes(value), 0, 8); + return 8; + } + + private static int Write(this Stream stream, long value) + { + stream.Write(BitConverter.GetBytes(value), 0, 8); + return 8; + } + + private static int Write(this Stream stream, ulong value) + { + stream.Write(BitConverter.GetBytes(value), 0, 8); + return 8; + } + + private static int Write(this Stream stream, DateTime value) + { + stream.Write(BitConverter.GetBytes(value.Ticks), 0, 8); + return 8; + } + + private static int Write(this Stream stream, bool value) + { + if (value) + { + stream.WriteByte(1); + } + else + { + stream.WriteByte(0); + } + return 1; + } + + private static int WriteWithSize(this Stream stream, string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + stream.WriteByte(0); + return 1; + } + else + { // TODO: improve this based on the protobuf way of handling this kind of variable info. + var bytes = System.Text.Encoding.Unicode.GetBytes(value); + var position = 0; + while(bytes.Length - position >= 255) + { // write in blocks of 255. + stream.WriteByte(255); + stream.Write(bytes, position, 255); + position += 256; // data + size + } + stream.WriteByte((byte)(bytes.Length - position)); + if (bytes.Length - position > 0) + { + stream.Write(bytes, position, bytes.Length - position); + } + return bytes.Length + 1; + } + } + + private static DateTime ReadDateTime(this Stream stream, byte[] buffer) + { + return new DateTime(stream.ReadInt64(buffer)); + } + + private static long ReadInt64(this Stream stream, byte[] buffer) + { + stream.Read(buffer, 0, 8); + return BitConverter.ToInt64(buffer, 0); + } + + private static int ReadInt32(this Stream stream, byte[] buffer) + { + stream.Read(buffer, 0, 4); + return BitConverter.ToInt32(buffer, 0); + } + + private static bool ReadBool(this Stream stream) + { + var v = stream.ReadByte(); + if (v == 0) + { + return false; + } + else if (v == 1) + { + return true; + } + else + { + throw new InvalidDataException("Cannot deserialize bool."); + } + } + + private static float ReadSingle(this Stream stream, byte[] buffer) + { + stream.Read(buffer, 0, 4); + return BitConverter.ToSingle(buffer, 0); + } + + private static double ReadDouble(this Stream stream, byte[] buffer) + { + stream.Read(buffer, 0, 8); + return BitConverter.ToDouble(buffer, 0); + } + + private static string ReadWithSizeString(this System.IO.Stream stream, byte[] buffer) + { + var size = stream.ReadByte(); + var position = 0; + while (size == 255) + { + stream.Read(buffer, position, (int)size); + size = stream.ReadByte(); + position += 256; + } + if (size > 0) + { + stream.Read(buffer, position, (int)size); + } + + + return System.Text.UnicodeEncoding.Unicode.GetString(buffer, 0, size); + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test.Functional/OsmSharp.IO.Binary.Test.Functional.csproj b/test/OsmSharp.IO.Binary.Test.Functional/OsmSharp.IO.Binary.Test.Functional.csproj index 9dfa6c9..780ca57 100644 --- a/test/OsmSharp.IO.Binary.Test.Functional/OsmSharp.IO.Binary.Test.Functional.csproj +++ b/test/OsmSharp.IO.Binary.Test.Functional/OsmSharp.IO.Binary.Test.Functional.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 OsmSharp.IO.Binary.Test.Functional Exe diff --git a/test/OsmSharp.IO.Binary.Test.Functional/Program.cs b/test/OsmSharp.IO.Binary.Test.Functional/Program.cs index 4c480a5..8fa35f9 100644 --- a/test/OsmSharp.IO.Binary.Test.Functional/Program.cs +++ b/test/OsmSharp.IO.Binary.Test.Functional/Program.cs @@ -45,7 +45,7 @@ public static void Main(string[] args) .CreateLogger(); // download test data. - Download.ToFile("http://files.itinero.tech/data/OSM/planet/europe/luxembourg-latest.osm.pbf", "test.osm.pbf").Wait(); + Download.ToFile("http://planet.anyways.eu/planet/europe/luxembourg/luxembourg-latest.osm.pbf", "test.osm.pbf").Wait(); // test read/writing an existing OSM file. Log.Information("Testing write to OSM binary formatted file..."); @@ -143,11 +143,11 @@ public static void Main(string[] args) } using (var sourceStream = File.OpenRead("test3.osm.bin")) - using (var targetStream = File.Open("test2.osm.pbf", FileMode.Create)) + using (var targetStream = File.Open("test2.osm", FileMode.Create)) { var source = new OsmSharp.Streams.BinaryOsmStreamSource(sourceStream); - var target = new OsmSharp.Streams.PBFOsmStreamTarget(targetStream); + var target = new OsmSharp.Streams.XmlOsmStreamTarget(targetStream); target.RegisterSource(source); target.Pull(); } diff --git a/test/OsmSharp.IO.Binary.Test/OsmSharp.IO.Binary.Test.csproj b/test/OsmSharp.IO.Binary.Test/OsmSharp.IO.Binary.Test.csproj index 8bca592..cf654cb 100644 --- a/test/OsmSharp.IO.Binary.Test/OsmSharp.IO.Binary.Test.csproj +++ b/test/OsmSharp.IO.Binary.Test/OsmSharp.IO.Binary.Test.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 false @@ -24,4 +24,9 @@ + + + + + diff --git a/test/OsmSharp.IO.Binary.Test/Staging/TestStreamsV0_1.cs b/test/OsmSharp.IO.Binary.Test/Staging/TestStreamsV0_1.cs new file mode 100644 index 0000000..e2dedcb --- /dev/null +++ b/test/OsmSharp.IO.Binary.Test/Staging/TestStreamsV0_1.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; +using OsmSharp.Tags; + +namespace OsmSharp.IO.Binary.Test.Staging +{ + internal static class TestStreamsV0_1 + { + public static Stream GetNodeTestStream() + { + var stream = new MemoryStream(); + var node1 = new Node() + { + Id = 1, + ChangeSetId = 2, + Latitude = 10, + TimeStamp = DateTime.Now, + Longitude = 11, + Tags = new TagsCollection(new Tag("name", "hu?")), + UserId = 12, + UserName = "Ben", + Version = 123, + Visible = true + }; + + v0_1.TestBinarySerializer.Append(stream, node1); + + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + public static Stream GetWayTestStream() + { + var stream = new MemoryStream(); + var way1 = new Way() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Nodes = new long[] + { + 1, + 2, + 3 + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }; + + v0_1.TestBinarySerializer.Append(stream, way1); + + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + public static Stream GetRelationTestStream() + { + var stream = new MemoryStream(); + var relation1 = new Relation() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Members = new RelationMember[] + { + new RelationMember() + { + Id = 1, + Role = "node", + Type = OsmGeoType.Node + }, + new RelationMember() + { + Id = 2, + Role = "way", + Type = OsmGeoType.Way + }, + new RelationMember() + { + Id = 3, + Role = "relation", + Type = OsmGeoType.Relation + } + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }; + + v0_1.TestBinarySerializer.Append(stream, relation1); + + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + public static Stream GetNodeWayAndRelationTestStream() + { + var stream = new MemoryStream(); + var node1 = new Node() + { + Id = 1, + ChangeSetId = 2, + Latitude = 10, + TimeStamp = DateTime.Now, + Longitude = 11, + Tags = new TagsCollection(new Tag("name", "hu?")), + UserId = 12, + UserName = "Ben", + Version = 123, + Visible = true + }; + + v0_1.TestBinarySerializer.Append(stream, node1); + + var way1 = new Way() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Nodes = new long[] + { + 1, + 2, + 3 + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }; + + v0_1.TestBinarySerializer.Append(stream, way1); + + var relation1 = new Relation() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Members = new RelationMember[] + { + new RelationMember() + { + Id = 1, + Role = "node", + Type = OsmGeoType.Node + }, + new RelationMember() + { + Id = 2, + Role = "way", + Type = OsmGeoType.Way + }, + new RelationMember() + { + Id = 3, + Role = "relation", + Type = OsmGeoType.Relation + } + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }; + + v0_1.TestBinarySerializer.Append(stream, relation1); + + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test/v0_1/BinaryOsmStreamSourceTests.cs b/test/OsmSharp.IO.Binary.Test/v0_1/BinaryOsmStreamSourceTests.cs new file mode 100644 index 0000000..846b181 --- /dev/null +++ b/test/OsmSharp.IO.Binary.Test/v0_1/BinaryOsmStreamSourceTests.cs @@ -0,0 +1,183 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OsmSharp.IO.Binary.v0_1; +using OsmSharp.Tags; + +namespace OsmSharp.IO.Binary.Test.v0_1 +{ + /// + /// Contains tests for the binary serializer core functions. + /// + [TestClass] + public class BinaryOsmStreamSourceTests + { + [TestMethod] + public void BinaryOsmStreamSource_ShouldReadNode() + { + using (var stream = Staging.TestStreamsV0_1.GetNodeTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var nodes = sourceStream.ToList(); + + Assert.AreEqual(1, nodes.Count); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldReadWay() + { + using (var stream = Staging.TestStreamsV0_1.GetWayTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var ways = sourceStream.ToList(); + + Assert.AreEqual(1, ways.Count); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldReadRelation() + { + using (var stream = Staging.TestStreamsV0_1.GetRelationTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var relations = sourceStream.ToList(); + + Assert.AreEqual(1, relations.Count); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldReadNodeWayAndRelation() + { + using (var stream = Staging.TestStreamsV0_1.GetNodeWayAndRelationTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + Assert.AreEqual(3, osmGeos.Count); + Assert.IsInstanceOfType(osmGeos[0], typeof(Node)); + Assert.IsInstanceOfType(osmGeos[1], typeof(Way)); + Assert.IsInstanceOfType(osmGeos[2], typeof(Relation)); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldIgnoreNode() + { + using (var stream = Staging.TestStreamsV0_1.GetNodeWayAndRelationTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + sourceStream.Reset(); + osmGeos = sourceStream.EnumerateAndIgore(true, false, false).ToList(); + + Assert.AreEqual(2, osmGeos.Count); + Assert.IsInstanceOfType(osmGeos[0], typeof(Way)); + Assert.IsInstanceOfType(osmGeos[1], typeof(Relation)); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldIgnoreWay() + { + using (var stream = Staging.TestStreamsV0_1.GetNodeWayAndRelationTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + sourceStream.Reset(); + osmGeos = sourceStream.EnumerateAndIgore(false, true, false).ToList(); + + Assert.AreEqual(2, osmGeos.Count); + Assert.IsInstanceOfType(osmGeos[0], typeof(Node)); + Assert.IsInstanceOfType(osmGeos[1], typeof(Relation)); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldIgnoreRelation() + { + using (var stream = Staging.TestStreamsV0_1.GetNodeWayAndRelationTestStream()) + { + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + sourceStream.Reset(); + osmGeos = sourceStream.EnumerateAndIgore(false, false, true).ToList(); + + Assert.AreEqual(2, osmGeos.Count); + Assert.IsInstanceOfType(osmGeos[0], typeof(Node)); + Assert.IsInstanceOfType(osmGeos[1], typeof(Way)); + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldReadFomDeflateStream() + { + using (var stream = new MemoryStream()) + { + using (var streamCompressed = new DeflateStream(stream, CompressionMode.Compress, true)) + { + var targetStream = new BinaryOsmStreamTarget(streamCompressed); + targetStream.Initialize(); + targetStream.AddRelation(new Relation() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Members = new RelationMember[] + { + new RelationMember() + { + Id = 1, + Role = "node", + Type = OsmGeoType.Node + }, + new RelationMember() + { + Id = 2, + Role = "way", + Type = OsmGeoType.Way + }, + new RelationMember() + { + Id = 3, + Role = "relation", + Type = OsmGeoType.Relation + } + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }); + } + + stream.Seek(0, SeekOrigin.Begin); + using (var streamCompressed = new DeflateStream(stream, CompressionMode.Decompress)) + { + var sourceStream = new BinaryOsmStreamSource(streamCompressed); + var osmGeos = sourceStream.ToList(); + + Assert.AreEqual(1, osmGeos.Count); + } + } + } + + [TestMethod] + public void BinaryOsmStreamSource_ShouldReadData1() + { + using var stream = TestStreams.GetData1Stream(); + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + Assert.AreEqual(27858, osmGeos.Count); + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test/v0_1/BinaryOsmStreamTargetTests.cs b/test/OsmSharp.IO.Binary.Test/v0_1/BinaryOsmStreamTargetTests.cs new file mode 100644 index 0000000..db0b45b --- /dev/null +++ b/test/OsmSharp.IO.Binary.Test/v0_1/BinaryOsmStreamTargetTests.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OsmSharp.IO.Binary.v0_1; +using OsmSharp.Tags; + +namespace OsmSharp.IO.Binary.Test.v0_1 +{ + /// + /// Contains tests for the binary serializer core functions. + /// + [TestClass] + public class BinaryOsmStreamTargetTests + { + [TestMethod] + public void BinaryOsmStreamTarget_ShouldWriteNode() + { + using (var stream = new MemoryStream()) + { + var targetStream = new BinaryOsmStreamTarget(stream); + targetStream.Initialize(); + targetStream.AddNode(new Node() + { + Id = 1, + ChangeSetId = 2, + Latitude = 10, + TimeStamp = DateTime.Now, + Longitude = 11, + Tags = new TagsCollection(new Tag("name", "hu?")), + UserId = 12, + UserName = "Ben", + Version = 123, + Visible = true + }); + + stream.Seek(0, SeekOrigin.Begin); + var sourceStream = new BinaryOsmStreamSource(stream); + var nodes = sourceStream.ToList(); + + Assert.AreEqual(1, nodes.Count); + } + } + + [TestMethod] + public void BinaryOsmStreamTarget_ShouldWriteWay() + { + using (var stream = new MemoryStream()) + { + var targetStream = new BinaryOsmStreamTarget(stream); + targetStream.Initialize(); + targetStream.AddWay(new Way() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Nodes = new long[] + { + 1, + 2, + 3 + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }); + + stream.Seek(0, SeekOrigin.Begin); + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + Assert.AreEqual(1, osmGeos.Count); + } + } + + [TestMethod] + public void BinaryOsmStreamTarget_ShouldWriteRelation() + { + using (var stream = new MemoryStream()) + { + var targetStream = new BinaryOsmStreamTarget(stream); + targetStream.Initialize(); + targetStream.AddRelation(new Relation() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Members = new RelationMember[] + { + new RelationMember() + { + Id = 1, + Role = "node", + Type = OsmGeoType.Node + }, + new RelationMember() + { + Id = 2, + Role = "way", + Type = OsmGeoType.Way + }, + new RelationMember() + { + Id = 3, + Role = "relation", + Type = OsmGeoType.Relation + } + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }); + + stream.Seek(0, SeekOrigin.Begin); + var sourceStream = new BinaryOsmStreamSource(stream); + var osmGeos = sourceStream.ToList(); + + Assert.AreEqual(1, osmGeos.Count); + } + } + + [TestMethod] + public void BinaryOsmStreamTarget_ShouldWriteToDeflateStream() + { + using (var stream = new MemoryStream()) + using (var streamCompressed = new System.IO.Compression.DeflateStream(stream, CompressionMode.Compress)) + { + var targetStream = new BinaryOsmStreamTarget(streamCompressed); + targetStream.Initialize(); + targetStream.AddRelation(new Relation() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Members = new RelationMember[] + { + new RelationMember() + { + Id = 1, + Role = "node", + Type = OsmGeoType.Node + }, + new RelationMember() + { + Id = 2, + Role = "way", + Type = OsmGeoType.Way + }, + new RelationMember() + { + Id = 3, + Role = "relation", + Type = OsmGeoType.Relation + } + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }); + } + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test/v0_1/BinarySerializerTests.cs b/test/OsmSharp.IO.Binary.Test/v0_1/BinarySerializerTests.cs new file mode 100644 index 0000000..9acebfb --- /dev/null +++ b/test/OsmSharp.IO.Binary.Test/v0_1/BinarySerializerTests.cs @@ -0,0 +1,177 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OsmSharp.Tags; +using OsmSharp.IO.Binary.v0_1; + +namespace OsmSharp.IO.Binary.Test.v0_1 +{ + [TestClass] + public class BinarySerializerTests + { + /// + /// Tests reading/writing a node. + /// + [TestMethod] + public void TestReadWriteNode() + { + using (var stream = new MemoryStream()) + { + var node1 = new Node() + { + Id = 1, + ChangeSetId = 2, + Latitude = 10, + TimeStamp = DateTime.Now, + Longitude = 11, + Tags = new TagsCollection(new Tag("name", "hu?")), + UserId = 12, + UserName = "Ben", + Version = 123, + Visible = true + }; + + // tests the actual serialization code. + TestBinarySerializer.Append(stream, node1); + stream.Seek(0, SeekOrigin.Begin); + + // read again and compare. + var osmGeo = Binary.v0_1.BinarySerializer.ReadOsmGeo(stream, new byte[1024]); + Assert.IsNotNull(osmGeo); + Assert.IsInstanceOfType(osmGeo, typeof(Node)); + var node2 = osmGeo as Node; + + Assert.AreEqual(node1.Id, node2.Id); + Assert.AreEqual(node1.Latitude, node2.Latitude); + Assert.AreEqual(node1.Longitude, node2.Longitude); + Assert.AreEqual(node1.ChangeSetId, node2.ChangeSetId); + Assert.AreEqual(node1.TimeStamp, node2.TimeStamp); + Assert.AreEqual(node1.UserId, node2.UserId); + Assert.AreEqual(node1.UserName, node2.UserName); + Assert.AreEqual(node1.Version, node2.Version); + Assert.AreEqual(node1.Visible, node2.Visible); + ExtraAssert.AreEqual(node1.Tags.ToArray(), node2.Tags.ToArray()); + } + } + + /// + /// Tests reading/writing a way. + /// + [TestMethod] + public void TestReadWriteWay() + { + using (var stream = new MemoryStream()) + { + var way1 = new Way() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Nodes = new long[] + { + 1, + 2, + 3 + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }; + + // tests the actual serialization code. + TestBinarySerializer.Append(stream, way1); + stream.Seek(0, SeekOrigin.Begin); + + // read again and compare. + var osmGeo = Binary.v0_1.BinarySerializer.ReadOsmGeo(stream, new byte[1024]); + Assert.IsNotNull(osmGeo); + Assert.IsInstanceOfType(osmGeo, typeof(Way)); + var way2 = osmGeo as Way; + + Assert.AreEqual(way1.Id, way2.Id); + Assert.AreEqual(way1.ChangeSetId, way2.ChangeSetId); + Assert.AreEqual(way1.TimeStamp, way2.TimeStamp); + Assert.AreEqual(way1.UserId, way2.UserId); + Assert.AreEqual(way1.UserName, way2.UserName); + Assert.AreEqual(way1.Version, way2.Version); + Assert.AreEqual(way1.Visible, way2.Visible); + Assert.AreEqual(way1.Nodes.Length, way2.Nodes.Length); + ExtraAssert.AreEqual(way1.Nodes, way2.Nodes); + ExtraAssert.AreEqual(way1.Tags.ToArray(), way2.Tags.ToArray()); + } + } + + /// + /// Tests reading/writing a relation. + /// + [TestMethod] + public void TestReadWriteRelation() + { + using (var stream = new MemoryStream()) + { + var relation1 = new Relation() + { + Id = 1, + ChangeSetId = 1, + TimeStamp = DateTime.Now, + Tags = new TagsCollection(new Tag("name", "hu?")), + Members = new RelationMember[] + { + new RelationMember() + { + Id = 1, + Role = "node", + Type = OsmGeoType.Node + }, + new RelationMember() + { + Id = 2, + Role = "way", + Type = OsmGeoType.Way + }, + new RelationMember() + { + Id = 3, + Role = "relation", + Type = OsmGeoType.Relation + } + }, + UserId = 1, + UserName = "Ben", + Version = 1, + Visible = true + }; + + // tests the actual serialization code. + TestBinarySerializer.Append(stream, relation1); + stream.Seek(0, SeekOrigin.Begin); + + // read again and compare. + var osmGeo = Binary.v0_1.BinarySerializer.ReadOsmGeo(stream, new byte[1024]); + Assert.IsNotNull(osmGeo); + Assert.IsInstanceOfType(osmGeo, typeof(Relation)); + var relation2 = osmGeo as Relation; + + Assert.AreEqual(relation1.Id, relation2.Id); + Assert.AreEqual(relation1.ChangeSetId, relation2.ChangeSetId); + Assert.AreEqual(relation1.TimeStamp, relation2.TimeStamp); + Assert.AreEqual(relation1.UserId, relation2.UserId); + Assert.AreEqual(relation1.UserName, relation2.UserName); + Assert.AreEqual(relation1.Version, relation2.Version); + Assert.AreEqual(relation1.Visible, relation2.Visible); + Assert.AreEqual(relation1.Members.Length, relation2.Members.Length); + ExtraAssert.AreEqual(relation1.Members, relation2.Members, (m1, m2) => + { + Assert.IsNotNull(m2); + Assert.AreEqual(m1.Id, m2.Id); + Assert.AreEqual(m1.Role, m2.Role); + Assert.AreEqual(m1.Type, m2.Type); + }); + ExtraAssert.AreEqual(relation1.Tags.ToArray(), relation2.Tags.ToArray()); + } + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test/v0_1/TestBinarySerializer.cs b/test/OsmSharp.IO.Binary.Test/v0_1/TestBinarySerializer.cs new file mode 100644 index 0000000..5e2d75e --- /dev/null +++ b/test/OsmSharp.IO.Binary.Test/v0_1/TestBinarySerializer.cs @@ -0,0 +1,248 @@ +using System; +using System.IO; + +namespace OsmSharp.IO.Binary.Test.v0_1 +{ + /// + /// Contains all old binary formatting writing code only. + /// + /// The old format is only supported as a reader, we include this here as a reference. + /// + public static class TestBinarySerializer + { + private static System.Text.Encoder _encoder = (new System.Text.UnicodeEncoding()).GetEncoder(); + + /// + /// Appends the header byte(s). + /// + public static int AppendHeader(this Stream stream, OsmGeo osmGeo) + { + // build header containing type and nullable flags. + byte header = 1; // a node. + if(osmGeo.Type == OsmGeoType.Way) + { + header = 2; + } + else if(osmGeo.Type == OsmGeoType.Relation) + { + header = 3; + } + if (!osmGeo.Id.HasValue) { header = (byte)(header | 4); } + if (!osmGeo.ChangeSetId.HasValue) { header = (byte)(header | 8); } + if (!osmGeo.TimeStamp.HasValue) { header = (byte)(header | 16); } + if (!osmGeo.UserId.HasValue) { header = (byte)(header | 32); } + if (!osmGeo.Version.HasValue) { header = (byte)(header | 64); } + if (!osmGeo.Visible.HasValue) { header = (byte)(header | 128); } + stream.WriteByte(header); + + return 1; + } + + /// + /// Writes the given node starting at the stream's current position. + /// + public static int Append(this Stream stream, Node node) + { + if (node == null) { throw new ArgumentNullException(nameof(node)); } + + // appends the header. + var size = stream.AppendHeader(node); + + // write osm geo data. + size += stream.AppendOsmGeo(node); + + // write lat/lon with nullable flags. + byte header = 0; + if (!node.Latitude.HasValue) { header = (byte)(header | 1); } + if (!node.Longitude.HasValue) { header = (byte)(header | 2); } + size += 1; + stream.WriteByte(header); + if (node.Latitude.HasValue) { size += stream.Write(node.Latitude.Value); } + if (node.Longitude.HasValue) { size += stream.Write(node.Longitude.Value); } + + return size; + } + + /// + /// Writes the given way starting at the stream's current position. + /// + public static int Append(this Stream stream, Way way) + { + if (way == null) { throw new ArgumentNullException(nameof(way)); } + + // appends the header. + var size = stream.AppendHeader(way); + + // write data. + size += stream.AppendOsmGeo(way); + + if (way.Nodes == null || + way.Nodes.Length == 0) + { + size += stream.Write(0); + } + else + { + size += stream.Write(way.Nodes.Length); + for (var i = 0; i < way.Nodes.Length; i++) + { + size += stream.Write(way.Nodes[i]); + } + } + + return size; + } + + /// + /// Writes the given relation starting at the stream's current position. + /// + public static int Append(this Stream stream, Relation relation) + { + if (relation == null) { throw new ArgumentNullException(nameof(relation)); } + + // appends the header. + var size = stream.AppendHeader(relation); + + // write data. + size += stream.AppendOsmGeo(relation); + + if (relation.Members == null || + relation.Members.Length == 0) + { + size += stream.Write(0); + } + else + { + size += stream.Write(relation.Members.Length); + for (var i = 0; i < relation.Members.Length; i++) + { + size += stream.Write(relation.Members[i].Id); + size += stream.WriteWithSize(relation.Members[i].Role); + switch (relation.Members[i].Type) + { + case OsmGeoType.Node: + stream.WriteByte((byte)1); + break; + case OsmGeoType.Way: + stream.WriteByte((byte)2); + break; + case OsmGeoType.Relation: + stream.WriteByte((byte)3); + break; + } + size += 1; + } + } + + return size; + } + + private static int AppendOsmGeo(this Stream stream, OsmGeo osmGeo) + { + var size = 0; + + if (osmGeo.Id.HasValue) { size += stream.Write(osmGeo.Id.Value); } + if (osmGeo.ChangeSetId.HasValue) { size += stream.Write(osmGeo.ChangeSetId.Value); } + if (osmGeo.TimeStamp.HasValue) { size += stream.Write(osmGeo.TimeStamp.Value); } + if (osmGeo.UserId.HasValue) { size += stream.Write(osmGeo.UserId.Value); } + size += stream.WriteWithSize(osmGeo.UserName); + if (osmGeo.Version.HasValue) { size += stream.Write((int)osmGeo.Version.Value); } + if (osmGeo.Visible.HasValue) { size += stream.Write(osmGeo.Visible.Value); } + + if (osmGeo.Tags == null || + osmGeo.Tags.Count == 0) + { + size += stream.Write(0); + } + else + { + size += stream.Write(osmGeo.Tags.Count); + foreach (var t in osmGeo.Tags) + { + size += stream.WriteWithSize(t.Key); + size += stream.WriteWithSize(t.Value); + } + } + + return size; + } + + /// + /// Writes the given value to the stream. + /// + public static int Write(this Stream stream, int value) + { + stream.Write(BitConverter.GetBytes(value), 0, 4); + return 4; + } + + private static int Write(this Stream stream, float value) + { + stream.Write(BitConverter.GetBytes(value), 0, 4); + return 4; + } + + private static int Write(this Stream stream, double value) + { + stream.Write(BitConverter.GetBytes(value), 0, 8); + return 8; + } + + private static int Write(this Stream stream, long value) + { + stream.Write(BitConverter.GetBytes(value), 0, 8); + return 8; + } + + private static int Write(this Stream stream, ulong value) + { + stream.Write(BitConverter.GetBytes(value), 0, 8); + return 8; + } + + private static int Write(this Stream stream, DateTime value) + { + stream.Write(BitConverter.GetBytes(value.Ticks), 0, 8); + return 8; + } + + private static int Write(this Stream stream, bool value) + { + if (value) + { + stream.WriteByte(1); + } + else + { + stream.WriteByte(0); + } + return 1; + } + + private static int WriteWithSize(this Stream stream, string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + stream.WriteByte(0); + return 1; + } + else + { // TODO: improve this based on the protobuf way of handling this kind of variable info. + var bytes = System.Text.Encoding.Unicode.GetBytes(value); + var position = 0; + while(bytes.Length - position >= 255) + { // write in blocks of 255. + stream.WriteByte(255); + stream.Write(bytes, position, 255); + position += 256; // data + size + } + stream.WriteByte((byte)(bytes.Length - position)); + if (bytes.Length - position > 0) + { + stream.Write(bytes, position, bytes.Length - position); + } + return bytes.Length + 1; + } + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test/v0_1/test-data/TestStreams.cs b/test/OsmSharp.IO.Binary.Test/v0_1/test-data/TestStreams.cs new file mode 100644 index 0000000..d237d9a --- /dev/null +++ b/test/OsmSharp.IO.Binary.Test/v0_1/test-data/TestStreams.cs @@ -0,0 +1,18 @@ +using System.IO; + +namespace OsmSharp.IO.Binary.Test.v0_1 +{ + public static class TestStreams + { + public static Stream GetData1Stream() + { + return TestStreams.LoadAsStream("OsmSharp.IO.Binary.Test.v0_1.test_data.data1.osm.bin"); + } + + public static Stream LoadAsStream(string path) + { + return System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream( + path); + } + } +} \ No newline at end of file diff --git a/test/OsmSharp.IO.Binary.Test/v0_1/test-data/data1.osm.bin b/test/OsmSharp.IO.Binary.Test/v0_1/test-data/data1.osm.bin new file mode 100644 index 0000000..4b42862 Binary files /dev/null and b/test/OsmSharp.IO.Binary.Test/v0_1/test-data/data1.osm.bin differ