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