Skip to content

Commit 60c775e

Browse files
Make MsSqlSpatial BoundingBox optional
RFC 7946 The GeoJSON Format states (https://tools.ietf.org/html/rfc7946): > A GeoJSON object MAY have a member named "bbox" to include information on the coordinate range for its Geometries, Features, or FeatureCollections There should be a way to make this property optional - this update provides an optional parameter to the method that does the conversion from SqlGeometry to GeoJSON so that this property can be made optional. The default value is set to `true` so that it does not interfere with existing code that uses the library but provides an extension point for new development that may not want this property set. Unit tests were added to verify that the behavior does not change when the interface is used instead of the concrete implementation. Resolves: #18
1 parent 9d3365d commit 60c775e

4 files changed

Lines changed: 155 additions & 43 deletions

File tree

src/GeoJSON.Net.Contrib.MsSqlSpatial/MsSqlSpatialConvertToGeoJson.cs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ public static partial class MsSqlSpatialConvert
2323
{
2424
#region SqlGeometry to GeoJSON
2525

26-
/// <summary>
27-
/// Converts a native Sql Server geometry (lat/lon) to GeoJSON geometry
28-
/// </summary>
29-
/// <param name="sqlGeometry">SQL Server geometry to convert</param>
30-
/// <returns>GeoJSON geometry</returns>
31-
public static IGeometryObject ToGeoJSONGeometry(this SqlGeometry sqlGeometry)
26+
/// <summary>
27+
/// Converts a native Sql Server geometry (lat/lon) to GeoJSON geometry
28+
/// </summary>
29+
/// <param name="sqlGeometry">SQL Server geometry to convert</param>
30+
/// <param name="withBoundingBox">Value indicating whether the feature's BoundingBox should be set.</param>
31+
/// <returns>GeoJSON geometry</returns>
32+
public static IGeometryObject ToGeoJSONGeometry(this SqlGeometry sqlGeometry, bool withBoundingBox = true)
3233
{
3334
if (sqlGeometry == null || sqlGeometry.IsNull)
3435
{
@@ -43,17 +44,18 @@ public static IGeometryObject ToGeoJSONGeometry(this SqlGeometry sqlGeometry)
4344
}
4445

4546
// Conversion using geometry sink
46-
SqlGeometryGeoJsonSink sink = new SqlGeometryGeoJsonSink();
47+
SqlGeometryGeoJsonSink sink = new SqlGeometryGeoJsonSink(withBoundingBox);
4748
sqlGeometry.Populate(sink);
4849
return sink.ConstructedGeometry;
4950
}
5051

51-
/// <summary>
52-
/// Converts a native Sql Server geometry (lat/lon) to GeoJSON geometry
53-
/// </summary>
54-
/// <param name="sqlGeometry">SQL Server geometry to convert</param>
55-
/// <returns>GeoJSON geometry</returns>
56-
public static T ToGeoJSONObject<T>(this SqlGeometry sqlGeometry) where T : GeoJSONObject
52+
/// <summary>
53+
/// Converts a native Sql Server geometry (lat/lon) to GeoJSON geometry
54+
/// </summary>
55+
/// <param name="sqlGeometry">SQL Server geometry to convert</param>
56+
/// <param name="withBoundingBox">Value indicating whether the feature's BoundingBox should be set.</param>
57+
/// <returns>GeoJSON geometry</returns>
58+
public static T ToGeoJSONObject<T>(this SqlGeometry sqlGeometry, bool withBoundingBox = true) where T : GeoJSONObject
5759
{
5860
if (sqlGeometry == null || sqlGeometry.IsNull)
5961
{
@@ -72,7 +74,8 @@ public static T ToGeoJSONObject<T>(this SqlGeometry sqlGeometry) where T : GeoJ
7274
SqlGeometryGeoJsonSink sink = new SqlGeometryGeoJsonSink();
7375
sqlGeometry.Populate(sink);
7476
geoJSONobj = sink.ConstructedGeometry as T;
75-
geoJSONobj.BoundingBoxes = sink.BoundingBox;
77+
78+
geoJSONobj.BoundingBoxes = withBoundingBox ? sink.BoundingBox : null;
7679

7780
return geoJSONobj;
7881
}
@@ -106,12 +109,13 @@ public static IGeometryObject ToGeoJSONGeometry(this SqlGeography sqlGeography)
106109
return sink.ConstructedGeography;
107110
}
108111

109-
/// <summary>
110-
/// Converts a native Sql Server geography to GeoJSON geometry
111-
/// </summary>
112-
/// <param name="sqlGeography">SQL Server geography to convert</param>
113-
/// <returns>GeoJSON geometry</returns>
114-
public static T ToGeoJSONObject<T>(this SqlGeography sqlGeography) where T : GeoJSONObject
112+
/// <summary>
113+
/// Converts a native Sql Server geography to GeoJSON geometry
114+
/// </summary>
115+
/// <param name="sqlGeography">SQL Server geography to convert</param>
116+
/// <param name="withBoundingBox">Value indicating whether the feature's BoundingBox should be set.</param>
117+
/// <returns>GeoJSON geometry</returns>
118+
public static T ToGeoJSONObject<T>(this SqlGeography sqlGeography, bool withBoundingBox = true) where T : GeoJSONObject
115119
{
116120
if (sqlGeography == null || sqlGeography.IsNull)
117121
{
@@ -130,7 +134,7 @@ public static T ToGeoJSONObject<T>(this SqlGeography sqlGeography) where T : Geo
130134
SqlGeographyGeoJsonSink sink = new SqlGeographyGeoJsonSink();
131135
sqlGeography.Populate(sink);
132136
geoJSONobj = sink.ConstructedGeography as T;
133-
geoJSONobj.BoundingBoxes = sink.BoundingBox;
137+
geoJSONobj.BoundingBoxes = withBoundingBox ? sink.BoundingBox : null;
134138

135139
return geoJSONobj;
136140
}

src/GeoJSON.Net.Contrib.MsSqlSpatial/Sinks/SqlGeographyGeoJSONSink.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ internal class SqlGeographyGeoJsonSink : IGeographySink110
2727
double _lonMax = -180;
2828
double _latMax = -90;
2929

30+
private readonly bool _withBoundingBox;
31+
32+
public SqlGeographyGeoJsonSink(bool withBoundingBox = true)
33+
{
34+
this._withBoundingBox = withBoundingBox;
35+
}
36+
3037
#region Sink implementation
3138

3239
public void BeginGeography(OpenGisGeographyType type)
@@ -136,7 +143,7 @@ public IGeometryObject ConstructedGeography
136143
List<IGeometryObject> subGeometries = _geomCollection.SubItems.Select(subItem => GeometryFromSinkGeometryCollection(subItem)).ToList();
137144
_geometry = new GeometryCollection(subGeometries);
138145

139-
((GeometryCollection)_geometry).BoundingBoxes = this.BoundingBox;
146+
((GeometryCollection)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
140147
break;
141148
default:
142149
throw new NotSupportedException("Geometry type " + _geomCollection.GeometryType.ToString() + " is not supported yet.");
@@ -155,33 +162,33 @@ private IGeometryObject GeometryFromSinkGeometryCollection(SinkGeometryCollectio
155162
{
156163
case OpenGisGeographyType.Point:
157164
_geometry = ConstructGeometryPart(sinkCollection[0]);
158-
((Point)_geometry).BoundingBoxes = this.BoundingBox;
165+
((Point)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
159166
break;
160167
case OpenGisGeographyType.MultiPoint:
161168
_geometry = new MultiPoint(sinkCollection.Skip(1)
162169
.Select(g => (Point)ConstructGeometryPart(g))
163170
.ToList());
164-
((MultiPoint)_geometry).BoundingBoxes = this.BoundingBox;
171+
((MultiPoint)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
165172
break;
166173
case OpenGisGeographyType.LineString:
167174
_geometry = ConstructGeometryPart(sinkCollection[0]);
168-
((LineString)_geometry).BoundingBoxes = this.BoundingBox;
175+
((LineString)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
169176
break;
170177
case OpenGisGeographyType.MultiLineString:
171178
_geometry = new MultiLineString(sinkCollection.Skip(1)
172179
.Select(g => (LineString)ConstructGeometryPart(g))
173180
.ToList());
174-
((MultiLineString)_geometry).BoundingBoxes = this.BoundingBox;
181+
((MultiLineString)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
175182
break;
176183
case OpenGisGeographyType.Polygon:
177184
_geometry = ConstructGeometryPart(sinkCollection.First());
178-
((Polygon)_geometry).BoundingBoxes = this.BoundingBox;
185+
((Polygon)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
179186
break;
180187
case OpenGisGeographyType.MultiPolygon:
181188
_geometry = new MultiPolygon(sinkCollection.Skip(1)
182189
.Select(g => (Polygon)ConstructGeometryPart(g))
183190
.ToList());
184-
((MultiPolygon)_geometry).BoundingBoxes = this.BoundingBox;
191+
((MultiPolygon)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
185192
break;
186193
default:
187194
throw new NotSupportedException("Geometry type " + sinkCollection.GeometryType.ToString() + " is not possible in GetConstructedGeometry.");
@@ -194,10 +201,7 @@ private IGeometryObject GeometryFromSinkGeometryCollection(SinkGeometryCollectio
194201

195202
public double[] BoundingBox
196203
{
197-
get
198-
{
199-
return new double[] { _lonMin, _latMin, _lonMax, _latMax };
200-
}
204+
get { return this._withBoundingBox ? new double[] {_lonMin, _latMin, _lonMax, _latMax} : null; }
201205
}
202206

203207
private IGeometryObject ConstructGeometryPart(SinkGeometry<OpenGisGeographyType> geomPart)

src/GeoJSON.Net.Contrib.MsSqlSpatial/Sinks/SqlGeometryGeoJSONSink.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ internal class SqlGeometryGeoJsonSink : IGeometrySink110
2828
double _xmax = double.MinValue;
2929
double _ymax = double.MinValue;
3030

31+
private readonly bool _withBoundingBox;
32+
33+
public SqlGeometryGeoJsonSink(bool withBoundingBox = true)
34+
{
35+
this._withBoundingBox = withBoundingBox;
36+
}
37+
3138
#region Sink implementation
3239

3340
public void BeginGeometry(OpenGisGeometryType type)
@@ -134,7 +141,7 @@ public IGeometryObject ConstructedGeometry
134141
List<IGeometryObject> subGeometries = _geomCollection.SubItems.Select(subItem => GeometryFromSinkGeometryCollection(subItem)).ToList();
135142
_geometry = new GeometryCollection(subGeometries);
136143

137-
((GeometryCollection)_geometry).BoundingBoxes = this.BoundingBox;
144+
((GeometryCollection)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
138145
break;
139146
default:
140147
throw new NotSupportedException("Geometry type " + _geomCollection.GeometryType.ToString() + " is not supported yet.");
@@ -153,33 +160,33 @@ private IGeometryObject GeometryFromSinkGeometryCollection(SinkGeometryCollectio
153160
{
154161
case OpenGisGeometryType.Point:
155162
_geometry = ConstructGeometryPart(sinkCollection[0]);
156-
((Point)_geometry).BoundingBoxes = this.BoundingBox;
163+
((Point)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
157164
break;
158165
case OpenGisGeometryType.MultiPoint:
159166
_geometry = new MultiPoint(sinkCollection.Skip(1)
160167
.Select(g => (Point)ConstructGeometryPart(g))
161168
.ToList());
162-
((MultiPoint)_geometry).BoundingBoxes = this.BoundingBox;
169+
((MultiPoint)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
163170
break;
164171
case OpenGisGeometryType.LineString:
165172
_geometry = ConstructGeometryPart(sinkCollection[0]);
166-
((LineString)_geometry).BoundingBoxes = this.BoundingBox;
173+
((LineString)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
167174
break;
168175
case OpenGisGeometryType.MultiLineString:
169176
_geometry = new MultiLineString(sinkCollection.Skip(1)
170177
.Select(g => (LineString)ConstructGeometryPart(g))
171178
.ToList());
172-
((MultiLineString)_geometry).BoundingBoxes = this.BoundingBox;
179+
((MultiLineString)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
173180
break;
174181
case OpenGisGeometryType.Polygon:
175182
_geometry = ConstructGeometryPart(sinkCollection.First());
176-
((Polygon)_geometry).BoundingBoxes = this.BoundingBox;
183+
((Polygon)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
177184
break;
178185
case OpenGisGeometryType.MultiPolygon:
179186
_geometry = new MultiPolygon(sinkCollection.Skip(1)
180187
.Select(g => (Polygon)ConstructGeometryPart(g))
181188
.ToList());
182-
((MultiPolygon)_geometry).BoundingBoxes = this.BoundingBox;
189+
((MultiPolygon)_geometry).BoundingBoxes = this._withBoundingBox ? this.BoundingBox : null;
183190
break;
184191
default:
185192
throw new NotSupportedException("Geometry type " + sinkCollection.GeometryType.ToString() + " is not possible in GetConstructedGeometry.");
@@ -191,10 +198,7 @@ private IGeometryObject GeometryFromSinkGeometryCollection(SinkGeometryCollectio
191198

192199
public double[] BoundingBox
193200
{
194-
get
195-
{
196-
return new double[] { _xmin, _ymin, _xmax, _ymax };
197-
}
201+
get { return this._withBoundingBox ? new double[] {_xmin, _ymin, _xmax, _ymax} : null; }
198202
}
199203

200204
private IGeometryObject ConstructGeometryPart(SinkGeometry<OpenGisGeometryType> geomPart)

test/GeoJSON.Net.Contrib.MsSqlSpatial.Test/ToGeoJSONGeographyTests.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,106 @@ public void TestEmptGeometryCollection_Geography()
228228

229229
}
230230

231+
[TestMethod]
232+
[TestCategory("ToGeoJSONGeography")]
233+
public void TestGeometryCollectionWithoutBoundingBox()
234+
{
235+
var geoJSONobj = this.geomCol.ToGeoJSONObject<GeometryCollection>(false);
236+
237+
Assert.IsNotNull(geoJSONobj);
238+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.GeometryCollection);
239+
Assert.IsNull(geoJSONobj.BoundingBoxes);
240+
Assert.AreEqual(geoJSONobj.Geometries.Count, 3);
241+
Assert.AreEqual(geoJSONobj.Geometries[0].Type, GeoJSONObjectType.Polygon);
242+
Assert.AreEqual(geoJSONobj.Geometries[1].Type, GeoJSONObjectType.Point);
243+
Assert.AreEqual(geoJSONobj.Geometries[2].Type, GeoJSONObjectType.MultiLineString);
244+
}
245+
246+
[TestMethod]
247+
[TestCategory("ToGeoJSONGeography")]
248+
public void TestLineStringWithoutBoundingBox()
249+
{
250+
var geoJSONobj = this.lineString.ToGeoJSONObject<LineString>(false);
251+
252+
Assert.IsNotNull(geoJSONobj);
253+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.LineString);
254+
Assert.IsNull(geoJSONobj.BoundingBoxes);
255+
}
256+
257+
[TestMethod]
258+
[TestCategory("ToGeoJSONGeography")]
259+
public void TestMultiLineStringWithoutBoundingBox()
260+
{
261+
var geoJSONobj = this.multiLineString.ToGeoJSONObject<MultiLineString>(false);
262+
263+
Assert.IsNotNull(geoJSONobj);
264+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.MultiLineString);
265+
Assert.IsNull(geoJSONobj.BoundingBoxes);
266+
}
267+
268+
[TestMethod]
269+
[TestCategory("ToGeoJSONGeography")]
270+
public void TestSimplePointWithoutBoundingBox()
271+
{
272+
Point geoJSONobj = this.simplePoint.ToGeoJSONObject<Point>(false);
273+
274+
Assert.IsNotNull(geoJSONobj);
275+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.Point);
276+
Assert.IsNull(geoJSONobj.BoundingBoxes);
277+
}
278+
279+
[TestMethod]
280+
[TestCategory("ToGeoJSONGeography")]
281+
public void TestMultiPointWithoutBoundingBox()
282+
{
283+
MultiPoint geoJSONobj = this.multiPoint.ToGeoJSONObject<MultiPoint>(false);
284+
285+
Assert.IsNotNull(geoJSONobj);
286+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.MultiPoint);
287+
Assert.IsNull(geoJSONobj.BoundingBoxes);
288+
}
289+
290+
[TestMethod]
291+
[TestCategory("ToGeoJSONGeography")]
292+
public void TestSimplePolygonWithoutBoundingBox()
293+
{
294+
var geoJSONobj = this.simplePoly.ToGeoJSONObject<Polygon>(false);
295+
296+
Assert.IsNotNull(geoJSONobj);
297+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.Polygon);
298+
Assert.IsNull(geoJSONobj.BoundingBoxes);
299+
Assert.AreEqual(geoJSONobj.Coordinates.Count, 1);
300+
}
301+
302+
[TestMethod]
303+
[TestCategory("ToGeoJSONGeography")]
304+
public void TestPolygonWitHoleWithoutBoundingBox()
305+
{
306+
var geoJSONobj = this.polyWithHole.ToGeoJSONObject<Polygon>(false);
307+
308+
Assert.IsNotNull(geoJSONobj);
309+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.Polygon);
310+
Assert.IsNull(geoJSONobj.BoundingBoxes);
311+
Assert.AreEqual(geoJSONobj.Coordinates.Count, 2);
312+
}
313+
314+
[TestMethod]
315+
[TestCategory("ToGeoJSONGeography")]
316+
public void TestMultiPolygonWithoutBoundingBox()
317+
{
318+
var geoJSONobj = this.multiPolygon.ToGeoJSONObject<MultiPolygon>(false);
319+
320+
Assert.IsNotNull(geoJSONobj);
321+
Assert.AreEqual(geoJSONobj.Type, GeoJSONObjectType.MultiPolygon);
322+
Assert.IsNull(geoJSONobj.BoundingBoxes);
323+
Assert.AreEqual(geoJSONobj.Coordinates.Count, 3);
324+
Assert.AreEqual(geoJSONobj.Coordinates[1].Coordinates.Count, 2);
325+
Assert.AreEqual(geoJSONobj.Coordinates[1].Coordinates[0].Coordinates.Count, 6);
326+
Assert.AreEqual(geoJSONobj.Coordinates[1].Coordinates[1].Coordinates.Count, 4);
327+
Assert.AreEqual(geoJSONobj.Coordinates[2].Coordinates[0].Coordinates.Count, 5);
328+
Assert.AreEqual(geoJSONobj.Coordinates[2].Coordinates[1].Coordinates.Count, 5);
329+
}
330+
231331
#region Test geographies
232332

233333
SqlGeography simplePoint = SqlGeography.Parse(new SqlString(WktSamples.POINT)).MakeValidIfInvalid();

0 commit comments

Comments
 (0)