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