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