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