LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/jsonfg - ogrjsonfgdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 403 478 84.3 %
Date: 2025-10-15 23:46:56 Functions: 20 20 100.0 %

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

Generated by: LCOV version 1.14