/*
* 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
}
}