Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implementation of OGC Features and Geometries JSON (JSON-FG)
5
 * Author:   Even Rouault <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogr_jsonfg.h"
14
15
#include "ogrgeojsonreader.h"
16
#include "ogrgeojsonutils.h"
17
#include "ogrlibjsonutils.h"
18
#include "ogrgeojsongeometry.h"
19
#include "ogr_geojson.h"
20
21
#include "cpl_vsi_virtual.h"
22
23
#include <json.h>  // JSON-C
24
25
/************************************************************************/
26
/*                 OGRJSONFGReader::~OGRJSONFGReader()                  */
27
/************************************************************************/
28
29
OGRJSONFGReader::~OGRJSONFGReader()
30
1.09k
{
31
1.09k
    if (poObject_)
32
36
        json_object_put(poObject_);
33
1.09k
}
34
35
/************************************************************************/
36
/*                       OGRJSONFGReader::Load()                        */
37
/************************************************************************/
38
39
bool OGRJSONFGReader::Load(OGRJSONFGDataset *poDS, const char *pszText,
40
                           const std::string &osDefaultLayerName)
41
485
{
42
485
    if (!OGRJSonParse(pszText, &poObject_))
43
464
        return false;
44
45
21
    poDS_ = poDS;
46
21
    osDefaultLayerName_ = osDefaultLayerName;
47
48
21
    GeoJSONObject::Type objType = OGRGeoJSONGetType(poObject_);
49
50
21
    if (objType != GeoJSONObject::eFeature &&
51
16
        objType != GeoJSONObject::eFeatureCollection &&
52
15
        objType != GeoJSONObject::eUnknown)
53
0
    {
54
0
        json_object *poObj = json_object_new_object();
55
0
        json_object_object_add(poObj, "type",
56
0
                               json_object_new_string("Feature"));
57
0
        json_object_object_add(poObj, "place", poObject_);
58
0
        poObject_ = poObj;
59
0
        objType = GeoJSONObject::eFeature;
60
0
    }
61
62
21
    if (!GenerateLayerDefns())
63
16
        return false;
64
65
5
    if (objType == GeoJSONObject::eFeature)
66
5
    {
67
5
        OGRJSONFGMemLayer *poLayer = nullptr;
68
5
        auto poFeat = ReadFeature(poObject_, nullptr, /* bHasM=*/false,
69
5
                                  &poLayer, nullptr);
70
5
        if (poFeat)
71
5
        {
72
5
            poLayer->AddFeature(std::move(poFeat));
73
5
            return true;
74
5
        }
75
0
        return false;
76
5
    }
77
0
    else if (objType == GeoJSONObject::eFeatureCollection)
78
0
    {
79
0
        const bool bHasM =
80
0
            OGRJSONFGHasMeasure(poObject_, /* bUpperLevelMValue = */ false);
81
0
        json_object *poObjFeatures =
82
0
            OGRGeoJSONFindMemberByName(poObject_, "features");
83
0
        if (nullptr != poObjFeatures &&
84
0
            json_type_array == json_object_get_type(poObjFeatures))
85
0
        {
86
0
            const auto nFeatures = json_object_array_length(poObjFeatures);
87
0
            for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
88
0
            {
89
0
                json_object *poObjFeature =
90
0
                    json_object_array_get_idx(poObjFeatures, i);
91
0
                OGRJSONFGMemLayer *poLayer = nullptr;
92
0
                auto poFeat = ReadFeature(poObjFeature, nullptr, bHasM,
93
0
                                          &poLayer, nullptr);
94
0
                if (!poFeat)
95
0
                    return false;
96
0
                poLayer->AddFeature(std::move(poFeat));
97
0
            }
98
0
        }
99
0
    }
100
0
    else
101
0
    {
102
0
        return false;
103
0
    }
104
105
0
    return true;
106
5
}
107
108
/************************************************************************/
109
/*                      OGRJSONFGReadCoordRefSys()                      */
110
/************************************************************************/
111
112
static std::unique_ptr<OGRSpatialReference>
113
OGRJSONFGReadCoordRefSys(json_object *poCoordRefSys, bool bCanRecurse = true)
114
9
{
115
9
    const auto eType = json_object_get_type(poCoordRefSys);
116
9
    if (eType == json_type_string)
117
9
    {
118
9
        const char *pszStr = json_object_get_string(poCoordRefSys);
119
9
        if (pszStr[0] == '[' && pszStr[strlen(pszStr) - 1] == ']')
120
9
        {
121
            // Safe CURIE, e.g. "[EPSG:4326]" (removed in JSONFG 0.3)
122
9
            const char *pszColon = strchr(pszStr + 1, ':');
123
9
            if (!pszColon)
124
0
            {
125
0
                CPLError(CE_Failure, CPLE_AppDefined,
126
0
                         "Invalid coordRefSys string: %s", pszStr);
127
0
                return nullptr;
128
0
            }
129
9
            std::string osURL("http://www.opengis.net/def/crs/");
130
9
            osURL.append(pszStr + 1, pszColon - (pszStr + 1));
131
9
            osURL += "/0/";
132
9
            osURL.append(pszColon + 1,
133
9
                         (pszStr + strlen(pszStr) - 1) - (pszColon + 1));
134
9
            auto poSRS = std::make_unique<OGRSpatialReference>();
135
9
            if (poSRS->importFromCRSURL(osURL.c_str()) != OGRERR_NONE)
136
3
            {
137
3
                return nullptr;
138
3
            }
139
6
            return poSRS;
140
9
        }
141
0
        else if (STARTS_WITH(pszStr, "http://www.opengis.net/def/crs/") ||
142
0
                 STARTS_WITH(pszStr, "https://www.opengis.net/def/crs/"))
143
0
        {
144
            // OGC URI, e.g. "http://www.opengis.net/def/crs/EPSG/0/4326"
145
0
            auto poSRS = std::make_unique<OGRSpatialReference>();
146
0
            if (poSRS->importFromCRSURL(pszStr) != OGRERR_NONE)
147
0
            {
148
0
                return nullptr;
149
0
            }
150
0
            return poSRS;
151
0
        }
152
0
        else
153
0
        {
154
0
            CPLError(CE_Failure, CPLE_AppDefined,
155
0
                     "Invalid coordRefSys string: %s", pszStr);
156
0
            return nullptr;
157
0
        }
158
9
    }
159
0
    else if (eType == json_type_object)
160
0
    {
161
        /* Things like
162
              {
163
                "type": "Reference",
164
                "href": "http://www.opengis.net/def/crs/EPSG/0/4258",
165
                "epoch": 2016.47
166
              }
167
        */
168
169
0
        json_object *poType = CPL_json_object_object_get(poCoordRefSys, "type");
170
0
        if (!poType)
171
0
        {
172
0
            CPLError(CE_Failure, CPLE_AppDefined,
173
0
                     "Missing type member in coordRefSys object");
174
0
            return nullptr;
175
0
        }
176
0
        if (json_object_get_type(poType) != json_type_string)
177
0
        {
178
0
            CPLError(CE_Failure, CPLE_AppDefined,
179
0
                     "Type member of coordRefSys object is not a string");
180
0
            return nullptr;
181
0
        }
182
0
        const char *pszType = json_object_get_string(poType);
183
0
        std::unique_ptr<OGRSpatialReference> poSRS;
184
0
        if (strcmp(pszType, "Reference") == 0)
185
0
        {
186
0
            json_object *poHRef =
187
0
                CPL_json_object_object_get(poCoordRefSys, "href");
188
0
            if (!poHRef)
189
0
            {
190
0
                CPLError(CE_Failure, CPLE_AppDefined,
191
0
                         "Missing href member in coordRefSys object");
192
0
                return nullptr;
193
0
            }
194
195
0
            poSRS = OGRJSONFGReadCoordRefSys(poHRef);
196
0
        }
197
0
        else if (strcmp(pszType, "PROJJSON") == 0)
198
0
        {
199
0
            json_object *poValue =
200
0
                CPL_json_object_object_get(poCoordRefSys, "value");
201
0
            if (!poValue)
202
0
            {
203
0
                CPLError(CE_Failure, CPLE_AppDefined,
204
0
                         "Missing value member in coordRefSys object");
205
0
                return nullptr;
206
0
            }
207
0
            if (json_object_get_type(poValue) != json_type_object)
208
0
            {
209
0
                CPLError(CE_Failure, CPLE_AppDefined,
210
0
                         "Invalid type for coordRefSys.value member");
211
0
                return nullptr;
212
0
            }
213
214
0
            const char *pszPROJJSON = json_object_to_json_string(poValue);
215
0
            poSRS = std::make_unique<OGRSpatialReference>();
216
0
            if (poSRS->SetFromUserInput(
217
0
                    pszPROJJSON,
218
0
                    OGRSpatialReference::
219
0
                        SET_FROM_USER_INPUT_LIMITATIONS_get()) != OGRERR_NONE)
220
0
            {
221
0
                poSRS.reset();
222
0
            }
223
0
        }
224
0
        else
225
0
        {
226
0
            CPLError(CE_Failure, CPLE_NotSupported,
227
0
                     "Unsupported coordRefSys.type: %s", pszType);
228
0
            return nullptr;
229
0
        }
230
231
0
        if (poSRS)
232
0
        {
233
0
            json_object *poEpoch =
234
0
                CPL_json_object_object_get(poCoordRefSys, "epoch");
235
0
            if (poEpoch)
236
0
            {
237
0
                const auto epochType = json_object_get_type(poEpoch);
238
0
                if (epochType != json_type_int && epochType != json_type_double)
239
0
                {
240
0
                    CPLError(CE_Failure, CPLE_AppDefined,
241
0
                             "Wrong value type for epoch member in coordRefSys "
242
0
                             "object");
243
0
                    return nullptr;
244
0
                }
245
246
0
                poSRS->SetCoordinateEpoch(json_object_get_double(poEpoch));
247
0
            }
248
0
        }
249
250
0
        return poSRS;
251
0
    }
252
0
    else if (eType == json_type_array && bCanRecurse)
253
0
    {
254
0
        if (json_object_array_length(poCoordRefSys) != 2)
255
0
        {
256
0
            CPLError(CE_Failure, CPLE_AppDefined,
257
0
                     "Expected 2 items in coordRefSys array");
258
0
            return nullptr;
259
0
        }
260
0
        auto poSRS1 = OGRJSONFGReadCoordRefSys(
261
0
            json_object_array_get_idx(poCoordRefSys, 0),
262
0
            /* bCanRecurse = */ false);
263
0
        if (!poSRS1)
264
0
            return nullptr;
265
0
        auto poSRS2 = OGRJSONFGReadCoordRefSys(
266
0
            json_object_array_get_idx(poCoordRefSys, 1),
267
0
            /* bCanRecurse = */ false);
268
0
        if (!poSRS2)
269
0
            return nullptr;
270
0
        auto poSRS = std::make_unique<OGRSpatialReference>();
271
272
0
        std::string osName;
273
0
        const char *pszName1 = poSRS1->GetName();
274
0
        osName = pszName1 ? pszName1 : "unnamed";
275
0
        osName += " + ";
276
0
        const char *pszName2 = poSRS2->GetName();
277
0
        osName += pszName2 ? pszName2 : "unnamed";
278
279
0
        if (poSRS->SetCompoundCS(osName.c_str(), poSRS1.get(), poSRS2.get()) !=
280
0
            OGRERR_NONE)
281
0
            return nullptr;
282
0
        const double dfEpoch = poSRS1->GetCoordinateEpoch();
283
0
        if (dfEpoch > 0)
284
0
            poSRS->SetCoordinateEpoch(dfEpoch);
285
0
        return poSRS;
286
0
    }
287
0
    else
288
0
    {
289
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid coordRefSys object");
290
0
    }
291
0
    return nullptr;
292
9
}
293
294
/************************************************************************/
295
/*            OGRJSONFGReader::AnalyzeWithStreamingParser()             */
296
/************************************************************************/
297
298
bool OGRJSONFGReader::AnalyzeWithStreamingParser(
299
    OGRJSONFGDataset *poDS, VSILFILE *fp, const std::string &osDefaultLayerName,
300
    bool &bCanTryWithNonStreamingParserOut, bool &bHasTopLevelMeasures)
301
256
{
302
256
    poDS_ = poDS;
303
256
    osDefaultLayerName_ = osDefaultLayerName;
304
305
256
    bCanTryWithNonStreamingParserOut = false;
306
256
    OGRJSONFGStreamingParser oParser(*this, /*bFirstPass = */ true,
307
256
                                     /* bHasTopLevelMeasures =*/false);
308
309
256
    std::vector<GByte> abyBuffer;
310
256
    abyBuffer.resize(4096 * 10);
311
456
    while (true)
312
456
    {
313
456
        size_t nRead = VSIFReadL(abyBuffer.data(), 1, abyBuffer.size(), fp);
314
456
        const bool bFinished = nRead < abyBuffer.size();
315
456
        if (!oParser.Parse(
316
456
                std::string_view(
317
456
                    reinterpret_cast<const char *>(abyBuffer.data()), nRead),
318
456
                bFinished) ||
319
220
            oParser.ExceptionOccurred())
320
236
        {
321
236
            return false;
322
236
        }
323
220
        if (oParser.IsTypeKnown() && !oParser.IsFeatureCollection())
324
2
        {
325
2
            break;
326
2
        }
327
218
        if (bFinished)
328
18
            break;
329
218
    }
330
331
20
    if (!oParser.IsTypeKnown() || !oParser.IsFeatureCollection())
332
5
    {
333
5
        fp->Seek(0, SEEK_END);
334
5
        const vsi_l_offset nFileSize = fp->Tell();
335
5
        const vsi_l_offset nRAM =
336
5
            static_cast<vsi_l_offset>(CPLGetUsablePhysicalRAM());
337
5
        if (nRAM == 0 || nRAM > nFileSize * 20)
338
5
        {
339
            // Only try full ingestion if we have 20x more RAM than the file
340
            // size
341
5
            bCanTryWithNonStreamingParserOut = true;
342
5
        }
343
5
        return false;
344
5
    }
345
346
15
    poObject_ = oParser.StealRootObject();
347
15
    bHasTopLevelMeasures = oParser.HasTopLevelMeasures();
348
349
15
    return FinalizeGenerateLayerDefns(true);
350
20
}
351
352
/************************************************************************/
353
/*                OGRJSONFGReader::GenerateLayerDefns()                 */
354
/************************************************************************/
355
356
bool OGRJSONFGReader::GenerateLayerDefns()
357
21
{
358
21
    const GeoJSONObject::Type objType = OGRGeoJSONGetType(poObject_);
359
21
    if (objType == GeoJSONObject::eFeature)
360
5
    {
361
5
        if (!GenerateLayerDefnFromFeature(poObject_))
362
0
            return false;
363
5
    }
364
16
    else if (objType == GeoJSONObject::eFeatureCollection)
365
1
    {
366
1
        json_object *poObjFeatures =
367
1
            OGRGeoJSONFindMemberByName(poObject_, "features");
368
1
        if (nullptr != poObjFeatures &&
369
0
            json_type_array == json_object_get_type(poObjFeatures))
370
0
        {
371
0
            const auto nFeatures = json_object_array_length(poObjFeatures);
372
0
            for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
373
0
            {
374
0
                json_object *poObjFeature =
375
0
                    json_object_array_get_idx(poObjFeatures, i);
376
0
                if (!GenerateLayerDefnFromFeature(poObjFeature))
377
0
                {
378
0
                    return false;
379
0
                }
380
0
            }
381
0
        }
382
1
        else
383
1
        {
384
1
            CPLError(CE_Failure, CPLE_AppDefined,
385
1
                     "Invalid FeatureCollection object. "
386
1
                     "Missing \'features\' member.");
387
1
            return false;
388
1
        }
389
1
    }
390
15
    else
391
15
    {
392
15
        CPLError(CE_Failure, CPLE_AppDefined,
393
15
                 "Missing or unhandled root type object");
394
15
        return false;
395
15
    }
396
397
5
    return FinalizeGenerateLayerDefns(false);
398
21
}
399
400
/************************************************************************/
401
/*            OGRJSONFGReader::FinalizeGenerateLayerDefns()             */
402
/************************************************************************/
403
404
bool OGRJSONFGReader::FinalizeGenerateLayerDefns(bool bStreamedLayer)
405
20
{
406
20
    json_object *poName = CPL_json_object_object_get(poObject_, "featureType");
407
20
    if (poName && json_object_get_type(poName) == json_type_string)
408
0
    {
409
        // Remap from hard-coded default layer name to the one of featureType
410
0
        auto oIter = oMapBuildContext_.find(osDefaultLayerName_);
411
0
        osDefaultLayerName_ = json_object_get_string(poName);
412
0
        if (oIter != oMapBuildContext_.end())
413
0
        {
414
0
            auto oBuildContext = std::move(oIter->second);
415
0
            oMapBuildContext_.erase(oIter);
416
0
            oMapBuildContext_[osDefaultLayerName_] = std::move(oBuildContext);
417
0
        }
418
0
    }
419
20
    else if (poName && json_object_get_type(poName) == json_type_array)
420
0
    {
421
0
        static bool bWarningMsgEmitted = false;
422
0
        if (!bWarningMsgEmitted)
423
0
        {
424
0
            CPLError(CE_Warning, CPLE_AppDefined,
425
0
                     "featureType value as an array is not supported.");
426
0
            bWarningMsgEmitted = true;
427
0
        }
428
0
    }
429
430
20
    json_object *poCoordRefSys = nullptr;
431
20
    std::unique_ptr<OGRSpatialReference> poSRSTopLevel;
432
20
    bool bInvalidCRS = false;
433
20
    bool bSwapPlacesXYTopLevel = false;
434
20
    if (json_object_object_get_ex(poObject_, "coordRefSys", &poCoordRefSys) &&
435
0
        eGeometryElement_ != GeometryElement::GEOMETRY)
436
0
    {
437
0
        poSRSTopLevel = OGRJSONFGReadCoordRefSys(poCoordRefSys);
438
0
        if (poSRSTopLevel)
439
0
        {
440
0
            poSRSTopLevel->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
441
0
            bSwapPlacesXYTopLevel = OGRJSONFGMustSwapXY(poSRSTopLevel.get());
442
0
        }
443
0
        else
444
0
        {
445
0
            bInvalidCRS = true;
446
0
        }
447
0
    }
448
449
20
    json_object *poMeasures = nullptr;
450
20
    if (json_object_object_get_ex(poObject_, "measures", &poMeasures) &&
451
0
        json_object_get_type(poMeasures) == json_type_object)
452
0
    {
453
0
        json_object *poEnabled = nullptr;
454
0
        if (json_object_object_get_ex(poMeasures, "enabled", &poEnabled) &&
455
0
            json_object_get_type(poEnabled) == json_type_boolean &&
456
0
            json_object_get_boolean(poEnabled))
457
0
        {
458
0
            json_object *poUnit = nullptr;
459
0
            if (json_object_object_get_ex(poMeasures, "unit", &poUnit) &&
460
0
                json_object_get_type(poUnit) == json_type_string)
461
0
            {
462
0
                osMeasureUnit_ = json_object_get_string(poUnit);
463
0
            }
464
465
0
            json_object *poDescription = nullptr;
466
0
            if (json_object_object_get_ex(poMeasures, "description",
467
0
                                          &poDescription) &&
468
0
                json_object_get_type(poDescription) == json_type_string)
469
0
            {
470
0
                osMeasureDescription_ = json_object_get_string(poDescription);
471
0
            }
472
0
        }
473
0
    }
474
475
    // Finalize layer definition building and create OGRLayer objects
476
20
    for (auto &oBuildContextIter : oMapBuildContext_)
477
20
    {
478
20
        const char *pszLayerName = oBuildContextIter.first.c_str();
479
20
        auto &oBuildContext = oBuildContextIter.second;
480
481
20
        FinalizeBuildContext(oBuildContext, pszLayerName, bStreamedLayer,
482
20
                             bInvalidCRS, bSwapPlacesXYTopLevel,
483
20
                             poSRSTopLevel.get());
484
20
    }
485
486
20
    return true;
487
20
}
488
489
/************************************************************************/
490
/*               OGRJSONFGReader::FinalizeBuildContext()                */
491
/************************************************************************/
492
493
void OGRJSONFGReader::FinalizeBuildContext(LayerDefnBuildContext &oBuildContext,
494
                                           const char *pszLayerName,
495
                                           bool bStreamedLayer,
496
                                           bool bInvalidCRS,
497
                                           bool bSwapPlacesXYTopLevel,
498
                                           OGRSpatialReference *poSRSTopLevel)
499
20
{
500
20
    std::unique_ptr<OGRSpatialReference> poSRSWGS84(
501
20
        OGRSpatialReference::GetWGS84SRS()->Clone());
502
20
    poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
503
504
20
    OGRSpatialReference *poSRSLayer = nullptr;
505
20
    if (oBuildContext.poCRSAtFeatureLevel)
506
5
    {
507
5
        poSRSLayer = oBuildContext.poCRSAtFeatureLevel.get();
508
5
        oBuildContext.bSwapPlacesXY = OGRJSONFGMustSwapXY(poSRSLayer);
509
5
    }
510
15
    else if (poSRSTopLevel)
511
0
    {
512
0
        poSRSLayer = poSRSTopLevel;
513
0
        oBuildContext.bSwapPlacesXY = bSwapPlacesXYTopLevel;
514
0
    }
515
20
    if (!bInvalidCRS)
516
20
    {
517
20
        if (!poSRSLayer && !oBuildContext.bHasCoordRefSysAtFeatureLevel)
518
13
        {
519
            // No coordRefSys member found anywhere ? Fallback to WGS 84
520
13
            poSRSLayer = poSRSWGS84.get();
521
13
            oBuildContext.bLayerCRSIsWGS84 = true;
522
13
        }
523
7
        else if (poSRSLayer && poSRSLayer->IsSame(poSRSWGS84.get()))
524
0
        {
525
0
            oBuildContext.bLayerCRSIsWGS84 = true;
526
0
        }
527
7
        else if (poSRSLayer)
528
5
        {
529
5
            const char *pszAuthName = poSRSLayer->GetAuthorityName();
530
5
            const char *pszAuthCode = poSRSLayer->GetAuthorityCode();
531
5
            if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "OGC") &&
532
0
                EQUAL(pszAuthCode, "CRS84"))
533
0
            {
534
                // Normalize reported CRS to EPSG:4326
535
0
                poSRSLayer = poSRSWGS84.get();
536
0
                oBuildContext.bLayerCRSIsWGS84 = true;
537
0
            }
538
5
            else if (!(pszAuthName && STARTS_WITH(pszAuthName, "IAU")))
539
5
            {
540
5
                oBuildContext.poCTWGS84ToLayerCRS.reset(
541
5
                    OGRCreateCoordinateTransformation(poSRSWGS84.get(),
542
5
                                                      poSRSLayer));
543
5
            }
544
5
        }
545
20
    }
546
547
20
    std::unique_ptr<OGRJSONFGMemLayer> poMemLayer;
548
20
    std::unique_ptr<OGRJSONFGStreamedLayer> poStreamedLayer;
549
20
    OGRLayer *poLayer;
550
20
    if (bStreamedLayer)
551
15
    {
552
15
        poStreamedLayer = std::make_unique<OGRJSONFGStreamedLayer>(
553
15
            poDS_, pszLayerName, poSRSLayer, oBuildContext.eLayerGeomType);
554
15
        poLayer = poStreamedLayer.get();
555
15
    }
556
5
    else
557
5
    {
558
5
        poMemLayer = std::make_unique<OGRJSONFGMemLayer>(
559
5
            poDS_, pszLayerName, poSRSLayer, oBuildContext.eLayerGeomType);
560
5
        poLayer = poMemLayer.get();
561
5
    }
562
563
    // Note: the current strategy will not produce stable output, depending
564
    // on the order of features, if there are conflicting order / cycles.
565
    // See https://github.com/OSGeo/gdal/pull/4552 for a number of potential
566
    // resolutions if that has to be solved in the future.
567
20
    OGRFeatureDefn *poLayerDefn = poLayer->GetLayerDefn();
568
20
    auto oTemporaryUnsealer(poLayerDefn->GetTemporaryUnsealer());
569
570
20
    if (poLayer->GetLayerDefn()->GetGeomType() != wkbNone)
571
20
    {
572
20
        OGRGeoJSONWriteOptions options;
573
574
20
        json_object *poXYRes = CPL_json_object_object_get(
575
20
            poObject_, "xy_coordinate_resolution_place");
576
20
        if (poXYRes && (json_object_get_type(poXYRes) == json_type_double ||
577
0
                        json_object_get_type(poXYRes) == json_type_int))
578
0
        {
579
0
            auto poGeomFieldDefn = poLayerDefn->GetGeomFieldDefn(0);
580
0
            OGRGeomCoordinatePrecision oCoordPrec(
581
0
                poGeomFieldDefn->GetCoordinatePrecision());
582
0
            oCoordPrec.dfXYResolution = json_object_get_double(poXYRes);
583
0
            poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
584
0
        }
585
586
20
        json_object *poZRes = CPL_json_object_object_get(
587
20
            poObject_, "z_coordinate_resolution_place");
588
20
        if (poZRes && (json_object_get_type(poZRes) == json_type_double ||
589
0
                       json_object_get_type(poZRes) == json_type_int))
590
0
        {
591
0
            auto poGeomFieldDefn = poLayerDefn->GetGeomFieldDefn(0);
592
0
            OGRGeomCoordinatePrecision oCoordPrec(
593
0
                poGeomFieldDefn->GetCoordinatePrecision());
594
0
            oCoordPrec.dfZResolution = json_object_get_double(poZRes);
595
0
            poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
596
0
        }
597
20
    }
598
599
20
    std::set<std::string> oSetFieldNames;
600
20
    for (const auto &poFieldDefn : oBuildContext.apoFieldDefn)
601
6
        oSetFieldNames.insert(poFieldDefn->GetNameRef());
602
603
20
    auto AddTimeField =
604
20
        [poLayerDefn, &oSetFieldNames](const char *pszName, OGRFieldType eType)
605
20
    {
606
0
        if (oSetFieldNames.find(pszName) == oSetFieldNames.end())
607
0
        {
608
0
            OGRFieldDefn oFieldDefn(pszName, eType);
609
0
            poLayerDefn->AddFieldDefn(&oFieldDefn);
610
0
        }
611
0
        else
612
0
        {
613
0
            OGRFieldDefn oFieldDefn((std::string("jsonfg_") + pszName).c_str(),
614
0
                                    eType);
615
0
            poLayerDefn->AddFieldDefn(&oFieldDefn);
616
0
        }
617
0
        return poLayerDefn->GetFieldCount() - 1;
618
0
    };
619
620
20
    if (oBuildContext.bHasTimeTimestamp)
621
0
    {
622
0
        oBuildContext.nIdxFieldTime = AddTimeField("time", OFTDateTime);
623
0
    }
624
20
    else if (oBuildContext.bHasTimeDate)
625
0
    {
626
0
        oBuildContext.nIdxFieldTime = AddTimeField("time", OFTDate);
627
0
    }
628
629
20
    if (oBuildContext.bHasTimeIntervalStartDate ||
630
20
        oBuildContext.bHasTimeIntervalStartTimestamp ||
631
20
        oBuildContext.bHasTimeIntervalEndDate ||
632
20
        oBuildContext.bHasTimeIntervalEndTimestamp)
633
0
    {
634
        // Mix of Date/DateTime for start/end is not supposed to happen,
635
        // but be tolerant to that
636
0
        if (oBuildContext.bHasTimeIntervalStartTimestamp)
637
0
        {
638
0
            oBuildContext.nIdxFieldTimeStart =
639
0
                AddTimeField("time_start", OFTDateTime);
640
0
        }
641
0
        else if (oBuildContext.bHasTimeIntervalStartDate)
642
0
        {
643
0
            oBuildContext.nIdxFieldTimeStart =
644
0
                AddTimeField("time_start", OFTDate);
645
0
        }
646
0
        else if (oBuildContext.bHasTimeIntervalEndTimestamp)
647
0
        {
648
0
            oBuildContext.nIdxFieldTimeStart =
649
0
                AddTimeField("time_start", OFTDateTime);
650
0
        }
651
0
        else /* if( oBuildContext.bHasTimeIntervalEndDate ) */
652
0
        {
653
0
            oBuildContext.nIdxFieldTimeStart =
654
0
                AddTimeField("time_start", OFTDate);
655
0
        }
656
657
0
        if (oBuildContext.bHasTimeIntervalEndTimestamp)
658
0
        {
659
0
            oBuildContext.nIdxFieldTimeEnd =
660
0
                AddTimeField("time_end", OFTDateTime);
661
0
        }
662
0
        else if (oBuildContext.bHasTimeIntervalEndDate)
663
0
        {
664
0
            oBuildContext.nIdxFieldTimeEnd = AddTimeField("time_end", OFTDate);
665
0
        }
666
0
        else if (oBuildContext.bHasTimeIntervalStartTimestamp)
667
0
        {
668
0
            oBuildContext.nIdxFieldTimeEnd =
669
0
                AddTimeField("time_end", OFTDateTime);
670
0
        }
671
0
        else /* if( oBuildContext.bHasTimeIntervalStartDate ) */
672
0
        {
673
0
            oBuildContext.nIdxFieldTimeEnd = AddTimeField("time_end", OFTDate);
674
0
        }
675
0
    }
676
677
20
    const auto sortedFields = oBuildContext.dag.getTopologicalOrdering();
678
20
    CPLAssert(sortedFields.size() == oBuildContext.apoFieldDefn.size());
679
20
    for (int idx : sortedFields)
680
6
    {
681
6
        poLayerDefn->AddFieldDefn(oBuildContext.apoFieldDefn[idx].get());
682
6
    }
683
684
20
    if (!oBuildContext.bFeatureLevelIdAsFID)
685
7
    {
686
7
        const int idx = poLayerDefn->GetFieldIndexCaseSensitive("id");
687
7
        if (idx >= 0)
688
0
        {
689
0
            OGRFieldDefn *poFDefn = poLayerDefn->GetFieldDefn(idx);
690
0
            if (poFDefn->GetType() == OFTInteger ||
691
0
                poFDefn->GetType() == OFTInteger64)
692
0
            {
693
0
                if (poStreamedLayer)
694
0
                {
695
0
                    poStreamedLayer->SetFIDColumn(
696
0
                        poLayerDefn->GetFieldDefn(idx)->GetNameRef());
697
0
                }
698
0
                else
699
0
                {
700
0
                    poMemLayer->SetFIDColumn(
701
0
                        poLayerDefn->GetFieldDefn(idx)->GetNameRef());
702
0
                }
703
0
            }
704
0
        }
705
7
    }
706
707
20
    if (oBuildContext.bNeedFID64)
708
0
        poLayer->SetMetadataItem(OLMD_FID64, "YES");
709
710
20
    if (oBuildContext.bSameMeasureMetadata &&
711
20
        (!oBuildContext.osMeasureUnit.empty() ||
712
20
         !oBuildContext.osMeasureDescription.empty()))
713
0
    {
714
0
        if (!oBuildContext.osMeasureUnit.empty())
715
0
        {
716
0
            poLayer->SetMetadataItem(
717
0
                "UNIT", oBuildContext.osMeasureUnit.c_str(), "MEASURES");
718
0
        }
719
720
0
        if (!oBuildContext.osMeasureDescription.empty())
721
0
        {
722
0
            poLayer->SetMetadataItem("DESCRIPTION",
723
0
                                     oBuildContext.osMeasureDescription.c_str(),
724
0
                                     "MEASURES");
725
0
        }
726
0
    }
727
20
    else
728
20
    {
729
20
        if (!osMeasureUnit_.empty())
730
0
        {
731
0
            poLayer->SetMetadataItem("UNIT", osMeasureUnit_.c_str(),
732
0
                                     "MEASURES");
733
0
        }
734
735
20
        if (!osMeasureDescription_.empty())
736
0
        {
737
0
            poLayer->SetMetadataItem("DESCRIPTION",
738
0
                                     osMeasureDescription_.c_str(), "MEASURES");
739
0
        }
740
20
    }
741
742
20
    if (poStreamedLayer)
743
15
    {
744
15
        poStreamedLayer->SetFeatureCount(oBuildContext.nFeatureCount);
745
15
        oBuildContext.poStreamedLayer =
746
15
            poDS_->AddLayer(std::move(poStreamedLayer));
747
15
    }
748
5
    else
749
5
    {
750
5
        oBuildContext.poMemLayer = poDS_->AddLayer(std::move(poMemLayer));
751
5
    }
752
20
}
753
754
/************************************************************************/
755
/*              OGRJSONFGReader::GetLayerNameForFeature()               */
756
/************************************************************************/
757
758
const char *OGRJSONFGReader::GetLayerNameForFeature(json_object *poObj) const
759
371
{
760
371
    const char *pszName = osDefaultLayerName_.c_str();
761
371
    json_object *poName = CPL_json_object_object_get(poObj, "featureType");
762
    // The spec allows an array of strings, but we don't support that
763
371
    if (poName != nullptr && json_object_get_type(poName) == json_type_string)
764
112
    {
765
112
        pszName = json_object_get_string(poName);
766
112
    }
767
371
    return pszName;
768
371
}
769
770
/************************************************************************/
771
/*                    OGRJSONFGGetOGRGeometryType()                     */
772
/************************************************************************/
773
774
static OGRwkbGeometryType OGRJSONFGGetOGRGeometryType(json_object *poObj,
775
                                                      bool bHasM)
776
16
{
777
16
    const auto eType = OGRGeoJSONGetOGRGeometryType(poObj, bHasM);
778
16
    if (eType != wkbUnknown)
779
13
        return eType;
780
781
3
    json_object *poObjType = CPL_json_object_object_get(poObj, "type");
782
3
    const char *pszType = json_object_get_string(poObjType);
783
3
    if (!pszType)
784
2
        return wkbNone;
785
786
1
    if (strcmp(pszType, "Polyhedron") == 0)
787
0
    {
788
0
        auto eRetType = wkbPolyhedralSurfaceZ;
789
790
0
        bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
791
792
0
        if (bHasM)
793
0
            eRetType = OGR_GT_SetM(eRetType);
794
0
        return eRetType;
795
0
    }
796
1
    else if (strcmp(pszType, "Prism") == 0)
797
0
    {
798
0
        auto poBase = CPL_json_object_object_get(poObj, "base");
799
0
        if (!poBase || json_object_get_type(poBase) != json_type_object)
800
0
        {
801
0
            return wkbNone;
802
0
        }
803
804
0
        bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
805
806
0
        const auto eBaseGeomType = OGRGeoJSONGetOGRGeometryType(poBase, bHasM);
807
0
        auto eRetType = wkbNone;
808
0
        if (eBaseGeomType == wkbPoint)
809
0
        {
810
0
            eRetType = wkbLineString25D;
811
0
        }
812
0
        else if (eBaseGeomType == wkbLineString)
813
0
        {
814
0
            eRetType = wkbMultiPolygon25D;
815
0
        }
816
0
        else if (eBaseGeomType == wkbPolygon)
817
0
        {
818
0
            eRetType = wkbPolyhedralSurfaceZ;
819
0
        }
820
0
        if (eRetType != wkbNone)
821
0
        {
822
0
            if (bHasM)
823
0
                eRetType = OGR_GT_SetM(eRetType);
824
0
            return eRetType;
825
0
        }
826
0
    }
827
1
    return wkbNone;
828
1
}
829
830
/************************************************************************/
831
/*                 OGRJSONFGCreateNonGeoJSONGeometry()                  */
832
/************************************************************************/
833
834
static std::unique_ptr<OGRGeometry>
835
OGRJSONFGCreateNonGeoJSONGeometry(json_object *poObj, bool bHasM, bool bWarn)
836
0
{
837
0
    json_object *poObjType = CPL_json_object_object_get(poObj, "type");
838
0
    const char *pszType = json_object_get_string(poObjType);
839
0
    if (!pszType)
840
0
        return nullptr;
841
842
0
    if (strcmp(pszType, "Polyhedron") == 0)
843
0
    {
844
0
        auto poCoordinates = CPL_json_object_object_get(poObj, "coordinates");
845
0
        if (!poCoordinates ||
846
0
            json_object_get_type(poCoordinates) != json_type_array)
847
0
        {
848
0
            CPLError(CE_Failure, CPLE_AppDefined,
849
0
                     "Missing or invalid coordinates in Polyhedron");
850
0
            return nullptr;
851
0
        }
852
0
        if (json_object_array_length(poCoordinates) != 1)
853
0
        {
854
0
            if (bWarn)
855
0
            {
856
0
                CPLError(CE_Warning, CPLE_AppDefined,
857
0
                         "Polyhedron with inner shells not supported");
858
0
            }
859
0
            return nullptr;
860
0
        }
861
862
0
        bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
863
864
0
        auto poJOuterShell = json_object_array_get_idx(poCoordinates, 0);
865
0
        auto poGeom = std::make_unique<OGRPolyhedralSurface>();
866
0
        const auto nPolys = json_object_array_length(poJOuterShell);
867
0
        for (auto i = decltype(nPolys){0}; i < nPolys; ++i)
868
0
        {
869
0
            auto poJPoly = json_object_array_get_idx(poJOuterShell, i);
870
0
            if (!poJPoly)
871
0
                return nullptr;
872
0
            auto poPoly =
873
0
                OGRGeoJSONReadPolygon(poJPoly, bHasM, /*bRaw = */ true);
874
0
            if (!poPoly)
875
0
                return nullptr;
876
0
            if (poGeom->addGeometry(std::move(poPoly)) != OGRERR_NONE)
877
0
                return nullptr;
878
0
        }
879
0
        if (nPolys == 0)
880
0
            poGeom->set3D(true);
881
882
0
        return poGeom;
883
0
    }
884
0
    else if (strcmp(pszType, "Prism") == 0)
885
0
    {
886
0
        auto poBase = CPL_json_object_object_get(poObj, "base");
887
0
        if (!poBase || json_object_get_type(poBase) != json_type_object)
888
0
        {
889
0
            CPLError(CE_Failure, CPLE_AppDefined,
890
0
                     "Missing or invalid base in Prism");
891
0
            return nullptr;
892
0
        }
893
894
0
        bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
895
896
0
        json_object *poLower = CPL_json_object_object_get(poObj, "lower");
897
0
        const double dfLower = poLower ? json_object_get_double(poLower) : 0.0;
898
0
        json_object *poUpper = CPL_json_object_object_get(poObj, "upper");
899
0
        const double dfUpper = poUpper ? json_object_get_double(poUpper) : 0.0;
900
901
0
        auto poBaseGeom = std::unique_ptr<OGRGeometry>(OGRGeoJSONReadGeometry(
902
0
            poBase, bHasM, /* OGRSpatialReference* = */ nullptr));
903
0
        if (!poBaseGeom)
904
0
            return nullptr;
905
0
        const auto eBaseGeomType = poBaseGeom->getGeometryType();
906
0
        if (eBaseGeomType == wkbPoint)
907
0
        {
908
0
            const auto poPoint = poBaseGeom.get()->toPoint();
909
0
            auto poGeom = std::make_unique<OGRLineString>();
910
0
            if (bHasM)
911
0
            {
912
0
                poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfLower,
913
0
                                 poPoint->getM());
914
0
                poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfUpper,
915
0
                                 poPoint->getM());
916
0
            }
917
0
            else
918
0
            {
919
0
                poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfLower);
920
0
                poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfUpper);
921
0
            }
922
0
            return poGeom;
923
0
        }
924
0
        else if (eBaseGeomType == wkbLineString)
925
0
        {
926
0
            const auto poLS = poBaseGeom.get()->toLineString();
927
0
            auto poGeom = std::make_unique<OGRMultiPolygon>();
928
0
            for (int i = 0; i < poLS->getNumPoints() - 1; ++i)
929
0
            {
930
0
                auto poPoly = new OGRPolygon();
931
0
                auto poRing = new OGRLinearRing();
932
0
                if (bHasM)
933
0
                {
934
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
935
0
                                     poLS->getM(i));
936
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
937
0
                                     dfLower, poLS->getM(i + 1));
938
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
939
0
                                     dfUpper, poLS->getM(i + 1));
940
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper,
941
0
                                     poLS->getM(i));
942
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
943
0
                                     poLS->getM(i));
944
0
                }
945
0
                else
946
0
                {
947
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
948
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
949
0
                                     dfLower);
950
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
951
0
                                     dfUpper);
952
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper);
953
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
954
0
                }
955
0
                poPoly->addRingDirectly(poRing);
956
0
                poGeom->addGeometryDirectly(poPoly);
957
0
            }
958
0
            return poGeom;
959
0
        }
960
0
        else if (eBaseGeomType == wkbPolygon)
961
0
        {
962
0
            const auto poBasePoly = poBaseGeom.get()->toPolygon();
963
0
            if (poBasePoly->getNumInteriorRings() > 0)
964
0
            {
965
0
                if (bWarn)
966
0
                {
967
0
                    CPLError(CE_Warning, CPLE_AppDefined,
968
0
                             "Polygon with holes is not supported as the base "
969
0
                             "for Prism");
970
0
                }
971
0
                return nullptr;
972
0
            }
973
0
            const auto poLS = poBasePoly->getExteriorRing();
974
0
            if (poLS == nullptr)
975
0
            {
976
0
                return nullptr;
977
0
            }
978
0
            auto poGeom = std::make_unique<OGRPolyhedralSurface>();
979
            // Build lower face
980
0
            {
981
0
                auto poPoly = new OGRPolygon();
982
0
                auto poRing = new OGRLinearRing();
983
0
                for (int i = 0; i < poLS->getNumPoints(); ++i)
984
0
                {
985
0
                    if (bHasM)
986
0
                        poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
987
0
                                         poLS->getM(i));
988
0
                    else
989
0
                        poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
990
0
                }
991
0
                poPoly->addRingDirectly(poRing);
992
0
                poGeom->addGeometryDirectly(poPoly);
993
0
            }
994
            // Build side faces
995
0
            for (int i = 0; i < poLS->getNumPoints() - 1; ++i)
996
0
            {
997
0
                auto poPoly = new OGRPolygon();
998
0
                auto poRing = new OGRLinearRing();
999
0
                if (bHasM)
1000
0
                {
1001
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
1002
0
                                     poLS->getM(i));
1003
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
1004
0
                                     dfLower, poLS->getM(i + 1));
1005
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
1006
0
                                     dfUpper, poLS->getM(i + 1));
1007
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper,
1008
0
                                     poLS->getM(i));
1009
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
1010
0
                                     poLS->getM(i));
1011
0
                }
1012
0
                else
1013
0
                {
1014
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
1015
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
1016
0
                                     dfLower);
1017
0
                    poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
1018
0
                                     dfUpper);
1019
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper);
1020
0
                    poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
1021
0
                }
1022
0
                poPoly->addRingDirectly(poRing);
1023
0
                poGeom->addGeometryDirectly(poPoly);
1024
0
            }
1025
            // Build upper face
1026
0
            {
1027
0
                auto poPoly = new OGRPolygon();
1028
0
                auto poRing = new OGRLinearRing();
1029
0
                for (int i = 0; i < poLS->getNumPoints(); ++i)
1030
0
                {
1031
0
                    if (bHasM)
1032
0
                        poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper,
1033
0
                                         poLS->getM(i));
1034
0
                    else
1035
0
                        poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper);
1036
0
                }
1037
0
                poPoly->addRingDirectly(poRing);
1038
0
                poGeom->addGeometryDirectly(poPoly);
1039
0
            }
1040
0
            return poGeom;
1041
0
        }
1042
0
        else
1043
0
        {
1044
0
            if (bWarn)
1045
0
            {
1046
0
                CPLError(CE_Warning, CPLE_AppDefined,
1047
0
                         "Unsupported base geometry type for Prism");
1048
0
            }
1049
0
            return nullptr;
1050
0
        }
1051
0
    }
1052
0
    else
1053
0
    {
1054
0
        if (bWarn)
1055
0
        {
1056
0
            CPLError(CE_Warning, CPLE_AppDefined, "Unhandled place.type = %s",
1057
0
                     pszType);
1058
0
        }
1059
0
        return nullptr;
1060
0
    }
1061
0
}
1062
1063
/************************************************************************/
1064
/*           OGRJSONFGReader::GenerateLayerDefnFromFeature()            */
1065
/************************************************************************/
1066
1067
bool OGRJSONFGReader::GenerateLayerDefnFromFeature(json_object *poObj)
1068
338
{
1069
338
    const GeoJSONObject::Type objType = OGRGeoJSONGetType(poObj);
1070
338
    if (objType != GeoJSONObject::eFeature)
1071
0
    {
1072
0
        CPLError(CE_Failure, CPLE_AppDefined, "Did not get a Feature");
1073
0
        return false;
1074
0
    }
1075
1076
338
    const bool bHasM =
1077
338
        OGRJSONFGHasMeasure(poObj, /* bUpperLevelMValue = */ false);
1078
1079
338
    const char *psLayerName = GetLayerNameForFeature(poObj);
1080
1081
338
    auto oBuildContextIter = oMapBuildContext_.find(psLayerName);
1082
338
    if (oBuildContextIter == oMapBuildContext_.end())
1083
131
    {
1084
131
        LayerDefnBuildContext oContext;
1085
131
        oMapBuildContext_[psLayerName] = std::move(oContext);
1086
131
        oBuildContextIter = oMapBuildContext_.find(psLayerName);
1087
131
    }
1088
338
    LayerDefnBuildContext *poContext = &(oBuildContextIter->second);
1089
1090
338
    ++poContext->nFeatureCount;
1091
1092
338
    json_object *poCoordRefSys = nullptr;
1093
338
    json_object *poPlace = nullptr;
1094
338
    if (eGeometryElement_ != GeometryElement::GEOMETRY)
1095
338
    {
1096
338
        poPlace = CPL_json_object_object_get(poObj, "place");
1097
338
        if (poPlace && json_object_get_type(poPlace) == json_type_object)
1098
16
        {
1099
16
            poCoordRefSys = CPL_json_object_object_get(poPlace, "coordRefSys");
1100
16
        }
1101
338
        if (!poCoordRefSys)
1102
338
            poCoordRefSys = CPL_json_object_object_get(poObj, "coordRefSys");
1103
1104
338
        if (poCoordRefSys)
1105
8
        {
1106
8
            std::string osVal = json_object_to_json_string(poCoordRefSys);
1107
8
            if (!poContext->bHasCoordRefSysAtFeatureLevel)
1108
8
            {
1109
8
                poContext->bHasCoordRefSysAtFeatureLevel = true;
1110
8
                poContext->osCoordRefSysAtFeatureLevel = std::move(osVal);
1111
8
                poContext->poCRSAtFeatureLevel =
1112
8
                    OGRJSONFGReadCoordRefSys(poCoordRefSys);
1113
8
                if (poContext->poCRSAtFeatureLevel)
1114
6
                {
1115
6
                    poContext->poCRSAtFeatureLevel->SetAxisMappingStrategy(
1116
6
                        OAMS_TRADITIONAL_GIS_ORDER);
1117
6
                }
1118
8
            }
1119
0
            else if (poContext->osCoordRefSysAtFeatureLevel != osVal)
1120
0
            {
1121
0
                poContext->osCoordRefSysAtFeatureLevel.clear();
1122
0
                poContext->poCRSAtFeatureLevel.reset();
1123
0
            }
1124
8
        }
1125
338
    }
1126
1127
338
    if (poContext->bSameMeasureMetadata)
1128
338
    {
1129
338
        json_object *poMeasures = nullptr;
1130
338
        if (json_object_object_get_ex(poObj, "measures", &poMeasures) &&
1131
0
            json_object_get_type(poMeasures) == json_type_object)
1132
0
        {
1133
0
            json_object *poEnabled = nullptr;
1134
0
            if (json_object_object_get_ex(poMeasures, "enabled", &poEnabled) &&
1135
0
                json_object_get_type(poEnabled) == json_type_boolean &&
1136
0
                json_object_get_boolean(poEnabled))
1137
0
            {
1138
0
                json_object *poUnit = nullptr;
1139
0
                if (json_object_object_get_ex(poMeasures, "unit", &poUnit) &&
1140
0
                    json_object_get_type(poUnit) == json_type_string)
1141
0
                {
1142
0
                    if (poContext->osMeasureUnit.empty())
1143
0
                        poContext->osMeasureUnit =
1144
0
                            json_object_get_string(poUnit);
1145
0
                    else if (poContext->osMeasureUnit !=
1146
0
                             json_object_get_string(poUnit))
1147
0
                        poContext->bSameMeasureMetadata = false;
1148
0
                }
1149
1150
0
                json_object *poDescription = nullptr;
1151
0
                if (json_object_object_get_ex(poMeasures, "description",
1152
0
                                              &poDescription) &&
1153
0
                    json_object_get_type(poDescription) == json_type_string)
1154
0
                {
1155
0
                    if (poContext->osMeasureDescription.empty())
1156
0
                        poContext->osMeasureDescription =
1157
0
                            json_object_get_string(poDescription);
1158
0
                    else if (poContext->osMeasureDescription !=
1159
0
                             json_object_get_string(poDescription))
1160
0
                        poContext->bSameMeasureMetadata = false;
1161
0
                }
1162
0
            }
1163
0
        }
1164
338
    }
1165
1166
    /* -------------------------------------------------------------------- */
1167
    /*      Deal with place / geometry                                      */
1168
    /* -------------------------------------------------------------------- */
1169
1170
338
    if (poContext->bDetectLayerGeomType)
1171
300
    {
1172
300
        bool bFallbackToGeometry =
1173
300
            (eGeometryElement_ != GeometryElement::PLACE);
1174
300
        if (poPlace && json_object_get_type(poPlace) == json_type_object)
1175
16
        {
1176
16
            const auto eType = OGRJSONFGGetOGRGeometryType(poPlace, bHasM);
1177
16
            if (eType != wkbNone)
1178
13
            {
1179
13
                bFallbackToGeometry = false;
1180
13
                poContext->bDetectLayerGeomType = OGRGeoJSONUpdateLayerGeomType(
1181
13
                    poContext->bFirstGeometry, eType,
1182
13
                    poContext->eLayerGeomType);
1183
13
            }
1184
16
        }
1185
1186
300
        if (bFallbackToGeometry)
1187
287
        {
1188
287
            json_object *poGeomObj =
1189
287
                CPL_json_object_object_get(poObj, "geometry");
1190
287
            if (poGeomObj &&
1191
48
                json_object_get_type(poGeomObj) == json_type_object)
1192
48
            {
1193
48
                const auto eType =
1194
48
                    OGRGeoJSONGetOGRGeometryType(poGeomObj, bHasM);
1195
48
                poContext->bDetectLayerGeomType = OGRGeoJSONUpdateLayerGeomType(
1196
48
                    poContext->bFirstGeometry, eType,
1197
48
                    poContext->eLayerGeomType);
1198
48
            }
1199
287
        }
1200
300
    }
1201
1202
    /* -------------------------------------------------------------------- */
1203
    /*      Deal with time                                                  */
1204
    /* -------------------------------------------------------------------- */
1205
338
    json_object *poTime = CPL_json_object_object_get(poObj, "time");
1206
338
    if (poTime)
1207
0
    {
1208
0
        json_object *poDate = CPL_json_object_object_get(poTime, "date");
1209
0
        if (poDate && json_object_get_type(poDate) == json_type_string)
1210
0
            poContext->bHasTimeDate = true;
1211
1212
0
        json_object *poTimestamp =
1213
0
            CPL_json_object_object_get(poTime, "timestamp");
1214
0
        if (poTimestamp &&
1215
0
            json_object_get_type(poTimestamp) == json_type_string)
1216
0
            poContext->bHasTimeTimestamp = true;
1217
1218
0
        json_object *poInterval =
1219
0
            CPL_json_object_object_get(poTime, "interval");
1220
0
        if (poInterval && json_object_get_type(poInterval) == json_type_array &&
1221
0
            json_object_array_length(poInterval) == 2)
1222
0
        {
1223
0
            json_object *poStart = json_object_array_get_idx(poInterval, 0);
1224
0
            if (poStart && json_object_get_type(poStart) == json_type_string)
1225
0
            {
1226
0
                const char *pszStart = json_object_get_string(poStart);
1227
0
                if (strchr(pszStart, 'Z'))
1228
0
                    poContext->bHasTimeIntervalStartTimestamp = true;
1229
0
                else if (strcmp(pszStart, "..") != 0)
1230
0
                    poContext->bHasTimeIntervalStartDate = true;
1231
0
            }
1232
1233
0
            json_object *poEnd = json_object_array_get_idx(poInterval, 1);
1234
0
            if (poEnd && json_object_get_type(poEnd) == json_type_string)
1235
0
            {
1236
0
                const char *pszEnd = json_object_get_string(poEnd);
1237
0
                if (strchr(pszEnd, 'Z'))
1238
0
                    poContext->bHasTimeIntervalEndTimestamp = true;
1239
0
                else if (strcmp(pszEnd, "..") != 0)
1240
0
                    poContext->bHasTimeIntervalEndDate = true;
1241
0
            }
1242
0
        }
1243
0
    }
1244
1245
    /* -------------------------------------------------------------------- */
1246
    /*      Read collection of properties.                                  */
1247
    /* -------------------------------------------------------------------- */
1248
338
    json_object *poObjProps = CPL_json_object_object_get(poObj, "properties");
1249
1250
338
    int nPrevFieldIdx = -1;
1251
1252
    // First deal with id, either at top level or in properties["id"]
1253
338
    OGRGeoJSONGenerateFeatureDefnDealWithID(
1254
338
        poObj, poObjProps, nPrevFieldIdx, poContext->oMapFieldNameToIdx,
1255
338
        poContext->apoFieldDefn, poContext->dag,
1256
338
        poContext->bFeatureLevelIdAsFID, poContext->bFeatureLevelIdAsAttribute,
1257
338
        poContext->bNeedFID64);
1258
1259
338
    if (nullptr != poObjProps &&
1260
174
        json_object_get_type(poObjProps) == json_type_object)
1261
174
    {
1262
174
        json_object_iter it;
1263
174
        it.key = nullptr;
1264
174
        it.val = nullptr;
1265
174
        it.entry = nullptr;
1266
174
        std::vector<int> anCurFieldIndices;
1267
174
        json_object_object_foreachC(poObjProps, it)
1268
547
        {
1269
547
            anCurFieldIndices.clear();
1270
547
            OGRGeoJSONReaderAddOrUpdateField(
1271
547
                anCurFieldIndices, poContext->oMapFieldNameToIdx,
1272
547
                poContext->apoFieldDefn, it.key, it.val,
1273
547
                bFlattenNestedAttributes_, chNestedAttributeSeparator_,
1274
547
                bArrayAsString_, bDateAsString_,
1275
547
                poContext->aoSetUndeterminedTypeFields);
1276
547
            for (int idx : anCurFieldIndices)
1277
547
            {
1278
547
                poContext->dag.addNode(
1279
547
                    idx, poContext->apoFieldDefn[idx]->GetNameRef());
1280
547
                if (nPrevFieldIdx != -1)
1281
427
                {
1282
427
                    poContext->dag.addEdge(nPrevFieldIdx, idx);
1283
427
                }
1284
547
                nPrevFieldIdx = idx;
1285
547
            }
1286
547
        }
1287
174
    }
1288
1289
338
    return true;
1290
338
}
1291
1292
/************************************************************************/
1293
/*                    OGRJSONFGReader::ReadFeature()                    */
1294
/************************************************************************/
1295
1296
std::unique_ptr<OGRFeature>
1297
OGRJSONFGReader::ReadFeature(json_object *poObj, const char *pszRequestedLayer,
1298
                             bool bHasM, OGRJSONFGMemLayer **pOutMemLayer,
1299
                             OGRJSONFGStreamedLayer **pOutStreamedLayer)
1300
33
{
1301
33
    const char *pszLayerName = GetLayerNameForFeature(poObj);
1302
33
    if (pszRequestedLayer && strcmp(pszLayerName, pszRequestedLayer) != 0)
1303
0
        return nullptr;
1304
1305
33
    bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
1306
1307
33
    auto oBuildContextIter = oMapBuildContext_.find(pszLayerName);
1308
33
    CPLAssert(oBuildContextIter != oMapBuildContext_.end());
1309
33
    auto &oBuildContext = oBuildContextIter->second;
1310
33
    OGRLayer *poLayer =
1311
33
        oBuildContext.poStreamedLayer
1312
33
            ? static_cast<OGRLayer *>(oBuildContext.poStreamedLayer)
1313
33
            : static_cast<OGRLayer *>(oBuildContext.poMemLayer);
1314
1315
33
    if (pOutMemLayer)
1316
5
        *pOutMemLayer = oBuildContext.poMemLayer;
1317
28
    else if (pOutStreamedLayer)
1318
28
        *pOutStreamedLayer = oBuildContext.poStreamedLayer;
1319
1320
33
    OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
1321
33
    auto poFeature = std::make_unique<OGRFeature>(poFDefn);
1322
1323
    /* -------------------------------------------------------------------- */
1324
    /*      Translate GeoJSON "properties" object to feature attributes.    */
1325
    /* -------------------------------------------------------------------- */
1326
1327
33
    json_object *poObjProps = CPL_json_object_object_get(poObj, "properties");
1328
33
    if (nullptr != poObjProps &&
1329
8
        json_object_get_type(poObjProps) == json_type_object)
1330
8
    {
1331
8
        json_object_iter it;
1332
8
        it.key = nullptr;
1333
8
        it.val = nullptr;
1334
8
        it.entry = nullptr;
1335
8
        json_object_object_foreachC(poObjProps, it)
1336
8
        {
1337
8
            const int nField = poFDefn->GetFieldIndexCaseSensitive(it.key);
1338
8
            if (nField < 0 &&
1339
0
                !(bFlattenNestedAttributes_ && it.val != nullptr &&
1340
0
                  json_object_get_type(it.val) == json_type_object))
1341
0
            {
1342
0
                CPLDebug("JSONFG", "Cannot find field %s", it.key);
1343
0
            }
1344
8
            else
1345
8
            {
1346
8
                OGRGeoJSONReaderSetField(
1347
8
                    poLayer, poFeature.get(), nField, it.key, it.val,
1348
8
                    bFlattenNestedAttributes_, chNestedAttributeSeparator_);
1349
8
            }
1350
8
        }
1351
8
    }
1352
1353
    /* -------------------------------------------------------------------- */
1354
    /*      Try to use feature-level ID if available                        */
1355
    /*      and of integral type. Otherwise, leave unset (-1) then index    */
1356
    /*      in features sequence will be used as FID.                       */
1357
    /* -------------------------------------------------------------------- */
1358
33
    json_object *poObjId = CPL_json_object_object_get(poObj, "id");
1359
33
    if (nullptr != poObjId && oBuildContext.bFeatureLevelIdAsFID)
1360
21
    {
1361
21
        poFeature->SetFID(static_cast<GIntBig>(json_object_get_int64(poObjId)));
1362
21
    }
1363
1364
    /* -------------------------------------------------------------------- */
1365
    /*      Handle the case where the special id is in a regular field.     */
1366
    /* -------------------------------------------------------------------- */
1367
12
    else if (nullptr != poObjId)
1368
0
    {
1369
0
        const int nIdx = poFDefn->GetFieldIndexCaseSensitive("id");
1370
0
        if (nIdx >= 0 && !poFeature->IsFieldSet(nIdx))
1371
0
        {
1372
0
            poFeature->SetField(nIdx, json_object_get_string(poObjId));
1373
0
        }
1374
0
    }
1375
1376
    /* -------------------------------------------------------------------- */
1377
    /*      Deal with time                                                  */
1378
    /* -------------------------------------------------------------------- */
1379
33
    json_object *poTime = CPL_json_object_object_get(poObj, "time");
1380
33
    if (poTime)
1381
0
    {
1382
0
        json_object *poDate = CPL_json_object_object_get(poTime, "date");
1383
0
        if (poDate && json_object_get_type(poDate) == json_type_string)
1384
0
        {
1385
0
            poFeature->SetField(oBuildContext.nIdxFieldTime,
1386
0
                                json_object_get_string(poDate));
1387
0
        }
1388
1389
0
        json_object *poTimestamp =
1390
0
            CPL_json_object_object_get(poTime, "timestamp");
1391
0
        if (poTimestamp &&
1392
0
            json_object_get_type(poTimestamp) == json_type_string)
1393
0
        {
1394
0
            poFeature->SetField(oBuildContext.nIdxFieldTime,
1395
0
                                json_object_get_string(poTimestamp));
1396
0
        }
1397
1398
0
        json_object *poInterval =
1399
0
            CPL_json_object_object_get(poTime, "interval");
1400
0
        if (poInterval && json_object_get_type(poInterval) == json_type_array &&
1401
0
            json_object_array_length(poInterval) == 2)
1402
0
        {
1403
0
            json_object *poStart = json_object_array_get_idx(poInterval, 0);
1404
0
            if (poStart && json_object_get_type(poStart) == json_type_string)
1405
0
            {
1406
0
                const char *pszStart = json_object_get_string(poStart);
1407
0
                if (strcmp(pszStart, "..") != 0)
1408
0
                    poFeature->SetField(oBuildContext.nIdxFieldTimeStart,
1409
0
                                        pszStart);
1410
0
            }
1411
1412
0
            json_object *poEnd = json_object_array_get_idx(poInterval, 1);
1413
0
            if (poEnd && json_object_get_type(poEnd) == json_type_string)
1414
0
            {
1415
0
                const char *pszEnd = json_object_get_string(poEnd);
1416
0
                if (strcmp(pszEnd, "..") != 0)
1417
0
                    poFeature->SetField(oBuildContext.nIdxFieldTimeEnd, pszEnd);
1418
0
            }
1419
0
        }
1420
0
    }
1421
1422
    /* -------------------------------------------------------------------- */
1423
    /*      Translate "place" (and fallback to "geometry") sub-object       */
1424
    /* -------------------------------------------------------------------- */
1425
33
    json_object *poPlace = nullptr;
1426
33
    bool bFallbackToGeometry = (eGeometryElement_ != GeometryElement::PLACE);
1427
1428
33
    if (eGeometryElement_ != GeometryElement::GEOMETRY)
1429
33
    {
1430
33
        poPlace = CPL_json_object_object_get(poObj, "place");
1431
33
    }
1432
33
    if (poPlace && json_object_get_type(poPlace) == json_type_object)
1433
14
    {
1434
14
        bHasM = OGRJSONFGHasMeasure(poPlace, bHasM);
1435
14
        json_object *poCoordRefSys = nullptr;
1436
14
        if (!oBuildContext.poCRSAtFeatureLevel)
1437
6
        {
1438
6
            poCoordRefSys = CPL_json_object_object_get(poPlace, "coordRefSys");
1439
6
            if (!poCoordRefSys)
1440
6
            {
1441
6
                poCoordRefSys =
1442
6
                    CPL_json_object_object_get(poObj, "coordRefSys");
1443
6
            }
1444
6
        }
1445
1446
14
        std::unique_ptr<OGRGeometry> poGeometry;
1447
14
        json_object *poObjType = CPL_json_object_object_get(poPlace, "type");
1448
14
        const char *pszType = json_object_get_string(poObjType);
1449
14
        if (pszType && (strcmp(pszType, "Polyhedron") == 0 ||
1450
13
                        strcmp(pszType, "Prism") == 0))
1451
0
        {
1452
0
            poGeometry = OGRJSONFGCreateNonGeoJSONGeometry(poPlace, bHasM,
1453
0
                                                           /* bWarn=*/false);
1454
0
        }
1455
14
        else
1456
14
        {
1457
14
            poGeometry = OGRGeoJSONReadGeometry(poPlace, bHasM, nullptr);
1458
14
        }
1459
14
        if (poGeometry)
1460
7
            bFallbackToGeometry = false;
1461
1462
14
        auto poLayerSRS = poLayer->GetSpatialRef();
1463
14
        if (!poGeometry)
1464
7
        {
1465
            // nothing to do
1466
7
        }
1467
7
        else if (poCoordRefSys)
1468
1
        {
1469
1
            auto poFeatureCRS = OGRJSONFGReadCoordRefSys(poCoordRefSys);
1470
1
            if (poFeatureCRS)
1471
0
            {
1472
0
                poFeatureCRS->SetAxisMappingStrategy(
1473
0
                    OAMS_TRADITIONAL_GIS_ORDER);
1474
0
                const bool bFeatureCRSNeedSwapXY =
1475
0
                    OGRJSONFGMustSwapXY(poFeatureCRS.get());
1476
0
                if (poLayerSRS)
1477
0
                {
1478
                    // Both feature and layer-level CRS. Reproject if needed
1479
0
                    if (!poFeatureCRS->IsSame(poLayerSRS))
1480
0
                    {
1481
0
                        auto poCT =
1482
0
                            std::unique_ptr<OGRCoordinateTransformation>(
1483
0
                                OGRCreateCoordinateTransformation(
1484
0
                                    poFeatureCRS.get(), poLayerSRS));
1485
0
                        if (poCT)
1486
0
                        {
1487
0
                            if (bFeatureCRSNeedSwapXY)
1488
0
                                poGeometry->swapXY();
1489
0
                            if (poGeometry->transform(poCT.get()) ==
1490
0
                                OGRERR_NONE)
1491
0
                            {
1492
0
                                poGeometry->assignSpatialReference(poLayerSRS);
1493
0
                                poFeature->SetGeometryDirectly(
1494
0
                                    poGeometry.release());
1495
0
                            }
1496
0
                        }
1497
0
                    }
1498
0
                    else
1499
0
                    {
1500
0
                        poGeometry->assignSpatialReference(poLayerSRS);
1501
0
                        if (oBuildContext.bSwapPlacesXY)
1502
0
                            poGeometry->swapXY();
1503
0
                        poFeature->SetGeometryDirectly(poGeometry.release());
1504
0
                    }
1505
0
                }
1506
0
                else
1507
0
                {
1508
                    // No layer-level CRS
1509
0
                    auto poFeatureCRSBorrowed = poFeatureCRS.release();
1510
0
                    poGeometry->assignSpatialReference(poFeatureCRSBorrowed);
1511
0
                    poFeatureCRSBorrowed->Release();
1512
0
                    if (bFeatureCRSNeedSwapXY)
1513
0
                        poGeometry->swapXY();
1514
0
                    poFeature->SetGeometryDirectly(poGeometry.release());
1515
0
                }
1516
0
            }
1517
1
        }
1518
6
        else
1519
6
        {
1520
6
            poGeometry->assignSpatialReference(poLayerSRS);
1521
6
            if (oBuildContext.bSwapPlacesXY)
1522
0
                poGeometry->swapXY();
1523
6
            poFeature->SetGeometryDirectly(poGeometry.release());
1524
6
        }
1525
14
    }
1526
1527
33
    if (bFallbackToGeometry &&
1528
26
        (oBuildContext.poCTWGS84ToLayerCRS || oBuildContext.bLayerCRSIsWGS84))
1529
24
    {
1530
24
        json_object *poGeomObj = CPL_json_object_object_get(poObj, "geometry");
1531
24
        if (nullptr != poGeomObj)
1532
6
        {
1533
6
            auto poGeometry =
1534
6
                std::unique_ptr<OGRGeometry>(OGRGeoJSONReadGeometry(
1535
6
                    poGeomObj, /* bHasM = */ false, nullptr));
1536
6
            if (poGeometry)
1537
4
            {
1538
4
                if (oBuildContext.poCTWGS84ToLayerCRS)
1539
3
                {
1540
3
                    if (poGeometry->transform(
1541
3
                            oBuildContext.poCTWGS84ToLayerCRS.get()) ==
1542
3
                        OGRERR_NONE)
1543
3
                    {
1544
3
                        poGeometry->assignSpatialReference(
1545
3
                            poLayer->GetSpatialRef());
1546
3
                        poFeature->SetGeometryDirectly(poGeometry.release());
1547
3
                    }
1548
3
                }
1549
1
                else /* if (oBuildContext.bLayerCRSIsWGS84) */
1550
1
                {
1551
1
                    poGeometry->assignSpatialReference(
1552
1
                        poLayer->GetSpatialRef());
1553
1
                    poFeature->SetGeometryDirectly(poGeometry.release());
1554
1
                }
1555
4
            }
1556
6
        }
1557
24
    }
1558
1559
33
    return poFeature;
1560
33
}