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