Coverage Report

Created: 2025-06-09 07:07

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