/src/gdal/ogr/ogrgeojsonwriter.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: OpenGIS Simple Features Reference Implementation |
4 | | * Purpose: Implementation of GeoJSON writer utilities (OGR GeoJSON Driver). |
5 | | * Author: Mateusz Loskot, mateusz@loskot.net |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2007, Mateusz Loskot |
9 | | * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | /*! @cond Doxygen_Suppress */ |
15 | | |
16 | | #define JSON_C_VER_013 (13 << 8) |
17 | | |
18 | | #include "ogrgeojsonwriter.h" |
19 | | #include "ogr_geometry.h" |
20 | | #include "ogrgeojsongeometry.h" |
21 | | #include "ogrlibjsonutils.h" |
22 | | #include "ogr_feature.h" |
23 | | #include "ogr_p.h" |
24 | | #include <json.h> // JSON-C |
25 | | |
26 | | #if (!defined(JSON_C_VERSION_NUM)) || (JSON_C_VERSION_NUM < JSON_C_VER_013) |
27 | | #include <json_object_private.h> |
28 | | #endif |
29 | | |
30 | | #include <printbuf.h> |
31 | | #include "ogr_api.h" |
32 | | |
33 | | #include <algorithm> |
34 | | #include <cmath> |
35 | | #include <cstdint> |
36 | | #include <limits> |
37 | | |
38 | | static json_object * |
39 | | json_object_new_float_with_significant_figures(float fVal, |
40 | | int nSignificantFigures); |
41 | | |
42 | | static json_object * |
43 | | OGRGeoJSONWritePoint(const OGRPoint *poPoint, |
44 | | const OGRGeoJSONWriteOptions &oOptions); |
45 | | |
46 | | static json_object * |
47 | | OGRGeoJSONWriteLineString(const OGRLineString *poLine, |
48 | | const OGRGeoJSONWriteOptions &oOptions); |
49 | | |
50 | | static json_object * |
51 | | OGRGeoJSONWriteMultiPoint(const OGRMultiPoint *poGeometry, |
52 | | const OGRGeoJSONWriteOptions &oOptions); |
53 | | |
54 | | static json_object * |
55 | | OGRGeoJSONWriteMultiLineString(const OGRMultiLineString *poGeometry, |
56 | | const OGRGeoJSONWriteOptions &oOptions); |
57 | | |
58 | | static json_object * |
59 | | OGRGeoJSONWriteMultiPolygon(const OGRMultiPolygon *poGeometry, |
60 | | const OGRGeoJSONWriteOptions &oOptions); |
61 | | |
62 | | static json_object * |
63 | | OGRGeoJSONWriteGeometryCollection(const OGRGeometryCollection *poGeometry, |
64 | | const OGRGeoJSONWriteOptions &oOptions); |
65 | | |
66 | | static json_object * |
67 | | OGRGeoJSONWriteCoords(double const &fX, double const &fY, |
68 | | const OGRGeoJSONWriteOptions &oOptions); |
69 | | |
70 | | static json_object * |
71 | | OGRGeoJSONWriteCoords(double const &fX, double const &fY, double const &fZ, |
72 | | const OGRGeoJSONWriteOptions &oOptions); |
73 | | |
74 | | static json_object * |
75 | | OGRGeoJSONWriteLineCoords(const OGRLineString *poLine, |
76 | | const OGRGeoJSONWriteOptions &oOptions); |
77 | | |
78 | | static json_object * |
79 | | OGRGeoJSONWriteRingCoords(const OGRLinearRing *poLine, bool bIsExteriorRing, |
80 | | const OGRGeoJSONWriteOptions &oOptions); |
81 | | |
82 | | /************************************************************************/ |
83 | | /* SetRFC7946Settings() */ |
84 | | /************************************************************************/ |
85 | | |
86 | | /*! @cond Doxygen_Suppress */ |
87 | | void OGRGeoJSONWriteOptions::SetRFC7946Settings() |
88 | 0 | { |
89 | 0 | bBBOXRFC7946 = true; |
90 | 0 | if (nXYCoordPrecision < 0) |
91 | 0 | nXYCoordPrecision = 7; |
92 | 0 | if (nZCoordPrecision < 0) |
93 | 0 | nZCoordPrecision = 3; |
94 | 0 | bPolygonRightHandRule = true; |
95 | 0 | bCanPatchCoordinatesWithNativeData = false; |
96 | 0 | bHonourReservedRFC7946Members = true; |
97 | 0 | } |
98 | | |
99 | | void OGRGeoJSONWriteOptions::SetIDOptions(CSLConstList papszOptions) |
100 | 0 | { |
101 | |
|
102 | 0 | osIDField = CSLFetchNameValueDef(papszOptions, "ID_FIELD", ""); |
103 | 0 | const char *pszIDFieldType = CSLFetchNameValue(papszOptions, "ID_TYPE"); |
104 | 0 | if (pszIDFieldType) |
105 | 0 | { |
106 | 0 | if (EQUAL(pszIDFieldType, "String")) |
107 | 0 | { |
108 | 0 | bForceIDFieldType = true; |
109 | 0 | eForcedIDFieldType = OFTString; |
110 | 0 | } |
111 | 0 | else if (EQUAL(pszIDFieldType, "Integer")) |
112 | 0 | { |
113 | 0 | bForceIDFieldType = true; |
114 | 0 | eForcedIDFieldType = OFTInteger64; |
115 | 0 | } |
116 | 0 | } |
117 | 0 | bGenerateID = |
118 | 0 | CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "ID_GENERATE", false)); |
119 | 0 | } |
120 | | |
121 | | /*! @endcond */ |
122 | | |
123 | | /************************************************************************/ |
124 | | /* json_object_new_coord() */ |
125 | | /************************************************************************/ |
126 | | |
127 | | static json_object * |
128 | | json_object_new_coord(double dfVal, int nDimIdx, |
129 | | const OGRGeoJSONWriteOptions &oOptions) |
130 | 0 | { |
131 | | // If coordinate precision is specified, or significant figures is not |
132 | | // then use the '%f' formatting. |
133 | 0 | if (nDimIdx <= 2) |
134 | 0 | { |
135 | 0 | if (oOptions.nXYCoordPrecision >= 0 || oOptions.nSignificantFigures < 0) |
136 | 0 | return json_object_new_double_with_precision( |
137 | 0 | dfVal, oOptions.nXYCoordPrecision); |
138 | 0 | } |
139 | 0 | else |
140 | 0 | { |
141 | 0 | if (oOptions.nZCoordPrecision >= 0 || oOptions.nSignificantFigures < 0) |
142 | 0 | return json_object_new_double_with_precision( |
143 | 0 | dfVal, oOptions.nZCoordPrecision); |
144 | 0 | } |
145 | | |
146 | 0 | return json_object_new_double_with_significant_figures( |
147 | 0 | dfVal, oOptions.nSignificantFigures); |
148 | 0 | } |
149 | | |
150 | | /************************************************************************/ |
151 | | /* OGRGeoJSONIsPatchablePosition() */ |
152 | | /************************************************************************/ |
153 | | |
154 | | static bool OGRGeoJSONIsPatchablePosition(json_object *poJSonCoordinates, |
155 | | json_object *poNativeCoordinates) |
156 | 0 | { |
157 | 0 | return json_object_get_type(poJSonCoordinates) == json_type_array && |
158 | 0 | json_object_get_type(poNativeCoordinates) == json_type_array && |
159 | 0 | json_object_array_length(poJSonCoordinates) == 3 && |
160 | 0 | json_object_array_length(poNativeCoordinates) >= 4 && |
161 | 0 | json_object_get_type(json_object_array_get_idx( |
162 | 0 | poJSonCoordinates, 0)) != json_type_array && |
163 | 0 | json_object_get_type(json_object_array_get_idx( |
164 | 0 | poNativeCoordinates, 0)) != json_type_array; |
165 | 0 | } |
166 | | |
167 | | /************************************************************************/ |
168 | | /* OGRGeoJSONIsCompatiblePosition() */ |
169 | | /************************************************************************/ |
170 | | |
171 | | static bool OGRGeoJSONIsCompatiblePosition(json_object *poJSonCoordinates, |
172 | | json_object *poNativeCoordinates) |
173 | 0 | { |
174 | 0 | return json_object_get_type(poJSonCoordinates) == json_type_array && |
175 | 0 | json_object_get_type(poNativeCoordinates) == json_type_array && |
176 | 0 | json_object_array_length(poJSonCoordinates) == |
177 | 0 | json_object_array_length(poNativeCoordinates) && |
178 | 0 | json_object_get_type(json_object_array_get_idx( |
179 | 0 | poJSonCoordinates, 0)) != json_type_array && |
180 | 0 | json_object_get_type(json_object_array_get_idx( |
181 | 0 | poNativeCoordinates, 0)) != json_type_array; |
182 | 0 | } |
183 | | |
184 | | /************************************************************************/ |
185 | | /* OGRGeoJSONPatchPosition() */ |
186 | | /************************************************************************/ |
187 | | |
188 | | static void OGRGeoJSONPatchPosition(json_object *poJSonCoordinates, |
189 | | json_object *poNativeCoordinates) |
190 | 0 | { |
191 | 0 | const auto nLength = json_object_array_length(poNativeCoordinates); |
192 | 0 | for (auto i = decltype(nLength){3}; i < nLength; i++) |
193 | 0 | { |
194 | 0 | json_object_array_add( |
195 | 0 | poJSonCoordinates, |
196 | 0 | json_object_get(json_object_array_get_idx(poNativeCoordinates, i))); |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | | /************************************************************************/ |
201 | | /* OGRGeoJSONIsPatchableArray() */ |
202 | | /************************************************************************/ |
203 | | |
204 | | static bool OGRGeoJSONIsPatchableArray(json_object *poJSonArray, |
205 | | json_object *poNativeArray, int nDepth) |
206 | 0 | { |
207 | 0 | if (nDepth == 0) |
208 | 0 | return OGRGeoJSONIsPatchablePosition(poJSonArray, poNativeArray); |
209 | | |
210 | 0 | if (json_object_get_type(poJSonArray) == json_type_array && |
211 | 0 | json_object_get_type(poNativeArray) == json_type_array) |
212 | 0 | { |
213 | 0 | const auto nLength = json_object_array_length(poJSonArray); |
214 | 0 | if (nLength == json_object_array_length(poNativeArray)) |
215 | 0 | { |
216 | 0 | if (nLength > 0) |
217 | 0 | { |
218 | 0 | json_object *poJSonChild = |
219 | 0 | json_object_array_get_idx(poJSonArray, 0); |
220 | 0 | json_object *poNativeChild = |
221 | 0 | json_object_array_get_idx(poNativeArray, 0); |
222 | 0 | if (!OGRGeoJSONIsPatchableArray(poJSonChild, poNativeChild, |
223 | 0 | nDepth - 1)) |
224 | 0 | { |
225 | 0 | return false; |
226 | 0 | } |
227 | | // Light check as a former extensive check was done in |
228 | | // OGRGeoJSONComputePatchableOrCompatibleArray |
229 | 0 | } |
230 | 0 | return true; |
231 | 0 | } |
232 | 0 | } |
233 | 0 | return false; |
234 | 0 | } |
235 | | |
236 | | /************************************************************************/ |
237 | | /* OGRGeoJSONComputePatchableOrCompatibleArray() */ |
238 | | /************************************************************************/ |
239 | | |
240 | | /* Returns true if the objects are comparable, ie Point vs Point, LineString |
241 | | vs LineString, but they might not be patchable or compatible */ |
242 | | static bool OGRGeoJSONComputePatchableOrCompatibleArrayInternal( |
243 | | json_object *poJSonArray, json_object *poNativeArray, int nDepth, |
244 | | bool &bOutPatchable, bool &bOutCompatible) |
245 | 0 | { |
246 | 0 | if (nDepth == 0) |
247 | 0 | { |
248 | 0 | bOutPatchable &= |
249 | 0 | OGRGeoJSONIsPatchablePosition(poJSonArray, poNativeArray); |
250 | 0 | bOutCompatible &= |
251 | 0 | OGRGeoJSONIsCompatiblePosition(poJSonArray, poNativeArray); |
252 | 0 | return json_object_get_type(poJSonArray) == json_type_array && |
253 | 0 | json_object_get_type(poNativeArray) == json_type_array && |
254 | 0 | json_object_get_type(json_object_array_get_idx( |
255 | 0 | poJSonArray, 0)) != json_type_array && |
256 | 0 | json_object_get_type(json_object_array_get_idx( |
257 | 0 | poNativeArray, 0)) != json_type_array; |
258 | 0 | } |
259 | | |
260 | 0 | if (json_object_get_type(poJSonArray) == json_type_array && |
261 | 0 | json_object_get_type(poNativeArray) == json_type_array) |
262 | 0 | { |
263 | 0 | const auto nLength = json_object_array_length(poJSonArray); |
264 | 0 | if (nLength == json_object_array_length(poNativeArray)) |
265 | 0 | { |
266 | 0 | for (auto i = decltype(nLength){0}; i < nLength; i++) |
267 | 0 | { |
268 | 0 | json_object *poJSonChild = |
269 | 0 | json_object_array_get_idx(poJSonArray, i); |
270 | 0 | json_object *poNativeChild = |
271 | 0 | json_object_array_get_idx(poNativeArray, i); |
272 | 0 | if (!OGRGeoJSONComputePatchableOrCompatibleArrayInternal( |
273 | 0 | poJSonChild, poNativeChild, nDepth - 1, bOutPatchable, |
274 | 0 | bOutCompatible)) |
275 | 0 | { |
276 | 0 | return false; |
277 | 0 | } |
278 | 0 | if (!bOutPatchable && !bOutCompatible) |
279 | 0 | break; |
280 | 0 | } |
281 | 0 | return true; |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | 0 | bOutPatchable = false; |
286 | 0 | bOutCompatible = false; |
287 | 0 | return false; |
288 | 0 | } |
289 | | |
290 | | /* Returns true if the objects are comparable, ie Point vs Point, LineString |
291 | | vs LineString, but they might not be patchable or compatible */ |
292 | | static bool OGRGeoJSONComputePatchableOrCompatibleArray( |
293 | | json_object *poJSonArray, json_object *poNativeArray, int nDepth, |
294 | | bool &bOutPatchable, bool &bOutCompatible) |
295 | 0 | { |
296 | 0 | bOutPatchable = true; |
297 | 0 | bOutCompatible = true; |
298 | 0 | return OGRGeoJSONComputePatchableOrCompatibleArrayInternal( |
299 | 0 | poJSonArray, poNativeArray, nDepth, bOutPatchable, bOutCompatible); |
300 | 0 | } |
301 | | |
302 | | /************************************************************************/ |
303 | | /* OGRGeoJSONPatchArray() */ |
304 | | /************************************************************************/ |
305 | | |
306 | | static void OGRGeoJSONPatchArray(json_object *poJSonArray, |
307 | | json_object *poNativeArray, int nDepth) |
308 | 0 | { |
309 | 0 | if (nDepth == 0) |
310 | 0 | { |
311 | 0 | OGRGeoJSONPatchPosition(poJSonArray, poNativeArray); |
312 | 0 | return; |
313 | 0 | } |
314 | 0 | const auto nLength = json_object_array_length(poJSonArray); |
315 | 0 | for (auto i = decltype(nLength){0}; i < nLength; i++) |
316 | 0 | { |
317 | 0 | json_object *poJSonChild = json_object_array_get_idx(poJSonArray, i); |
318 | 0 | json_object *poNativeChild = |
319 | 0 | json_object_array_get_idx(poNativeArray, i); |
320 | 0 | OGRGeoJSONPatchArray(poJSonChild, poNativeChild, nDepth - 1); |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | /************************************************************************/ |
325 | | /* OGRGeoJSONIsPatchableGeometry() */ |
326 | | /************************************************************************/ |
327 | | |
328 | | static bool OGRGeoJSONIsPatchableGeometry(json_object *poJSonGeometry, |
329 | | json_object *poNativeGeometry, |
330 | | bool &bOutPatchableCoords, |
331 | | bool &bOutCompatibleCoords) |
332 | 0 | { |
333 | 0 | if (json_object_get_type(poJSonGeometry) != json_type_object || |
334 | 0 | json_object_get_type(poNativeGeometry) != json_type_object) |
335 | 0 | { |
336 | 0 | return false; |
337 | 0 | } |
338 | | |
339 | 0 | json_object *poType = CPL_json_object_object_get(poJSonGeometry, "type"); |
340 | 0 | json_object *poNativeType = |
341 | 0 | CPL_json_object_object_get(poNativeGeometry, "type"); |
342 | 0 | if (poType == nullptr || poNativeType == nullptr || |
343 | 0 | json_object_get_type(poType) != json_type_string || |
344 | 0 | json_object_get_type(poNativeType) != json_type_string || |
345 | 0 | strcmp(json_object_get_string(poType), |
346 | 0 | json_object_get_string(poNativeType)) != 0) |
347 | 0 | { |
348 | 0 | return false; |
349 | 0 | } |
350 | | |
351 | 0 | json_object_iter it; |
352 | 0 | it.key = nullptr; |
353 | 0 | it.val = nullptr; |
354 | 0 | it.entry = nullptr; |
355 | 0 | json_object_object_foreachC(poNativeGeometry, it) |
356 | 0 | { |
357 | 0 | if (strcmp(it.key, "coordinates") == 0) |
358 | 0 | { |
359 | 0 | json_object *poJSonCoordinates = |
360 | 0 | CPL_json_object_object_get(poJSonGeometry, "coordinates"); |
361 | 0 | json_object *poNativeCoordinates = it.val; |
362 | | // 0 = Point |
363 | | // 1 = LineString or MultiPoint |
364 | | // 2 = MultiLineString or Polygon |
365 | | // 3 = MultiPolygon |
366 | 0 | for (int i = 0; i <= 3; i++) |
367 | 0 | { |
368 | 0 | if (OGRGeoJSONComputePatchableOrCompatibleArray( |
369 | 0 | poJSonCoordinates, poNativeCoordinates, i, |
370 | 0 | bOutPatchableCoords, bOutCompatibleCoords)) |
371 | 0 | { |
372 | 0 | return bOutPatchableCoords || bOutCompatibleCoords; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | return false; |
376 | 0 | } |
377 | 0 | if (strcmp(it.key, "geometries") == 0) |
378 | 0 | { |
379 | 0 | json_object *poJSonGeometries = |
380 | 0 | CPL_json_object_object_get(poJSonGeometry, "geometries"); |
381 | 0 | json_object *poNativeGeometries = it.val; |
382 | 0 | if (json_object_get_type(poJSonGeometries) == json_type_array && |
383 | 0 | json_object_get_type(poNativeGeometries) == json_type_array) |
384 | 0 | { |
385 | 0 | const auto nLength = json_object_array_length(poJSonGeometries); |
386 | 0 | if (nLength == json_object_array_length(poNativeGeometries)) |
387 | 0 | { |
388 | 0 | for (auto i = decltype(nLength){0}; i < nLength; i++) |
389 | 0 | { |
390 | 0 | json_object *poJSonChild = |
391 | 0 | json_object_array_get_idx(poJSonGeometries, i); |
392 | 0 | json_object *poNativeChild = |
393 | 0 | json_object_array_get_idx(poNativeGeometries, i); |
394 | 0 | if (!OGRGeoJSONIsPatchableGeometry( |
395 | 0 | poJSonChild, poNativeChild, bOutPatchableCoords, |
396 | 0 | bOutCompatibleCoords)) |
397 | 0 | { |
398 | 0 | return false; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | return true; |
402 | 0 | } |
403 | 0 | } |
404 | 0 | return false; |
405 | 0 | } |
406 | 0 | } |
407 | 0 | return false; |
408 | 0 | } |
409 | | |
410 | | /************************************************************************/ |
411 | | /* OGRGeoJSONPatchGeometry() */ |
412 | | /************************************************************************/ |
413 | | |
414 | | static void OGRGeoJSONPatchGeometry(json_object *poJSonGeometry, |
415 | | json_object *poNativeGeometry, |
416 | | bool bPatchableCoordinates, |
417 | | const OGRGeoJSONWriteOptions &oOptions) |
418 | 0 | { |
419 | 0 | json_object_iter it; |
420 | 0 | it.key = nullptr; |
421 | 0 | it.val = nullptr; |
422 | 0 | it.entry = nullptr; |
423 | 0 | json_object_object_foreachC(poNativeGeometry, it) |
424 | 0 | { |
425 | 0 | if (strcmp(it.key, "type") == 0 || strcmp(it.key, "bbox") == 0) |
426 | 0 | { |
427 | 0 | continue; |
428 | 0 | } |
429 | 0 | if (strcmp(it.key, "coordinates") == 0) |
430 | 0 | { |
431 | 0 | if (!bPatchableCoordinates && |
432 | 0 | !oOptions.bCanPatchCoordinatesWithNativeData) |
433 | 0 | { |
434 | 0 | continue; |
435 | 0 | } |
436 | | |
437 | 0 | json_object *poJSonCoordinates = |
438 | 0 | CPL_json_object_object_get(poJSonGeometry, "coordinates"); |
439 | 0 | json_object *poNativeCoordinates = it.val; |
440 | 0 | for (int i = 0; i <= 3; i++) |
441 | 0 | { |
442 | 0 | if (OGRGeoJSONIsPatchableArray(poJSonCoordinates, |
443 | 0 | poNativeCoordinates, i)) |
444 | 0 | { |
445 | 0 | OGRGeoJSONPatchArray(poJSonCoordinates, poNativeCoordinates, |
446 | 0 | i); |
447 | 0 | break; |
448 | 0 | } |
449 | 0 | } |
450 | |
|
451 | 0 | continue; |
452 | 0 | } |
453 | 0 | if (strcmp(it.key, "geometries") == 0) |
454 | 0 | { |
455 | 0 | json_object *poJSonGeometries = |
456 | 0 | CPL_json_object_object_get(poJSonGeometry, "geometries"); |
457 | 0 | json_object *poNativeGeometries = it.val; |
458 | 0 | const auto nLength = json_object_array_length(poJSonGeometries); |
459 | 0 | for (auto i = decltype(nLength){0}; i < nLength; i++) |
460 | 0 | { |
461 | 0 | json_object *poJSonChild = |
462 | 0 | json_object_array_get_idx(poJSonGeometries, i); |
463 | 0 | json_object *poNativeChild = |
464 | 0 | json_object_array_get_idx(poNativeGeometries, i); |
465 | 0 | OGRGeoJSONPatchGeometry(poJSonChild, poNativeChild, |
466 | 0 | bPatchableCoordinates, oOptions); |
467 | 0 | } |
468 | |
|
469 | 0 | continue; |
470 | 0 | } |
471 | | |
472 | | // See https://tools.ietf.org/html/rfc7946#section-7.1 |
473 | 0 | if (oOptions.bHonourReservedRFC7946Members && |
474 | 0 | (strcmp(it.key, "geometry") == 0 || |
475 | 0 | strcmp(it.key, "properties") == 0 || |
476 | 0 | strcmp(it.key, "features") == 0)) |
477 | 0 | { |
478 | 0 | continue; |
479 | 0 | } |
480 | | |
481 | 0 | json_object_object_add(poJSonGeometry, it.key, json_object_get(it.val)); |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | | /************************************************************************/ |
486 | | /* OGRGeoJSONGetBBox */ |
487 | | /************************************************************************/ |
488 | | |
489 | | OGREnvelope3D OGRGeoJSONGetBBox(const OGRGeometry *poGeometry, |
490 | | const OGRGeoJSONWriteOptions &oOptions) |
491 | 0 | { |
492 | 0 | OGREnvelope3D sEnvelope; |
493 | 0 | poGeometry->getEnvelope(&sEnvelope); |
494 | |
|
495 | 0 | if (oOptions.bBBOXRFC7946) |
496 | 0 | { |
497 | | // Heuristics to determine if the geometry was split along the |
498 | | // date line. |
499 | 0 | const double EPS = 1e-7; |
500 | 0 | const OGRwkbGeometryType eType = |
501 | 0 | wkbFlatten(poGeometry->getGeometryType()); |
502 | 0 | const bool bMultiPart = |
503 | 0 | OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) && |
504 | 0 | poGeometry->toGeometryCollection()->getNumGeometries() >= 2; |
505 | 0 | if (bMultiPart && fabs(sEnvelope.MinX - (-180.0)) < EPS && |
506 | 0 | fabs(sEnvelope.MaxX - 180.0) < EPS) |
507 | 0 | { |
508 | | // First heuristics (quite safe) when the geometry looks to |
509 | | // have been really split at the dateline. |
510 | 0 | const auto *poGC = poGeometry->toGeometryCollection(); |
511 | 0 | double dfWestLimit = -180.0; |
512 | 0 | double dfEastLimit = 180.0; |
513 | 0 | bool bWestLimitIsInit = false; |
514 | 0 | bool bEastLimitIsInit = false; |
515 | 0 | for (const auto *poMember : poGC) |
516 | 0 | { |
517 | 0 | OGREnvelope sEnvelopePart; |
518 | 0 | if (poMember->IsEmpty()) |
519 | 0 | continue; |
520 | 0 | poMember->getEnvelope(&sEnvelopePart); |
521 | 0 | const bool bTouchesMinus180 = |
522 | 0 | fabs(sEnvelopePart.MinX - (-180.0)) < EPS; |
523 | 0 | const bool bTouchesPlus180 = |
524 | 0 | fabs(sEnvelopePart.MaxX - 180.0) < EPS; |
525 | 0 | if (bTouchesMinus180 && !bTouchesPlus180) |
526 | 0 | { |
527 | 0 | if (sEnvelopePart.MaxX > dfEastLimit || !bEastLimitIsInit) |
528 | 0 | { |
529 | 0 | bEastLimitIsInit = true; |
530 | 0 | dfEastLimit = sEnvelopePart.MaxX; |
531 | 0 | } |
532 | 0 | } |
533 | 0 | else if (bTouchesPlus180 && !bTouchesMinus180) |
534 | 0 | { |
535 | 0 | if (sEnvelopePart.MinX < dfWestLimit || !bWestLimitIsInit) |
536 | 0 | { |
537 | 0 | bWestLimitIsInit = true; |
538 | 0 | dfWestLimit = sEnvelopePart.MinX; |
539 | 0 | } |
540 | 0 | } |
541 | 0 | else if (!bTouchesMinus180 && !bTouchesPlus180) |
542 | 0 | { |
543 | 0 | if (sEnvelopePart.MinX > 0 && |
544 | 0 | (sEnvelopePart.MinX < dfWestLimit || !bWestLimitIsInit)) |
545 | 0 | { |
546 | 0 | bWestLimitIsInit = true; |
547 | 0 | dfWestLimit = sEnvelopePart.MinX; |
548 | 0 | } |
549 | 0 | else if (sEnvelopePart.MaxX < 0 && |
550 | 0 | (sEnvelopePart.MaxX > dfEastLimit || |
551 | 0 | !bEastLimitIsInit)) |
552 | 0 | { |
553 | 0 | bEastLimitIsInit = true; |
554 | 0 | dfEastLimit = sEnvelopePart.MaxX; |
555 | 0 | } |
556 | 0 | } |
557 | 0 | } |
558 | 0 | sEnvelope.MinX = dfWestLimit; |
559 | 0 | sEnvelope.MaxX = dfEastLimit; |
560 | 0 | } |
561 | 0 | else if (bMultiPart && sEnvelope.MaxX - sEnvelope.MinX > 180 && |
562 | 0 | sEnvelope.MinX >= -180 && sEnvelope.MaxX <= 180) |
563 | 0 | { |
564 | | // More fragile heuristics for a geometry like Alaska |
565 | | // (https://github.com/qgis/QGIS/issues/42827) which spans over |
566 | | // the antimeridian but does not touch it. |
567 | 0 | const auto *poGC = poGeometry->toGeometryCollection(); |
568 | 0 | double dfWestLimit = std::numeric_limits<double>::infinity(); |
569 | 0 | double dfEastLimit = -std::numeric_limits<double>::infinity(); |
570 | 0 | for (const auto *poMember : poGC) |
571 | 0 | { |
572 | 0 | OGREnvelope sEnvelopePart; |
573 | 0 | if (poMember->IsEmpty()) |
574 | 0 | continue; |
575 | 0 | poMember->getEnvelope(&sEnvelopePart); |
576 | 0 | if (sEnvelopePart.MinX > -120 && sEnvelopePart.MaxX < 120) |
577 | 0 | { |
578 | 0 | dfWestLimit = std::numeric_limits<double>::infinity(); |
579 | 0 | dfEastLimit = -std::numeric_limits<double>::infinity(); |
580 | 0 | break; |
581 | 0 | } |
582 | 0 | if (sEnvelopePart.MinX > 0) |
583 | 0 | { |
584 | 0 | dfWestLimit = std::min(dfWestLimit, sEnvelopePart.MinX); |
585 | 0 | } |
586 | 0 | else |
587 | 0 | { |
588 | 0 | CPLAssert(sEnvelopePart.MaxX < 0); |
589 | 0 | dfEastLimit = std::max(dfEastLimit, sEnvelopePart.MaxX); |
590 | 0 | } |
591 | 0 | } |
592 | 0 | if (dfWestLimit != std::numeric_limits<double>::infinity() && |
593 | 0 | dfEastLimit + 360 - dfWestLimit < 180) |
594 | 0 | { |
595 | 0 | sEnvelope.MinX = dfWestLimit; |
596 | 0 | sEnvelope.MaxX = dfEastLimit; |
597 | 0 | } |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | 0 | return sEnvelope; |
602 | 0 | } |
603 | | |
604 | | /************************************************************************/ |
605 | | /* OGRGeoJSONWriteFeature */ |
606 | | /************************************************************************/ |
607 | | |
608 | | json_object *OGRGeoJSONWriteFeature(OGRFeature *poFeature, |
609 | | const OGRGeoJSONWriteOptions &oOptions) |
610 | 0 | { |
611 | 0 | CPLAssert(nullptr != poFeature); |
612 | | |
613 | 0 | bool bWriteBBOX = oOptions.bWriteBBOX; |
614 | |
|
615 | 0 | json_object *poObj = json_object_new_object(); |
616 | 0 | CPLAssert(nullptr != poObj); |
617 | | |
618 | 0 | json_object_object_add(poObj, "type", json_object_new_string("Feature")); |
619 | | |
620 | | /* -------------------------------------------------------------------- */ |
621 | | /* Write native JSon data. */ |
622 | | /* -------------------------------------------------------------------- */ |
623 | 0 | bool bIdAlreadyWritten = false; |
624 | 0 | const char *pszNativeMediaType = poFeature->GetNativeMediaType(); |
625 | 0 | json_object *poNativeGeom = nullptr; |
626 | 0 | bool bHasProperties = true; |
627 | 0 | bool bWriteIdIfFoundInAttributes = true; |
628 | 0 | if (pszNativeMediaType && |
629 | 0 | EQUAL(pszNativeMediaType, "application/vnd.geo+json")) |
630 | 0 | { |
631 | 0 | const char *pszNativeData = poFeature->GetNativeData(); |
632 | 0 | json_object *poNativeJSon = nullptr; |
633 | 0 | if (pszNativeData && OGRJSonParse(pszNativeData, &poNativeJSon) && |
634 | 0 | json_object_get_type(poNativeJSon) == json_type_object) |
635 | 0 | { |
636 | 0 | json_object_iter it; |
637 | 0 | it.key = nullptr; |
638 | 0 | it.val = nullptr; |
639 | 0 | it.entry = nullptr; |
640 | 0 | CPLString osNativeData; |
641 | 0 | bHasProperties = false; |
642 | 0 | json_object_object_foreachC(poNativeJSon, it) |
643 | 0 | { |
644 | 0 | if (strcmp(it.key, "type") == 0) |
645 | 0 | { |
646 | 0 | continue; |
647 | 0 | } |
648 | 0 | if (strcmp(it.key, "properties") == 0) |
649 | 0 | { |
650 | 0 | bHasProperties = true; |
651 | 0 | continue; |
652 | 0 | } |
653 | 0 | if (strcmp(it.key, "bbox") == 0) |
654 | 0 | { |
655 | 0 | bWriteBBOX = true; |
656 | 0 | continue; |
657 | 0 | } |
658 | 0 | if (strcmp(it.key, "geometry") == 0) |
659 | 0 | { |
660 | 0 | poNativeGeom = json_object_get(it.val); |
661 | 0 | continue; |
662 | 0 | } |
663 | 0 | if (strcmp(it.key, "id") == 0) |
664 | 0 | { |
665 | 0 | const auto eType = json_object_get_type(it.val); |
666 | | // See https://tools.ietf.org/html/rfc7946#section-3.2 |
667 | 0 | if (oOptions.bHonourReservedRFC7946Members && |
668 | 0 | !oOptions.bForceIDFieldType && |
669 | 0 | eType != json_type_string && eType != json_type_int && |
670 | 0 | eType != json_type_double) |
671 | 0 | { |
672 | 0 | continue; |
673 | 0 | } |
674 | | |
675 | 0 | bIdAlreadyWritten = true; |
676 | |
|
677 | 0 | if (it.val && oOptions.bForceIDFieldType && |
678 | 0 | oOptions.eForcedIDFieldType == OFTInteger64) |
679 | 0 | { |
680 | 0 | if (eType != json_type_int) |
681 | 0 | { |
682 | 0 | json_object_object_add( |
683 | 0 | poObj, it.key, |
684 | 0 | json_object_new_int64(CPLAtoGIntBig( |
685 | 0 | json_object_get_string(it.val)))); |
686 | 0 | bWriteIdIfFoundInAttributes = false; |
687 | 0 | continue; |
688 | 0 | } |
689 | 0 | } |
690 | 0 | else if (it.val && oOptions.bForceIDFieldType && |
691 | 0 | oOptions.eForcedIDFieldType == OFTString) |
692 | 0 | { |
693 | 0 | if (eType != json_type_string) |
694 | 0 | { |
695 | 0 | json_object_object_add( |
696 | 0 | poObj, it.key, |
697 | 0 | json_object_new_string( |
698 | 0 | json_object_get_string(it.val))); |
699 | 0 | bWriteIdIfFoundInAttributes = false; |
700 | 0 | continue; |
701 | 0 | } |
702 | 0 | } |
703 | | |
704 | 0 | if (it.val != nullptr) |
705 | 0 | { |
706 | 0 | int nIdx = |
707 | 0 | poFeature->GetDefnRef()->GetFieldIndexCaseSensitive( |
708 | 0 | "id"); |
709 | 0 | if (eType == json_type_string && nIdx >= 0 && |
710 | 0 | poFeature->GetFieldDefnRef(nIdx)->GetType() == |
711 | 0 | OFTString && |
712 | 0 | strcmp(json_object_get_string(it.val), |
713 | 0 | poFeature->GetFieldAsString(nIdx)) == 0) |
714 | 0 | { |
715 | 0 | bWriteIdIfFoundInAttributes = false; |
716 | 0 | } |
717 | 0 | else if (eType == json_type_int && nIdx >= 0 && |
718 | 0 | (poFeature->GetFieldDefnRef(nIdx)->GetType() == |
719 | 0 | OFTInteger || |
720 | 0 | poFeature->GetFieldDefnRef(nIdx)->GetType() == |
721 | 0 | OFTInteger64) && |
722 | 0 | json_object_get_int64(it.val) == |
723 | 0 | poFeature->GetFieldAsInteger64(nIdx)) |
724 | 0 | { |
725 | 0 | bWriteIdIfFoundInAttributes = false; |
726 | 0 | } |
727 | 0 | } |
728 | 0 | } |
729 | | |
730 | | // See https://tools.ietf.org/html/rfc7946#section-7.1 |
731 | 0 | if (oOptions.bHonourReservedRFC7946Members && |
732 | 0 | (strcmp(it.key, "coordinates") == 0 || |
733 | 0 | strcmp(it.key, "geometries") == 0 || |
734 | 0 | strcmp(it.key, "features") == 0)) |
735 | 0 | { |
736 | 0 | continue; |
737 | 0 | } |
738 | | |
739 | 0 | json_object_object_add(poObj, it.key, json_object_get(it.val)); |
740 | 0 | } |
741 | 0 | json_object_put(poNativeJSon); |
742 | 0 | } |
743 | 0 | } |
744 | | |
745 | | /* -------------------------------------------------------------------- */ |
746 | | /* Write FID if available */ |
747 | | /* -------------------------------------------------------------------- */ |
748 | 0 | OGRGeoJSONWriteId(poFeature, poObj, bIdAlreadyWritten, oOptions); |
749 | | |
750 | | /* -------------------------------------------------------------------- */ |
751 | | /* Write feature attributes to GeoJSON "properties" object. */ |
752 | | /* -------------------------------------------------------------------- */ |
753 | 0 | if (bHasProperties) |
754 | 0 | { |
755 | 0 | json_object *poObjProps = OGRGeoJSONWriteAttributes( |
756 | 0 | poFeature, bWriteIdIfFoundInAttributes, oOptions); |
757 | 0 | json_object_object_add(poObj, "properties", poObjProps); |
758 | 0 | } |
759 | | |
760 | | /* -------------------------------------------------------------------- */ |
761 | | /* Write feature geometry to GeoJSON "geometry" object. */ |
762 | | /* Null geometries are allowed, according to the GeoJSON Spec. */ |
763 | | /* -------------------------------------------------------------------- */ |
764 | 0 | json_object *poObjGeom = nullptr; |
765 | |
|
766 | 0 | OGRGeometry *poGeometry = poFeature->GetGeometryRef(); |
767 | 0 | if (nullptr != poGeometry) |
768 | 0 | { |
769 | 0 | poObjGeom = OGRGeoJSONWriteGeometry(poGeometry, oOptions); |
770 | |
|
771 | 0 | if (bWriteBBOX && !poGeometry->IsEmpty()) |
772 | 0 | { |
773 | 0 | OGREnvelope3D sEnvelope = OGRGeoJSONGetBBox(poGeometry, oOptions); |
774 | |
|
775 | 0 | json_object *poObjBBOX = json_object_new_array(); |
776 | 0 | json_object_array_add( |
777 | 0 | poObjBBOX, json_object_new_coord(sEnvelope.MinX, 1, oOptions)); |
778 | 0 | json_object_array_add( |
779 | 0 | poObjBBOX, json_object_new_coord(sEnvelope.MinY, 2, oOptions)); |
780 | 0 | if (wkbHasZ(poGeometry->getGeometryType())) |
781 | 0 | json_object_array_add( |
782 | 0 | poObjBBOX, |
783 | 0 | json_object_new_coord(sEnvelope.MinZ, 3, oOptions)); |
784 | 0 | json_object_array_add( |
785 | 0 | poObjBBOX, json_object_new_coord(sEnvelope.MaxX, 1, oOptions)); |
786 | 0 | json_object_array_add( |
787 | 0 | poObjBBOX, json_object_new_coord(sEnvelope.MaxY, 2, oOptions)); |
788 | 0 | if (wkbHasZ(poGeometry->getGeometryType())) |
789 | 0 | json_object_array_add( |
790 | 0 | poObjBBOX, |
791 | 0 | json_object_new_coord(sEnvelope.MaxZ, 3, oOptions)); |
792 | |
|
793 | 0 | json_object_object_add(poObj, "bbox", poObjBBOX); |
794 | 0 | } |
795 | |
|
796 | 0 | bool bOutPatchableCoords = false; |
797 | 0 | bool bOutCompatibleCoords = false; |
798 | 0 | if (OGRGeoJSONIsPatchableGeometry(poObjGeom, poNativeGeom, |
799 | 0 | bOutPatchableCoords, |
800 | 0 | bOutCompatibleCoords)) |
801 | 0 | { |
802 | 0 | OGRGeoJSONPatchGeometry(poObjGeom, poNativeGeom, |
803 | 0 | bOutPatchableCoords, oOptions); |
804 | 0 | } |
805 | 0 | } |
806 | |
|
807 | 0 | json_object_object_add(poObj, "geometry", poObjGeom); |
808 | |
|
809 | 0 | if (poNativeGeom != nullptr) |
810 | 0 | json_object_put(poNativeGeom); |
811 | |
|
812 | 0 | return poObj; |
813 | 0 | } |
814 | | |
815 | | /************************************************************************/ |
816 | | /* OGRGeoJSONWriteId */ |
817 | | /************************************************************************/ |
818 | | |
819 | | void OGRGeoJSONWriteId(const OGRFeature *poFeature, json_object *poObj, |
820 | | bool bIdAlreadyWritten, |
821 | | const OGRGeoJSONWriteOptions &oOptions) |
822 | 0 | { |
823 | 0 | if (!oOptions.osIDField.empty()) |
824 | 0 | { |
825 | 0 | int nIdx = poFeature->GetDefnRef()->GetFieldIndexCaseSensitive( |
826 | 0 | oOptions.osIDField); |
827 | 0 | if (nIdx >= 0) |
828 | 0 | { |
829 | 0 | if ((oOptions.bForceIDFieldType && |
830 | 0 | oOptions.eForcedIDFieldType == OFTInteger64) || |
831 | 0 | (!oOptions.bForceIDFieldType && |
832 | 0 | (poFeature->GetFieldDefnRef(nIdx)->GetType() == OFTInteger || |
833 | 0 | poFeature->GetFieldDefnRef(nIdx)->GetType() == OFTInteger64))) |
834 | 0 | { |
835 | 0 | json_object_object_add( |
836 | 0 | poObj, "id", |
837 | 0 | json_object_new_int64( |
838 | 0 | poFeature->GetFieldAsInteger64(nIdx))); |
839 | 0 | } |
840 | 0 | else |
841 | 0 | { |
842 | 0 | json_object_object_add( |
843 | 0 | poObj, "id", |
844 | 0 | json_object_new_string(poFeature->GetFieldAsString(nIdx))); |
845 | 0 | } |
846 | 0 | } |
847 | 0 | } |
848 | 0 | else if (poFeature->GetFID() != OGRNullFID && !bIdAlreadyWritten) |
849 | 0 | { |
850 | 0 | if (oOptions.bForceIDFieldType && |
851 | 0 | oOptions.eForcedIDFieldType == OFTString) |
852 | 0 | { |
853 | 0 | json_object_object_add(poObj, "id", |
854 | 0 | json_object_new_string(CPLSPrintf( |
855 | 0 | CPL_FRMT_GIB, poFeature->GetFID()))); |
856 | 0 | } |
857 | 0 | else |
858 | 0 | { |
859 | 0 | json_object_object_add(poObj, "id", |
860 | 0 | json_object_new_int64(poFeature->GetFID())); |
861 | 0 | } |
862 | 0 | } |
863 | 0 | } |
864 | | |
865 | | /************************************************************************/ |
866 | | /* OGRGeoJSONWriteAttributes */ |
867 | | /************************************************************************/ |
868 | | |
869 | | json_object *OGRGeoJSONWriteAttributes(OGRFeature *poFeature, |
870 | | bool bWriteIdIfFoundInAttributes, |
871 | | const OGRGeoJSONWriteOptions &oOptions) |
872 | 0 | { |
873 | 0 | CPLAssert(nullptr != poFeature); |
874 | | |
875 | 0 | json_object *poObjProps = json_object_new_object(); |
876 | 0 | CPLAssert(nullptr != poObjProps); |
877 | | |
878 | 0 | OGRFeatureDefn *poDefn = poFeature->GetDefnRef(); |
879 | |
|
880 | 0 | const int nIDField = |
881 | 0 | !oOptions.osIDField.empty() |
882 | 0 | ? poDefn->GetFieldIndexCaseSensitive(oOptions.osIDField) |
883 | 0 | : -1; |
884 | |
|
885 | 0 | constexpr int MAX_SIGNIFICANT_DIGITS_FLOAT32 = 8; |
886 | 0 | const int nFloat32SignificantDigits = |
887 | 0 | oOptions.nSignificantFigures >= 0 |
888 | 0 | ? std::min(oOptions.nSignificantFigures, |
889 | 0 | MAX_SIGNIFICANT_DIGITS_FLOAT32) |
890 | 0 | : MAX_SIGNIFICANT_DIGITS_FLOAT32; |
891 | |
|
892 | 0 | const int nFieldCount = poDefn->GetFieldCount(); |
893 | |
|
894 | 0 | json_object *poNativeObjProp = nullptr; |
895 | 0 | json_object *poProperties = nullptr; |
896 | | |
897 | | // Scan the fields to determine if there is a chance of |
898 | | // mixed types and we can use native media |
899 | 0 | bool bUseNativeMedia{false}; |
900 | |
|
901 | 0 | if (poFeature->GetNativeMediaType() && |
902 | 0 | strcmp(poFeature->GetNativeMediaType(), "application/vnd.geo+json") == |
903 | 0 | 0 && |
904 | 0 | poFeature->GetNativeData()) |
905 | 0 | { |
906 | 0 | for (int nField = 0; nField < nFieldCount; ++nField) |
907 | 0 | { |
908 | 0 | if (poDefn->GetFieldDefn(nField)->GetSubType() == OFSTJSON) |
909 | 0 | { |
910 | 0 | if (OGRJSonParse(poFeature->GetNativeData(), &poNativeObjProp, |
911 | 0 | false)) |
912 | 0 | { |
913 | 0 | poProperties = OGRGeoJSONFindMemberByName(poNativeObjProp, |
914 | 0 | "properties"); |
915 | 0 | bUseNativeMedia = poProperties != nullptr; |
916 | 0 | } |
917 | 0 | break; |
918 | 0 | } |
919 | 0 | } |
920 | 0 | } |
921 | |
|
922 | 0 | for (int nField = 0; nField < nFieldCount; ++nField) |
923 | 0 | { |
924 | 0 | if (!poFeature->IsFieldSet(nField) || nField == nIDField) |
925 | 0 | { |
926 | 0 | continue; |
927 | 0 | } |
928 | | |
929 | 0 | OGRFieldDefn *poFieldDefn = poDefn->GetFieldDefn(nField); |
930 | 0 | CPLAssert(nullptr != poFieldDefn); |
931 | 0 | const OGRFieldType eType = poFieldDefn->GetType(); |
932 | 0 | const OGRFieldSubType eSubType = poFieldDefn->GetSubType(); |
933 | |
|
934 | 0 | if (!bWriteIdIfFoundInAttributes && |
935 | 0 | strcmp(poFieldDefn->GetNameRef(), "id") == 0) |
936 | 0 | { |
937 | 0 | continue; |
938 | 0 | } |
939 | | |
940 | 0 | json_object *poObjProp = nullptr; |
941 | |
|
942 | 0 | if (poFeature->IsFieldNull(nField)) |
943 | 0 | { |
944 | | // poObjProp = NULL; |
945 | 0 | } |
946 | 0 | else if (OFTInteger == eType) |
947 | 0 | { |
948 | 0 | if (eSubType == OFSTBoolean) |
949 | 0 | poObjProp = json_object_new_boolean( |
950 | 0 | poFeature->GetFieldAsInteger(nField)); |
951 | 0 | else |
952 | 0 | poObjProp = |
953 | 0 | json_object_new_int(poFeature->GetFieldAsInteger(nField)); |
954 | 0 | } |
955 | 0 | else if (OFTInteger64 == eType) |
956 | 0 | { |
957 | 0 | if (eSubType == OFSTBoolean) |
958 | 0 | poObjProp = json_object_new_boolean(static_cast<json_bool>( |
959 | 0 | poFeature->GetFieldAsInteger64(nField))); |
960 | 0 | else |
961 | 0 | poObjProp = json_object_new_int64( |
962 | 0 | poFeature->GetFieldAsInteger64(nField)); |
963 | 0 | } |
964 | 0 | else if (OFTReal == eType) |
965 | 0 | { |
966 | 0 | const double val = poFeature->GetFieldAsDouble(nField); |
967 | 0 | if (!std::isfinite(val)) |
968 | 0 | { |
969 | 0 | if (!oOptions.bAllowNonFiniteValues) |
970 | 0 | { |
971 | 0 | CPLErrorOnce(CE_Warning, CPLE_AppDefined, |
972 | 0 | "NaN of Infinity value found. Skipped"); |
973 | 0 | continue; |
974 | 0 | } |
975 | 0 | } |
976 | 0 | if (eSubType == OFSTFloat32) |
977 | 0 | { |
978 | 0 | poObjProp = json_object_new_float_with_significant_figures( |
979 | 0 | static_cast<float>(val), nFloat32SignificantDigits); |
980 | 0 | } |
981 | 0 | else |
982 | 0 | { |
983 | 0 | poObjProp = json_object_new_double_with_significant_figures( |
984 | 0 | val, oOptions.nSignificantFigures); |
985 | 0 | } |
986 | 0 | } |
987 | 0 | else if (OFTString == eType) |
988 | 0 | { |
989 | 0 | const char *pszStr = poFeature->GetFieldAsString(nField); |
990 | 0 | const size_t nLen = strlen(pszStr); |
991 | |
|
992 | 0 | if (eSubType == OFSTJSON || |
993 | 0 | (oOptions.bAutodetectJsonStrings && |
994 | 0 | ((pszStr[0] == '{' && pszStr[nLen - 1] == '}') || |
995 | 0 | (pszStr[0] == '[' && pszStr[nLen - 1] == ']')))) |
996 | 0 | { |
997 | 0 | if (bUseNativeMedia) |
998 | 0 | { |
999 | 0 | if (json_object *poProperty = OGRGeoJSONFindMemberByName( |
1000 | 0 | poProperties, poFieldDefn->GetNameRef())) |
1001 | 0 | { |
1002 | 0 | const char *pszProp{json_object_get_string(poProperty)}; |
1003 | 0 | if (pszProp && strcmp(pszProp, pszStr) == 0) |
1004 | 0 | { |
1005 | 0 | poObjProp = json_object_get(poProperty); |
1006 | 0 | } |
1007 | 0 | } |
1008 | 0 | } |
1009 | |
|
1010 | 0 | if (poObjProp == nullptr) |
1011 | 0 | { |
1012 | 0 | if ((pszStr[0] == '{' && pszStr[nLen - 1] == '}') || |
1013 | 0 | (pszStr[0] == '[' && pszStr[nLen - 1] == ']')) |
1014 | 0 | { |
1015 | 0 | OGRJSonParse(pszStr, &poObjProp, false); |
1016 | 0 | } |
1017 | 0 | } |
1018 | 0 | } |
1019 | |
|
1020 | 0 | if (poObjProp == nullptr) |
1021 | 0 | poObjProp = json_object_new_string(pszStr); |
1022 | 0 | } |
1023 | 0 | else if (OFTIntegerList == eType) |
1024 | 0 | { |
1025 | 0 | int nSize = 0; |
1026 | 0 | const int *panList = |
1027 | 0 | poFeature->GetFieldAsIntegerList(nField, &nSize); |
1028 | 0 | poObjProp = json_object_new_array(); |
1029 | 0 | for (int i = 0; i < nSize; i++) |
1030 | 0 | { |
1031 | 0 | if (eSubType == OFSTBoolean) |
1032 | 0 | json_object_array_add(poObjProp, |
1033 | 0 | json_object_new_boolean(panList[i])); |
1034 | 0 | else |
1035 | 0 | json_object_array_add(poObjProp, |
1036 | 0 | json_object_new_int(panList[i])); |
1037 | 0 | } |
1038 | 0 | } |
1039 | 0 | else if (OFTInteger64List == eType) |
1040 | 0 | { |
1041 | 0 | int nSize = 0; |
1042 | 0 | const GIntBig *panList = |
1043 | 0 | poFeature->GetFieldAsInteger64List(nField, &nSize); |
1044 | 0 | poObjProp = json_object_new_array(); |
1045 | 0 | for (int i = 0; i < nSize; i++) |
1046 | 0 | { |
1047 | 0 | if (eSubType == OFSTBoolean) |
1048 | 0 | json_object_array_add( |
1049 | 0 | poObjProp, json_object_new_boolean( |
1050 | 0 | static_cast<json_bool>(panList[i]))); |
1051 | 0 | else |
1052 | 0 | json_object_array_add(poObjProp, |
1053 | 0 | json_object_new_int64(panList[i])); |
1054 | 0 | } |
1055 | 0 | } |
1056 | 0 | else if (OFTRealList == eType) |
1057 | 0 | { |
1058 | 0 | int nSize = 0; |
1059 | 0 | const double *padfList = |
1060 | 0 | poFeature->GetFieldAsDoubleList(nField, &nSize); |
1061 | 0 | poObjProp = json_object_new_array(); |
1062 | 0 | for (int i = 0; i < nSize; i++) |
1063 | 0 | { |
1064 | 0 | if (eSubType == OFSTFloat32) |
1065 | 0 | { |
1066 | 0 | json_object_array_add( |
1067 | 0 | poObjProp, |
1068 | 0 | json_object_new_float_with_significant_figures( |
1069 | 0 | static_cast<float>(padfList[i]), |
1070 | 0 | nFloat32SignificantDigits)); |
1071 | 0 | } |
1072 | 0 | else |
1073 | 0 | { |
1074 | 0 | json_object_array_add( |
1075 | 0 | poObjProp, |
1076 | 0 | json_object_new_double_with_significant_figures( |
1077 | 0 | padfList[i], oOptions.nSignificantFigures)); |
1078 | 0 | } |
1079 | 0 | } |
1080 | 0 | } |
1081 | 0 | else if (OFTStringList == eType) |
1082 | 0 | { |
1083 | 0 | char **papszStringList = poFeature->GetFieldAsStringList(nField); |
1084 | 0 | poObjProp = json_object_new_array(); |
1085 | 0 | for (int i = 0; papszStringList && papszStringList[i]; i++) |
1086 | 0 | { |
1087 | 0 | json_object_array_add( |
1088 | 0 | poObjProp, json_object_new_string(papszStringList[i])); |
1089 | 0 | } |
1090 | 0 | } |
1091 | 0 | else if (OFTDateTime == eType || OFTDate == eType) |
1092 | 0 | { |
1093 | 0 | char *pszDT = OGRGetXMLDateTime(poFeature->GetRawFieldRef(nField)); |
1094 | 0 | if (eType == OFTDate) |
1095 | 0 | { |
1096 | 0 | char *pszT = strchr(pszDT, 'T'); |
1097 | 0 | if (pszT) |
1098 | 0 | *pszT = 0; |
1099 | 0 | } |
1100 | 0 | poObjProp = json_object_new_string(pszDT); |
1101 | 0 | CPLFree(pszDT); |
1102 | 0 | } |
1103 | 0 | else |
1104 | 0 | { |
1105 | 0 | poObjProp = |
1106 | 0 | json_object_new_string(poFeature->GetFieldAsString(nField)); |
1107 | 0 | } |
1108 | | |
1109 | 0 | json_object_object_add(poObjProps, poFieldDefn->GetNameRef(), |
1110 | 0 | poObjProp); |
1111 | 0 | } |
1112 | | |
1113 | 0 | if (bUseNativeMedia) |
1114 | 0 | { |
1115 | 0 | json_object_put(poNativeObjProp); |
1116 | 0 | } |
1117 | |
|
1118 | 0 | return poObjProps; |
1119 | 0 | } |
1120 | | |
1121 | | /************************************************************************/ |
1122 | | /* OGRGeoJSONWriteGeometry */ |
1123 | | /************************************************************************/ |
1124 | | |
1125 | | json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry, |
1126 | | const OGRGeoJSONWriteOptions &oOptions) |
1127 | 0 | { |
1128 | 0 | if (poGeometry == nullptr) |
1129 | 0 | { |
1130 | 0 | CPLAssert(false); |
1131 | 0 | return nullptr; |
1132 | 0 | } |
1133 | | |
1134 | 0 | OGRwkbGeometryType eFType = wkbFlatten(poGeometry->getGeometryType()); |
1135 | | // For point empty, return a null geometry. For other empty geometry types, |
1136 | | // we will generate an empty coordinate array, which is probably also |
1137 | | // borderline. |
1138 | 0 | if (eFType == wkbPoint && poGeometry->IsEmpty()) |
1139 | 0 | { |
1140 | 0 | return nullptr; |
1141 | 0 | } |
1142 | | |
1143 | 0 | json_object *poObj = json_object_new_object(); |
1144 | 0 | CPLAssert(nullptr != poObj); |
1145 | | |
1146 | | /* -------------------------------------------------------------------- */ |
1147 | | /* Build "type" member of GeoJSOn "geometry" object. */ |
1148 | | /* -------------------------------------------------------------------- */ |
1149 | | |
1150 | | // XXX - mloskot: workaround hack for pure JSON-C API design. |
1151 | 0 | char *pszName = const_cast<char *>(OGRGeoJSONGetGeometryName(poGeometry)); |
1152 | 0 | json_object_object_add(poObj, "type", json_object_new_string(pszName)); |
1153 | | |
1154 | | /* -------------------------------------------------------------------- */ |
1155 | | /* Build "coordinates" member of GeoJSOn "geometry" object. */ |
1156 | | /* -------------------------------------------------------------------- */ |
1157 | 0 | json_object *poObjGeom = nullptr; |
1158 | |
|
1159 | 0 | if (eFType == wkbGeometryCollection) |
1160 | 0 | { |
1161 | 0 | poObjGeom = OGRGeoJSONWriteGeometryCollection( |
1162 | 0 | poGeometry->toGeometryCollection(), oOptions); |
1163 | 0 | json_object_object_add(poObj, "geometries", poObjGeom); |
1164 | 0 | } |
1165 | 0 | else |
1166 | 0 | { |
1167 | 0 | if (wkbPoint == eFType) |
1168 | 0 | poObjGeom = OGRGeoJSONWritePoint(poGeometry->toPoint(), oOptions); |
1169 | 0 | else if (wkbLineString == eFType) |
1170 | 0 | poObjGeom = |
1171 | 0 | OGRGeoJSONWriteLineString(poGeometry->toLineString(), oOptions); |
1172 | 0 | else if (wkbPolygon == eFType) |
1173 | 0 | poObjGeom = |
1174 | 0 | OGRGeoJSONWritePolygon(poGeometry->toPolygon(), oOptions); |
1175 | 0 | else if (wkbMultiPoint == eFType) |
1176 | 0 | poObjGeom = |
1177 | 0 | OGRGeoJSONWriteMultiPoint(poGeometry->toMultiPoint(), oOptions); |
1178 | 0 | else if (wkbMultiLineString == eFType) |
1179 | 0 | poObjGeom = OGRGeoJSONWriteMultiLineString( |
1180 | 0 | poGeometry->toMultiLineString(), oOptions); |
1181 | 0 | else if (wkbMultiPolygon == eFType) |
1182 | 0 | poObjGeom = OGRGeoJSONWriteMultiPolygon( |
1183 | 0 | poGeometry->toMultiPolygon(), oOptions); |
1184 | 0 | else |
1185 | 0 | { |
1186 | 0 | CPLError( |
1187 | 0 | CE_Failure, CPLE_NotSupported, |
1188 | 0 | "OGR geometry type unsupported as a GeoJSON geometry detected. " |
1189 | 0 | "Feature gets NULL geometry assigned."); |
1190 | 0 | } |
1191 | |
|
1192 | 0 | if (poObjGeom != nullptr) |
1193 | 0 | { |
1194 | 0 | json_object_object_add(poObj, "coordinates", poObjGeom); |
1195 | 0 | } |
1196 | 0 | else |
1197 | 0 | { |
1198 | 0 | json_object_put(poObj); |
1199 | 0 | poObj = nullptr; |
1200 | 0 | } |
1201 | 0 | } |
1202 | |
|
1203 | 0 | return poObj; |
1204 | 0 | } |
1205 | | |
1206 | | /************************************************************************/ |
1207 | | /* OGRGeoJSONWritePoint */ |
1208 | | /************************************************************************/ |
1209 | | |
1210 | | json_object *OGRGeoJSONWritePoint(const OGRPoint *poPoint, |
1211 | | const OGRGeoJSONWriteOptions &oOptions) |
1212 | 0 | { |
1213 | 0 | CPLAssert(nullptr != poPoint); |
1214 | | |
1215 | 0 | json_object *poObj = nullptr; |
1216 | | |
1217 | | // Generate "coordinates" object for 2D or 3D dimension. |
1218 | 0 | if (wkbHasZ(poPoint->getGeometryType())) |
1219 | 0 | { |
1220 | 0 | poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(), |
1221 | 0 | poPoint->getZ(), oOptions); |
1222 | 0 | } |
1223 | 0 | else if (!poPoint->IsEmpty()) |
1224 | 0 | { |
1225 | 0 | poObj = |
1226 | 0 | OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(), oOptions); |
1227 | 0 | } |
1228 | |
|
1229 | 0 | return poObj; |
1230 | 0 | } |
1231 | | |
1232 | | /************************************************************************/ |
1233 | | /* OGRGeoJSONWriteLineString */ |
1234 | | /************************************************************************/ |
1235 | | |
1236 | | json_object *OGRGeoJSONWriteLineString(const OGRLineString *poLine, |
1237 | | const OGRGeoJSONWriteOptions &oOptions) |
1238 | 0 | { |
1239 | 0 | CPLAssert(nullptr != poLine); |
1240 | | |
1241 | | // Generate "coordinates" object for 2D or 3D dimension. |
1242 | 0 | json_object *poObj = OGRGeoJSONWriteLineCoords(poLine, oOptions); |
1243 | |
|
1244 | 0 | return poObj; |
1245 | 0 | } |
1246 | | |
1247 | | /************************************************************************/ |
1248 | | /* OGRGeoJSONWritePolygon */ |
1249 | | /************************************************************************/ |
1250 | | |
1251 | | json_object *OGRGeoJSONWritePolygon(const OGRPolygon *poPolygon, |
1252 | | const OGRGeoJSONWriteOptions &oOptions) |
1253 | 0 | { |
1254 | 0 | CPLAssert(nullptr != poPolygon); |
1255 | | |
1256 | | // Generate "coordinates" array object. |
1257 | 0 | json_object *poObj = json_object_new_array(); |
1258 | | |
1259 | | // Exterior ring. |
1260 | 0 | const OGRLinearRing *poRing = poPolygon->getExteriorRing(); |
1261 | 0 | if (poRing == nullptr) |
1262 | 0 | return poObj; |
1263 | | |
1264 | 0 | json_object *poObjRing = OGRGeoJSONWriteRingCoords(poRing, true, oOptions); |
1265 | 0 | if (poObjRing == nullptr) |
1266 | 0 | { |
1267 | 0 | json_object_put(poObj); |
1268 | 0 | return nullptr; |
1269 | 0 | } |
1270 | 0 | json_object_array_add(poObj, poObjRing); |
1271 | | |
1272 | | // Interior rings. |
1273 | 0 | const int nCount = poPolygon->getNumInteriorRings(); |
1274 | 0 | for (int i = 0; i < nCount; ++i) |
1275 | 0 | { |
1276 | 0 | poRing = poPolygon->getInteriorRing(i); |
1277 | 0 | CPLAssert(poRing); |
1278 | | |
1279 | 0 | poObjRing = OGRGeoJSONWriteRingCoords(poRing, false, oOptions); |
1280 | 0 | if (poObjRing == nullptr) |
1281 | 0 | { |
1282 | 0 | json_object_put(poObj); |
1283 | 0 | return nullptr; |
1284 | 0 | } |
1285 | | |
1286 | 0 | json_object_array_add(poObj, poObjRing); |
1287 | 0 | } |
1288 | | |
1289 | 0 | return poObj; |
1290 | 0 | } |
1291 | | |
1292 | | /************************************************************************/ |
1293 | | /* OGRGeoJSONWriteMultiPoint */ |
1294 | | /************************************************************************/ |
1295 | | |
1296 | | json_object *OGRGeoJSONWriteMultiPoint(const OGRMultiPoint *poGeometry, |
1297 | | const OGRGeoJSONWriteOptions &oOptions) |
1298 | 0 | { |
1299 | 0 | CPLAssert(nullptr != poGeometry); |
1300 | | |
1301 | | // Generate "coordinates" object for 2D or 3D dimension. |
1302 | 0 | json_object *poObj = json_object_new_array(); |
1303 | |
|
1304 | 0 | for (int i = 0; i < poGeometry->getNumGeometries(); ++i) |
1305 | 0 | { |
1306 | 0 | const OGRGeometry *poGeom = poGeometry->getGeometryRef(i); |
1307 | 0 | CPLAssert(nullptr != poGeom); |
1308 | 0 | const OGRPoint *poPoint = poGeom->toPoint(); |
1309 | |
|
1310 | 0 | json_object *poObjPoint = OGRGeoJSONWritePoint(poPoint, oOptions); |
1311 | 0 | if (poObjPoint == nullptr) |
1312 | 0 | { |
1313 | 0 | json_object_put(poObj); |
1314 | 0 | return nullptr; |
1315 | 0 | } |
1316 | | |
1317 | 0 | json_object_array_add(poObj, poObjPoint); |
1318 | 0 | } |
1319 | | |
1320 | 0 | return poObj; |
1321 | 0 | } |
1322 | | |
1323 | | /************************************************************************/ |
1324 | | /* OGRGeoJSONWriteMultiLineString */ |
1325 | | /************************************************************************/ |
1326 | | |
1327 | | json_object * |
1328 | | OGRGeoJSONWriteMultiLineString(const OGRMultiLineString *poGeometry, |
1329 | | const OGRGeoJSONWriteOptions &oOptions) |
1330 | 0 | { |
1331 | 0 | CPLAssert(nullptr != poGeometry); |
1332 | | |
1333 | | // Generate "coordinates" object for 2D or 3D dimension. |
1334 | 0 | json_object *poObj = json_object_new_array(); |
1335 | |
|
1336 | 0 | for (int i = 0; i < poGeometry->getNumGeometries(); ++i) |
1337 | 0 | { |
1338 | 0 | const OGRGeometry *poGeom = poGeometry->getGeometryRef(i); |
1339 | 0 | CPLAssert(nullptr != poGeom); |
1340 | 0 | const OGRLineString *poLine = poGeom->toLineString(); |
1341 | |
|
1342 | 0 | json_object *poObjLine = OGRGeoJSONWriteLineString(poLine, oOptions); |
1343 | 0 | if (poObjLine == nullptr) |
1344 | 0 | { |
1345 | 0 | json_object_put(poObj); |
1346 | 0 | return nullptr; |
1347 | 0 | } |
1348 | | |
1349 | 0 | json_object_array_add(poObj, poObjLine); |
1350 | 0 | } |
1351 | | |
1352 | 0 | return poObj; |
1353 | 0 | } |
1354 | | |
1355 | | /************************************************************************/ |
1356 | | /* OGRGeoJSONWriteMultiPolygon */ |
1357 | | /************************************************************************/ |
1358 | | |
1359 | | json_object *OGRGeoJSONWriteMultiPolygon(const OGRMultiPolygon *poGeometry, |
1360 | | const OGRGeoJSONWriteOptions &oOptions) |
1361 | 0 | { |
1362 | 0 | CPLAssert(nullptr != poGeometry); |
1363 | | |
1364 | | // Generate "coordinates" object for 2D or 3D dimension. |
1365 | 0 | json_object *poObj = json_object_new_array(); |
1366 | |
|
1367 | 0 | for (int i = 0; i < poGeometry->getNumGeometries(); ++i) |
1368 | 0 | { |
1369 | 0 | const OGRGeometry *poGeom = poGeometry->getGeometryRef(i); |
1370 | 0 | CPLAssert(nullptr != poGeom); |
1371 | 0 | const OGRPolygon *poPoly = poGeom->toPolygon(); |
1372 | |
|
1373 | 0 | json_object *poObjPoly = OGRGeoJSONWritePolygon(poPoly, oOptions); |
1374 | 0 | if (poObjPoly == nullptr) |
1375 | 0 | { |
1376 | 0 | json_object_put(poObj); |
1377 | 0 | return nullptr; |
1378 | 0 | } |
1379 | | |
1380 | 0 | json_object_array_add(poObj, poObjPoly); |
1381 | 0 | } |
1382 | | |
1383 | 0 | return poObj; |
1384 | 0 | } |
1385 | | |
1386 | | /************************************************************************/ |
1387 | | /* OGRGeoJSONWriteGeometryCollection */ |
1388 | | /************************************************************************/ |
1389 | | |
1390 | | json_object * |
1391 | | OGRGeoJSONWriteGeometryCollection(const OGRGeometryCollection *poGeometry, |
1392 | | const OGRGeoJSONWriteOptions &oOptions) |
1393 | 0 | { |
1394 | 0 | CPLAssert(nullptr != poGeometry); |
1395 | | |
1396 | | /* Generate "geometries" object. */ |
1397 | 0 | json_object *poObj = json_object_new_array(); |
1398 | |
|
1399 | 0 | for (int i = 0; i < poGeometry->getNumGeometries(); ++i) |
1400 | 0 | { |
1401 | 0 | const OGRGeometry *poGeom = poGeometry->getGeometryRef(i); |
1402 | 0 | CPLAssert(nullptr != poGeom); |
1403 | | |
1404 | 0 | json_object *poObjGeom = OGRGeoJSONWriteGeometry(poGeom, oOptions); |
1405 | 0 | if (poObjGeom == nullptr) |
1406 | 0 | { |
1407 | 0 | json_object_put(poObj); |
1408 | 0 | return nullptr; |
1409 | 0 | } |
1410 | | |
1411 | 0 | json_object_array_add(poObj, poObjGeom); |
1412 | 0 | } |
1413 | | |
1414 | 0 | return poObj; |
1415 | 0 | } |
1416 | | |
1417 | | /************************************************************************/ |
1418 | | /* OGRGeoJSONWriteCoords */ |
1419 | | /************************************************************************/ |
1420 | | |
1421 | | json_object *OGRGeoJSONWriteCoords(double const &fX, double const &fY, |
1422 | | const OGRGeoJSONWriteOptions &oOptions) |
1423 | 0 | { |
1424 | 0 | json_object *poObjCoords = nullptr; |
1425 | 0 | if (std::isinf(fX) || std::isinf(fY) || std::isnan(fX) || std::isnan(fY)) |
1426 | 0 | { |
1427 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1428 | 0 | "Infinite or NaN coordinate encountered"); |
1429 | 0 | return nullptr; |
1430 | 0 | } |
1431 | 0 | poObjCoords = json_object_new_array(); |
1432 | 0 | json_object_array_add(poObjCoords, json_object_new_coord(fX, 1, oOptions)); |
1433 | 0 | json_object_array_add(poObjCoords, json_object_new_coord(fY, 2, oOptions)); |
1434 | |
|
1435 | 0 | return poObjCoords; |
1436 | 0 | } |
1437 | | |
1438 | | json_object *OGRGeoJSONWriteCoords(double const &fX, double const &fY, |
1439 | | double const &fZ, |
1440 | | const OGRGeoJSONWriteOptions &oOptions) |
1441 | 0 | { |
1442 | 0 | if (std::isinf(fX) || std::isinf(fY) || std::isinf(fZ) || std::isnan(fX) || |
1443 | 0 | std::isnan(fY) || std::isnan(fZ)) |
1444 | 0 | { |
1445 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1446 | 0 | "Infinite or NaN coordinate encountered"); |
1447 | 0 | return nullptr; |
1448 | 0 | } |
1449 | 0 | json_object *poObjCoords = json_object_new_array(); |
1450 | 0 | json_object_array_add(poObjCoords, json_object_new_coord(fX, 1, oOptions)); |
1451 | 0 | json_object_array_add(poObjCoords, json_object_new_coord(fY, 2, oOptions)); |
1452 | 0 | json_object_array_add(poObjCoords, json_object_new_coord(fZ, 3, oOptions)); |
1453 | |
|
1454 | 0 | return poObjCoords; |
1455 | 0 | } |
1456 | | |
1457 | | /************************************************************************/ |
1458 | | /* OGRGeoJSONWriteLineCoords */ |
1459 | | /************************************************************************/ |
1460 | | |
1461 | | json_object *OGRGeoJSONWriteLineCoords(const OGRLineString *poLine, |
1462 | | const OGRGeoJSONWriteOptions &oOptions) |
1463 | 0 | { |
1464 | 0 | json_object *poObjPoint = nullptr; |
1465 | 0 | json_object *poObjCoords = json_object_new_array(); |
1466 | |
|
1467 | 0 | const int nCount = poLine->getNumPoints(); |
1468 | 0 | const bool bHasZ = wkbHasZ(poLine->getGeometryType()); |
1469 | 0 | for (int i = 0; i < nCount; ++i) |
1470 | 0 | { |
1471 | 0 | if (!bHasZ) |
1472 | 0 | poObjPoint = OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i), |
1473 | 0 | oOptions); |
1474 | 0 | else |
1475 | 0 | poObjPoint = OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i), |
1476 | 0 | poLine->getZ(i), oOptions); |
1477 | 0 | if (poObjPoint == nullptr) |
1478 | 0 | { |
1479 | 0 | json_object_put(poObjCoords); |
1480 | 0 | return nullptr; |
1481 | 0 | } |
1482 | 0 | json_object_array_add(poObjCoords, poObjPoint); |
1483 | 0 | } |
1484 | | |
1485 | 0 | return poObjCoords; |
1486 | 0 | } |
1487 | | |
1488 | | /************************************************************************/ |
1489 | | /* OGRGeoJSONWriteRingCoords */ |
1490 | | /************************************************************************/ |
1491 | | |
1492 | | json_object *OGRGeoJSONWriteRingCoords(const OGRLinearRing *poLine, |
1493 | | bool bIsExteriorRing, |
1494 | | const OGRGeoJSONWriteOptions &oOptions) |
1495 | 0 | { |
1496 | 0 | json_object *poObjPoint = nullptr; |
1497 | 0 | json_object *poObjCoords = json_object_new_array(); |
1498 | |
|
1499 | 0 | bool bInvertOrder = oOptions.bPolygonRightHandRule && |
1500 | 0 | ((bIsExteriorRing && poLine->isClockwise()) || |
1501 | 0 | (!bIsExteriorRing && !poLine->isClockwise())); |
1502 | |
|
1503 | 0 | const int nCount = poLine->getNumPoints(); |
1504 | 0 | const bool bHasZ = wkbHasZ(poLine->getGeometryType()); |
1505 | 0 | for (int i = 0; i < nCount; ++i) |
1506 | 0 | { |
1507 | 0 | const int nIdx = (bInvertOrder) ? nCount - 1 - i : i; |
1508 | 0 | if (!bHasZ) |
1509 | 0 | poObjPoint = OGRGeoJSONWriteCoords(poLine->getX(nIdx), |
1510 | 0 | poLine->getY(nIdx), oOptions); |
1511 | 0 | else |
1512 | 0 | poObjPoint = |
1513 | 0 | OGRGeoJSONWriteCoords(poLine->getX(nIdx), poLine->getY(nIdx), |
1514 | 0 | poLine->getZ(nIdx), oOptions); |
1515 | 0 | if (poObjPoint == nullptr) |
1516 | 0 | { |
1517 | 0 | json_object_put(poObjCoords); |
1518 | 0 | return nullptr; |
1519 | 0 | } |
1520 | 0 | json_object_array_add(poObjCoords, poObjPoint); |
1521 | 0 | } |
1522 | | |
1523 | 0 | return poObjCoords; |
1524 | 0 | } |
1525 | | |
1526 | | /************************************************************************/ |
1527 | | /* OGR_json_float_with_significant_figures_to_string() */ |
1528 | | /************************************************************************/ |
1529 | | |
1530 | | static int OGR_json_float_with_significant_figures_to_string( |
1531 | | struct json_object *jso, struct printbuf *pb, int /* level */, |
1532 | | int /* flags */) |
1533 | 0 | { |
1534 | 0 | char szBuffer[75] = {}; |
1535 | 0 | int nSize = 0; |
1536 | 0 | const float fVal = static_cast<float>(json_object_get_double(jso)); |
1537 | 0 | if (std::isnan(fVal)) |
1538 | 0 | nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "NaN"); |
1539 | 0 | else if (std::isinf(fVal)) |
1540 | 0 | { |
1541 | 0 | if (fVal > 0) |
1542 | 0 | nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "Infinity"); |
1543 | 0 | else |
1544 | 0 | nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "-Infinity"); |
1545 | 0 | } |
1546 | 0 | else |
1547 | 0 | { |
1548 | 0 | const void *userData = |
1549 | | #if (!defined(JSON_C_VERSION_NUM)) || (JSON_C_VERSION_NUM < JSON_C_VER_013) |
1550 | | jso->_userdata; |
1551 | | #else |
1552 | 0 | json_object_get_userdata(jso); |
1553 | 0 | #endif |
1554 | 0 | const uintptr_t nSignificantFigures = |
1555 | 0 | reinterpret_cast<uintptr_t>(userData); |
1556 | 0 | const bool bSignificantFiguresIsNegative = |
1557 | 0 | (nSignificantFigures >> (8 * sizeof(nSignificantFigures) - 1)) != 0; |
1558 | 0 | const int nInitialSignificantFigures = |
1559 | 0 | bSignificantFiguresIsNegative |
1560 | 0 | ? 8 |
1561 | 0 | : static_cast<int>(nSignificantFigures); |
1562 | 0 | nSize = OGRFormatFloat(szBuffer, sizeof(szBuffer), fVal, |
1563 | 0 | nInitialSignificantFigures, 'g'); |
1564 | 0 | } |
1565 | |
|
1566 | 0 | return printbuf_memappend(pb, szBuffer, nSize); |
1567 | 0 | } |
1568 | | |
1569 | | /************************************************************************/ |
1570 | | /* json_object_new_float_with_significant_figures() */ |
1571 | | /************************************************************************/ |
1572 | | |
1573 | | json_object * |
1574 | | json_object_new_float_with_significant_figures(float fVal, |
1575 | | int nSignificantFigures) |
1576 | 0 | { |
1577 | 0 | json_object *jso = json_object_new_double(fVal); |
1578 | 0 | json_object_set_serializer( |
1579 | 0 | jso, OGR_json_float_with_significant_figures_to_string, |
1580 | 0 | reinterpret_cast<void *>(static_cast<uintptr_t>(nSignificantFigures)), |
1581 | 0 | nullptr); |
1582 | 0 | return jso; |
1583 | 0 | } |
1584 | | |
1585 | | /*! @endcond */ |
1586 | | |
1587 | | /************************************************************************/ |
1588 | | /* OGR_G_ExportToJson */ |
1589 | | /************************************************************************/ |
1590 | | |
1591 | | /** |
1592 | | * \brief Convert a geometry into GeoJSON format. |
1593 | | * |
1594 | | * The returned string should be freed with CPLFree() when no longer required. |
1595 | | * |
1596 | | * This method is the same as the C++ method OGRGeometry::exportToJson(). |
1597 | | * |
1598 | | * @param hGeometry handle to the geometry. |
1599 | | * @return A GeoJSON fragment or NULL in case of error. |
1600 | | */ |
1601 | | |
1602 | | char *OGR_G_ExportToJson(OGRGeometryH hGeometry) |
1603 | 0 | { |
1604 | 0 | return OGR_G_ExportToJsonEx(hGeometry, nullptr); |
1605 | 0 | } |
1606 | | |
1607 | | /************************************************************************/ |
1608 | | /* OGR_G_ExportToJsonEx */ |
1609 | | /************************************************************************/ |
1610 | | |
1611 | | /** |
1612 | | * \brief Convert a geometry into GeoJSON format. |
1613 | | * |
1614 | | * The returned string should be freed with CPLFree() when no longer required. |
1615 | | * |
1616 | | * The following options are supported : |
1617 | | * <ul> |
1618 | | * <li>COORDINATE_PRECISION=number: maximum number of figures after decimal |
1619 | | * separator to write in coordinates.</li> |
1620 | | * <li>XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates |
1621 | | * (added in GDAL 3.9)</li> |
1622 | | * <li>Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates |
1623 | | * (added in GDAL 3.9)</li> |
1624 | | * <li>SIGNIFICANT_FIGURES=number: |
1625 | | * maximum number of significant figures (GDAL >= 2.1).</li> |
1626 | | * </ul> |
1627 | | * |
1628 | | * If XY_COORD_PRECISION or Z_COORD_PRECISION is specified, COORDINATE_PRECISION |
1629 | | * or SIGNIFICANT_FIGURES will be ignored if specified. |
1630 | | * If COORDINATE_PRECISION is defined, SIGNIFICANT_FIGURES will be ignored if |
1631 | | * specified. |
1632 | | * When none are defined, the default is COORDINATE_PRECISION=15. |
1633 | | * |
1634 | | * This method is the same as the C++ method OGRGeometry::exportToJson(). |
1635 | | * |
1636 | | * @param hGeometry handle to the geometry. |
1637 | | * @param papszOptions a null terminated list of options. |
1638 | | * @return A GeoJSON fragment or NULL in case of error. |
1639 | | * |
1640 | | * @since OGR 1.9.0 |
1641 | | */ |
1642 | | |
1643 | | char *OGR_G_ExportToJsonEx(OGRGeometryH hGeometry, char **papszOptions) |
1644 | 0 | { |
1645 | 0 | VALIDATE_POINTER1(hGeometry, "OGR_G_ExportToJson", nullptr); |
1646 | | |
1647 | 0 | OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry); |
1648 | |
|
1649 | 0 | const char *pszCoordPrecision = |
1650 | 0 | CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1"); |
1651 | |
|
1652 | 0 | const int nSignificantFigures = |
1653 | 0 | atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); |
1654 | |
|
1655 | 0 | OGRGeoJSONWriteOptions oOptions; |
1656 | 0 | oOptions.nXYCoordPrecision = atoi(CSLFetchNameValueDef( |
1657 | 0 | papszOptions, "XY_COORD_PRECISION", pszCoordPrecision)); |
1658 | 0 | oOptions.nZCoordPrecision = atoi(CSLFetchNameValueDef( |
1659 | 0 | papszOptions, "Z_COORD_PRECISION", pszCoordPrecision)); |
1660 | 0 | oOptions.nSignificantFigures = nSignificantFigures; |
1661 | | |
1662 | | // If the CRS has latitude, longitude (or northing, easting) axis order, |
1663 | | // and the data axis to SRS axis mapping doesn't change that order, |
1664 | | // then swap X and Y values. |
1665 | 0 | bool bHasSwappedXY = false; |
1666 | 0 | const auto poSRS = poGeometry->getSpatialReference(); |
1667 | 0 | if (poSRS && |
1668 | 0 | (poSRS->EPSGTreatsAsLatLong() || |
1669 | 0 | poSRS->EPSGTreatsAsNorthingEasting()) && |
1670 | 0 | poSRS->GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2}) |
1671 | 0 | { |
1672 | 0 | poGeometry->swapXY(); |
1673 | 0 | bHasSwappedXY = true; |
1674 | 0 | } |
1675 | |
|
1676 | 0 | json_object *poObj = OGRGeoJSONWriteGeometry(poGeometry, oOptions); |
1677 | | |
1678 | | // Unswap back |
1679 | 0 | if (bHasSwappedXY) |
1680 | 0 | poGeometry->swapXY(); |
1681 | |
|
1682 | 0 | if (nullptr != poObj) |
1683 | 0 | { |
1684 | 0 | char *pszJson = CPLStrdup(json_object_to_json_string(poObj)); |
1685 | | |
1686 | | // Release JSON tree. |
1687 | 0 | json_object_put(poObj); |
1688 | |
|
1689 | 0 | return pszJson; |
1690 | 0 | } |
1691 | | |
1692 | | // Translation failed. |
1693 | 0 | return nullptr; |
1694 | 0 | } |