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

Generated by: LCOV version 1.14