Coverage Report

Created: 2026-07-14 06:21

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