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 25985 : 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 25985 : if (nDimIdx <= 2)
140 : {
141 24530 : if (oOptions.nXYCoordPrecision >= 0 || oOptions.nSignificantFigures < 0)
142 49056 : return json_object_new_double_with_precision(
143 24528 : 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 : /* GetLinearCollection() */
1129 : /************************************************************************/
1130 :
1131 : static std::unique_ptr<OGRGeometry>
1132 2 : GetLinearCollection(const OGRGeometryCollection *poGeomColl)
1133 : {
1134 4 : auto poFlatGeom = std::make_unique<OGRGeometryCollection>();
1135 5 : for (const auto *poSubGeom : *poGeomColl)
1136 : {
1137 3 : if (wkbFlatten(poSubGeom->getGeometryType()) == wkbGeometryCollection)
1138 : {
1139 2 : poFlatGeom->addGeometry(
1140 2 : GetLinearCollection(poSubGeom->toGeometryCollection()));
1141 : }
1142 : else
1143 : {
1144 6 : poFlatGeom->addGeometryDirectly(OGRGeometryFactory::forceTo(
1145 2 : poSubGeom->clone(),
1146 2 : OGR_GT_GetLinear(poSubGeom->getGeometryType())));
1147 : }
1148 : }
1149 4 : return poFlatGeom;
1150 : }
1151 :
1152 : /************************************************************************/
1153 : /* OGRGeoJSONWriteGeometry */
1154 : /************************************************************************/
1155 :
1156 1836 : json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry,
1157 : const OGRGeoJSONWriteOptions &oOptions)
1158 : {
1159 1836 : if (poGeometry == nullptr)
1160 : {
1161 0 : CPLAssert(false);
1162 : return nullptr;
1163 : }
1164 :
1165 1836 : if (!oOptions.bAllowCurve && poGeometry->hasCurveGeometry(true))
1166 : {
1167 : const OGRwkbGeometryType eTargetType =
1168 23 : OGR_GT_GetLinear(poGeometry->getGeometryType());
1169 23 : std::unique_ptr<OGRGeometry> poFlatGeom;
1170 23 : if (wkbFlatten(eTargetType) == wkbGeometryCollection)
1171 : {
1172 : poFlatGeom =
1173 1 : GetLinearCollection(poGeometry->toGeometryCollection());
1174 : }
1175 : else
1176 : {
1177 22 : poFlatGeom.reset(
1178 22 : OGRGeometryFactory::forceTo(poGeometry->clone(), eTargetType));
1179 : }
1180 23 : return OGRGeoJSONWriteGeometry(poFlatGeom.get(), oOptions);
1181 : }
1182 :
1183 1813 : OGRwkbGeometryType eFType = wkbFlatten(poGeometry->getGeometryType());
1184 : // For point empty, return a null geometry. For other empty geometry types,
1185 : // we will generate an empty coordinate array, which is probably also
1186 : // borderline.
1187 1813 : if (eFType == wkbPoint && poGeometry->IsEmpty())
1188 : {
1189 2 : return nullptr;
1190 : }
1191 :
1192 0 : std::unique_ptr<OGRGeometry> poTmpGeom; // keep in that scope
1193 1811 : if (eFType == wkbCircularString)
1194 : {
1195 24 : auto poCS = poGeometry->toCircularString();
1196 24 : const int nNumPoints = poCS->getNumPoints();
1197 24 : constexpr int MAX_POINTS_PER_CC = 11;
1198 24 : if (nNumPoints > MAX_POINTS_PER_CC)
1199 : {
1200 2 : auto poCC = std::make_unique<OGRCompoundCurve>();
1201 1 : auto poSubCS = std::make_unique<OGRCircularString>();
1202 14 : for (int i = 0; i < nNumPoints; ++i)
1203 : {
1204 26 : OGRPoint oPoint;
1205 13 : poCS->getPoint(i, &oPoint);
1206 13 : poSubCS->addPoint(&oPoint);
1207 13 : if (poSubCS->getNumPoints() == MAX_POINTS_PER_CC)
1208 : {
1209 1 : poCC->addCurve(std::move(poSubCS));
1210 1 : poSubCS = std::make_unique<OGRCircularString>();
1211 1 : poSubCS->addPoint(&oPoint);
1212 : }
1213 : }
1214 1 : if (poSubCS->getNumPoints() > 1)
1215 1 : poCC->addCurve(std::move(poSubCS));
1216 1 : poTmpGeom = std::move(poCC);
1217 1 : poGeometry = poTmpGeom.get();
1218 1 : eFType = wkbCompoundCurve;
1219 : }
1220 : }
1221 :
1222 1811 : json_object *poObj = json_object_new_object();
1223 1811 : CPLAssert(nullptr != poObj);
1224 :
1225 : /* -------------------------------------------------------------------- */
1226 : /* Build "type" member of GeoJSON "geometry" object. */
1227 : /* -------------------------------------------------------------------- */
1228 :
1229 1811 : const char *pszName = OGRGeoJSONGetGeometryName(poGeometry);
1230 1811 : json_object_object_add(poObj, "type", json_object_new_string(pszName));
1231 :
1232 : /* -------------------------------------------------------------------- */
1233 : /* Build "coordinates" member of GeoJSON "geometry" object. */
1234 : /* -------------------------------------------------------------------- */
1235 1811 : json_object *poObjGeom = nullptr;
1236 :
1237 1811 : if (eFType == wkbGeometryCollection || eFType == wkbMultiCurve ||
1238 : eFType == wkbMultiSurface)
1239 : {
1240 35 : poObjGeom = OGRGeoJSONWriteGeometryCollection(
1241 : poGeometry->toGeometryCollection(), oOptions);
1242 35 : json_object_object_add(poObj, "geometries", poObjGeom);
1243 : }
1244 1776 : else if (eFType == wkbCompoundCurve)
1245 : {
1246 14 : poObjGeom = OGRGeoJSONWriteCompoundCurve(poGeometry->toCompoundCurve(),
1247 : oOptions);
1248 14 : json_object_object_add(poObj, "geometries", poObjGeom);
1249 : }
1250 1762 : else if (eFType == wkbCurvePolygon)
1251 : {
1252 : poObjGeom =
1253 9 : OGRGeoJSONWriteCurvePolygon(poGeometry->toCurvePolygon(), oOptions);
1254 9 : json_object_object_add(poObj, "geometries", poObjGeom);
1255 : }
1256 : else
1257 : {
1258 1753 : if (wkbPoint == eFType)
1259 179 : poObjGeom = OGRGeoJSONWritePoint(poGeometry->toPoint(), oOptions);
1260 1574 : else if (wkbLineString == eFType || wkbCircularString == eFType)
1261 121 : poObjGeom = OGRGeoJSONWriteSimpleCurve(poGeometry->toSimpleCurve(),
1262 : oOptions);
1263 1453 : else if (wkbPolygon == eFType)
1264 : poObjGeom =
1265 1302 : OGRGeoJSONWritePolygon(poGeometry->toPolygon(), oOptions);
1266 151 : else if (wkbMultiPoint == eFType)
1267 : poObjGeom =
1268 64 : OGRGeoJSONWriteMultiPoint(poGeometry->toMultiPoint(), oOptions);
1269 87 : else if (wkbMultiLineString == eFType)
1270 38 : poObjGeom = OGRGeoJSONWriteMultiLineString(
1271 : poGeometry->toMultiLineString(), oOptions);
1272 49 : else if (wkbMultiPolygon == eFType)
1273 48 : poObjGeom = OGRGeoJSONWriteMultiPolygon(
1274 : poGeometry->toMultiPolygon(), oOptions);
1275 : else
1276 : {
1277 1 : CPLError(
1278 : CE_Failure, CPLE_NotSupported,
1279 : "OGR geometry type unsupported as a GeoJSON geometry detected. "
1280 : "Feature gets NULL geometry assigned.");
1281 : }
1282 :
1283 1753 : if (poObjGeom != nullptr)
1284 : {
1285 1744 : json_object_object_add(poObj, "coordinates", poObjGeom);
1286 : }
1287 : else
1288 : {
1289 9 : json_object_put(poObj);
1290 9 : poObj = nullptr;
1291 : }
1292 : }
1293 :
1294 1811 : return poObj;
1295 : }
1296 :
1297 : /************************************************************************/
1298 : /* OGRGeoJSONWritePoint */
1299 : /************************************************************************/
1300 :
1301 371 : json_object *OGRGeoJSONWritePoint(const OGRPoint *poPoint,
1302 : const OGRGeoJSONWriteOptions &oOptions)
1303 : {
1304 371 : CPLAssert(nullptr != poPoint);
1305 :
1306 371 : json_object *poObj = nullptr;
1307 :
1308 : // Generate "coordinates" object
1309 371 : if (!poPoint->IsEmpty())
1310 : {
1311 371 : if (oOptions.bAllowMeasure && poPoint->IsMeasured())
1312 : {
1313 6 : if (poPoint->Is3D())
1314 : {
1315 1 : poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
1316 2 : poPoint->getZ(), poPoint->getM(),
1317 : oOptions);
1318 : }
1319 : else
1320 : {
1321 5 : poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
1322 10 : std::nullopt, poPoint->getM(),
1323 : oOptions);
1324 : }
1325 : }
1326 365 : else if (poPoint->Is3D())
1327 : {
1328 : poObj =
1329 448 : OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
1330 448 : poPoint->getZ(), std::nullopt, oOptions);
1331 : }
1332 : else
1333 : {
1334 141 : poObj = OGRGeoJSONWriteCoords(poPoint->getX(), poPoint->getY(),
1335 : std::nullopt, std::nullopt, oOptions);
1336 : }
1337 : }
1338 :
1339 371 : return poObj;
1340 : }
1341 :
1342 : /************************************************************************/
1343 : /* OGRGeoJSONWriteSimpleCurve */
1344 : /************************************************************************/
1345 :
1346 174 : json_object *OGRGeoJSONWriteSimpleCurve(const OGRSimpleCurve *poLine,
1347 : const OGRGeoJSONWriteOptions &oOptions)
1348 : {
1349 174 : CPLAssert(nullptr != poLine);
1350 :
1351 : // Generate "coordinates" object for 2D or 3D dimension.
1352 174 : json_object *poObj = OGRGeoJSONWriteLineCoords(poLine, oOptions);
1353 :
1354 174 : return poObj;
1355 : }
1356 :
1357 : /************************************************************************/
1358 : /* OGRGeoJSONWritePolygon */
1359 : /************************************************************************/
1360 :
1361 1380 : json_object *OGRGeoJSONWritePolygon(const OGRPolygon *poPolygon,
1362 : const OGRGeoJSONWriteOptions &oOptions)
1363 : {
1364 1380 : CPLAssert(nullptr != poPolygon);
1365 :
1366 : // Generate "coordinates" array object.
1367 1380 : json_object *poObj = json_object_new_array();
1368 :
1369 1380 : bool bExteriorRing = true;
1370 2765 : for (const auto *poRing : *poPolygon)
1371 : {
1372 : json_object *poObjRing =
1373 1388 : OGRGeoJSONWriteRingCoords(poRing, bExteriorRing, oOptions);
1374 1388 : bExteriorRing = false;
1375 1388 : if (poObjRing == nullptr)
1376 : {
1377 3 : json_object_put(poObj);
1378 3 : return nullptr;
1379 : }
1380 1385 : json_object_array_add(poObj, poObjRing);
1381 : }
1382 :
1383 1377 : return poObj;
1384 : }
1385 :
1386 : /************************************************************************/
1387 : /* OGRGeoJSONWriteMultiPoint */
1388 : /************************************************************************/
1389 :
1390 64 : json_object *OGRGeoJSONWriteMultiPoint(const OGRMultiPoint *poGeometry,
1391 : const OGRGeoJSONWriteOptions &oOptions)
1392 : {
1393 64 : CPLAssert(nullptr != poGeometry);
1394 :
1395 : // Generate "coordinates" object
1396 64 : json_object *poObj = json_object_new_array();
1397 :
1398 255 : for (const auto *poPoint : poGeometry)
1399 : {
1400 192 : json_object *poObjPoint = OGRGeoJSONWritePoint(poPoint, oOptions);
1401 192 : if (poObjPoint == nullptr)
1402 : {
1403 1 : json_object_put(poObj);
1404 1 : return nullptr;
1405 : }
1406 :
1407 191 : json_object_array_add(poObj, poObjPoint);
1408 : }
1409 :
1410 63 : return poObj;
1411 : }
1412 :
1413 : /************************************************************************/
1414 : /* OGRGeoJSONWriteMultiLineString */
1415 : /************************************************************************/
1416 :
1417 : json_object *
1418 38 : OGRGeoJSONWriteMultiLineString(const OGRMultiLineString *poGeometry,
1419 : const OGRGeoJSONWriteOptions &oOptions)
1420 : {
1421 38 : CPLAssert(nullptr != poGeometry);
1422 :
1423 : // Generate "coordinates" object
1424 38 : json_object *poObj = json_object_new_array();
1425 :
1426 90 : for (const auto *poLine : poGeometry)
1427 : {
1428 53 : json_object *poObjLine = OGRGeoJSONWriteSimpleCurve(poLine, oOptions);
1429 53 : if (poObjLine == nullptr)
1430 : {
1431 1 : json_object_put(poObj);
1432 1 : return nullptr;
1433 : }
1434 :
1435 52 : json_object_array_add(poObj, poObjLine);
1436 : }
1437 :
1438 37 : return poObj;
1439 : }
1440 :
1441 : /************************************************************************/
1442 : /* OGRGeoJSONWriteMultiPolygon */
1443 : /************************************************************************/
1444 :
1445 48 : json_object *OGRGeoJSONWriteMultiPolygon(const OGRMultiPolygon *poGeometry,
1446 : const OGRGeoJSONWriteOptions &oOptions)
1447 : {
1448 48 : CPLAssert(nullptr != poGeometry);
1449 :
1450 : // Generate "coordinates" object
1451 48 : json_object *poObj = json_object_new_array();
1452 :
1453 120 : for (const auto *poPoly : poGeometry)
1454 : {
1455 73 : json_object *poObjPoly = OGRGeoJSONWritePolygon(poPoly, oOptions);
1456 73 : if (poObjPoly == nullptr)
1457 : {
1458 1 : json_object_put(poObj);
1459 1 : return nullptr;
1460 : }
1461 :
1462 72 : json_object_array_add(poObj, poObjPoly);
1463 : }
1464 :
1465 47 : return poObj;
1466 : }
1467 :
1468 : /************************************************************************/
1469 : /* OGRGeoJSONWriteCollectionGeneric() */
1470 : /************************************************************************/
1471 :
1472 : template <class T>
1473 : static json_object *
1474 58 : OGRGeoJSONWriteCollectionGeneric(const T *poGeometry,
1475 : const OGRGeoJSONWriteOptions &oOptions)
1476 : {
1477 58 : CPLAssert(nullptr != poGeometry);
1478 :
1479 : /* Generate "geometries" object. */
1480 58 : json_object *poObj = json_object_new_array();
1481 :
1482 187 : for (const OGRGeometry *poGeom : *poGeometry)
1483 : {
1484 130 : json_object *poObjGeom = OGRGeoJSONWriteGeometry(poGeom, oOptions);
1485 130 : if (poObjGeom == nullptr)
1486 : {
1487 1 : json_object_put(poObj);
1488 1 : return nullptr;
1489 : }
1490 :
1491 129 : json_object_array_add(poObj, poObjGeom);
1492 : }
1493 :
1494 57 : return poObj;
1495 : }
1496 :
1497 : /************************************************************************/
1498 : /* OGRGeoJSONWriteGeometryCollection */
1499 : /************************************************************************/
1500 :
1501 : json_object *
1502 35 : OGRGeoJSONWriteGeometryCollection(const OGRGeometryCollection *poGeometry,
1503 : const OGRGeoJSONWriteOptions &oOptions)
1504 : {
1505 35 : return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
1506 : }
1507 :
1508 : /************************************************************************/
1509 : /* OGRGeoJSONWriteCompoundCurve */
1510 : /************************************************************************/
1511 :
1512 : json_object *
1513 14 : OGRGeoJSONWriteCompoundCurve(const OGRCompoundCurve *poGeometry,
1514 : const OGRGeoJSONWriteOptions &oOptions)
1515 : {
1516 14 : return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
1517 : }
1518 :
1519 : /************************************************************************/
1520 : /* OGRGeoJSONWriteCurvePolygon */
1521 : /************************************************************************/
1522 :
1523 9 : json_object *OGRGeoJSONWriteCurvePolygon(const OGRCurvePolygon *poGeometry,
1524 : const OGRGeoJSONWriteOptions &oOptions)
1525 : {
1526 9 : return OGRGeoJSONWriteCollectionGeneric(poGeometry, oOptions);
1527 : }
1528 :
1529 : /************************************************************************/
1530 : /* OGRGeoJSONWriteCoords */
1531 : /************************************************************************/
1532 :
1533 12189 : json_object *OGRGeoJSONWriteCoords(double dfX, double dfY,
1534 : std::optional<double> dfZ,
1535 : std::optional<double> dfM,
1536 : const OGRGeoJSONWriteOptions &oOptions)
1537 : {
1538 12189 : json_object *poObjCoords = nullptr;
1539 36551 : if (!std::isfinite(dfX) || !std::isfinite(dfY) ||
1540 36551 : (dfZ && !std::isfinite(*dfZ)) || (dfM && !std::isfinite(*dfM)))
1541 : {
1542 8 : CPLError(CE_Warning, CPLE_AppDefined,
1543 : "Infinite or NaN coordinate encountered");
1544 8 : return nullptr;
1545 : }
1546 12181 : poObjCoords = json_object_new_array();
1547 12181 : json_object_array_add(poObjCoords, json_object_new_coord(dfX, 1, oOptions));
1548 12181 : json_object_array_add(poObjCoords, json_object_new_coord(dfY, 2, oOptions));
1549 12181 : int nIdx = 3;
1550 12181 : if (dfZ)
1551 : {
1552 1347 : json_object_array_add(poObjCoords,
1553 1347 : json_object_new_coord(*dfZ, nIdx, oOptions));
1554 1347 : nIdx++;
1555 : }
1556 12181 : if (dfM)
1557 : {
1558 106 : json_object_array_add(poObjCoords,
1559 106 : json_object_new_coord(*dfM, nIdx, oOptions));
1560 : }
1561 :
1562 12181 : return poObjCoords;
1563 : }
1564 :
1565 : /************************************************************************/
1566 : /* OGRGeoJSONWriteLineCoords */
1567 : /************************************************************************/
1568 :
1569 174 : json_object *OGRGeoJSONWriteLineCoords(const OGRSimpleCurve *poLine,
1570 : const OGRGeoJSONWriteOptions &oOptions)
1571 : {
1572 174 : json_object *poObjCoords = json_object_new_array();
1573 :
1574 174 : const int nCount = poLine->getNumPoints();
1575 174 : const auto bHasZ = poLine->Is3D();
1576 174 : const auto bHasM = oOptions.bAllowMeasure && poLine->IsMeasured();
1577 1157 : for (int i = 0; i < nCount; ++i)
1578 : {
1579 : json_object *poObjPoint;
1580 986 : if (bHasZ)
1581 : {
1582 440 : if (bHasM)
1583 : {
1584 54 : poObjPoint = OGRGeoJSONWriteCoords(
1585 54 : poLine->getX(i), poLine->getY(i), poLine->getZ(i),
1586 108 : poLine->getM(i), oOptions);
1587 : }
1588 : else
1589 : {
1590 772 : poObjPoint = OGRGeoJSONWriteCoords(
1591 772 : poLine->getX(i), poLine->getY(i), poLine->getZ(i),
1592 : std::nullopt, oOptions);
1593 : }
1594 : }
1595 546 : else if (bHasM)
1596 : {
1597 : poObjPoint =
1598 38 : OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i),
1599 76 : std::nullopt, poLine->getM(i), oOptions);
1600 : }
1601 : else
1602 : {
1603 : poObjPoint =
1604 508 : OGRGeoJSONWriteCoords(poLine->getX(i), poLine->getY(i),
1605 : std::nullopt, std::nullopt, oOptions);
1606 : }
1607 986 : if (poObjPoint == nullptr)
1608 : {
1609 3 : json_object_put(poObjCoords);
1610 3 : return nullptr;
1611 : }
1612 983 : json_object_array_add(poObjCoords, poObjPoint);
1613 : }
1614 :
1615 171 : return poObjCoords;
1616 : }
1617 :
1618 : /************************************************************************/
1619 : /* OGRGeoJSONWriteRingCoords */
1620 : /************************************************************************/
1621 :
1622 1388 : json_object *OGRGeoJSONWriteRingCoords(const OGRLinearRing *poLine,
1623 : bool bIsExteriorRing,
1624 : const OGRGeoJSONWriteOptions &oOptions)
1625 : {
1626 1388 : json_object *poObjCoords = json_object_new_array();
1627 :
1628 1486 : const bool bInvertOrder = oOptions.bPolygonRightHandRule &&
1629 86 : ((bIsExteriorRing && poLine->isClockwise()) ||
1630 29 : (!bIsExteriorRing && !poLine->isClockwise()));
1631 :
1632 1388 : const int nCount = poLine->getNumPoints();
1633 1388 : const auto bHasZ = poLine->Is3D();
1634 1388 : const auto bHasM = oOptions.bAllowMeasure && poLine->IsMeasured();
1635 12217 : for (int i = 0; i < nCount; ++i)
1636 : {
1637 10832 : const int nIdx = (bInvertOrder) ? nCount - 1 - i : i;
1638 : json_object *poObjPoint;
1639 10832 : if (bHasZ)
1640 : {
1641 683 : if (bHasM)
1642 : {
1643 4 : poObjPoint = OGRGeoJSONWriteCoords(
1644 4 : poLine->getX(nIdx), poLine->getY(nIdx), poLine->getZ(nIdx),
1645 8 : poLine->getM(nIdx), oOptions);
1646 : }
1647 : else
1648 : {
1649 1358 : poObjPoint = OGRGeoJSONWriteCoords(
1650 1358 : poLine->getX(nIdx), poLine->getY(nIdx), poLine->getZ(nIdx),
1651 : std::nullopt, oOptions);
1652 : }
1653 : }
1654 10149 : else if (bHasM)
1655 : {
1656 4 : poObjPoint = OGRGeoJSONWriteCoords(poLine->getX(nIdx),
1657 : poLine->getY(nIdx), std::nullopt,
1658 8 : poLine->getM(nIdx), oOptions);
1659 : }
1660 : else
1661 : {
1662 : poObjPoint =
1663 10145 : OGRGeoJSONWriteCoords(poLine->getX(nIdx), poLine->getY(nIdx),
1664 : std::nullopt, std::nullopt, oOptions);
1665 : }
1666 10832 : if (poObjPoint == nullptr)
1667 : {
1668 3 : json_object_put(poObjCoords);
1669 3 : return nullptr;
1670 : }
1671 10829 : json_object_array_add(poObjCoords, poObjPoint);
1672 : }
1673 :
1674 1385 : return poObjCoords;
1675 : }
1676 :
1677 : /************************************************************************/
1678 : /* OGR_json_float_with_significant_figures_to_string() */
1679 : /************************************************************************/
1680 :
1681 9 : static int OGR_json_float_with_significant_figures_to_string(
1682 : struct json_object *jso, struct printbuf *pb, int /* level */,
1683 : int /* flags */)
1684 : {
1685 9 : char szBuffer[75] = {};
1686 9 : int nSize = 0;
1687 9 : const float fVal = static_cast<float>(json_object_get_double(jso));
1688 9 : if (std::isnan(fVal))
1689 0 : nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "NaN");
1690 9 : else if (std::isinf(fVal))
1691 : {
1692 0 : if (fVal > 0)
1693 0 : nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "Infinity");
1694 : else
1695 0 : nSize = CPLsnprintf(szBuffer, sizeof(szBuffer), "-Infinity");
1696 : }
1697 : else
1698 : {
1699 : const void *userData =
1700 : #if (!defined(JSON_C_VERSION_NUM)) || (JSON_C_VERSION_NUM < JSON_C_VER_013)
1701 : jso->_userdata;
1702 : #else
1703 9 : json_object_get_userdata(jso);
1704 : #endif
1705 9 : const uintptr_t nSignificantFigures =
1706 : reinterpret_cast<uintptr_t>(userData);
1707 9 : const bool bSignificantFiguresIsNegative =
1708 9 : (nSignificantFigures >> (8 * sizeof(nSignificantFigures) - 1)) != 0;
1709 9 : const int nInitialSignificantFigures =
1710 : bSignificantFiguresIsNegative
1711 9 : ? 8
1712 : : static_cast<int>(nSignificantFigures);
1713 9 : nSize = OGRFormatFloat(szBuffer, sizeof(szBuffer), fVal,
1714 : nInitialSignificantFigures, 'g');
1715 : }
1716 :
1717 18 : return printbuf_memappend(pb, szBuffer, nSize);
1718 : }
1719 :
1720 : /************************************************************************/
1721 : /* json_object_new_float_with_significant_figures() */
1722 : /************************************************************************/
1723 :
1724 : json_object *
1725 9 : json_object_new_float_with_significant_figures(float fVal,
1726 : int nSignificantFigures)
1727 : {
1728 9 : json_object *jso = json_object_new_double(double(fVal));
1729 9 : json_object_set_serializer(
1730 : jso, OGR_json_float_with_significant_figures_to_string,
1731 9 : reinterpret_cast<void *>(static_cast<uintptr_t>(nSignificantFigures)),
1732 : nullptr);
1733 9 : return jso;
1734 : }
1735 :
1736 : /*! @endcond */
1737 :
1738 : /************************************************************************/
1739 : /* OGR_G_ExportToJson */
1740 : /************************************************************************/
1741 :
1742 : /**
1743 : * \brief Convert a geometry into GeoJSON format.
1744 : *
1745 : * The returned string should be freed with CPLFree() when no longer required.
1746 : *
1747 : * This method is the same as the C++ method OGRGeometry::exportToJson().
1748 : *
1749 : * @param hGeometry handle to the geometry.
1750 : * @return A GeoJSON fragment or NULL in case of error.
1751 : */
1752 :
1753 2 : char *OGR_G_ExportToJson(OGRGeometryH hGeometry)
1754 : {
1755 2 : return OGR_G_ExportToJsonEx(hGeometry, nullptr);
1756 : }
1757 :
1758 : /************************************************************************/
1759 : /* OGR_G_ExportToJsonEx */
1760 : /************************************************************************/
1761 :
1762 : /**
1763 : * \brief Convert a geometry into GeoJSON format.
1764 : *
1765 : * The returned string should be freed with CPLFree() when no longer required.
1766 : *
1767 : * The following options are supported :
1768 : * <ul>
1769 : * <li>COORDINATE_PRECISION=number: maximum number of figures after decimal
1770 : * separator to write in coordinates.</li>
1771 : * <li>XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates
1772 : * (added in GDAL 3.9)</li>
1773 : * <li>Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates
1774 : * (added in GDAL 3.9)</li>
1775 : * <li>SIGNIFICANT_FIGURES=number:
1776 : * maximum number of significant figures (GDAL >= 2.1).</li>
1777 : * </ul>
1778 : *
1779 : * If XY_COORD_PRECISION or Z_COORD_PRECISION is specified, COORDINATE_PRECISION
1780 : * or SIGNIFICANT_FIGURES will be ignored if specified.
1781 : * If COORDINATE_PRECISION is defined, SIGNIFICANT_FIGURES will be ignored if
1782 : * specified.
1783 : * When none are defined, the default is COORDINATE_PRECISION=15.
1784 : *
1785 : * This method is the same as the C++ method OGRGeometry::exportToJson().
1786 : *
1787 : * @param hGeometry handle to the geometry.
1788 : * @param papszOptions a null terminated list of options.
1789 : * @return A GeoJSON fragment or NULL in case of error.
1790 : *
1791 : */
1792 :
1793 133 : char *OGR_G_ExportToJsonEx(OGRGeometryH hGeometry, char **papszOptions)
1794 : {
1795 133 : VALIDATE_POINTER1(hGeometry, "OGR_G_ExportToJson", nullptr);
1796 :
1797 133 : OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry);
1798 :
1799 : const char *pszCoordPrecision =
1800 133 : CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1");
1801 :
1802 : const int nSignificantFigures =
1803 133 : atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
1804 :
1805 266 : OGRGeoJSONWriteOptions oOptions;
1806 133 : oOptions.nXYCoordPrecision = atoi(CSLFetchNameValueDef(
1807 : papszOptions, "XY_COORD_PRECISION", pszCoordPrecision));
1808 133 : oOptions.nZCoordPrecision = atoi(CSLFetchNameValueDef(
1809 : papszOptions, "Z_COORD_PRECISION", pszCoordPrecision));
1810 133 : oOptions.nSignificantFigures = nSignificantFigures;
1811 :
1812 : // If the CRS has latitude, longitude (or northing, easting) axis order,
1813 : // and the data axis to SRS axis mapping doesn't change that order,
1814 : // then swap X and Y values.
1815 133 : bool bHasSwappedXY = false;
1816 133 : const auto poSRS = poGeometry->getSpatialReference();
1817 212 : if (poSRS &&
1818 79 : (poSRS->EPSGTreatsAsLatLong() ||
1819 213 : poSRS->EPSGTreatsAsNorthingEasting()) &&
1820 145 : poSRS->GetDataAxisToSRSAxisMapping() == std::vector<int>{1, 2})
1821 : {
1822 2 : poGeometry->swapXY();
1823 2 : bHasSwappedXY = true;
1824 : }
1825 :
1826 133 : json_object *poObj = OGRGeoJSONWriteGeometry(poGeometry, oOptions);
1827 :
1828 : // Unswap back
1829 133 : if (bHasSwappedXY)
1830 2 : poGeometry->swapXY();
1831 :
1832 133 : if (nullptr != poObj)
1833 : {
1834 130 : char *pszJson = CPLStrdup(json_object_to_json_string(poObj));
1835 :
1836 : // Release JSON tree.
1837 130 : json_object_put(poObj);
1838 :
1839 130 : return pszJson;
1840 : }
1841 :
1842 : // Translation failed.
1843 3 : return nullptr;
1844 : }
|