/* * Obtained code curtecy of MHeyman from http://stackoverflow.com/questions/16120171/dbgeography-polygon-to-json * Comprehensively shown at: http://snipt.org/Rihji1 * * Adapted by Francois Grobler by removing dependency on AplPed.Common and created own TypeNameExtension to be used here. */ using System; using System.Collections.Generic; using System.Data.Entity.Spatial; using System.IO; using System.Linq; using System.Text.RegularExpressions; //using AplPed.Common; //Removed this dependancy: see TypeNameExtension using Microsoft.SqlServer.Types; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace GeoJsonLibrary { /// /// The GeoJSON converter /// public class DbGeographyGeoJsonConverter : JsonConverter { /// /// Well-known-binary point value. /// private const int PointWkb = 1; /// /// Well-known-binary line value. /// private const int LineStringWkb = 2; /// /// Well-known-binary polygon value. /// private const int PolygonWkb = 3; /// /// Well-known-binary multi-point value. /// private const int MultiPointWkb = 4; /// /// Well-known-binary multi-line value. /// private const int MultiLineStringWkb = 5; /// /// Well-known-binary multi-polygon value. /// private const int MultiPolygonWkb = 6; /// /// Well-known-binary geometry collection value. /// private const int GeometryCollectionWkb = 7; /// /// Well-known-binary line value. /// private static readonly Dictionary WkbTypes = new Dictionary { { PointWkb, "Point" }, { LineStringWkb, "LineString" }, { PolygonWkb, "Polygon" }, { MultiPointWkb, "MultiPoint" }, { MultiLineStringWkb, "MultiLineString" }, { MultiPolygonWkb, "MultiPolygon" }, { GeometryCollectionWkb, "GeometryCollection" } }; /// /// The types derived from accessed by name. /// private static readonly Dictionary GeoBases = new Dictionary { { "Point", typeof(Point) }, { "LineString", typeof(LineString) }, { "Polygon", typeof(Polygon) }, { "MultiPoint", typeof(MultiPoint) }, { "MultiLineString", typeof(MultiLineString) }, { "MultiPolygon", typeof(MultiPolygon) }, { "GeometryCollection", typeof(Collection) }, }; /// /// The indexes per point. /// public enum IndexesPerPoint { /// /// Two indexes per point. /// Two, /// /// Three indexes per point. /// Three, /// /// Four indexes per point. /// Four } /// /// Read the GeoJSON type value. /// /// /// The JSON object. /// /// /// The coordinate System. /// /// /// A function that can read the value. /// /// /// Unexpected JSON. /// /// /// Leaves the reader positioned where the value should start. /// public static GeoBase ParseJsonObjectToGeoBase(JObject jsonObject, out int? coordinateSystem) { var type = jsonObject["type"]; if (type == null) { throw new ArgumentException(string.Format("Expected a \"type\" property, found [{0}]", string.Join(", ", jsonObject.Properties().Select(p => p.Name)))); } if (type.Type != JTokenType.String) { throw new ArgumentException(string.Format("Expected a string token for the type of the GeoJSON type, got {0}", type.Type), "jsonObject"); } Type geoType; if (!GeoBases.TryGetValue(type.Value(), out geoType)) { throw new ArgumentException( string.Format( "Got unsupported GeoJSON object type {0}. Expected one of [{1}]", type.Value(), string.Join(", ", GeoBases.Keys)), "jsonObject"); } var geoObject = geoType == typeof(Collection) ? jsonObject["geometries"] : jsonObject["coordinates"]; if (geoObject == null) { throw new ArgumentException( string.Format( "Expected a field named \"{0}\", found [{1}]", geoType == typeof(Collection) ? "geometries" : "coordinates", string.Join(", ", jsonObject.Properties().Select(p => p.Name))), "jsonObject"); } var crs = jsonObject["crs"]; coordinateSystem = crs != null ? ParseCrs(crs.Value()) : null; var geo = (GeoBase)Activator.CreateInstance(geoType); geo.ParseJson(geoObject.Value()); return geo; } /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { BinaryReader lebr; BinaryReader bebr; var geographyValue = value as DbGeography; int coordinateSystemId; if (geographyValue != null) { //Check if this is a SqlGeography, if so use the Sql As BinaryZM, else use the Generic AsBinary var sqlGeography = geographyValue.ProviderValue as SqlGeography; var br = new BinaryReader(new MemoryStream(sqlGeography != null ? sqlGeography.AsBinaryZM().Value : geographyValue.AsBinary())); lebr = BitConverter.IsLittleEndian ? br : new ReverseEndianBinaryReader(br.BaseStream); bebr = BitConverter.IsLittleEndian ? new ReverseEndianBinaryReader(br.BaseStream) : br; coordinateSystemId = geographyValue.CoordinateSystemId; } else { var geometryValue = value as DbGeometry; if (geometryValue != null) { //Check if this is a SqlGeography, if so use the Sql As BinaryZM, else use the Generic AsBinary var sqlGeometry = geometryValue.ProviderValue as SqlGeometry; var br = new BinaryReader(new MemoryStream(sqlGeometry != null ? sqlGeometry.AsBinaryZM().Value : geometryValue.AsBinary())); lebr = BitConverter.IsLittleEndian ? br : new ReverseEndianBinaryReader(br.BaseStream); bebr = BitConverter.IsLittleEndian ? new ReverseEndianBinaryReader(br.BaseStream) : br; coordinateSystemId = geometryValue.CoordinateSystemId; } else { throw new ArgumentException( string.Format("Expecting DbGeography or DbGeometry, got {0}", value.GetType().CSharpName()), "value"); } } var jsonObject = WriteObject(lebr, bebr); jsonObject.Add( "crs", new JObject { new JProperty("type", "name"), new JProperty( "properties", new JObject { new JProperty("name", string.Format("EPSG:{0}", coordinateSystemId)) }) }); writer.WriteRawValue(jsonObject.ToString(Formatting.None)); } /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Load JObject from stream JObject jsonObject = JObject.Load(reader); // Create target object based on JObject object target = CreateDbGeo(jsonObject, objectType); // Populate the object properties serializer.Populate(jsonObject.CreateReader(), target); return target; } /// public override bool CanConvert(Type objectType) { return objectType == typeof(DbGeography) || objectType == typeof(DbGeometry); } /// /// Parse the coordinate system object and return it's value. /// /// /// The JSON object. /// /// /// The coordinate system value; null if couldn't parse it (only a couple EPSG-style values). /// private static int? ParseCrs(JObject jsonObject) { var properties = jsonObject["properties"]; if (properties != null && properties.Type == JTokenType.Object) { var p = properties.Value(); var name = p["name"]; if (name != null) { var s = name.Value(); if (!string.IsNullOrWhiteSpace(s)) { var m = Regex.Match( s, @"^\s*(urn\s*:\s*ogc\s*:\s*def\s*:crs\s*:EPSG\s*:\s*[\d.]*\s*:|EPSG\s*:)\s*(?\d+)\s*$", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); if (m.Success) { return int.Parse(m.Groups["value"].Value); } } } } return null; } /// /// Get well known binary from a . /// /// /// The JSON object. /// /// /// The default coordinate system id. /// /// /// A tuple of the well-known-binary and the coordinate system identifier. /// /// /// Unexpected JSON. /// private static Tuple GetWellKnownBinary(JObject jsonObject, int defaultCoordinateSystemId) { var ob = new MemoryStream(); int? coordinateSystemId; var geoBase = ParseJsonObjectToGeoBase(jsonObject, out coordinateSystemId); geoBase.WellKnownBinary(ob); return new Tuple(ob.ToArray(), coordinateSystemId.HasValue ? coordinateSystemId.Value : defaultCoordinateSystemId); } /// /// Write a well-known binary object to JSON. /// /// /// The little-endian binary reader. /// /// /// The big-endian binary reader. /// /// /// Unexpected well-known binary. /// /// /// The for the given binary data. /// private static JObject WriteObject(BinaryReader lebr, BinaryReader bebr) { var jsonObject = new JObject(); var br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; int gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); gtype %= 1000; string objTypeName; if (!WkbTypes.TryGetValue(gtype, out objTypeName)) { throw new ArgumentException( string.Format( "Unsupported type {0}. Supported types: {1}", gtype, string.Join(", ", WkbTypes.Select(kv => string.Format("({0}, {1}", kv.Key, kv.Value))))); } jsonObject.Add("type", objTypeName); if (ipp == IndexesPerPoint.Two) { WriteXy(jsonObject, gtype, br, lebr, bebr); } else if (ipp == IndexesPerPoint.Three) { WriteXyz(jsonObject, gtype, br, lebr, bebr); } else { WriteXyzm(jsonObject, gtype, br, lebr, bebr); } return jsonObject; } /// /// Write elements that don't have altitude information. /// /// /// The JSON object. /// /// /// The type to write. /// /// /// The binary reader. /// /// /// The little-endian binary reader. /// /// /// The big-endian binary reader. /// /// /// Something like a multi-line string isn't made up of line strings. /// private static void WriteXy(JObject jsonObject, int gtype, BinaryReader br, BinaryReader lebr, BinaryReader bebr) { if (gtype == GeometryCollectionWkb) { var array = new JArray(); int count = br.ReadInt32(); for (int i = 0; i < count; ++i) { array.Add(WriteObject(lebr, bebr)); } jsonObject.Add("geometries", array); } else { var array = new JArray(); switch (gtype) { case PointWkb: array.Add(br.ReadDouble()); array.Add(br.ReadDouble()); break; case LineStringWkb: foreach (var a in WriteLineXy(br)) { array.Add(a); } break; case PolygonWkb: foreach (var a in WritePolygonXy(br)) { array.Add(a); } break; case MultiPointWkb: int pointCount = br.ReadInt32(); for (int i = 0; i < pointCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != PointWkb) { throw new ArgumentException(string.Format("Expected a type of 1, got {0}", gtype), "lebr"); } array.Add( ipp == IndexesPerPoint.Two ? new JArray { br.ReadDouble(), br.ReadDouble() } : ipp == IndexesPerPoint.Three ? new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble() } : new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble(), br.ReadDouble() }); } break; case MultiLineStringWkb: int lineCount = br.ReadInt32(); for (int i = 0; i < lineCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != LineStringWkb) { throw new ArgumentException(string.Format("Expected a type of 2, got {0}", gtype), "lebr"); } var lineArray = new JArray(); foreach (var a in ipp == IndexesPerPoint.Two ? WriteLineXy(br) : ipp == IndexesPerPoint.Three ? WriteLineXyz(br) : WriteLineXyzm(br)) { lineArray.Add(a); } array.Add(lineArray); } break; case MultiPolygonWkb: int polygonCount = br.ReadInt32(); for (int i = 0; i < polygonCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != PolygonWkb) { throw new ArgumentException(string.Format("Expected a type of 3, got {0}", gtype), "lebr"); } var polygonArray = new JArray(); foreach (var a in ipp == IndexesPerPoint.Two ? WritePolygonXy(br) : ipp == IndexesPerPoint.Three ? WritePolygonXyz(br) : WritePolygonXyzm(br)) { polygonArray.Add(a); } array.Add(polygonArray); } break; default: throw new ArgumentException(string.Format("Unsupported geo-type {0}", gtype), "lebr"); } jsonObject.Add("coordinates", array); } } /// /// Write elements that have altitude information. /// /// /// The JSON object. /// /// /// The type to write. /// /// /// The binary reader. /// /// /// The little-endian binary reader. /// /// /// The big-endian binary reader. /// /// /// Something like a multi-line string isn't made up of line strings. /// private static void WriteXyz(JObject jsonObject, int gtype, BinaryReader br, BinaryReader lebr, BinaryReader bebr) { if (gtype == GeometryCollectionWkb) { var array = new JArray(); int count = br.ReadInt32(); for (int i = 0; i < count; ++i) { array.Add(WriteObject(lebr, bebr)); } jsonObject.Add("geometries", array); } else { var array = new JArray(); switch (gtype) { case PointWkb: array.Add(br.ReadDouble()); array.Add(br.ReadDouble()); array.Add(br.ReadDouble()); break; case LineStringWkb: foreach (var a in WriteLineXyz(br)) { array.Add(a); } break; case PolygonWkb: foreach (var a in WritePolygonXyz(br)) { array.Add(a); } break; case MultiPointWkb: int pointCount = br.ReadInt32(); for (int i = 0; i < pointCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != PointWkb) { throw new ArgumentException(string.Format("Expected a type of 1, got {0}", gtype), "lebr"); } array.Add( ipp == IndexesPerPoint.Four ? new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble(), br.ReadDouble() } : ipp == IndexesPerPoint.Three ? new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble() } : new JArray { br.ReadDouble(), br.ReadDouble() }); } break; case MultiLineStringWkb: int lineCount = br.ReadInt32(); for (int i = 0; i < lineCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != LineStringWkb) { throw new ArgumentException(string.Format("Expected a type of 2, got {0}", gtype), "lebr"); } var lineArray = new JArray(); foreach (var a in ipp == IndexesPerPoint.Two ? WriteLineXy(br) : ipp == IndexesPerPoint.Three ? WriteLineXyz(br) : WriteLineXyzm(br)) { lineArray.Add(a); } array.Add(lineArray); } break; case MultiPolygonWkb: int polygonCount = br.ReadInt32(); for (int i = 0; i < polygonCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != PolygonWkb) { throw new ArgumentException(string.Format("Expected a type of 3, got {0}", gtype), "lebr"); } var polygonArray = new JArray(); foreach (var a in ipp == IndexesPerPoint.Two ? WritePolygonXy(br) : ipp == IndexesPerPoint.Three ? WritePolygonXyz(br) : WritePolygonXyzm(br)) { polygonArray.Add(a); } array.Add(polygonArray); } break; default: throw new ArgumentException(string.Format("Unsupported geo-type {0}", gtype), "lebr"); } jsonObject.Add("coordinates", array); } } /// /// Write elements that have altitude information. /// /// /// The JSON object. /// /// /// The type to write. /// /// /// The binary reader. /// /// /// The little-endian binary reader. /// /// /// The big-endian binary reader. /// /// /// Something like a multi-line string isn't made up of line strings. /// private static void WriteXyzm(JObject jsonObject, int gtype, BinaryReader br, BinaryReader lebr, BinaryReader bebr) { if (gtype == GeometryCollectionWkb) { var array = new JArray(); int count = br.ReadInt32(); for (int i = 0; i < count; ++i) { array.Add(WriteObject(lebr, bebr)); } jsonObject.Add("geometries", array); } else { var array = new JArray(); switch (gtype) { case PointWkb: array.Add(br.ReadDouble()); array.Add(br.ReadDouble()); array.Add(br.ReadDouble()); array.Add(br.ReadDouble()); break; case LineStringWkb: foreach (var a in WriteLineXyzm(br)) { array.Add(a); } break; case PolygonWkb: foreach (var a in WritePolygonXyzm(br)) { array.Add(a); } break; case MultiPointWkb: int pointCount = br.ReadInt32(); for (int i = 0; i < pointCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != PointWkb) { throw new ArgumentException(string.Format("Expected a type of 1, got {0}", gtype), "lebr"); } array.Add( ipp == IndexesPerPoint.Four ? new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble(), br.ReadDouble() } : ipp == IndexesPerPoint.Three ? new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble() } : new JArray { br.ReadDouble(), br.ReadDouble() }); } break; case MultiLineStringWkb: int lineCount = br.ReadInt32(); for (int i = 0; i < lineCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != LineStringWkb) { throw new ArgumentException(string.Format("Expected a type of 2, got {0}", gtype), "lebr"); } var lineArray = new JArray(); foreach (var a in ipp == IndexesPerPoint.Two ? WriteLineXy(br) : ipp == IndexesPerPoint.Three ? WriteLineXyz(br) : WriteLineXyzm(br)) { lineArray.Add(a); } array.Add(lineArray); } break; case MultiPolygonWkb: int polygonCount = br.ReadInt32(); for (int i = 0; i < polygonCount; ++i) { br = lebr.ReadByte() == 0 ? bebr : lebr; IndexesPerPoint ipp; gtype = JsonSafeWellKnownBinaryGeographicType(br, out ipp); if ((gtype % 1000) != PolygonWkb) { throw new ArgumentException(string.Format("Expected a type of 3, got {0}", gtype), "lebr"); } var polygonArray = new JArray(); foreach (var a in ipp == IndexesPerPoint.Two ? WritePolygonXy(br) : ipp == IndexesPerPoint.Three ? WritePolygonXyz(br) : WritePolygonXyzm(br)) { polygonArray.Add(a); } array.Add(polygonArray); } break; default: throw new ArgumentException(string.Format("Unsupported geo-type {0}", gtype), "lebr"); } jsonObject.Add("coordinates", array); } } /// /// The GeoJSON safe well-known-binary type. /// /// /// The binary reader to get the geographic type from. /// /// /// The indexes per point. /// /// /// The . /// /// /// The geographic type read from isn't supported by GeoJSON. /// private static int JsonSafeWellKnownBinaryGeographicType(BinaryReader br, out IndexesPerPoint indexesPerPoint) { int ret = br.ReadInt32(); int gtype = ret % 1000; if (WkbTypes.ContainsKey(gtype)) { int mod = ret / 1000; switch (mod) { case 0: indexesPerPoint = IndexesPerPoint.Two; return ret; case 1: indexesPerPoint = IndexesPerPoint.Three; return ret; case 2: throw new NotSupportedException( "Found point with XYM instead of XYZM format. GeoJSON does not support having an M value in the point without having a Z coordinate"); case 3: indexesPerPoint = IndexesPerPoint.Four; return ret; } throw new NotSupportedException( string.Format( "Got {0} with modifier {1} ({2}). Don't know how to deal with modifier {1}.", WkbTypes[gtype], mod, ret)); } throw new NotSupportedException( string.Format( "Found well-known-binary type {0}, GeoJSON only handles values: {1}", gtype, string.Join(", ", WkbTypes.Select(kv => string.Format("{0}({1})", kv.Key, kv.Value))))); } /// /// Write a JSON polygon from well-known binary. /// /// /// Read from this. /// /// /// The enumerable for the polygon. /// private static IEnumerable WritePolygonXy(BinaryReader br) { var ret = new List(); int ringCount = br.ReadInt32(); for (int ri = 0; ri < ringCount; ++ri) { var array = new JArray(); foreach (var a in WriteLineXy(br)) { array.Add(a); } ret.Add(array); } return ret; } /// /// Write a JSON polygon from well-known binary. /// /// /// Read from this. /// /// /// The enumerable for the polygon. /// private static IEnumerable WritePolygonXyz(BinaryReader br) { var ret = new List(); int ringCount = br.ReadInt32(); for (int ri = 0; ri < ringCount; ++ri) { var array = new JArray(); foreach (var a in WriteLineXyz(br)) { array.Add(a); } ret.Add(array); } return ret; } /// /// Write a JSON polygon from well-known binary. /// /// /// Read from this. /// /// /// The enumerable for the polygon. /// private static IEnumerable WritePolygonXyzm(BinaryReader br) { var ret = new List(); int ringCount = br.ReadInt32(); for (int ri = 0; ri < ringCount; ++ri) { var array = new JArray(); foreach (var a in WriteLineXyzm(br)) { array.Add(a); } ret.Add(array); } return ret; } /// /// Write a JSON line from well-known binary. /// /// /// Read from this. /// /// /// The enumerable for the line. /// private static IEnumerable WriteLineXy(BinaryReader br) { var ret = new List(); int count = br.ReadInt32() * 2; for (int i = 0; i < count; i += 2) { var array = new JArray { br.ReadDouble(), br.ReadDouble() }; ret.Add(array); } return ret; } /// /// Write a JSON line from well-known binary. /// /// /// Read from this. /// /// /// The enumerable for the line. /// private static IEnumerable WriteLineXyz(BinaryReader br) { var ret = new List(); int count = br.ReadInt32() * 2; for (int i = 0; i < count; i += 2) { var array = new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble() }; ret.Add(array); } return ret; } /// /// Write a JSON line from well-known binary. /// /// /// Read from this. /// /// /// The enumerable for the line. /// private static IEnumerable WriteLineXyzm(BinaryReader br) { var ret = new List(); int count = br.ReadInt32() * 2; for (int i = 0; i < count; i += 2) { var array = new JArray { br.ReadDouble(), br.ReadDouble(), br.ReadDouble(), br.ReadDouble() }; ret.Add(array); } return ret; } /// /// Create a or from . /// /// /// The JSON object. /// /// /// The object type. /// /// /// The or /// /// /// is not a or . /// private static object CreateDbGeo(JObject jsonObject, Type objectType) { Func, object> returnValue; int defaultCoordinateSystemId; if (typeof(DbGeography).IsAssignableFrom(objectType)) { returnValue = x => (object)DbGeography.FromBinary(x.Item1, x.Item2); defaultCoordinateSystemId = DbGeography.DefaultCoordinateSystemId; } else if (typeof(DbGeometry).IsAssignableFrom(objectType)) { returnValue = x => (object)DbGeometry.FromBinary(x.Item1, x.Item2); defaultCoordinateSystemId = DbGeometry.DefaultCoordinateSystemId; } else { throw new ArgumentException(string.Format("Expected a DbGeography or DbGeometry objectType. Got {0}", objectType.CSharpName()), "objectType"); } return jsonObject.Type == JTokenType.Null || jsonObject.Type == JTokenType.None ? null : returnValue(GetWellKnownBinary(jsonObject, defaultCoordinateSystemId)); } /// /// A that expects byte-reversed numeric values. /// public class ReverseEndianBinaryReader : BinaryReader { /// /// Initializes a new instance of the class. /// /// /// The stream. /// public ReverseEndianBinaryReader(Stream stream) : base(stream) { } /// public override short ReadInt16() { return BitConverter.ToInt16(this.ReadBytes(2).Reverse().ToArray(), 0); } /// public override int ReadInt32() { return BitConverter.ToInt32(this.ReadBytes(4).Reverse().ToArray(), 0); } /// public override long ReadInt64() { return BitConverter.ToInt64(this.ReadBytes(8).Reverse().ToArray(), 0); } /// public override ushort ReadUInt16() { return BitConverter.ToUInt16(this.ReadBytes(2).Reverse().ToArray(), 0); } /// public override uint ReadUInt32() { return BitConverter.ToUInt32(this.ReadBytes(4).Reverse().ToArray(), 0); } /// public override ulong ReadUInt64() { return BitConverter.ToUInt64(this.ReadBytes(8).Reverse().ToArray(), 0); } /// public override float ReadSingle() { return BitConverter.ToSingle(this.ReadBytes(4).Reverse().ToArray(), 0); } /// public override double ReadDouble() { return BitConverter.ToDouble(this.ReadBytes(8).Reverse().ToArray(), 0); } } /// /// Base class for the types that know how to parse JSON and write well-known binary. /// public abstract class GeoBase { /// /// The point well-known bytes descriptor. /// protected static readonly byte[] PointXyWkbs = BitConverter.GetBytes(PointWkb); /// /// The line-string well-known bytes descriptor. /// protected static readonly byte[] LineStringXyWkbs = BitConverter.GetBytes(LineStringWkb); /// /// The polygon well-known bytes descriptor. /// protected static readonly byte[] PolygonXyWkbs = BitConverter.GetBytes(PolygonWkb); /// /// The multi-point well-known bytes descriptor. /// protected static readonly byte[] MultiPointXyWkbs = BitConverter.GetBytes(MultiPointWkb); /// /// The multi-line-string well-known bytes descriptor. /// protected static readonly byte[] MultiLineStringXyWkbs = BitConverter.GetBytes(MultiLineStringWkb); /// /// The multi-polygon well-known bytes descriptor. /// protected static readonly byte[] MultiPolygonXyWkbs = BitConverter.GetBytes(MultiPolygonWkb); /// /// The collection well-known bytes descriptor. /// protected static readonly byte[] GeometryCollectionXyWkbs = BitConverter.GetBytes(GeometryCollectionWkb); /// /// The point well-known bytes descriptor. /// protected static readonly byte[] PointXyzWkbs = BitConverter.GetBytes(PointWkb + 1000); /// /// The line-string well-known bytes descriptor. /// protected static readonly byte[] LineStringXyzWkbs = BitConverter.GetBytes(LineStringWkb + 1000); /// /// The polygon well-known bytes descriptor. /// protected static readonly byte[] PolygonXyzWkbs = BitConverter.GetBytes(PolygonWkb + 1000); /// /// The multi-point well-known bytes descriptor. /// protected static readonly byte[] MultiPointXyzWkbs = BitConverter.GetBytes(MultiPointWkb + 1000); /// /// The multi-line-string well-known bytes descriptor. /// protected static readonly byte[] MultiLineStringXyzWkbs = BitConverter.GetBytes(MultiLineStringWkb + 1000); /// /// The multi-polygon well-known bytes descriptor. /// protected static readonly byte[] MultiPolygonXyzWkbs = BitConverter.GetBytes(MultiPolygonWkb + 1000); /// /// The collection well-known bytes descriptor. /// protected static readonly byte[] GeometryCollectionXyzWkbs = BitConverter.GetBytes(GeometryCollectionWkb + 1000); /// /// The point well-known bytes descriptor. /// protected static readonly byte[] PointXyzmWkbs = BitConverter.GetBytes(PointWkb + 3000); /// /// The line-string well-known bytes descriptor. /// protected static readonly byte[] LineStringXyzmWkbs = BitConverter.GetBytes(LineStringWkb + 3000); /// /// The polygon well-known bytes descriptor. /// protected static readonly byte[] PolygonXyzmWkbs = BitConverter.GetBytes(PolygonWkb + 3000); /// /// The multi-point well-known bytes descriptor. /// protected static readonly byte[] MultiPointXyzmWkbs = BitConverter.GetBytes(MultiPointWkb + 3000); /// /// The multi-line-string well-known bytes descriptor. /// protected static readonly byte[] MultiLineStringXyzmWkbs = BitConverter.GetBytes(MultiLineStringWkb + 3000); /// /// The multi-polygon well-known bytes descriptor. /// protected static readonly byte[] MultiPolygonXyzmWkbs = BitConverter.GetBytes(MultiPolygonWkb + 3000); /// /// The collection well-known bytes descriptor. /// protected static readonly byte[] GeometryCollectionXyzmWkbs = BitConverter.GetBytes(GeometryCollectionWkb + 3000); /// /// Helper function to parse a of . /// /// /// Get JSON from this. /// /// /// The parsed JSON. /// /// /// Unexpected JSON. /// public static List ParseListPosition(JArray array) { if (array.Cast().Any(l => l.Count < 2)) { throw new ArgumentException( string.Format( "Expected all points to have greater than two points, got {0} with zero and {1} with one", array.Cast().Count(l => l.Count == 0), array.Cast().Count(l => l.Count == 1)), "array"); } return array.Select(l => new Position(l)).ToList(); } /// /// Helper function to parse a of of . /// /// /// Get JSON from this. /// /// /// The parsed JSON. /// /// /// Unexpected JSON. /// public static List> ParseListListPosition(JArray array) { if (array.Cast().Any(r => r.Cast().Any(l => l.Count < 2))) { throw new ArgumentException( string.Format( "Expected all points to have greater than two points, got {0} with zero and {1} with one", array.Cast().Sum(r => r.Cast().Count(l => l.Count == 0)), array.Cast().Sum(r => r.Cast().Count(l => l.Count == 1))), "array"); } return array.Select(r => r.Select(l => new Position(l)).ToList()).ToList(); } /// /// Helper function to parse a of of of . /// /// /// Get JSON from this. /// /// /// The parsed JSON. /// /// /// Unexpected JSON. /// public static List>> ParseListListListPosition(JArray array) { if (array.Cast().Any(p => p.Cast().Any(r => r.Cast().Any(l => l.Count < 2)))) { throw new ArgumentException( string.Format( "Expected all points to have greater than two points, got {0} with zero and {1} with one", array.Cast().Sum(p => p.Cast().Sum(r => r.Cast().Count(l => l.Count == 0))), array.Cast().Sum(p => p.Cast().Sum(r => r.Cast().Count(l => l.Count == 1)))), "array"); } return array.Select(p => p.Select(r => r.Select(l => new Position(l)).ToList()).ToList()).ToList(); } /// /// Write the contents to in well-known /// binary format. /// /// /// The stream to write the position to. /// public abstract void WellKnownBinary(Stream sout); /// /// Parse JSON into the -derived type. /// /// /// Get JSON from this. /// public abstract void ParseJson(JArray array); } /// /// The position. /// public class Position : GeoBase { /// /// Initializes a new instance of the class. /// /// /// The holding 2 or three doubles. /// /// /// If holds less than 2 values. /// public Position(JToken token) { if (token.Count() < 2) { throw new ArgumentException( string.Format("Expected at least 2 elements, got {0}", token.Count()), "token"); } this.P1 = (double)token[0]; this.P2 = (double)token[1]; if (token.Count() > 2) { this.P3 = (double)token[2]; if (token.Count() > 3) { this.P4 = (double)token[3]; } } } /// /// Initializes a new instance of the class. /// /// /// The holding 2 or three doubles. /// /// /// If holds less than 2 values. /// public Position(JArray array) { if (array.Count() < 2) { throw new ArgumentException( string.Format("Expected at least 2 elements, got {0}", array.Count()), "array"); } this.P1 = (double)array[0]; this.P2 = (double)array[1]; if (array.Count() > 2) { this.P3 = (double)array[2]; if (array.Count() > 3) { this.P4 = (double)array[3]; } } } /// /// Gets or sets the first value of the position. /// public double P1 { get; set; } /// /// Gets or sets the second value of the position. /// public double P2 { get; set; } /// /// Gets or sets the third value of the position. /// public double? P3 { get; set; } /// /// Gets or sets the fourth value of the position. /// public double? P4 { get; set; } /// /// Gets or sets the indexes per point. Can only reduce the /// indexes per point on set, it is an error to try to increase the /// value. /// public IndexesPerPoint IndexesPerPoint { get { return this.P4.HasValue ? IndexesPerPoint.Four : this.P3.HasValue ? IndexesPerPoint.Three : IndexesPerPoint.Two; } set { switch (IndexesPerPoint) { case IndexesPerPoint.Two: switch (value) { case IndexesPerPoint.Four: this.P4 = double.NaN; goto case IndexesPerPoint.Three; case IndexesPerPoint.Three: this.P3 = double.NaN; break; } break; case IndexesPerPoint.Three: switch (value) { case IndexesPerPoint.Two: this.P3 = null; break; case IndexesPerPoint.Four: this.P4 = double.NaN; break; } break; case IndexesPerPoint.Four: switch (value) { case IndexesPerPoint.Two: this.P3 = null; goto case IndexesPerPoint.Three; case IndexesPerPoint.Three: this.P4 = null; break; } break; } } } /// public override void WellKnownBinary(Stream sout) { sout.Write(BitConverter.GetBytes(this.P1), 0, 8); sout.Write(BitConverter.GetBytes(this.P2), 0, 8); if (this.P3.HasValue) { sout.Write(BitConverter.GetBytes(this.P3.Value), 0, 8); if (this.P4.HasValue) { sout.Write(BitConverter.GetBytes(this.P4.Value), 0, 8); } } } /// public override void ParseJson(JArray array) { if (array.Count < 2) { throw new ArgumentException(string.Format("Expected at least 2 points for a position, got {0}", array.Count), "array"); } this.P1 = (double)array[0]; this.P2 = (double)array[1]; if (array.Count > 2) { this.P3 = (double)array[2]; if (array.Count > 3) { this.P4 = (double)array[3]; } } } } // ReSharper disable RedundantNameQualifier /// /// The point. /// public class Point : GeoBase { /// /// Gets or sets the position. /// public Position Position { get; set; } /// public override void WellKnownBinary(Stream sout) { sout.WriteByte(BitConverter.IsLittleEndian ? (byte)1 : (byte)0); sout.Write( this.Position.P3.HasValue ? this.Position.P4.HasValue ? GeoBase.PointXyzmWkbs : GeoBase.PointXyzWkbs : GeoBase.PointXyWkbs, 0, 4); this.Position.WellKnownBinary(sout); } /// public override void ParseJson(JArray array) { this.Position = new Position(array); } } /// /// The line string. /// public class LineString : GeoBase { /// /// Gets or sets the points. /// public List Points { get; set; } /// public override void WellKnownBinary(Stream sout) { sout.WriteByte(BitConverter.IsLittleEndian ? (byte)1 : (byte)0); sout.Write(this.Normalize(), 0, 4); sout.Write(BitConverter.GetBytes(this.Points.Count), 0, 4); foreach (var point in this.Points) { point.Position.WellKnownBinary(sout); } } /// public override void ParseJson(JArray array) { if (array.Cast().Any(l => l.Count < 2)) { throw new ArgumentException( string.Format( "Expected all points to have greater than two points, got {0} with zero and {1} with one", array.Cast().Count(l => l.Count == 0), array.Cast().Count(l => l.Count == 1)), "array"); } this.Points = array.Cast().Select(l => new Point { Position = new Position(l) }).ToList(); } /// /// Validate all the positions have the same number of indexes and return the well-known-bytes for that number. /// /// /// The well-known-bytes describing the geographic type. /// private byte[] Normalize() { if (this.Points.Any()) { var low = IndexesPerPoint.Four; var high = IndexesPerPoint.Two; foreach (var point in this.Points) { if (point.Position.IndexesPerPoint > high) { high = point.Position.IndexesPerPoint; } if (point.Position.IndexesPerPoint < low) { low = point.Position.IndexesPerPoint; } } if (high != low) { foreach (var point in this.Points) { point.Position.IndexesPerPoint = high; } } return low == IndexesPerPoint.Two ? GeoBase.LineStringXyWkbs : low == IndexesPerPoint.Three ? GeoBase.LineStringXyzWkbs : GeoBase.LineStringXyzmWkbs; } return GeoBase.LineStringXyWkbs; } } /// /// The polygon. /// public class Polygon : GeoBase { /// /// Gets or sets the rings. /// public List> Rings { get; set; } /// public override void WellKnownBinary(Stream sout) { sout.WriteByte(BitConverter.IsLittleEndian ? (byte)1 : (byte)0); sout.Write(this.Normalize(), 0, 4); sout.Write(BitConverter.GetBytes(this.Rings.Count), 0, 4); foreach (var ring in this.Rings) { sout.Write(BitConverter.GetBytes(ring.Count), 0, 4); foreach (var position in ring) { position.WellKnownBinary(sout); } } } /// public override void ParseJson(JArray array) { this.Rings = GeoBase.ParseListListPosition(array); } /// /// Validate all the positions have the same number of indexes and return the well-known-bytes for that number. /// /// /// The well-known-bytes describing the geographic type. /// private byte[] Normalize() { if (this.Rings.Any()) { var low = IndexesPerPoint.Four; var high = IndexesPerPoint.Two; foreach (var position in this.Rings.SelectMany(r => r)) { if (position.IndexesPerPoint > high) { high = position.IndexesPerPoint; } if (position.IndexesPerPoint < low) { low = position.IndexesPerPoint; } } if (high != low) { foreach (var position in this.Rings.SelectMany(r => r)) { position.IndexesPerPoint = high; } } return low == IndexesPerPoint.Two ? GeoBase.PolygonXyWkbs : low == IndexesPerPoint.Three ? GeoBase.PolygonXyzWkbs : GeoBase.PolygonXyzmWkbs; } return GeoBase.PolygonXyWkbs; } } /// /// The multi-point. /// public class MultiPoint : GeoBase { /// /// Gets or sets the points. /// public List Points { get; set; } /// public override void WellKnownBinary(Stream sout) { byte order = BitConverter.IsLittleEndian ? (byte)1 : (byte)0; sout.WriteByte(order); sout.Write(this.MaxWkbsType(), 0, 4); sout.Write(BitConverter.GetBytes(this.Points.Count), 0, 4); foreach (var point in this.Points) { sout.WriteByte(order); var ipp = point.IndexesPerPoint; sout.Write(ipp == IndexesPerPoint.Two ? GeoBase.PointXyWkbs : ipp == IndexesPerPoint.Three ? GeoBase.PointXyzWkbs : GeoBase.PointXyzmWkbs, 0, 4); point.WellKnownBinary(sout); } } /// public override void ParseJson(JArray array) { this.Points = GeoBase.ParseListPosition(array); } /// /// Validate all the positions have the same number of indexes and return the well-known-bytes for that number. /// /// /// The well-known-bytes describing the geographic type. /// private byte[] MaxWkbsType() { var high = IndexesPerPoint.Two; foreach (var position in this.Points) { if (position.IndexesPerPoint > high) { high = position.IndexesPerPoint; } } return high == IndexesPerPoint.Two ? GeoBase.MultiPointXyWkbs : high == IndexesPerPoint.Three ? GeoBase.MultiPointXyzWkbs : GeoBase.MultiPointXyzmWkbs; } } /// /// The multi-line. /// public class MultiLineString : GeoBase { /// /// Gets or sets the line strings. /// public List> LineStrings { get; set; } /// public override void WellKnownBinary(Stream sout) { byte order = BitConverter.IsLittleEndian ? (byte)1 : (byte)0; sout.WriteByte(order); // ReSharper disable once RedundantNameQualifier sout.Write(this.WkbsType(), 0, 4); sout.Write(BitConverter.GetBytes(this.LineStrings.Count), 0, 4); foreach (var lineString in this.LineStrings) { sout.WriteByte(order); var ipp = lineString.Any() ? lineString.First().IndexesPerPoint : IndexesPerPoint.Two; sout.Write(ipp == IndexesPerPoint.Two ? GeoBase.LineStringXyWkbs : ipp == IndexesPerPoint.Three ? GeoBase.LineStringXyzWkbs : GeoBase.LineStringXyzmWkbs, 0, 4); sout.Write(BitConverter.GetBytes(lineString.Count), 0, 4); foreach (var position in lineString) { position.WellKnownBinary(sout); } } } /// public override void ParseJson(JArray array) { this.LineStrings = GeoBase.ParseListListPosition(array); } /// /// Validate all the positions have the same number of indexes and return the well-known-bytes for that number. /// /// /// The well-known-bytes describing the geographic type. /// private byte[] WkbsType() { if (this.LineStrings.Any()) { var maxIpp = IndexesPerPoint.Two; foreach (var lineString in this.LineStrings) { var low = IndexesPerPoint.Four; var high = IndexesPerPoint.Two; foreach (var position in lineString) { if (position.IndexesPerPoint > high) { high = position.IndexesPerPoint; } if (position.IndexesPerPoint < low) { low = position.IndexesPerPoint; } } if (high != low) { foreach (var position in lineString) { position.IndexesPerPoint = high; } } if (low > maxIpp) { maxIpp = low; } } return maxIpp == IndexesPerPoint.Two ? GeoBase.MultiLineStringXyWkbs : maxIpp == IndexesPerPoint.Three ? GeoBase.MultiLineStringXyzWkbs : GeoBase.MultiLineStringXyzmWkbs; } return GeoBase.MultiLineStringXyWkbs; } } /// /// The multi-polygon. /// public class MultiPolygon : GeoBase { /// /// Gets or sets the polygons. /// public List>> Polygons { get; set; } /// public override void WellKnownBinary(Stream sout) { byte order = BitConverter.IsLittleEndian ? (byte)1 : (byte)0; sout.WriteByte(order); sout.Write(this.WkbsType(), 0, 4); sout.Write(BitConverter.GetBytes(this.Polygons.Count), 0, 4); foreach (var polygon in this.Polygons) { sout.WriteByte(order); var ipp = polygon.Any() && polygon.First().Any() ? polygon.First().First().IndexesPerPoint : IndexesPerPoint.Two; sout.Write(ipp == IndexesPerPoint.Two ? GeoBase.PolygonXyWkbs : ipp == IndexesPerPoint.Three ? GeoBase.PolygonXyzWkbs : GeoBase.PolygonXyzmWkbs, 0, 4); sout.Write(BitConverter.GetBytes(polygon.Count), 0, 4); foreach (var ring in polygon) { sout.Write(BitConverter.GetBytes(ring.Count), 0, 4); foreach (var position in ring) { position.WellKnownBinary(sout); } } } } /// public override void ParseJson(JArray array) { this.Polygons = GeoBase.ParseListListListPosition(array); } /// /// Validate all the positions have the same number of indexes and return the well-known-bytes for that number. /// /// /// The well-known-bytes describing the geographic type. /// private byte[] WkbsType() { if (this.Polygons.Any()) { var maxIpp = IndexesPerPoint.Two; foreach (var polygon in this.Polygons) { var low = IndexesPerPoint.Four; var high = IndexesPerPoint.Two; foreach (var position in polygon.SelectMany(r => r)) { if (position.IndexesPerPoint > high) { high = position.IndexesPerPoint; } if (position.IndexesPerPoint < low) { low = position.IndexesPerPoint; } } if (high != low) { foreach (var position in polygon.SelectMany(r => r)) { position.IndexesPerPoint = high; } } if (high > maxIpp) { maxIpp = high; } } return maxIpp == IndexesPerPoint.Two ? GeoBase.MultiPolygonXyWkbs : maxIpp == IndexesPerPoint.Three ? GeoBase.MultiPolygonXyzWkbs : GeoBase.MultiPolygonXyzmWkbs; } return GeoBase.MultiPolygonXyWkbs; } } /// /// The collection. /// public class Collection : GeoBase { /// /// Gets or sets the entries. /// public List Entries { get; set; } /// public override void WellKnownBinary(Stream o) { o.WriteByte(BitConverter.IsLittleEndian ? (byte)1 : (byte)0); o.Write(GeoBase.GeometryCollectionXyWkbs, 0, 4); o.Write(BitConverter.GetBytes(this.Entries.Count), 0, 4); foreach (var entry in this.Entries) { entry.WellKnownBinary(o); } } /// public override void ParseJson(JArray array) { this.Entries = new List(); foreach (var elem in array) { if (elem.Type != JTokenType.Object) { throw new ArgumentException( string.Format("Expected object elements of the collection array, got {0}", elem.Type), "array"); } int? dummyCoordinateSystem; this.Entries.Add(DbGeographyGeoJsonConverter.ParseJsonObjectToGeoBase((JObject)elem, out dummyCoordinateSystem)); } } } // ReSharper restore RedundantNameQualifier } }