diff --git a/GeoJSON.Tests/GeoJSON.Tests.csproj b/GeoJSON.Tests/GeoJSON.Tests.csproj index b2ba915..a0af335 100644 --- a/GeoJSON.Tests/GeoJSON.Tests.csproj +++ b/GeoJSON.Tests/GeoJSON.Tests.csproj @@ -2,6 +2,11 @@ netcoreapp3.1 + true + + + + DEBUG;TRACE diff --git a/GeoJSON.Tests/WkbUnitTests.cs b/GeoJSON.Tests/WkbUnitTests.cs new file mode 100644 index 0000000..c8cafbe --- /dev/null +++ b/GeoJSON.Tests/WkbUnitTests.cs @@ -0,0 +1,627 @@ +using BAMCIS.GeoJSON; +using BAMCIS.GeoJSON.Wkb; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Xunit; + +namespace GeoJSON.Tests +{ + public class WkbUnitTests + { + #region WkbConverter Tests + + [Fact] + public void WkbConverterTest_ToBinary_BigEndian() + { + // ARRANGE + byte[] expectedBytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + Point point = new Point(new Position(2.0, 4.0)); + + // ACT + byte[] bytes = WkbConverter.ToBinary(point, Endianness.BIG); + + // ASSERT + Assert.Equal(expectedBytes, bytes); + } + + [Fact] + public void WkbConverterTest_FromBinary_BigEndian() + { + // ARRANGE + byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + + // ACT + Point point = WkbConverter.FromBinary(bytes); + + // ASSERT + Assert.Equal(2.0, point.GetLongitude()); + Assert.Equal(4.0, point.GetLatitude()); + } + + #endregion + + #region Point Tests + + [Fact] + public void PointTest_Conversion() + { + // ARRANGE + Point point = new Point(new Position(10.0, 10.0)); + + // ACT + byte[] bytes = point.ToWkb(); + Geometry geo = Point.FromWkb(bytes); + + // ASSERT + point = Assert.IsType(geo); + } + + [Fact] + public void PointTest_Conversion2() + { + // ARRANGE + Point point = new Point(new Position(10.0, 10.0)); + + // ACT + byte[] bytes = point.ToWkb(); + point = Geometry.FromWkb(bytes); + + // ASSERT + point = Assert.IsType(point); + Assert.Equal(10.0, point.GetLongitude()); + Assert.Equal(10.0, point.GetLatitude()); + } + + [Fact] + public void PointTest_FromBinary_BigEndian() + { + // ARRANGE + + // POINT(2.0 4.0) BIG ENDIAN + byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + Point point = Assert.IsType(geo); + Assert.Equal(2.0, point.Coordinates.Longitude); + Assert.Equal(4.0, point.Coordinates.Latitude); + } + + [Fact] + public void PointTest_ToBinary_BigEndian() + { + // ARRANGE + + // POINT(2.0 4.0) BIG ENDIAN + byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void PointTest_FromBinary_LittleEndian() + { + // ARRANGE + + // POINT(1.2345 2.3456) LITTLE ENDIAN + byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + Point point = Assert.IsType(geo); + Assert.Equal(1.2345, point.Coordinates.Longitude); + Assert.Equal(2.3456, point.Coordinates.Latitude); + } + + [Fact] + public void PointTest_ToBinary_LittleEndian() + { + // ARRANGE + + // POINT(1.2345 2.3456) LITTLE ENDIAN + byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region LineString Tests + + [Fact] + public void LineStringTest_FromBinary_BigEndian() + { + // ARRANGE + + // LINESTRING(30 10, 10 30, 40 40) + byte[] bytes = HexStringToByteArray("000000000200000003403E00000000000040240000000000004024000000000000403E00000000000040440000000000004044000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + LineString lineString = Assert.IsType(geo); + Assert.Equal(3, lineString.Coordinates.Count()); + Assert.Equal(new Position(30, 10), lineString.Coordinates.ElementAt(0)); + Assert.Equal(new Position(10, 30), lineString.Coordinates.ElementAt(1)); + Assert.Equal(new Position(40, 40), lineString.Coordinates.ElementAt(2)); + } + + [Fact] + public void LineStringTest_FromBinary_LittleEndian() + { + // ARRANGE + + // LINESTRING(30 10, 10 30, 40 40) + byte[] bytes = HexStringToByteArray("0102000000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + LineString lineString = Assert.IsType(geo); + Assert.Equal(3, lineString.Coordinates.Count()); + Assert.Equal(new Position(30, 10), lineString.Coordinates.ElementAt(0)); + Assert.Equal(new Position(10, 30), lineString.Coordinates.ElementAt(1)); + Assert.Equal(new Position(40, 40), lineString.Coordinates.ElementAt(2)); + } + + [Fact] + public void LineStringTestWithDoubles_FromBinary_BigEndian() + { + // ARRANGE + + // LINESTRING(30.1234 10.6, 10.77 30.85, 40.1 40.2, 21 07, 19 77) + byte[] bytes = HexStringToByteArray("000000000200000005403E1F972474538F402533333333333340258A3D70A3D70A403ED9999999999A40440CCCCCCCCCCD404419999999999A4035000000000000401C00000000000040330000000000004053400000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + LineString lineString = Assert.IsType(geo); + Assert.Equal(5, lineString.Coordinates.Count()); + Assert.Equal(new Position(30.1234, 10.6), lineString.Coordinates.ElementAt(0)); + Assert.Equal(new Position(10.77, 30.85), lineString.Coordinates.ElementAt(1)); + Assert.Equal(new Position(19, 77), lineString.Coordinates.ElementAt(4)); + } + + [Fact] + public void LineStringTest_ToBinary_BigEndian() + { + // ARRANGE + + // LINESTRING(30 10, 10 30, 40 40) + byte[] bytes = HexStringToByteArray("000000000200000003403E00000000000040240000000000004024000000000000403E00000000000040440000000000004044000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void LineStringTest_ToBinary_LittleEndian() + { + // ARRANGE + + // LINESTRING(30 10, 10 30, 40 40) + byte[] bytes = HexStringToByteArray("0102000000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void LineStringTestWithDoubles_ToBinary_BigEndian() + { + // ARRANGE + + // LINESTRING(30.1234 10.6, 10.77 30.85, 40.1 40.2, 21 07, 19 77) + byte[] bytes = HexStringToByteArray("000000000200000005403E1F972474538F402533333333333340258A3D70A3D70A403ED9999999999A40440CCCCCCCCCCD404419999999999A4035000000000000401C00000000000040330000000000004053400000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region MultiLineString Tests + + [Fact] + public void MultiLineStringTest_FromBinary_BigEndian() + { + // ARRANGE + + // MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) + byte[] bytes = HexStringToByteArray("00000000050000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403E000000000000403E00000000000040440000000000004034000000000000403E0000000000004024000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + MultiLineString lineString = Assert.IsType(geo); + Assert.Equal(2, lineString.Coordinates.Count()); + LineString ls1 = Assert.IsType(lineString.Coordinates.ElementAt(1)); + Assert.Equal(40, ls1.Coordinates.ElementAt(2).Longitude); + Assert.Equal(20, ls1.Coordinates.ElementAt(2).Latitude); + } + + [Fact] + public void MultiLineStringTest_FromBinary_LittleEndian() + { + // ARRANGE + + // MULTILINESTRING((10 10,20 20,10 40),(40 40,30 30,40 20,30 10)) + byte[] bytes = HexStringToByteArray("010500000002000000010200000003000000000000000000244000000000000024400000000000003440000000000000344000000000000024400000000000004440010200000004000000000000000000444000000000000044400000000000003e400000000000003e40000000000000444000000000000034400000000000003e400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + MultiLineString lineString = Assert.IsType(geo); + Assert.Equal(2, lineString.Coordinates.Count()); + LineString ls1 = Assert.IsType(lineString.Coordinates.ElementAt(1)); + Assert.Equal(40, ls1.Coordinates.ElementAt(2).Longitude); + Assert.Equal(20, ls1.Coordinates.ElementAt(2).Latitude); + } + + [Fact] + public void MultiLineStringTest_ToBinary_BigEndian() + { + // ARRANGE + + // MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) + byte[] bytes = HexStringToByteArray("00000000050000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403E000000000000403E00000000000040440000000000004034000000000000403E0000000000004024000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void MultiLineStringTest_ToBinary_LittleEndian() + { + // ARRANGE + + // MULTILINESTRING((10 10,20 20,10 40),(40 40,30 30,40 20,30 10)) + byte[] bytes = HexStringToByteArray("010500000002000000010200000003000000000000000000244000000000000024400000000000003440000000000000344000000000000024400000000000004440010200000004000000000000000000444000000000000044400000000000003e400000000000003e40000000000000444000000000000034400000000000003e400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region MultiPoint Tests + + [Fact] + public void MultiPointTest_FromBinary_LittleEndian() + { + // ARRANGE + + // MULTIPOINT(10 40,40 30,20 20,30 10) + byte[] bytes = HexStringToByteArray("010400000004000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e4001010000000000000000003440000000000000344001010000000000000000003e400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + MultiPoint mp = Assert.IsType(geo); + } + + [Fact] + public void MultiPointTest_FromBinary_BigEndian() + { + // ARRANGE + + // MULTIPOINT ((21.06 19.77), (03.02 19.54), (40 20), (30 10)) + byte[] bytes = HexStringToByteArray("000000000400000004000000000140350F5C28F5C28F4033C51EB851EB850000000001400828F5C28F5C2940338A3D70A3D70A0000000001404400000000000040340000000000000000000001403E0000000000004024000000000000"); + + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + MultiPoint mp = Assert.IsType(geo); + Assert.Equal(21.06, mp.Coordinates.ElementAt(0).Longitude); + Assert.Equal(19.77, mp.Coordinates.ElementAt(0).Latitude); + } + + [Fact] + public void MultiPointTest_ToBinary_BigEndian() + { + // ARRANGE + + // MULTIPOINT ((21.06 19.77), (03.02 19.54), (40 20), (30 10)) + byte[] bytes = HexStringToByteArray("000000000400000004000000000140350F5C28F5C28F4033C51EB851EB850000000001400828F5C28F5C2940338A3D70A3D70A0000000001404400000000000040340000000000000000000001403E0000000000004024000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void MultiPointTest_ToBinary_LittleEndian() + { + // ARRANGE + + // MULTIPOINT(10 40,40 30,20 20,30 10) + byte[] bytes = HexStringToByteArray("010400000004000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e4001010000000000000000003440000000000000344001010000000000000000003e400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region MultiPolygon Tests + + [Fact] + public void MultiPolygonTest_FromBinary_BigEndian() + { + // ARRANGE + + // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) + byte[] bytes = HexStringToByteArray("00000000060000000200000000030000000100000004403E00000000000040340000000000004046800000000000404400000000000040240000000000004044000000000000403E000000000000403400000000000000000000030000000100000005402E0000000000004014000000000000404400000000000040240000000000004024000000000000403400000000000040140000000000004024000000000000402E0000000000004014000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + MultiPolygon mp = Assert.IsType(geo); + } + + [Fact] + public void MultiPolygonTest_FromBinary_LittleEndian() + { + // ARRANGE + + // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) + byte[] bytes = HexStringToByteArray("010600000002000000010300000001000000040000000000000000003e40000000000000344000000000008046400000000000004440000000000000244000000000000044400000000000003e400000000000003440010300000001000000050000000000000000002e4000000000000014400000000000004440000000000000244000000000000024400000000000003440000000000000144000000000000024400000000000002e400000000000001440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + MultiPolygon mp = Assert.IsType(geo); + } + + [Fact] + public void MultiPolygonTest_ToBinary_LittleEndian() + { + // ARRANGE + + // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) + byte[] bytes = HexStringToByteArray("010600000002000000010300000001000000040000000000000000003e40000000000000344000000000008046400000000000004440000000000000244000000000000044400000000000003e400000000000003440010300000001000000050000000000000000002e4000000000000014400000000000004440000000000000244000000000000024400000000000003440000000000000144000000000000024400000000000002e400000000000001440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void MultiPolygonTest_ToBinary_BigEndian() + { + // ARRANGE + + // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) + byte[] bytes = HexStringToByteArray("00000000060000000200000000030000000100000004403E00000000000040340000000000004046800000000000404400000000000040240000000000004044000000000000403E000000000000403400000000000000000000030000000100000005402E0000000000004014000000000000404400000000000040240000000000004024000000000000403400000000000040140000000000004024000000000000402E0000000000004014000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region Polygon Tests + + [Fact] + public void PolygonTest_FromBinary_BigEndian() + { + // ARRANGE + + // POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)) + byte[] bytes = HexStringToByteArray("00000000030000000100000005403E0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403E0000000000004024000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + Polygon polygon = Assert.IsType(geo); + } + + [Fact] + public void PolygonTest_FromBinary_LittleEndian() + { + // ARRANGE + + // POLYGON((30 10,40 40,20 40,10 20,30 10)) + byte[] bytes = HexStringToByteArray("010300000001000000050000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440000000000000244000000000000034400000000000003e400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + Polygon polygon = Assert.IsType(geo); + } + + [Fact] + public void PolygonTest_ToBinary_LittleEndian() + { + // ARRANGE + + // POLYGON((30 10,40 40,20 40,10 20,30 10)) + byte[] bytes = HexStringToByteArray("010300000001000000050000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440000000000000244000000000000034400000000000003e400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void PolygonTest_ToBinary_BigEndian() + { + // ARRANGE + + // POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)) + byte[] bytes = HexStringToByteArray("00000000030000000100000005403E0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403E0000000000004024000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region GeometryCollection Tests + + [Fact] + public void GeometryCollectionTest_FromBinary_BigEndian() + { + // ARRANGE + + // GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) + byte[] bytes = HexStringToByteArray("0000000007000000030000000001404400000000000040240000000000000000000002000000034024000000000000402400000000000040340000000000004034000000000000402400000000000040440000000000000000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403E00000000000040440000000000004044000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + GeometryCollection geoCollection = Assert.IsType(geo); + Point point = Assert.IsType(geoCollection.Geometries.ElementAt(0)); + LineString lineString = Assert.IsType(geoCollection.Geometries.ElementAt(1)); + Polygon polygon = Assert.IsType(geoCollection.Geometries.ElementAt(2)); + Assert.Equal(40, point.Coordinates.Longitude); + Assert.Equal(10, point.Coordinates.Latitude); + } + + [Fact] + public void GeometryCollectionTest_FromBinary_LittleEndian() + { + // ARRANGE + + // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) + byte[] bytes = HexStringToByteArray("010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + + // ASSERT + GeometryCollection geoCollection = Assert.IsType(geo); + Point point = Assert.IsType(geoCollection.Geometries.ElementAt(0)); + LineString lineString = Assert.IsType(geoCollection.Geometries.ElementAt(1)); + Assert.Equal(4, point.Coordinates.Longitude); + Assert.Equal(6, point.Coordinates.Latitude); + } + + [Fact] + public void GeometryCollectionTest_ToBinary_LittleEndian() + { + // ARRANGE + + // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) + byte[] bytes = HexStringToByteArray("010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + [Fact] + public void GeometryCollectionTest_ToBinary_BigEndian() + { + // ARRANGE + + // GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) + byte[] bytes = HexStringToByteArray("0000000007000000030000000001404400000000000040240000000000000000000002000000034024000000000000402400000000000040340000000000004034000000000000402400000000000040440000000000000000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403E00000000000040440000000000004044000000000000"); + + // ACT + Geometry geo = WkbConverter.FromBinary(bytes); + byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); + + // ASSERT + Assert.Equal(bytes, newBytes); + } + + #endregion + + #region Private Methods + + private static byte[] HexStringToByteArray(string hexString) + { + if (hexString.Length % 2 != 0) + { + throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString)); + } + + byte[] data = new byte[hexString.Length / 2]; + + for (int index = 0; index < data.Length; index++) + { + string byteValue = hexString.Substring(index * 2, 2); + data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + return data; + } + + #endregion + } +} diff --git a/GeoJSON/Feature.cs b/GeoJSON/Feature.cs index 67f9fbe..ca7fa78 100644 --- a/GeoJSON/Feature.cs +++ b/GeoJSON/Feature.cs @@ -1,6 +1,5 @@ using BAMCIS.GeoJSON.Serde; using Newtonsoft.Json; -using System; using System.Collections.Generic; using System.Linq; diff --git a/GeoJSON/GeoJSON.csproj b/GeoJSON/GeoJSON.csproj index 066b965..9184852 100644 --- a/GeoJSON/GeoJSON.csproj +++ b/GeoJSON/GeoJSON.csproj @@ -4,7 +4,7 @@ netstandard1.6;netstandard2.0;net45 1.6 BAMCIS.GeoJSON - 2.2.0 + 2.3.0 Michael Haken bamcis.io A .NET Core library for serializing and deserializing GeoJSON formatted JSON data. Complies with RFC 7946. @@ -16,7 +16,7 @@ https://github.com/bamcis-io/GeoJSON Git GeoJSON RFC7946 - Added Id property for Feature. + Added Well-Known Binary serialization and deserialization support for `Geometry` objects. true GeoJSON.snk false diff --git a/GeoJSON/GeoJson.cs b/GeoJSON/GeoJson.cs index edad540..155034c 100644 --- a/GeoJSON/GeoJson.cs +++ b/GeoJSON/GeoJson.cs @@ -1,4 +1,5 @@ using BAMCIS.GeoJSON.Serde; +using BAMCIS.GeoJSON.Wkb; using Newtonsoft.Json; using System; using System.Collections.Generic; diff --git a/GeoJSON/Geometry.cs b/GeoJSON/Geometry.cs index c2b113f..003a131 100644 --- a/GeoJSON/Geometry.cs +++ b/GeoJSON/Geometry.cs @@ -1,4 +1,5 @@ using BAMCIS.GeoJSON.Serde; +using BAMCIS.GeoJSON.Wkb; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -62,6 +63,36 @@ protected Geometry(GeoJsonType type, bool is3D, IEnumerable boundingBox return JsonConvert.DeserializeObject(json); } + /// + /// Deserialize WKB byte array to Geometry. + /// + /// + /// + public static Geometry FromWkb(byte[] bytes) + { + return WkbConverter.FromBinary(bytes); + } + + /// + /// Deserialize WKB byte array to Geometry. + /// + /// + /// + public static T FromWkb(byte[] bytes) where T : Geometry + { + return WkbConverter.FromBinary(bytes); + } + + /// + /// Serialize this geometry object to WKB + /// + /// The default is LITTLE + /// + public byte[] ToWkb(Endianness endianness = Endianness.LITTLE) + { + return WkbConverter.ToBinary(this, endianness); + } + /// /// Gets the appropriate class type corresponding to the enum /// representing the type diff --git a/GeoJSON/Point.cs b/GeoJSON/Point.cs index d66b701..4ce724e 100644 --- a/GeoJSON/Point.cs +++ b/GeoJSON/Point.cs @@ -12,7 +12,6 @@ namespace BAMCIS.GeoJSON [JsonConverter(typeof(InheritanceBlockerConverter))] public class Point : Geometry { - #region Public Properties /// diff --git a/GeoJSON/Wkb/EndianAwareBinaryReader.cs b/GeoJSON/Wkb/EndianAwareBinaryReader.cs new file mode 100644 index 0000000..aed1c70 --- /dev/null +++ b/GeoJSON/Wkb/EndianAwareBinaryReader.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.Text; + +namespace BAMCIS.GeoJSON.Wkb +{ + /// + /// An extension to the binary reader class that allows you to + /// specify the endianess of the binary data you are reading + /// + public class EndianAwareBinaryReader : BinaryReader + { + #region Private Fields + + private Endianness _endianness = Endianness.LITTLE; + + #endregion + + #region Constructors + + public EndianAwareBinaryReader(Stream input) : base(input) + { + } + + public EndianAwareBinaryReader(Stream input, Encoding encoding) : base(input, encoding) + { + } + + public EndianAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen) + { + } + + public EndianAwareBinaryReader(Stream input, Endianness endianness) : base(input) + { + _endianness = endianness; + } + + public EndianAwareBinaryReader(Stream input, Encoding encoding, Endianness endianness) : base(input, encoding) + { + _endianness = endianness; + } + + public EndianAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen, Endianness endianness) : base(input, encoding, leaveOpen) + { + _endianness = endianness; + } + + #endregion + + #region Public Override Methods + + public override short ReadInt16() => ReadInt16(_endianness); + + public override int ReadInt32() => ReadInt32(_endianness); + + public override long ReadInt64() => ReadInt64(_endianness); + + public override ushort ReadUInt16() => ReadUInt16(_endianness); + + public override uint ReadUInt32() => ReadUInt32(_endianness); + + public override ulong ReadUInt64() => ReadUInt64(_endianness); + + public override double ReadDouble() => ReadDouble(_endianness); + + public override float ReadSingle() => ReadSingle(_endianness); + + public override bool ReadBoolean() => ReadBoolean(_endianness); + + public override char ReadChar() => ReadChar(_endianness); + + #endregion + + #region Public Methods + + public void SetEndianness(Endianness endianness) + { + this._endianness = endianness; + } + + public short ReadInt16(Endianness endianness) => BitConverter.ToInt16(ReadForEndianness(sizeof(short), endianness), 0); + + public int ReadInt32(Endianness endianness) => BitConverter.ToInt32(ReadForEndianness(sizeof(int), endianness), 0); + + public long ReadInt64(Endianness endianness) => BitConverter.ToInt64(ReadForEndianness(sizeof(long), endianness), 0); + + public ushort ReadUInt16(Endianness endianness) => BitConverter.ToUInt16(ReadForEndianness(sizeof(ushort), endianness), 0); + + public uint ReadUInt32(Endianness endianness) => BitConverter.ToUInt32(ReadForEndianness(sizeof(uint), endianness), 0); + + public ulong ReadUInt64(Endianness endianness) => BitConverter.ToUInt64(ReadForEndianness(sizeof(ulong), endianness), 0); + + public float ReadSingle(Endianness endianness) => BitConverter.ToSingle(ReadForEndianness(sizeof(float), endianness), 0); + + public double ReadDouble(Endianness endianness) + { + byte[] temp = ReadForEndianness(sizeof(double), endianness); + + try + { + + return BitConverter.ToDouble(temp, 0); + } + catch (Exception e) + { + return 0; + } + } + + public char ReadChar(Endianness endianness) => BitConverter.ToChar(ReadForEndianness(sizeof(char), endianness), 0); + + public bool ReadBoolean(Endianness endianness) => BitConverter.ToBoolean(ReadForEndianness(sizeof(bool), endianness), 0); + + #endregion + + #region Private Methods + + private byte[] ReadForEndianness(int bytesToRead, Endianness endianness) + { + try + { + byte[] bytesRead = this.ReadBytes(bytesToRead); + + if ((endianness == Endianness.LITTLE && !BitConverter.IsLittleEndian) + || (endianness == Endianness.BIG && BitConverter.IsLittleEndian)) + { + Array.Reverse(bytesRead); + } + + return bytesRead; + } + catch (Exception e) + { + return null; + } + } + + #endregion + } +} diff --git a/GeoJSON/Wkb/EndianAwareBinaryWriter.cs b/GeoJSON/Wkb/EndianAwareBinaryWriter.cs new file mode 100644 index 0000000..7c60b74 --- /dev/null +++ b/GeoJSON/Wkb/EndianAwareBinaryWriter.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace BAMCIS.GeoJSON.Wkb +{ + public class EndianAwareBinaryWriter : BinaryWriter + { + #region Private Fields + + private readonly Endianness _endianness = Endianness.LITTLE; + + #endregion + + #region Constructors + + public EndianAwareBinaryWriter(Stream input) : base(input) + { + } + + public EndianAwareBinaryWriter(Stream input, Encoding encoding) : base(input, encoding) + { + } + + public EndianAwareBinaryWriter(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen) + { + } + + public EndianAwareBinaryWriter(Stream input, Endianness endianness) : base(input) + { + _endianness = endianness; + } + + public EndianAwareBinaryWriter(Stream input, Encoding encoding, Endianness endianness) : base(input, encoding) + { + _endianness = endianness; + } + + public EndianAwareBinaryWriter(Stream input, Encoding encoding, bool leaveOpen, Endianness endianness) : base(input, encoding, leaveOpen) + { + _endianness = endianness; + } + + #endregion + + #region Public Override Methods + + public override void Write(byte value) => Write(value, this._endianness); + + public override void Write(byte[] buffer) => Write(buffer, this._endianness); + + public override void Write(char ch) => Write(ch, this._endianness); + + public override void Write(double value) => Write(value, this._endianness); + + public override void Write(float value) => Write(value, this._endianness); + + public override void Write(int value) => Write(value, this._endianness); + + public override void Write(long value) => Write(value, this._endianness); + + public override void Write(short value) => Write(value, this._endianness); + + public override void Write(uint value) => Write(value, this._endianness); + + public override void Write(ushort value) => Write(value, this._endianness); + + public override void Write(ulong value) => Write(value, this._endianness); + + public override void Write(sbyte value) => Write(value, this._endianness); + + public override void Write(bool value) => Write(value, this._endianness); + + public override void Write(char[] chars) => Write(chars, this._endianness); + #endregion + + #region Public Methods + + public void Write(byte value, Endianness endianness) => this.WriteForEndianness(new byte[1] { value }, endianness); + + public void Write(byte[] value, Endianness endianness) => this.WriteForEndianness(value, endianness); + + public void Write(short value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(int value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(long value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(float value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(double value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(char value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(char[] chars, Endianness endianness) => this.WriteForEndianness(chars.Select(x => (byte)x).ToArray(), endianness); + + public void Write(UInt16 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(UInt32 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(UInt64 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(bool value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void Write(sbyte value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); + + public void WriteEndianness() => WriteEndianness(this._endianness); + + public void WriteEndianness(Endianness endianness) => this.Write((byte)endianness); + + #endregion + + #region Private Methods + + private EndianAwareBinaryWriter WriteForEndianness(byte[] bytesToWrite, Endianness endianness) + { + if ((endianness == Endianness.LITTLE && !BitConverter.IsLittleEndian) + || (endianness == Endianness.BIG && BitConverter.IsLittleEndian)) + { + Array.Reverse(bytesToWrite); + } + + this.BaseStream.Write(bytesToWrite, 0, bytesToWrite.Length); + + return this; + } + + #endregion + } +} diff --git a/GeoJSON/Wkb/Endianness.cs b/GeoJSON/Wkb/Endianness.cs new file mode 100644 index 0000000..2ea3903 --- /dev/null +++ b/GeoJSON/Wkb/Endianness.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BAMCIS.GeoJSON.Wkb +{ + public enum Endianness : byte + { + BIG = 0x00, + LITTLE = 0x01 + } +} diff --git a/GeoJSON/Wkb/WkbConverter.cs b/GeoJSON/Wkb/WkbConverter.cs new file mode 100644 index 0000000..10aab44 --- /dev/null +++ b/GeoJSON/Wkb/WkbConverter.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace BAMCIS.GeoJSON.Wkb +{ + public static class WkbConverter + { + #region Public Methods + + /// + /// Returns the geometry of the specified type from WKB. + /// + /// The geometry type, i.e. Point, LineString, etc + /// + /// + public static T FromBinary(byte[] bytes) where T : Geometry + { + return (T)FromBinary(bytes); + } + + /// + /// Returns the generic Geometry object from the WKB, but can be + /// casted to the specific underlying geometry object like Point, LineString, + /// Polygon, etc. + /// + /// + /// + public static Geometry FromBinary(byte[] bytes) + { + using (MemoryStream stream = new MemoryStream(bytes)) + { + using (EndianAwareBinaryReader reader = new EndianAwareBinaryReader(stream)) + { + return FromBinary(reader); + } + } + } + + /// + /// Converts a geometry object to WKB based on what it's real underlying + /// type is, like Point or LineString. + /// + /// The geometry to serialize + /// The endianness to use during serialization, defaults to LITTLE endian. + /// + public static byte[] ToBinary(Geometry geo, Endianness endianness = Endianness.LITTLE) + { + using (MemoryStream stream = new MemoryStream()) + { + using (EndianAwareBinaryWriter writer = new EndianAwareBinaryWriter(stream, endianness)) + { + ToBinary(writer, geo); + + return stream.ToArray(); + } + } + } + + #endregion + + #region Private Methods + + #region From Binary Methods + + private static Geometry FromBinary(EndianAwareBinaryReader reader) + { + Endianness endianness = (Endianness)reader.ReadByte(); + reader.SetEndianness(endianness); + + WkbType type = (WkbType)reader.ReadUInt32(); + + switch (type) + { + case WkbType.Geometry: + { + return FromBinary(reader); + } + case WkbType.Point: + { + return PointFrom(reader); + } + case WkbType.LineString: + { + return LineStringFrom(reader); + } + case WkbType.Polygon: + { + return PolygonFrom(reader); + } + case WkbType.MultiPoint: + { + return MultiPointFrom(reader); + } + case WkbType.MultiLineString: + { + return MultiLineStringFrom(reader); + } + case WkbType.MultiPolygon: + { + return MultiPolygonFrom(reader); + } + case WkbType.GeometryCollection: + { + return GeometryCollectionFrom(reader); + } + default: + { + throw new NotSupportedException($"Unsupported WKB type {type.ToString()}."); + } + } + } + + private static Point PointFrom(EndianAwareBinaryReader reader) + { + return new Point(new Position(reader.ReadDouble(), reader.ReadDouble())); + } + + private static LineString LineStringFrom(EndianAwareBinaryReader reader) + { + UInt32 amount = reader.ReadUInt32(); + List coordinates = new List(); + + for (int i = 0; i < amount; i++) + { + coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); + } + + return new LineString(coordinates); + } + + private static Polygon PolygonFrom(EndianAwareBinaryReader reader) + { + int ringQuantity = reader.ReadInt32(); + List rings = new List(ringQuantity); + + for (int i = 0; i < ringQuantity; i++) + { + int numberOfPositions = reader.ReadInt32(); + List coordinates = new List(numberOfPositions); + for (int j = 0; j < numberOfPositions; j++) + { + coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); + } + + rings.Add(new LinearRing(coordinates)); + } + + return new Polygon(rings); + } + + private static MultiPoint MultiPointFrom(EndianAwareBinaryReader reader) + { + List coordinates = new List(); + int numberOfGroups = reader.ReadInt32(); + + for (int i = 0; i < numberOfGroups; i++) + { + Endianness endianness = (Endianness)reader.ReadByte(); + + UInt32 numberOfPositions = reader.ReadUInt32((Endianness)endianness); + + for (int j = 0; j < numberOfPositions; j++) + { + coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); + } + } + + return new MultiPoint(coordinates); + } + + private static MultiLineString MultiLineStringFrom(EndianAwareBinaryReader reader) + { + List lineStrings = new List(); + int quantityOfLineStrings = reader.ReadInt32(); + + for (int i = 0; i < quantityOfLineStrings; i++) + { + /* + Endianness endianness = (Endianness)reader.ReadByte(); + WkbType type = (WkbType)reader.ReadUInt32(endianness); + lineStrings.Add(LineStringFrom(reader)); + */ + + lineStrings.Add((LineString)FromBinary(reader)); + } + + return new MultiLineString(lineStrings); + } + + private static MultiPolygon MultiPolygonFrom(EndianAwareBinaryReader reader) + { + List polygons = new List(); + int quantityOfPolygons = reader.ReadInt32(); + + for (int i = 0; i < quantityOfPolygons; i++) + { + /* + Endianness endianness = (Endianness)reader.ReadByte(); + WkbType type = (WkbType)reader.ReadUInt32(endianness); + polygons.Add(PolygonFrom(reader)); + */ + + polygons.Add((Polygon)FromBinary(reader)); + } + + return new MultiPolygon(polygons); + } + + private static GeometryCollection GeometryCollectionFrom(EndianAwareBinaryReader reader) + { + List geometries = new List(); + int quantity = reader.ReadInt32(); + + for (int i = 0; i < quantity; i++) + { + geometries.Add(FromBinary(reader)); + } + + return new GeometryCollection(geometries); + } + + #endregion + + #region To Binary Methods + + private static void ToBinary(EndianAwareBinaryWriter writer, Point point) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.Point); + WritePosition(writer, point.Coordinates); + } + + private static void ToBinary(EndianAwareBinaryWriter writer, LineString lineString) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.LineString); + writer.Write(lineString.Coordinates.Count()); + + foreach (Position pos in lineString.Coordinates) + { + WritePosition(writer, pos); + } + } + + private static void ToBinary(EndianAwareBinaryWriter writer, Polygon polygon) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.Polygon); + writer.Write((UInt32)polygon.Coordinates.Count()); + + foreach (LinearRing linearRing in polygon.Coordinates) + { + writer.Write((UInt32)linearRing.Coordinates.Count()); + + foreach (Position pos in linearRing.Coordinates) + { + WritePosition(writer, pos); + } + } + } + + private static void ToBinary(EndianAwareBinaryWriter writer, MultiPoint multiPoint) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.MultiPoint); + writer.Write((UInt32)multiPoint.Coordinates.Count()); + + foreach (Position pos in multiPoint.Coordinates) + { + Point point = new Point(pos); + ToBinary(writer, point); + } + } + + private static void ToBinary(EndianAwareBinaryWriter writer, MultiLineString multiLineString) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.MultiLineString); + writer.Write((UInt32)multiLineString.Coordinates.Count()); + + foreach (LineString lineString in multiLineString.Coordinates) + { + ToBinary(writer, lineString); + } + } + + private static void ToBinary(EndianAwareBinaryWriter writer, MultiPolygon multiPolygon) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.MultiPolygon); + writer.Write((UInt32)multiPolygon.Coordinates.Count()); + + foreach (Polygon polygon in multiPolygon.Coordinates) + { + ToBinary(writer, polygon); + } + } + + private static void ToBinary(EndianAwareBinaryWriter writer, GeometryCollection geometryCollection) + { + writer.WriteEndianness(); + writer.Write((UInt32)WkbType.GeometryCollection); + writer.Write((UInt32)geometryCollection.Geometries.Count()); + + foreach (Geometry geometry in geometryCollection.Geometries) + { + ToBinary(writer, geometry); + } + } + + private static void ToBinary(EndianAwareBinaryWriter writer, Geometry geometry) + { + switch (geometry.Type) + { + case GeoJsonType.Point: + { + ToBinary(writer, (Point)geometry); + break; + } + case GeoJsonType.LineString: + { + ToBinary(writer, (LineString)geometry); + break; + } + case GeoJsonType.Polygon: + { + ToBinary(writer, (Polygon)geometry); + break; + } + case GeoJsonType.MultiPoint: + { + ToBinary(writer, (MultiPoint)geometry); + break; + } + case GeoJsonType.MultiLineString: + { + ToBinary(writer, (MultiLineString)geometry); + break; + } + case GeoJsonType.MultiPolygon: + { + ToBinary(writer, (MultiPolygon)geometry); + break; + } + case GeoJsonType.GeometryCollection: + { + ToBinary(writer, (GeometryCollection)geometry); + break; + } + default: + { + throw new NotSupportedException($"The GeoJson type {geometry.Type.ToString()} is not supported for conversion to WKB."); + } + } + } + + private static EndianAwareBinaryWriter WritePosition(EndianAwareBinaryWriter writer, Position position) + { + writer.Write(position.Longitude); + writer.Write(position.Latitude); + + return writer; + } + + #endregion + + #endregion + } +} diff --git a/GeoJSON/Wkb/WkbType.cs b/GeoJSON/Wkb/WkbType.cs new file mode 100644 index 0000000..318c571 --- /dev/null +++ b/GeoJSON/Wkb/WkbType.cs @@ -0,0 +1,128 @@ +using System; + +namespace BAMCIS.GeoJSON.Wkb +{ + /// + /// The different geometry types available in WKB + /// + public enum WkbType : UInt32 + { + // 2D + Geometry = 0000, + Point = 0001, + LineString = 0002, + Polygon = 0003, + MultiPoint = 0004, + MultiLineString = 0005, + MultiPolygon = 0006, + GeometryCollection = 0007, + CircularString = 0008, + CompoundCurve = 0009, + CurvePolygon = 0010, + MultiCurve = 0011, + MultiSurface = 0012, + Curve = 0013, + Surface = 0014, + PolyhedralSurface = 0015, + TIN = 0016, + Triangle = 0017, + Circle = 0018, + GeodesicString = 0019, + EllipticalCurve = 0020, + NurbsCurve = 0021, + Clothoid = 0022, + SpiralCurve = 0023, + CompoundSurface = 0024, + BrepSolid = 0025, + AffinePlacement = 0102, + + // Z + Z_Geometry = 1000, + Z_Point = 1001, + Z_LineString = 1002, + Z_Polygon = 1003, + Z_MultiPoint = 1004, + Z_MultiLineString = 1005, + Z_MultiPolygon = 1006, + Z_GeometryCollection = 1007, + Z_CircularString = 1008, + Z_CompoundCurve = 1009, + Z_CurvePolygon = 1010, + Z_MultiCurve = 1011, + Z_MultiSurface = 1012, + Z_Curve = 1013, + Z_Surface = 1014, + Z_PolyhedralSurface = 1015, + Z_TIN = 1016, + Z_Triangle = 1017, + Z_Circle = 1018, + Z_GeodesicString = 1019, + Z_EllipticalCurve = 1020, + Z_NurbsCurve = 1021, + Z_Clothoid = 1022, + Z_SpiralCurve = 1023, + Z_CompoundSurface = 1024, + Z_BrepSolid = 1025, + Z_AffinePlacement = 1102, + + + // M + M_Geometry = 2000, + M_Point = 2001, + M_LineString = 2002, + M_Polygon = 2003, + M_MultiPoint = 2004, + M_MultiLineString = 2005, + M_MultiPolygon = 2006, + M_GeometryCollection = 2007, + M_CircularString = 2008, + M_CompoundCurve = 2009, + M_CurvePolygon = 2010, + M_MultiCurve = 2011, + M_MultiSurface = 2012, + M_Curve = 2013, + M_Surface = 2014, + M_PolyhedralSurface = 2015, + M_TIN = 2016, + M_Triangle = 2017, + M_Circle = 2018, + M_GeodesicString = 2019, + M_EllipticalCurve = 2020, + M_NurbsCurve = 2021, + M_Clothoid = 2022, + M_SpiralCurve = 2023, + M_CompoundSurface = 2024, + M_BrepSolid = 2025, + M_AffinePlacement = 2102, + + + // ZM + ZM_Geometry = 3000, + ZM_Point = 3001, + ZM_LineString = 3002, + ZM_Polygon = 3003, + ZM_MultiPoint = 3004, + ZM_MultiLineString = 3005, + ZM_MultiPolygon = 3006, + ZM_GeometryCollection = 3007, + ZM_CircularString = 3008, + ZM_CompoundCurve = 3009, + ZM_CurvePolygon = 3010, + ZM_MultiCurve = 3011, + ZM_MultiSurface = 3012, + ZM_Curve = 3013, + ZM_Surface = 3014, + ZM_PolyhedralSurface = 3015, + ZM_TIN = 3016, + ZM_Triangle = 3017, + ZM_Circle = 3018, + ZM_GeodesicString = 3019, + ZM_EllipticalCurve = 3020, + ZM_NurbsCurve = 3021, + ZM_Clothoid = 3022, + ZM_SpiralCurve = 3023, + ZM_CompoundSurface = 3024, + ZM_BrepSolid = 3025, + ZM_AffinePlacement = 3102 + } +} diff --git a/README.md b/README.md index b9f3bc7..4428e24 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ An implementation of GeoJSON written in .NET Core 2.0. The library complies with * [Example 1](#example-1) * [Example 2](#example-2) * [Example 3](#example-3) + * [Example 4](#example-4) * [Usage Notes](#usage-notes) * [Global Configuration](#global-configuration) - [Revision History](#revision-history) @@ -78,6 +79,52 @@ MultiPoint mp = new MultiPoint(new Position[] {pos1, pos2}); string json = JsonConvert.Serialize(mp); ``` +### Example 4 +The library also supports conversion of `Geometry` objects to and from Well-Known Binary (WKB). For example: + +```json +{ + "type": "Point", + "coordinates": [ 2.0, 4.0 ] +} +``` + +```csharp +Point point = new Point(102.0, 0.5); +byte[] wkb = point.ToWkb(); +point = Geometry.FromWkb(wkb); +``` + +The binary produced is `0x000000000140000000000000004010000000000000`. You can also convert this way. + +```csharp +Point point = new Point(102.0, 0.5); +byte[] wkb = point.ToWkb(); +Geometry geo = Point.FromWkb(wkb) +point = (Point)geo; +``` + +You can also specify the endianness of the binary encoding (the default is LITTLE). + +```csharp +Point point = new Point(102.0, 0.5); +byte[] wkb = point.ToWkb(Endianness.BIG); +Geometry geo = Point.FromWkb(wkb) +point = (Point)geo; +``` + +Finally, you can use the `WkbConverter` class directly. + +```csharp +Point point = new Point(new Position(2.0, 4.0)); +byte[] bytes = WkbConverter.ToBinary(point, Endianness.BIG); +``` + +```csharp +byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); +Point point = WkbConverter.FromBinary(bytes); +``` + ### Usage Notes Each of the 9 GeoJSON types: **Feature**, **FeatureCollection**, **GeometryCollection**, **LineString**, **MultiLineString**, **MultiPoint**, **MultiPolygon**, **Point**, and **Polygon** all have convenience methods ToJson() and FromJson() to make serialization and deserialization easy. @@ -118,6 +165,9 @@ Feature geo = JsonConvert.DeserializeObject(content); ## Revision History +### 2.3.0 +Added Well-Known Binary serialization and deserialization support for `Geometry` objects. + ### 2.2.0 Added an Id property to in `Feature`. Also added a global config object that can be used to ignore validation of coordinate values.