Coverage Report

Created: 2025-12-31 08:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.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
#include "ogr_geojson.h"
15
16
#include "cpl_http.h"
17
#include "cpl_vsi_error.h"
18
#include "cpl_vsi_virtual.h"
19
20
#include <cmath>
21
22
constexpr const char *CONFORMANCE_CORE =
23
    "http://www.opengis.net/spec/json-fg-1/0.3/conf/core";
24
constexpr const char *CONFORMANCE_FEATURE_TYPE =
25
    "http://www.opengis.net/spec/json-fg-1/0.3/conf/types-schemas";
26
constexpr const char *CONFORMANCE_POLYHEDRA =
27
    "http://www.opengis.net/spec/json-fg-1/0.3/conf/polyhedra";
28
constexpr const char *CONFORMANCE_CIRCULAR_ARCS =
29
    "http://www.opengis.net/spec/json-fg-1/0.3/conf/circular-arcs";
30
constexpr const char *CONFORMANCE_MEASURES =
31
    "http://www.opengis.net/spec/json-fg-1/0.3/conf/measures";
32
33
/************************************************************************/
34
/*                  OGRJSONFGDataset::~OGRJSONFGDataset()               */
35
/************************************************************************/
36
37
OGRJSONFGDataset::~OGRJSONFGDataset()
38
1.27k
{
39
1.27k
    OGRJSONFGDataset::Close();
40
1.27k
    CPLFree(pszGeoData_);
41
1.27k
}
42
43
/************************************************************************/
44
/*                        OGRJSONFGDataset::Close()                    */
45
/************************************************************************/
46
47
CPLErr OGRJSONFGDataset::Close(GDALProgressFunc, void *)
48
1.29k
{
49
1.29k
    CPLErr eErr = CE_None;
50
1.29k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
51
1.27k
    {
52
1.27k
        if (fpOut_)
53
0
        {
54
0
            eErr = GDAL::Combine(eErr, FinishWriting());
55
56
0
            eErr = GDAL::Combine(eErr, VSIFCloseL(fpOut_) == 0);
57
0
            fpOut_ = nullptr;
58
0
        }
59
60
1.27k
        apoLayers_.clear();
61
62
1.27k
        eErr = GDAL::Combine(eErr, GDALDataset::Close());
63
1.27k
    }
64
65
1.29k
    return eErr;
66
1.29k
}
67
68
/************************************************************************/
69
/*                           FinishWriting()                            */
70
/************************************************************************/
71
72
bool OGRJSONFGDataset::FinishWriting()
73
0
{
74
0
    bool ret = true;
75
0
    if (m_nPositionBeforeFCClosed == 0)
76
0
    {
77
0
        m_nPositionBeforeFCClosed = fpOut_->Tell();
78
79
0
        if (!EmitStartFeaturesIfNeededAndReturnIfFirstFeature())
80
0
            ret &= VSIFPrintfL(fpOut_, "\n") != 0;
81
0
        ret &= VSIFPrintfL(fpOut_, "]") != 0;
82
83
        // When we didn't know if there was a single layer, we omitted writing
84
        // the coordinate precision at ICreateLayer() time.
85
        // Now we can check if there was a single layer, or several layers with
86
        // same precision setting, and write it when possible.
87
0
        if (!bSingleOutputLayer_ && !apoLayers_.empty() &&
88
0
            apoLayers_.front()->GetLayerDefn()->GetGeomFieldCount() > 0)
89
0
        {
90
0
            const auto &oCoordPrec = apoLayers_.front()
91
0
                                         ->GetLayerDefn()
92
0
                                         ->GetGeomFieldDefn(0)
93
0
                                         ->GetCoordinatePrecision();
94
0
            bool bSameGeomCoordPrec =
95
0
                (oCoordPrec.dfXYResolution !=
96
0
                     OGRGeomCoordinatePrecision::UNKNOWN ||
97
0
                 oCoordPrec.dfZResolution !=
98
0
                     OGRGeomCoordinatePrecision::UNKNOWN);
99
0
            for (size_t i = 1; i < apoLayers_.size(); ++i)
100
0
            {
101
0
                if (apoLayers_[i]->GetLayerDefn()->GetGeomFieldCount() > 0)
102
0
                {
103
0
                    const auto &oOtherCoordPrec =
104
0
                        apoLayers_[i]
105
0
                            ->GetLayerDefn()
106
0
                            ->GetGeomFieldDefn(0)
107
0
                            ->GetCoordinatePrecision();
108
0
                    bSameGeomCoordPrec &= (oOtherCoordPrec.dfXYResolution ==
109
0
                                               oCoordPrec.dfXYResolution &&
110
0
                                           oOtherCoordPrec.dfZResolution ==
111
0
                                               oCoordPrec.dfZResolution);
112
0
                }
113
0
            }
114
0
            if (bSameGeomCoordPrec)
115
0
            {
116
0
                if (oCoordPrec.dfXYResolution !=
117
0
                    OGRGeomCoordinatePrecision::UNKNOWN)
118
0
                {
119
0
                    ret &=
120
0
                        VSIFPrintfL(fpOut_,
121
0
                                    ",\n\"xy_coordinate_resolution_place\":%g",
122
0
                                    oCoordPrec.dfXYResolution) != 0;
123
0
                }
124
0
                if (oCoordPrec.dfZResolution !=
125
0
                    OGRGeomCoordinatePrecision::UNKNOWN)
126
0
                {
127
0
                    ret &=
128
0
                        VSIFPrintfL(fpOut_,
129
0
                                    ",\n\"z_coordinate_resolution_place\":%g",
130
0
                                    oCoordPrec.dfZResolution) != 0;
131
0
                }
132
133
0
                OGRSpatialReference oSRSWGS84;
134
0
                oSRSWGS84.SetWellKnownGeogCS("WGS84");
135
0
                const auto oCoordPrecWGS84 = oCoordPrec.ConvertToOtherSRS(
136
0
                    apoLayers_.front()->GetSpatialRef(), &oSRSWGS84);
137
138
0
                if (oCoordPrecWGS84.dfXYResolution !=
139
0
                    OGRGeomCoordinatePrecision::UNKNOWN)
140
0
                {
141
0
                    ret &= VSIFPrintfL(fpOut_,
142
0
                                       ",\n\"xy_coordinate_resolution\":%g",
143
0
                                       oCoordPrecWGS84.dfXYResolution) != 0;
144
0
                }
145
0
                if (oCoordPrecWGS84.dfZResolution !=
146
0
                    OGRGeomCoordinatePrecision::UNKNOWN)
147
0
                {
148
0
                    ret &=
149
0
                        VSIFPrintfL(fpOut_, ",\n\"z_coordinate_resolution\":%g",
150
0
                                    oCoordPrecWGS84.dfZResolution) != 0;
151
0
                }
152
0
            }
153
0
        }
154
155
0
        bool bPolyhedra = false;
156
0
        bool bCurve = false;
157
0
        bool bMeasure = false;
158
0
        for (auto &poLayer : apoLayers_)
159
0
        {
160
0
            auto poWriteLayer =
161
0
                dynamic_cast<OGRJSONFGWriteLayer *>(poLayer.get());
162
0
            if (poWriteLayer)
163
0
            {
164
0
                bPolyhedra |= poWriteLayer->HasPolyhedra();
165
0
                bCurve |= poWriteLayer->HasCurve();
166
0
                bMeasure |= poWriteLayer->HasMeasure();
167
0
            }
168
0
        }
169
0
        if (bPolyhedra || bCurve || bMeasure ||
170
0
            m_nPositionBeforeConformsTo == 0)
171
0
        {
172
0
            if (m_nPositionBeforeConformsTo > 0)
173
0
            {
174
0
                ret &= VSIFSeekL(fpOut_, m_nPositionBeforeConformsTo,
175
0
                                 SEEK_SET) == 0;
176
0
            }
177
0
            else
178
0
            {
179
0
                ret &= VSIFPrintfL(fpOut_, ",\n") != 0;
180
0
            }
181
0
            ret &= VSIFPrintfL(fpOut_,
182
0
                               "\"conformsTo\": [\n"
183
0
                               "  \"%s\",\n  \"%s\"",
184
0
                               CONFORMANCE_CORE, CONFORMANCE_FEATURE_TYPE) != 0;
185
0
            if (bPolyhedra)
186
0
                ret &= VSIFPrintfL(fpOut_, ",\n  \"%s\"",
187
0
                                   CONFORMANCE_POLYHEDRA) != 0;
188
0
            if (bCurve)
189
0
                ret &= VSIFPrintfL(fpOut_, ",\n  \"%s\"",
190
0
                                   CONFORMANCE_CIRCULAR_ARCS) != 0;
191
0
            if (bMeasure)
192
0
                ret &= VSIFPrintfL(fpOut_, ",\n  \"%s\"",
193
0
                                   CONFORMANCE_MEASURES) != 0;
194
0
            if (m_nPositionBeforeConformsTo > 0)
195
0
            {
196
0
                ret &= VSIFPrintfL(fpOut_, "\n],") != 0;
197
0
                ret &= VSIFPrintfL(
198
0
                           fpOut_, "%s\n",
199
0
                           std::string(static_cast<size_t>(
200
0
                                           m_nPositionAfterConformsTo -
201
0
                                           strlen(",") - VSIFTellL(fpOut_)),
202
0
                                       ' ')
203
0
                               .c_str()) != 0;
204
205
0
                ret &= VSIFSeekL(fpOut_, 0, SEEK_END) == 0;
206
0
            }
207
0
            else
208
0
            {
209
0
                ret &= VSIFPrintfL(fpOut_, "\n]") != 0;
210
0
            }
211
0
        }
212
213
0
        ret &= VSIFPrintfL(fpOut_, "\n}\n") != 0;
214
215
0
        ret &= fpOut_->Flush() == 0;
216
0
    }
217
0
    return ret;
218
0
}
219
220
/************************************************************************/
221
/*                         SyncToDiskInternal()                         */
222
/************************************************************************/
223
224
OGRErr OGRJSONFGDataset::SyncToDiskInternal()
225
0
{
226
0
    if (m_nPositionBeforeFCClosed == 0 && GetFpOutputIsSeekable())
227
0
    {
228
0
        FinishWriting();
229
0
    }
230
231
0
    return OGRERR_NONE;
232
0
}
233
234
/************************************************************************/
235
/*                         BeforeCreateFeature()                        */
236
/************************************************************************/
237
238
void OGRJSONFGDataset::BeforeCreateFeature()
239
0
{
240
0
    if (m_nPositionBeforeFCClosed)
241
0
    {
242
        // If we had called SyncToDisk() previously, undo its effects
243
0
        fpOut_->Seek(m_nPositionBeforeFCClosed, SEEK_SET);
244
0
        m_nPositionBeforeFCClosed = 0;
245
0
    }
246
247
0
    if (!EmitStartFeaturesIfNeededAndReturnIfFirstFeature())
248
0
    {
249
0
        VSIFPrintfL(fpOut_, ",\n");
250
0
    }
251
0
}
252
253
/************************************************************************/
254
/*                           Open()                                     */
255
/************************************************************************/
256
257
bool OGRJSONFGDataset::Open(GDALOpenInfo *poOpenInfo,
258
                            GeoJSONSourceType nSrcType)
259
1.27k
{
260
1.27k
    const char *pszUnprefixed = poOpenInfo->pszFilename;
261
1.27k
    if (STARTS_WITH_CI(pszUnprefixed, "JSONFG:"))
262
148
    {
263
148
        pszUnprefixed += strlen("JSONFG:");
264
148
    }
265
266
1.27k
    std::string osDefaultLayerName;
267
268
1.27k
    VSIVirtualHandleUniquePtr fp;
269
1.27k
    if (nSrcType == eGeoJSONSourceService)
270
649
    {
271
649
        if (!ReadFromService(poOpenInfo, pszUnprefixed))
272
649
            return false;
273
0
        if (poOpenInfo->eAccess == GA_Update)
274
0
        {
275
0
            CPLError(CE_Failure, CPLE_NotSupported,
276
0
                     "Update from remote service not supported");
277
0
            return false;
278
0
        }
279
0
    }
280
628
    else if (nSrcType == eGeoJSONSourceText)
281
84
    {
282
84
        if (poOpenInfo->eAccess == GA_Update)
283
0
        {
284
0
            CPLError(CE_Failure, CPLE_NotSupported,
285
0
                     "Update from inline definition not supported");
286
0
            return false;
287
0
        }
288
84
        pszGeoData_ = CPLStrdup(pszUnprefixed);
289
84
    }
290
544
    else if (nSrcType == eGeoJSONSourceFile)
291
544
    {
292
544
        if (poOpenInfo->eAccess == GA_Update)
293
0
        {
294
0
            CPLError(CE_Failure, CPLE_NotSupported, "Update not supported");
295
0
            return false;
296
0
        }
297
544
        SetDescription(pszUnprefixed);
298
544
        osDefaultLayerName = CPLGetBasenameSafe(pszUnprefixed);
299
544
        eAccess = poOpenInfo->eAccess;
300
301
        // Ingests the first bytes of the file in pszGeoData_
302
544
        if (!EQUAL(pszUnprefixed, poOpenInfo->pszFilename))
303
75
        {
304
75
            GDALOpenInfo oOpenInfo(pszUnprefixed, GA_ReadOnly);
305
75
            if (oOpenInfo.fpL == nullptr || oOpenInfo.pabyHeader == nullptr)
306
10
                return false;
307
65
            pszGeoData_ =
308
65
                CPLStrdup(reinterpret_cast<const char *>(oOpenInfo.pabyHeader));
309
65
            fp.reset(oOpenInfo.fpL);
310
65
            oOpenInfo.fpL = nullptr;
311
65
        }
312
469
        else if (poOpenInfo->fpL == nullptr)
313
0
            return false;
314
469
        else
315
469
        {
316
469
            fp.reset(poOpenInfo->fpL);
317
469
            poOpenInfo->fpL = nullptr;
318
469
            pszGeoData_ = CPLStrdup(
319
469
                reinterpret_cast<const char *>(poOpenInfo->pabyHeader));
320
469
        }
321
544
    }
322
0
    else
323
0
    {
324
0
        return false;
325
0
    }
326
327
618
    if (osDefaultLayerName.empty())
328
109
        osDefaultLayerName = "features";
329
330
618
    const auto SetReaderOptions = [poOpenInfo](OGRJSONFGReader &oReader)
331
961
    {
332
961
        const char *pszGeometryElement = CSLFetchNameValueDef(
333
961
            poOpenInfo->papszOpenOptions, "GEOMETRY_ELEMENT", "AUTO");
334
961
        if (EQUAL(pszGeometryElement, "PLACE"))
335
0
            oReader.SetGeometryElement(OGRJSONFGReader::GeometryElement::PLACE);
336
961
        else if (EQUAL(pszGeometryElement, "GEOMETRY"))
337
0
            oReader.SetGeometryElement(
338
0
                OGRJSONFGReader::GeometryElement::GEOMETRY);
339
961
    };
340
341
618
    if (nSrcType == eGeoJSONSourceFile)
342
534
    {
343
534
        auto poReader = std::make_unique<OGRJSONFGReader>();
344
534
        SetReaderOptions(*(poReader.get()));
345
346
        // Try to use a streaming parser if the content of the file seems
347
        // to be FeatureCollection
348
534
        bool bUseStreamingInterface = false;
349
534
        const char *pszStr = strstr(pszGeoData_, "\"features\"");
350
534
        if (pszStr)
351
207
        {
352
207
            pszStr += strlen("\"features\"");
353
253
            while (*pszStr && isspace(static_cast<unsigned char>(*pszStr)))
354
46
                pszStr++;
355
207
            if (*pszStr == ':')
356
200
            {
357
200
                pszStr++;
358
333
                while (*pszStr && isspace(static_cast<unsigned char>(*pszStr)))
359
133
                    pszStr++;
360
200
                if (*pszStr == '[')
361
195
                {
362
195
                    bUseStreamingInterface = true;
363
195
                }
364
200
            }
365
207
        }
366
534
        if (bUseStreamingInterface)
367
195
        {
368
195
            bool bCanTryWithNonStreamingParserOut = true;
369
195
            bool bHasTopLevelMeasures = false;
370
195
            if (poReader->AnalyzeWithStreamingParser(
371
195
                    this, fp.get(), osDefaultLayerName,
372
195
                    bCanTryWithNonStreamingParserOut, bHasTopLevelMeasures))
373
8
            {
374
8
                if (!apoLayers_.empty())
375
8
                {
376
8
                    auto poLayer = cpl::down_cast<OGRJSONFGStreamedLayer *>(
377
8
                        apoLayers_[0].get());
378
8
                    poLayer->SetFile(std::move(fp));
379
8
                    auto poParser = std::make_unique<OGRJSONFGStreamingParser>(
380
8
                        *(poReader.get()), false, bHasTopLevelMeasures);
381
8
                    poLayer->SetStreamingParser(std::move(poParser));
382
8
                }
383
384
8
                for (size_t i = 1; i < apoLayers_.size(); ++i)
385
0
                {
386
0
                    auto poLayer = cpl::down_cast<OGRJSONFGStreamedLayer *>(
387
0
                        apoLayers_[i].get());
388
389
0
                    auto fpNew = VSIVirtualHandleUniquePtr(
390
0
                        VSIFOpenL(pszUnprefixed, "rb"));
391
0
                    if (!fpNew)
392
0
                    {
393
0
                        CPLError(CE_Failure, CPLE_FileIO,
394
0
                                 "Cannot open %s again", pszUnprefixed);
395
0
                        return false;
396
0
                    }
397
0
                    poLayer->SetFile(std::move(fpNew));
398
399
0
                    auto poParser = std::make_unique<OGRJSONFGStreamingParser>(
400
0
                        *(poReader.get()), false, bHasTopLevelMeasures);
401
0
                    poLayer->SetStreamingParser(std::move(poParser));
402
0
                }
403
8
                poReader_ = std::move(poReader);
404
8
                return true;
405
8
            }
406
187
            if (!bCanTryWithNonStreamingParserOut)
407
183
                return false;
408
187
        }
409
410
        // Fallback to in-memory ingestion
411
343
        CPLAssert(poOpenInfo->fpL == nullptr);
412
343
        poOpenInfo->fpL = fp.release();
413
343
        if (!ReadFromFile(poOpenInfo, pszUnprefixed))
414
0
            return false;
415
343
    }
416
417
    // In-memory ingestion of the file
418
427
    OGRJSONFGReader oReader;
419
427
    SetReaderOptions(oReader);
420
427
    const bool bRet = oReader.Load(this, pszGeoData_, osDefaultLayerName);
421
427
    CPLFree(pszGeoData_);
422
427
    pszGeoData_ = nullptr;
423
427
    return bRet;
424
618
}
425
426
/************************************************************************/
427
/*                  OGRJSONFGDataset::GetLayer()                        */
428
/************************************************************************/
429
430
const OGRLayer *OGRJSONFGDataset::GetLayer(int i) const
431
14
{
432
14
    if (i < 0 || i >= static_cast<int>(apoLayers_.size()))
433
0
        return nullptr;
434
14
    return apoLayers_[i].get();
435
14
}
436
437
/************************************************************************/
438
/*                  OGRJSONFGDataset::AddLayer()                        */
439
/************************************************************************/
440
441
OGRJSONFGMemLayer *
442
OGRJSONFGDataset::AddLayer(std::unique_ptr<OGRJSONFGMemLayer> &&poLayer)
443
6
{
444
6
    apoLayers_.emplace_back(std::move(poLayer));
445
6
    return static_cast<OGRJSONFGMemLayer *>(apoLayers_.back().get());
446
6
}
447
448
/************************************************************************/
449
/*                  OGRJSONFGDataset::AddLayer()                        */
450
/************************************************************************/
451
452
OGRJSONFGStreamedLayer *
453
OGRJSONFGDataset::AddLayer(std::unique_ptr<OGRJSONFGStreamedLayer> &&poLayer)
454
8
{
455
8
    apoLayers_.emplace_back(std::move(poLayer));
456
8
    return static_cast<OGRJSONFGStreamedLayer *>(apoLayers_.back().get());
457
8
}
458
459
/************************************************************************/
460
/*                           ReadFromFile()                             */
461
/************************************************************************/
462
463
bool OGRJSONFGDataset::ReadFromFile(GDALOpenInfo *poOpenInfo,
464
                                    const char *pszUnprefixed)
465
343
{
466
343
    GByte *pabyOut = nullptr;
467
343
    if (!EQUAL(poOpenInfo->pszFilename, pszUnprefixed))
468
65
    {
469
65
        GDALOpenInfo oOpenInfo(pszUnprefixed, GA_ReadOnly);
470
65
        if (oOpenInfo.fpL == nullptr || oOpenInfo.pabyHeader == nullptr)
471
0
            return false;
472
65
        VSIFSeekL(oOpenInfo.fpL, 0, SEEK_SET);
473
65
        if (!VSIIngestFile(oOpenInfo.fpL, pszUnprefixed, &pabyOut, nullptr, -1))
474
0
        {
475
0
            return false;
476
0
        }
477
65
    }
478
278
    else
479
278
    {
480
278
        if (poOpenInfo->fpL == nullptr)
481
0
            return false;
482
278
        VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
483
278
        if (!VSIIngestFile(poOpenInfo->fpL, poOpenInfo->pszFilename, &pabyOut,
484
278
                           nullptr, -1))
485
0
        {
486
0
            return false;
487
0
        }
488
489
278
        VSIFCloseL(poOpenInfo->fpL);
490
278
        poOpenInfo->fpL = nullptr;
491
278
    }
492
493
343
    CPLFree(pszGeoData_);
494
343
    pszGeoData_ = reinterpret_cast<char *>(pabyOut);
495
496
343
    CPLAssert(nullptr != pszGeoData_);
497
498
343
    return true;
499
343
}
500
501
/************************************************************************/
502
/*                           ReadFromService()                          */
503
/************************************************************************/
504
505
bool OGRJSONFGDataset::ReadFromService(GDALOpenInfo *poOpenInfo,
506
                                       const char *pszSource)
507
649
{
508
649
    CPLAssert(nullptr == pszGeoData_);
509
649
    CPLAssert(nullptr != pszSource);
510
511
649
    CPLErrorReset();
512
513
    /* -------------------------------------------------------------------- */
514
    /*      Look if we already cached the content.                          */
515
    /* -------------------------------------------------------------------- */
516
649
    char *pszStoredContent = OGRGeoJSONDriverStealStoredContent(pszSource);
517
649
    if (pszStoredContent != nullptr)
518
0
    {
519
0
        if (JSONFGIsObject(pszStoredContent, poOpenInfo))
520
0
        {
521
0
            pszGeoData_ = pszStoredContent;
522
0
            nGeoDataLen_ = strlen(pszGeoData_);
523
524
0
            SetDescription(pszSource);
525
0
            return true;
526
0
        }
527
528
0
        OGRGeoJSONDriverStoreContent(pszSource, pszStoredContent);
529
0
        return false;
530
0
    }
531
532
    /* -------------------------------------------------------------------- */
533
    /*      Fetch the result.                                               */
534
    /* -------------------------------------------------------------------- */
535
649
    char *papsOptions[] = {
536
649
        const_cast<char *>("HEADERS=Accept: text/plain, application/json"),
537
649
        nullptr};
538
539
649
    CPLHTTPResult *pResult = CPLHTTPFetch(pszSource, papsOptions);
540
541
    /* -------------------------------------------------------------------- */
542
    /*      Try to handle CURL/HTTP errors.                                 */
543
    /* -------------------------------------------------------------------- */
544
649
    if (nullptr == pResult || 0 == pResult->nDataLen ||
545
1
        0 != CPLGetLastErrorNo())
546
649
    {
547
649
        CPLHTTPDestroyResult(pResult);
548
649
        return false;
549
649
    }
550
551
0
    if (0 != pResult->nStatus)
552
0
    {
553
0
        CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
554
0
                 pResult->nStatus, pResult->pszErrBuf);
555
0
        CPLHTTPDestroyResult(pResult);
556
0
        return false;
557
0
    }
558
559
    /* -------------------------------------------------------------------- */
560
    /*      Copy returned GeoJSON data to text buffer.                      */
561
    /* -------------------------------------------------------------------- */
562
0
    char *pszData = reinterpret_cast<char *>(pResult->pabyData);
563
564
    // Directly assign CPLHTTPResult::pabyData to pszGeoData_.
565
0
    pszGeoData_ = pszData;
566
0
    nGeoDataLen_ = pResult->nDataLen;
567
0
    pResult->pabyData = nullptr;
568
0
    pResult->nDataLen = 0;
569
570
0
    SetDescription(pszSource);
571
572
    /* -------------------------------------------------------------------- */
573
    /*      Cleanup HTTP resources.                                         */
574
    /* -------------------------------------------------------------------- */
575
0
    CPLHTTPDestroyResult(pResult);
576
577
0
    CPLAssert(nullptr != pszGeoData_);
578
579
    /* -------------------------------------------------------------------- */
580
    /*      Cache the content if it is not handled by this driver, but      */
581
    /*      another related one.                                            */
582
    /* -------------------------------------------------------------------- */
583
0
    if (EQUAL(pszSource, poOpenInfo->pszFilename))
584
0
    {
585
0
        if (!JSONFGIsObject(pszGeoData_, poOpenInfo))
586
0
        {
587
0
            OGRGeoJSONDriverStoreContent(pszSource, pszGeoData_);
588
0
            pszGeoData_ = nullptr;
589
0
            nGeoDataLen_ = 0;
590
0
            return false;
591
0
        }
592
0
    }
593
594
0
    return true;
595
0
}
596
597
/************************************************************************/
598
/*                              Create()                                */
599
/************************************************************************/
600
601
bool OGRJSONFGDataset::Create(const char *pszName, CSLConstList papszOptions)
602
0
{
603
0
    CPLAssert(nullptr == fpOut_);
604
0
    bSingleOutputLayer_ =
605
0
        CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_LAYER", "NO"));
606
607
0
    bFpOutputIsSeekable_ = !(strcmp(pszName, "/vsistdout/") == 0 ||
608
0
                             STARTS_WITH(pszName, "/vsigzip/") ||
609
0
                             STARTS_WITH(pszName, "/vsizip/"));
610
611
0
    if (strcmp(pszName, "/dev/stdout") == 0)
612
0
        pszName = "/vsistdout/";
613
614
    /* -------------------------------------------------------------------- */
615
    /*     File overwrite not supported.                                    */
616
    /* -------------------------------------------------------------------- */
617
0
    VSIStatBufL sStatBuf;
618
0
    if (0 == VSIStatL(pszName, &sStatBuf))
619
0
    {
620
0
        CPLError(CE_Failure, CPLE_NotSupported,
621
0
                 "The JSONFG driver does not overwrite existing files.");
622
0
        return false;
623
0
    }
624
625
    /* -------------------------------------------------------------------- */
626
    /*      Create the output file.                                         */
627
    /* -------------------------------------------------------------------- */
628
0
    fpOut_ = VSIFOpenExL(pszName, "w", true);
629
0
    if (nullptr == fpOut_)
630
0
    {
631
0
        CPLError(CE_Failure, CPLE_OpenFailed,
632
0
                 "Failed to create JSONFG dataset: %s: %s", pszName,
633
0
                 VSIGetLastErrorMsg());
634
0
        return false;
635
0
    }
636
637
0
    SetDescription(pszName);
638
639
0
    VSIFPrintfL(fpOut_, "{\n\"type\": \"FeatureCollection\",\n");
640
0
    if (bFpOutputIsSeekable_)
641
0
    {
642
0
        m_nPositionBeforeConformsTo = VSIFTellL(fpOut_);
643
0
        VSIFPrintfL(fpOut_,
644
0
                    "\"conformsTo\": [\n"
645
0
                    "  \"%s\",\n"
646
0
                    "  \"%s\"\n"
647
0
                    "],\n",
648
0
                    CONFORMANCE_CORE, CONFORMANCE_FEATURE_TYPE);
649
0
        VSIFPrintfL(
650
0
            fpOut_, "%s",
651
0
            std::string(
652
0
                strlen(",") + strlen("  \"\",\n") +
653
0
                    strlen(CONFORMANCE_POLYHEDRA) + strlen("  \"\",\n") +
654
0
                    strlen(CONFORMANCE_CIRCULAR_ARCS) + strlen("  \"\",\n") +
655
0
                    strlen(CONFORMANCE_MEASURES) + strlen("\",\n"),
656
0
                ' ')
657
0
                .c_str());
658
0
        m_nPositionAfterConformsTo = VSIFTellL(fpOut_);
659
0
    }
660
661
0
    return true;
662
0
}
663
664
/************************************************************************/
665
/*                        EmitStartFeaturesIfNeeded()                   */
666
/************************************************************************/
667
668
bool OGRJSONFGDataset::EmitStartFeaturesIfNeededAndReturnIfFirstFeature()
669
0
{
670
0
    if (!bHasEmittedFeatures_)
671
0
    {
672
0
        bHasEmittedFeatures_ = true;
673
0
        VSIFPrintfL(fpOut_, "\"features\" : [\n");
674
0
        return true;
675
0
    }
676
0
    return false;
677
0
}
678
679
/************************************************************************/
680
/*                           ICreateLayer()                             */
681
/************************************************************************/
682
683
OGRLayer *
684
OGRJSONFGDataset::ICreateLayer(const char *pszNameIn,
685
                               const OGRGeomFieldDefn *poSrcGeomFieldDefn,
686
                               CSLConstList papszOptions)
687
0
{
688
0
    if (nullptr == fpOut_)
689
0
    {
690
0
        CPLError(CE_Failure, CPLE_NotSupported,
691
0
                 "JSONFG driver doesn't support creating a layer "
692
0
                 "on a read-only datasource");
693
0
        return nullptr;
694
0
    }
695
696
0
    if (bSingleOutputLayer_ && !apoLayers_.empty())
697
0
    {
698
0
        CPLError(CE_Failure, CPLE_AppDefined,
699
0
                 "Only one layer can be created since SINGLE_LAYER=YES "
700
0
                 "creation option has been used");
701
0
        return nullptr;
702
0
    }
703
704
0
    const auto eGType =
705
0
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
706
0
    const OGRSpatialReference *poSRS =
707
0
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
708
709
0
    std::string osCoordRefSys;
710
0
    std::unique_ptr<OGRCoordinateTransformation> poCTToWGS84;
711
0
    std::unique_ptr<OGRSpatialReference> poSRSTmp;  // keep in this scope
712
0
    if (poSRS)
713
0
    {
714
0
        const auto GetURI = [](const char *pszAuthName, const char *pszAuthCode)
715
0
        {
716
0
            std::string osRet = "http://www.opengis.net/def/crs/";
717
0
            if (STARTS_WITH(pszAuthName, "IAU_"))
718
0
            {
719
0
                osRet += "IAU/";
720
0
                osRet += pszAuthName + strlen("IAU_");
721
0
                osRet += '/';
722
0
            }
723
0
            else
724
0
            {
725
0
                osRet += pszAuthName;
726
0
                osRet += "/0/";
727
0
            }
728
0
            osRet += pszAuthCode;
729
0
            return osRet;
730
0
        };
731
732
0
        const auto GetCoordRefSys = [GetURI](const char *pszAuthName,
733
0
                                             const char *pszAuthCode,
734
0
                                             double dfCoordEpoch = 0)
735
0
        {
736
0
            if (dfCoordEpoch > 0)
737
0
            {
738
0
                json_object *poObj = json_object_new_object();
739
0
                json_object_object_add(poObj, "type",
740
0
                                       json_object_new_string("Reference"));
741
0
                json_object_object_add(
742
0
                    poObj, "href",
743
0
                    json_object_new_string(
744
0
                        GetURI(pszAuthName, pszAuthCode).c_str()));
745
0
                json_object_object_add(poObj, "epoch",
746
0
                                       json_object_new_double(dfCoordEpoch));
747
0
                return poObj;
748
0
            }
749
0
            else
750
0
            {
751
0
                return json_object_new_string(
752
0
                    GetURI(pszAuthName, pszAuthCode).c_str());
753
0
            }
754
0
        };
755
756
0
        const double dfCoordEpoch = poSRS->GetCoordinateEpoch();
757
0
        const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
758
0
        if (!pszAuthName)
759
0
        {
760
0
            auto poBestMatch = poSRS->FindBestMatch();
761
0
            if (poBestMatch)
762
0
            {
763
0
                poSRSTmp.reset(poBestMatch);
764
0
                if (dfCoordEpoch > 0)
765
0
                    poSRSTmp->SetCoordinateEpoch(dfCoordEpoch);
766
0
                poSRSTmp->SetDataAxisToSRSAxisMapping(
767
0
                    poSRS->GetDataAxisToSRSAxisMapping());
768
0
                poSRS = poSRSTmp.get();
769
0
                pszAuthName = poSRS->GetAuthorityName(nullptr);
770
0
            }
771
0
        }
772
0
        const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
773
0
        json_object *poObj = nullptr;
774
0
        if (pszAuthName && pszAuthCode)
775
0
        {
776
0
            poObj = GetCoordRefSys(pszAuthName, pszAuthCode, dfCoordEpoch);
777
0
        }
778
0
        else if (poSRS->IsCompound())
779
0
        {
780
0
            const char *pszAuthNameHoriz = poSRS->GetAuthorityName("HORIZCRS");
781
0
            const char *pszAuthCodeHoriz = poSRS->GetAuthorityCode("HORIZCRS");
782
0
            const char *pszAuthNameVert = poSRS->GetAuthorityName("VERTCRS");
783
0
            const char *pszAuthCodeVert = poSRS->GetAuthorityCode("VERTCRS");
784
0
            if (pszAuthNameHoriz && pszAuthCodeHoriz && pszAuthNameVert &&
785
0
                pszAuthCodeVert)
786
0
            {
787
0
                poObj = json_object_new_array();
788
0
                json_object_array_add(poObj, GetCoordRefSys(pszAuthNameHoriz,
789
0
                                                            pszAuthCodeHoriz,
790
0
                                                            dfCoordEpoch));
791
0
                json_object_array_add(
792
0
                    poObj, GetCoordRefSys(pszAuthNameVert, pszAuthCodeVert));
793
0
            }
794
0
        }
795
0
        else
796
0
        {
797
0
            char *pszPROJJSON = nullptr;
798
0
            if (poSRS->exportToPROJJSON(&pszPROJJSON, nullptr) == OGRERR_NONE)
799
0
            {
800
0
                CPLJSONDocument oDoc;
801
0
                if (oDoc.LoadMemory(pszPROJJSON))
802
0
                {
803
0
                    poObj = json_object_new_object();
804
0
                    json_object_object_add(poObj, "type",
805
0
                                           json_object_new_string("PROJJSON"));
806
0
                    auto poPROJJSON = reinterpret_cast<json_object *>(
807
0
                        oDoc.GetRoot().GetInternalHandle());
808
0
                    json_object_get(poPROJJSON);
809
0
                    json_object_object_add(poObj, "value", poPROJJSON);
810
0
                    if (dfCoordEpoch > 0)
811
0
                    {
812
0
                        json_object_object_add(
813
0
                            poObj, "epoch",
814
0
                            json_object_new_double(dfCoordEpoch));
815
0
                    }
816
0
                }
817
0
            }
818
0
            CPLFree(pszPROJJSON);
819
0
        }
820
821
0
        if (poObj)
822
0
        {
823
0
            osCoordRefSys = CPLString(json_object_to_json_string_ext(
824
0
                                          poObj, JSON_C_TO_STRING_SPACED))
825
0
                                .replaceAll("\\/", '/');
826
0
            json_object_put(poObj);
827
0
        }
828
0
        else
829
0
        {
830
0
            CPLError(CE_Failure, CPLE_NotSupported,
831
0
                     "Input CRS %s cannot be expressed as a reference (ie "
832
0
                     "well-known CRS by code). "
833
0
                     "Retry be reprojecting to a known CRS first",
834
0
                     poSRS->GetName());
835
0
            return nullptr;
836
0
        }
837
838
0
        if (!strstr(osCoordRefSys.c_str(),
839
0
                    "http://www.opengis.net/def/crs/IAU/"))
840
0
        {
841
0
            OGRSpatialReference oSRSWGS84;
842
0
            oSRSWGS84.SetWellKnownGeogCS("WGS84");
843
0
            oSRSWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
844
0
            poCTToWGS84.reset(
845
0
                OGRCreateCoordinateTransformation(poSRS, &oSRSWGS84));
846
0
        }
847
0
    }
848
0
    else if (eGType != wkbNone)
849
0
    {
850
0
        if (OGR_GT_HasZ(eGType))
851
0
            osCoordRefSys = "http://www.opengis.net/def/crs/OGC/0/CRS84h";
852
0
        else
853
0
            osCoordRefSys = "http://www.opengis.net/def/crs/OGC/0/CRS84";
854
0
        CPLError(CE_Warning, CPLE_AppDefined,
855
0
                 "No SRS set on layer. Assuming it is long/lat on WGS84 "
856
0
                 "ellipsoid");
857
0
    }
858
859
0
    CPLStringList aosOptions(papszOptions);
860
861
0
    if (const char *pszCoordPrecisionGeom =
862
0
            CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION_GEOMETRY"))
863
0
    {
864
0
        double dfXYResolutionGeometry =
865
0
            std::pow(10.0, -CPLAtof(pszCoordPrecisionGeom));
866
0
        double dfZResolutionGeometry = dfXYResolutionGeometry;
867
0
        aosOptions.SetNameValue("XY_COORD_PRECISION_GEOMETRY",
868
0
                                pszCoordPrecisionGeom);
869
0
        aosOptions.SetNameValue("Z_COORD_PRECISION_GEOMETRY",
870
0
                                pszCoordPrecisionGeom);
871
0
        if (IsSingleOutputLayer())
872
0
        {
873
0
            VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n",
874
0
                        dfXYResolutionGeometry);
875
0
            if (poSRS && poSRS->GetAxesCount() == 3)
876
0
            {
877
0
                VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n",
878
0
                            dfZResolutionGeometry);
879
0
            }
880
0
        }
881
0
    }
882
0
    else if (poSrcGeomFieldDefn &&
883
0
             poSrcGeomFieldDefn->GetCoordinatePrecision().dfXYResolution ==
884
0
                 OGRGeomCoordinatePrecision::UNKNOWN &&
885
0
             CSLFetchNameValue(papszOptions, "SIGNIFICANT_FIGURES") == nullptr)
886
0
    {
887
0
        const int nXYPrecisionGeometry = 7;
888
0
        const int nZPrecisionGeometry = 3;
889
0
        aosOptions.SetNameValue("XY_COORD_PRECISION_GEOMETRY",
890
0
                                CPLSPrintf("%d", nXYPrecisionGeometry));
891
0
        aosOptions.SetNameValue("Z_COORD_PRECISION_GEOMETRY",
892
0
                                CPLSPrintf("%d", nZPrecisionGeometry));
893
0
    }
894
895
0
    double dfXYResolution = OGRGeomCoordinatePrecision::UNKNOWN;
896
0
    double dfZResolution = OGRGeomCoordinatePrecision::UNKNOWN;
897
898
0
    if (const char *pszCoordPrecisionPlace =
899
0
            CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION_PLACE"))
900
0
    {
901
0
        dfXYResolution = std::pow(10.0, -CPLAtof(pszCoordPrecisionPlace));
902
0
        dfZResolution = dfXYResolution;
903
0
        if (IsSingleOutputLayer())
904
0
        {
905
0
            VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution_place\": %g,\n",
906
0
                        dfXYResolution);
907
0
            if (poSRS && poSRS->GetAxesCount() == 3)
908
0
            {
909
0
                VSIFPrintfL(fpOut_, "\"z_coordinate_resolution_place\": %g,\n",
910
0
                            dfZResolution);
911
0
            }
912
0
        }
913
0
    }
914
0
    else if (poSrcGeomFieldDefn &&
915
0
             CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION_PLACE") ==
916
0
                 nullptr &&
917
0
             CSLFetchNameValue(papszOptions, "SIGNIFICANT_FIGURES") == nullptr)
918
0
    {
919
0
        const auto &oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision();
920
0
        OGRSpatialReference oSRSWGS84;
921
0
        oSRSWGS84.SetWellKnownGeogCS("WGS84");
922
0
        const auto oCoordPrecWGS84 =
923
0
            oCoordPrec.ConvertToOtherSRS(poSRS, &oSRSWGS84);
924
925
0
        if (oCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN)
926
0
        {
927
0
            dfXYResolution = oCoordPrec.dfXYResolution;
928
0
            aosOptions.SetNameValue(
929
0
                "XY_COORD_PRECISION_PLACE",
930
0
                CPLSPrintf("%d",
931
0
                           OGRGeomCoordinatePrecision::ResolutionToPrecision(
932
0
                               oCoordPrec.dfXYResolution)));
933
0
            if (IsSingleOutputLayer())
934
0
            {
935
0
                VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution_place\": %g,\n",
936
0
                            oCoordPrec.dfXYResolution);
937
0
            }
938
939
0
            if (CSLFetchNameValue(papszOptions,
940
0
                                  "COORDINATE_PRECISION_GEOMETRY") == nullptr)
941
0
            {
942
0
                const double dfXYResolutionGeometry =
943
0
                    oCoordPrecWGS84.dfXYResolution;
944
945
0
                aosOptions.SetNameValue(
946
0
                    "XY_COORD_PRECISION_GEOMETRY",
947
0
                    CPLSPrintf(
948
0
                        "%d", OGRGeomCoordinatePrecision::ResolutionToPrecision(
949
0
                                  dfXYResolutionGeometry)));
950
0
                if (IsSingleOutputLayer())
951
0
                {
952
0
                    VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n",
953
0
                                dfXYResolutionGeometry);
954
0
                }
955
0
            }
956
0
        }
957
958
0
        if (oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN)
959
0
        {
960
0
            dfZResolution = oCoordPrec.dfZResolution;
961
0
            aosOptions.SetNameValue(
962
0
                "Z_COORD_PRECISION_PLACE",
963
0
                CPLSPrintf("%d",
964
0
                           OGRGeomCoordinatePrecision::ResolutionToPrecision(
965
0
                               dfZResolution)));
966
0
            if (IsSingleOutputLayer())
967
0
            {
968
0
                VSIFPrintfL(fpOut_, "\"z_coordinate_resolution_place\": %g,\n",
969
0
                            dfZResolution);
970
0
            }
971
972
0
            if (CSLFetchNameValue(papszOptions,
973
0
                                  "COORDINATE_PRECISION_GEOMETRY") == nullptr)
974
0
            {
975
0
                const double dfZResolutionGeometry =
976
0
                    oCoordPrecWGS84.dfZResolution;
977
978
0
                aosOptions.SetNameValue(
979
0
                    "Z_COORD_PRECISION_GEOMETRY",
980
0
                    CPLSPrintf(
981
0
                        "%d", OGRGeomCoordinatePrecision::ResolutionToPrecision(
982
0
                                  dfZResolutionGeometry)));
983
0
                if (IsSingleOutputLayer())
984
0
                {
985
0
                    VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n",
986
0
                                dfZResolutionGeometry);
987
0
                }
988
0
            }
989
0
        }
990
0
    }
991
992
0
    auto poLayer = std::make_unique<OGRJSONFGWriteLayer>(
993
0
        pszNameIn, poSRS, std::move(poCTToWGS84), osCoordRefSys, eGType,
994
0
        aosOptions.List(), this);
995
0
    apoLayers_.emplace_back(std::move(poLayer));
996
997
0
    auto poLayerAdded = apoLayers_.back().get();
998
0
    if (eGType != wkbNone &&
999
0
        dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN)
1000
0
    {
1001
0
        auto poGeomFieldDefn =
1002
0
            poLayerAdded->GetLayerDefn()->GetGeomFieldDefn(0);
1003
0
        OGRGeomCoordinatePrecision oCoordPrec(
1004
0
            poGeomFieldDefn->GetCoordinatePrecision());
1005
0
        oCoordPrec.dfXYResolution = dfXYResolution;
1006
0
        poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
1007
0
    }
1008
1009
0
    if (eGType != wkbNone &&
1010
0
        dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN)
1011
0
    {
1012
0
        auto poGeomFieldDefn =
1013
0
            poLayerAdded->GetLayerDefn()->GetGeomFieldDefn(0);
1014
0
        OGRGeomCoordinatePrecision oCoordPrec(
1015
0
            poGeomFieldDefn->GetCoordinatePrecision());
1016
0
        oCoordPrec.dfZResolution = dfZResolution;
1017
0
        poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
1018
0
    }
1019
1020
0
    return poLayerAdded;
1021
0
}
1022
1023
/************************************************************************/
1024
/*                           TestCapability()                           */
1025
/************************************************************************/
1026
1027
int OGRJSONFGDataset::TestCapability(const char *pszCap) const
1028
0
{
1029
0
    if (EQUAL(pszCap, ODsCCreateLayer))
1030
0
        return fpOut_ != nullptr &&
1031
0
               (!bSingleOutputLayer_ || apoLayers_.empty());
1032
0
    else if (EQUAL(pszCap, ODsCZGeometries) ||
1033
0
             EQUAL(pszCap, ODsCMeasuredGeometries) ||
1034
0
             EQUAL(pszCap, ODsCCurveGeometries))
1035
0
        return TRUE;
1036
1037
0
    return FALSE;
1038
0
}
1039
1040
/************************************************************************/
1041
/*                      OGRJSONFGMustSwapXY()                           */
1042
/************************************************************************/
1043
1044
bool OGRJSONFGMustSwapXY(const OGRSpatialReference *poSRS)
1045
3
{
1046
3
    return poSRS->GetDataAxisToSRSAxisMapping() == std::vector<int>{2, 1} ||
1047
3
           poSRS->GetDataAxisToSRSAxisMapping() == std::vector<int>{2, 1, 3};
1048
3
}