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