Skip to content

Commit 328f13d

Browse files
author
Jack Bond
authored
Expand spatial types supported by NewtonsoftJsonMicrosoftSpatialGeoJsonConverter. (Azure#24587)
* Added support for LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and Collection. Code structure mirrors MicrosoftSpatialGeoJsonConverter. * Reworded and slightly restructured comments. Renamed variable.
1 parent b150b82 commit 328f13d

13 files changed

+1283
-24
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Core.Serialization
5+
{
6+
/// <summary>
7+
/// The constants defined by the GeoJson standard
8+
/// A similar class exists in Microsoft.Spatial, unfortunately it is marked as internal.
9+
/// </summary>
10+
internal static class GeoJsonConstants
11+
{
12+
public const string PointTypeName = "Point";
13+
public const string LineStringTypeName = "LineString";
14+
public const string PolygonTypeName = "Polygon";
15+
public const string MultiPointTypeName = "MultiPoint";
16+
public const string MultiLineStringTypeName = "MultiLineString";
17+
public const string MultiPolygonTypeName = "MultiPolygon";
18+
public const string GeometryCollectionTypeName = "GeometryCollection";
19+
public const string CoordinatesPropertyName = "coordinates";
20+
public const string TypePropertyName = "type";
21+
public const string GeometriesPropertyName = "geometries";
22+
}
23+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Spatial;
5+
using Newtonsoft.Json;
6+
7+
namespace Azure.Core.Serialization
8+
{
9+
/// <summary>
10+
/// Advances a JsonReader positioned at coordinates property to identify GeoJson type.
11+
/// </summary>
12+
/// <remarks>
13+
/// The GeoJson standard essentially has four "schemas" for containing geography coordinates.
14+
/// <list type="bullet" >
15+
/// <item>
16+
/// <description>Level zero defines a Point an array of doubles for latitude, longitude, etc.</description>
17+
/// </item>
18+
/// <item>
19+
/// <description>
20+
/// Level one, defines a LineString or MultiPoint, both which are an array of points.
21+
/// </description>
22+
/// </item>
23+
/// <item>
24+
/// <description>
25+
/// Level two, defines a Polygon or MultiLineString, an array of an array of points.
26+
/// </description>
27+
/// </item>
28+
/// <item>
29+
/// <description>
30+
/// Level three, defines a MultiPolygon, an array of an array of an array of points.
31+
/// </description>
32+
/// </item>
33+
/// </list>
34+
/// </remarks>
35+
internal abstract class GeoJsonCoordinateReader
36+
{
37+
/// <summary>
38+
/// Advances the JsonReader and extracts the points which compose a Geography.
39+
/// </summary>
40+
/// <param name="reader"></param>
41+
public abstract void Process(JsonReader reader);
42+
43+
/// <summary>
44+
/// Accepts the GeoJson coordinates property, and iterates the top level array to determine
45+
/// which of the four schemas the coordinates match.
46+
/// </summary>
47+
/// <param name="reader">A JsonReader positioned at the GeoJson coordinates property.</param>
48+
/// <returns>A reader which will parse out correct parser based on the schema detected.</returns>
49+
/// <exception cref="JsonSerializationException"></exception>
50+
public static GeoJsonCoordinateReader Create(JsonReader reader)
51+
{
52+
GeoJsonCoordinateReader result;
53+
54+
reader.Expect(JsonToken.StartArray);
55+
56+
int detectedLevel = 0;
57+
58+
// How many nested StartArray tokens we find identifies which of the four schemas we're dealing with
59+
for (; reader.Read() && reader.TokenType == JsonToken.StartArray; detectedLevel++)
60+
{
61+
if (detectedLevel == 4)
62+
{
63+
throw new JsonSerializationException($"Deserialization failed: GeoJson property '{GeoJsonConstants.CoordinatesPropertyName}' does not contain recognizable GeoJson.");
64+
}
65+
}
66+
67+
reader.ExpectNumber();
68+
69+
switch (detectedLevel)
70+
{
71+
case 0:
72+
result = new LevelZeroGeoJsonCoordinateReader();
73+
break;
74+
75+
case 1:
76+
result = new LevelOneGeoJsonCoordinateReader();
77+
break;
78+
79+
case 2:
80+
result = new LevelTwoGeoJsonCoordinateReader();
81+
break;
82+
83+
default:
84+
result = new LevelThreeGeoJsonCoordinateReader();
85+
break;
86+
}
87+
88+
result.Process(reader);
89+
90+
return result;
91+
}
92+
93+
/// <summary>
94+
/// Returns a Geography created with the set of points extracted during the call to Process.
95+
/// </summary>
96+
/// <remarks>
97+
/// GetGeography will be called only after both the type and coordinates GeoJson properties have
98+
/// been read. As both LineString and MultiPoint share the same schema, a reader cannot know which
99+
/// type to create from the points alone. The same is true for Polygon and MultiLineString.
100+
/// </remarks>
101+
/// <param name="type">The type read from the GeoJson type property.</param>
102+
/// <returns>The Geography created by a level zero, one, two, or three reader.</returns>
103+
public abstract Geography GetGeography(string type);
104+
}
105+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Spatial;
5+
using Newtonsoft.Json;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
9+
namespace Azure.Core.Serialization
10+
{
11+
/// <summary>
12+
/// A collection of extension methods for GeographyFactory.
13+
/// </summary>
14+
/// <remarks>
15+
/// GeographyFactory does not contain overloads for adding existing Geographies during the Build process. For example,
16+
/// you cannot simply add a GeographyPolygon to a GeographyMultiPolygon. Instead you must enumerate an existing
17+
/// GeographyPolygon's rings and add them iteratively while creating a GeographyMultiPolygon. The methods of this
18+
/// class properly iterate creating Geographies which are composed of other Geographies.
19+
/// </remarks>
20+
internal static class GeographyFactoryExtensions
21+
{
22+
public static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, Geography geography)
23+
{
24+
if (geography is GeographyPoint point)
25+
{
26+
factory = factory.Add(point);
27+
}
28+
29+
else if (geography is GeographyLineString lineString)
30+
{
31+
factory = factory.Add(lineString);
32+
}
33+
34+
else if (geography is GeographyPolygon polygon)
35+
{
36+
factory = factory.Add(polygon);
37+
}
38+
39+
else if (geography is GeographyMultiPoint multiPoint)
40+
{
41+
factory = factory.Add(multiPoint);
42+
}
43+
44+
else if (geography is GeographyMultiLineString multiLineString)
45+
{
46+
factory = factory.Add(multiLineString);
47+
}
48+
49+
else if (geography is GeographyMultiPolygon multiPolygon)
50+
{
51+
factory = factory.Add(multiPolygon);
52+
}
53+
54+
else if (geography is GeographyCollection geographyCollection)
55+
{
56+
factory = factory.Add(geographyCollection);
57+
}
58+
59+
return factory;
60+
}
61+
62+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyPoint point)
63+
{
64+
factory = factory.Point(point.Latitude, point.Longitude);
65+
66+
return factory;
67+
}
68+
69+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyLineString lineString)
70+
{
71+
factory = factory.LineString();
72+
73+
foreach (GeographyPoint point in lineString.Points)
74+
{
75+
factory = factory.LineTo(point.Latitude, point.Longitude);
76+
}
77+
78+
return factory;
79+
}
80+
81+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyPolygon polygon)
82+
{
83+
factory = factory.Polygon();
84+
85+
foreach (GeographyLineString ring in polygon.Rings)
86+
{
87+
factory = factory.Ring(ring.Points[0].Latitude, ring.Points[0].Longitude);
88+
89+
for (int i = 1; i < ring.Points.Count; i++)
90+
{
91+
factory = factory.LineTo(ring.Points[i].Latitude, ring.Points[i].Longitude);
92+
}
93+
}
94+
95+
return factory;
96+
}
97+
98+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyMultiPoint multiPoint)
99+
{
100+
foreach (GeographyPoint point in multiPoint.Points)
101+
{
102+
factory = factory.Add(point);
103+
}
104+
105+
return factory;
106+
}
107+
108+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyMultiLineString multiLineString)
109+
{
110+
foreach (GeographyLineString lineString in multiLineString.LineStrings)
111+
{
112+
factory = factory.Add(lineString);
113+
}
114+
115+
return factory;
116+
}
117+
118+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyMultiPolygon multiPolygon)
119+
{
120+
foreach (GeographyPolygon polygon in multiPolygon.Polygons)
121+
{
122+
factory = factory.Add(polygon);
123+
}
124+
125+
return factory;
126+
}
127+
128+
private static GeographyFactory<GeographyCollection> Add(this GeographyFactory<GeographyCollection> factory, GeographyCollection geographyCollection)
129+
{
130+
factory = factory.Collection();
131+
132+
foreach (Geography geography in geographyCollection.Geographies)
133+
{
134+
factory = factory.Add(geography);
135+
}
136+
137+
return factory;
138+
}
139+
140+
public static GeographyLineString Create(this GeographyFactory<GeographyLineString> factory, List<GeographyPoint> points)
141+
{
142+
if (points.Count < 2)
143+
{
144+
throw new JsonSerializationException($"Deserialization failed: GeoJson type '{GeoJsonConstants.LineStringTypeName}' must contain at least two points.");
145+
}
146+
147+
foreach (GeographyPoint point in points)
148+
{
149+
factory = factory.LineTo(point.Latitude, point.Longitude);
150+
}
151+
152+
GeographyLineString result = factory.Build();
153+
154+
return result;
155+
}
156+
157+
public static GeographyPolygon Create(this GeographyFactory<GeographyPolygon> factory, List<List<GeographyPoint>> points)
158+
{
159+
foreach (List<GeographyPoint> pointList in points)
160+
{
161+
if (pointList.Count < 4)
162+
{
163+
throw new JsonSerializationException($"Deserialization failed: GeoJson type '{GeoJsonConstants.PolygonTypeName}' must contain at least four points.");
164+
}
165+
166+
else if (!pointList.First().Equals(pointList.Last()))
167+
{
168+
throw new JsonSerializationException($"Deserialization failed: GeoJson type '{GeoJsonConstants.PolygonTypeName}' has an invalid ring, its first and last points do not match.");
169+
}
170+
171+
factory = factory.Ring(pointList[0].Latitude, pointList[0].Longitude);
172+
173+
for (int i = 1; i < pointList.Count - 1; i++)
174+
{
175+
factory = factory.LineTo(pointList[i].Latitude, pointList[i].Longitude);
176+
}
177+
}
178+
179+
var result = factory.Build();
180+
181+
return result;
182+
}
183+
184+
public static GeographyMultiPoint Create(this GeographyFactory<GeographyMultiPoint> factory, List<GeographyPoint> points)
185+
{
186+
foreach (GeographyPoint point in points)
187+
{
188+
factory = factory.Point(point.Latitude, point.Longitude);
189+
}
190+
191+
GeographyMultiPoint result = factory.Build();
192+
193+
return result;
194+
}
195+
196+
public static GeographyMultiLineString Create(this GeographyFactory<GeographyMultiLineString> factory, List<List<GeographyPoint>> listOfPointList)
197+
{
198+
foreach (List<GeographyPoint> points in listOfPointList)
199+
{
200+
if (points.Count < 2)
201+
{
202+
throw new JsonSerializationException($"Deserialization failed: GeoJson type '{GeoJsonConstants.MultiLineStringTypeName}' must contain at least two points.");
203+
}
204+
205+
factory = factory.LineString();
206+
207+
foreach (GeographyPoint point in points)
208+
{
209+
factory = factory.LineTo(point.Latitude, point.Longitude);
210+
}
211+
}
212+
213+
GeographyMultiLineString result = factory.Build();
214+
215+
return result;
216+
}
217+
218+
public static GeographyMultiPolygon Create(this GeographyFactory<GeographyMultiPolygon> factory, List<List<List<GeographyPoint>>> points)
219+
{
220+
foreach (List<List<GeographyPoint>> polygon in points)
221+
{
222+
factory = factory.Polygon();
223+
224+
foreach (List<GeographyPoint> ring in polygon)
225+
{
226+
factory = factory.Ring(ring[0].Latitude, ring[0].Longitude);
227+
228+
for (int i = 1; i < ring.Count - 1; i++)
229+
{
230+
factory = factory.LineTo(ring[i].Latitude, ring[i].Longitude);
231+
}
232+
}
233+
}
234+
235+
GeographyMultiPolygon result = factory.Build();
236+
237+
return result;
238+
}
239+
240+
public static GeographyCollection Create(this GeographyFactory<GeographyCollection> factory, List<Geography> geographies)
241+
{
242+
foreach (Geography geography in geographies)
243+
{
244+
factory.Add(geography);
245+
}
246+
247+
return factory.Build();
248+
}
249+
}
250+
}

0 commit comments

Comments
 (0)