LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/jsonfg - ogrjsonfgreader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 674 766 88.0 %
Date: 2025-10-22 13:51:22 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         232 : OGRJSONFGReader::~OGRJSONFGReader()
      30             : {
      31         232 :     if (poObject_)
      32         202 :         json_object_put(poObject_);
      33         232 : }
      34             : 
      35             : /************************************************************************/
      36             : /*                  OGRJSONFGReader::Load()                             */
      37             : /************************************************************************/
      38             : 
      39          76 : bool OGRJSONFGReader::Load(OGRJSONFGDataset *poDS, const char *pszText,
      40             :                            const std::string &osDefaultLayerName)
      41             : {
      42          76 :     if (!OGRJSonParse(pszText, &poObject_))
      43           0 :         return false;
      44             : 
      45          76 :     poDS_ = poDS;
      46          76 :     osDefaultLayerName_ = osDefaultLayerName;
      47             : 
      48          76 :     GeoJSONObject::Type objType = OGRGeoJSONGetType(poObject_);
      49             : 
      50          76 :     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          76 :     if (!GenerateLayerDefns())
      63           0 :         return false;
      64             : 
      65          76 :     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          46 :     else if (objType == GeoJSONObject::eFeatureCollection)
      78             :     {
      79             :         const bool bHasM =
      80          46 :             OGRJSONFGHasMeasure(poObject_, /* bUpperLevelMValue = */ false);
      81             :         json_object *poObjFeatures =
      82          46 :             OGRGeoJSONFindMemberByName(poObject_, "features");
      83          92 :         if (nullptr != poObjFeatures &&
      84          46 :             json_type_array == json_object_get_type(poObjFeatures))
      85             :         {
      86          46 :             const auto nFeatures = json_object_array_length(poObjFeatures);
      87          94 :             for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
      88             :             {
      89             :                 json_object *poObjFeature =
      90          48 :                     json_object_array_get_idx(poObjFeatures, i);
      91          48 :                 OGRJSONFGMemLayer *poLayer = nullptr;
      92             :                 auto poFeat = ReadFeature(poObjFeature, nullptr, bHasM,
      93          48 :                                           &poLayer, nullptr);
      94          48 :                 if (!poFeat)
      95           0 :                     return false;
      96          48 :                 poLayer->AddFeature(std::move(poFeat));
      97             :             }
      98             :         }
      99             :     }
     100             :     else
     101             :     {
     102           0 :         return false;
     103             :     }
     104             : 
     105          46 :     return true;
     106             : }
     107             : 
     108             : /************************************************************************/
     109             : /*                    OGRJSONFGReadCoordRefSys()                        */
     110             : /************************************************************************/
     111             : 
     112             : static std::unique_ptr<OGRSpatialReference>
     113         169 : OGRJSONFGReadCoordRefSys(json_object *poCoordRefSys, bool bCanRecurse = true)
     114             : {
     115         169 :     const auto eType = json_object_get_type(poCoordRefSys);
     116         169 :     if (eType == json_type_string)
     117             :     {
     118         136 :         const char *pszStr = json_object_get_string(poCoordRefSys);
     119         136 :         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          90 :         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         172 :             auto poSRS = std::make_unique<OGRSpatialReference>();
     146          86 :             if (poSRS->importFromCRSURL(pszStr) != OGRERR_NONE)
     147             :             {
     148           1 :                 return nullptr;
     149             :             }
     150          85 :             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          76 : bool OGRJSONFGReader::GenerateLayerDefns()
     357             : {
     358          76 :     const GeoJSONObject::Type objType = OGRGeoJSONGetType(poObject_);
     359          76 :     if (objType == GeoJSONObject::eFeature)
     360             :     {
     361          30 :         if (!GenerateLayerDefnFromFeature(poObject_))
     362           0 :             return false;
     363             :     }
     364          46 :     else if (objType == GeoJSONObject::eFeatureCollection)
     365             :     {
     366             :         json_object *poObjFeatures =
     367          46 :             OGRGeoJSONFindMemberByName(poObject_, "features");
     368          92 :         if (nullptr != poObjFeatures &&
     369          46 :             json_type_array == json_object_get_type(poObjFeatures))
     370             :         {
     371          46 :             const auto nFeatures = json_object_array_length(poObjFeatures);
     372          94 :             for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     373             :             {
     374             :                 json_object *poObjFeature =
     375          48 :                     json_object_array_get_idx(poObjFeatures, i);
     376          48 :                 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          76 :     return FinalizeGenerateLayerDefns(false);
     398             : }
     399             : 
     400             : /************************************************************************/
     401             : /*             OGRJSONFGReader::FinalizeGenerateLayerDefns()            */
     402             : /************************************************************************/
     403             : 
     404         202 : bool OGRJSONFGReader::FinalizeGenerateLayerDefns(bool bStreamedLayer)
     405             : {
     406         202 :     json_object *poName = CPL_json_object_object_get(poObject_, "featureType");
     407         202 :     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         182 :     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         202 :     json_object *poCoordRefSys = nullptr;
     431           0 :     std::unique_ptr<OGRSpatialReference> poSRSTopLevel;
     432         202 :     bool bInvalidCRS = false;
     433         202 :     bool bSwapPlacesXYTopLevel = false;
     434         273 :     if (json_object_object_get_ex(poObject_, "coordRefSys", &poCoordRefSys) &&
     435          71 :         eGeometryElement_ != GeometryElement::GEOMETRY)
     436             :     {
     437          68 :         poSRSTopLevel = OGRJSONFGReadCoordRefSys(poCoordRefSys);
     438          68 :         if (poSRSTopLevel)
     439             :         {
     440          46 :             poSRSTopLevel->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     441          46 :             bSwapPlacesXYTopLevel = OGRJSONFGMustSwapXY(poSRSTopLevel.get());
     442             :         }
     443             :         else
     444             :         {
     445          22 :             bInvalidCRS = true;
     446             :         }
     447             :     }
     448             : 
     449         202 :     json_object *poMeasures = nullptr;
     450         206 :     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         408 :     for (auto &oBuildContextIter : oMapBuildContext_)
     477             :     {
     478         206 :         const char *pszLayerName = oBuildContextIter.first.c_str();
     479         206 :         auto &oBuildContext = oBuildContextIter.second;
     480             : 
     481         206 :         FinalizeBuildContext(oBuildContext, pszLayerName, bStreamedLayer,
     482             :                              bInvalidCRS, bSwapPlacesXYTopLevel,
     483             :                              poSRSTopLevel.get());
     484             :     }
     485             : 
     486         404 :     return true;
     487             : }
     488             : 
     489             : /************************************************************************/
     490             : /*                OGRJSONFGReader::FinalizeBuildContext()               */
     491             : /************************************************************************/
     492             : 
     493         206 : 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         412 :         OGRSpatialReference::GetWGS84SRS()->Clone());
     502         206 :     poSRSWGS84->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     503             : 
     504         206 :     OGRSpatialReference *poSRSLayer = nullptr;
     505         206 :     if (oBuildContext.poCRSAtFeatureLevel)
     506             :     {
     507          64 :         poSRSLayer = oBuildContext.poCRSAtFeatureLevel.get();
     508          64 :         oBuildContext.bSwapPlacesXY = OGRJSONFGMustSwapXY(poSRSLayer);
     509             :     }
     510         142 :     else if (poSRSTopLevel)
     511             :     {
     512          38 :         poSRSLayer = poSRSTopLevel;
     513          38 :         oBuildContext.bSwapPlacesXY = bSwapPlacesXYTopLevel;
     514             :     }
     515         206 :     if (!bInvalidCRS)
     516             :     {
     517         184 :         if (!poSRSLayer && !oBuildContext.bHasCoordRefSysAtFeatureLevel)
     518             :         {
     519             :             // No coordRefSys member found anywhere ? Fallback to WGS 84
     520          81 :             poSRSLayer = poSRSWGS84.get();
     521             :         }
     522             : 
     523         184 :         if (poSRSLayer && poSRSLayer->IsSame(poSRSWGS84.get()))
     524             :         {
     525          89 :             oBuildContext.bLayerCRSIsWGS84 = true;
     526             :         }
     527          95 :         else if (poSRSLayer)
     528             :         {
     529          94 :             const char *pszAuthName = poSRSLayer->GetAuthorityName(nullptr);
     530          94 :             if (!(pszAuthName && STARTS_WITH(pszAuthName, "IAU")))
     531             :             {
     532          94 :                 oBuildContext.poCTWGS84ToLayerCRS.reset(
     533          94 :                     OGRCreateCoordinateTransformation(poSRSWGS84.get(),
     534             :                                                       poSRSLayer));
     535             :             }
     536             :         }
     537             :     }
     538             : 
     539         206 :     std::unique_ptr<OGRJSONFGMemLayer> poMemLayer;
     540         206 :     std::unique_ptr<OGRJSONFGStreamedLayer> poStreamedLayer;
     541             :     OGRLayer *poLayer;
     542         206 :     if (bStreamedLayer)
     543             :     {
     544         130 :         poStreamedLayer = std::make_unique<OGRJSONFGStreamedLayer>(
     545         130 :             poDS_, pszLayerName, poSRSLayer, oBuildContext.eLayerGeomType);
     546         130 :         poLayer = poStreamedLayer.get();
     547             :     }
     548             :     else
     549             :     {
     550          76 :         poMemLayer = std::make_unique<OGRJSONFGMemLayer>(
     551          76 :             poDS_, pszLayerName, poSRSLayer, oBuildContext.eLayerGeomType);
     552          76 :         poLayer = poMemLayer.get();
     553             :     }
     554             : 
     555             :     // Note: the current strategy will not produce stable output, depending
     556             :     // on the order of features, if there are conflicting order / cycles.
     557             :     // See https://github.com/OSGeo/gdal/pull/4552 for a number of potential
     558             :     // resolutions if that has to be solved in the future.
     559         206 :     OGRFeatureDefn *poLayerDefn = poLayer->GetLayerDefn();
     560         412 :     auto oTemporaryUnsealer(poLayerDefn->GetTemporaryUnsealer());
     561             : 
     562         206 :     if (poLayer->GetLayerDefn()->GetGeomType() != wkbNone)
     563             :     {
     564         412 :         OGRGeoJSONWriteOptions options;
     565             : 
     566         206 :         json_object *poXYRes = CPL_json_object_object_get(
     567             :             poObject_, "xy_coordinate_resolution_place");
     568         206 :         if (poXYRes && (json_object_get_type(poXYRes) == json_type_double ||
     569           0 :                         json_object_get_type(poXYRes) == json_type_int))
     570             :         {
     571           2 :             auto poGeomFieldDefn = poLayerDefn->GetGeomFieldDefn(0);
     572             :             OGRGeomCoordinatePrecision oCoordPrec(
     573           4 :                 poGeomFieldDefn->GetCoordinatePrecision());
     574           2 :             oCoordPrec.dfXYResolution = json_object_get_double(poXYRes);
     575           2 :             poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
     576             :         }
     577             : 
     578         206 :         json_object *poZRes = CPL_json_object_object_get(
     579             :             poObject_, "z_coordinate_resolution_place");
     580         206 :         if (poZRes && (json_object_get_type(poZRes) == json_type_double ||
     581           0 :                        json_object_get_type(poZRes) == json_type_int))
     582             :         {
     583           2 :             auto poGeomFieldDefn = poLayerDefn->GetGeomFieldDefn(0);
     584             :             OGRGeomCoordinatePrecision oCoordPrec(
     585           4 :                 poGeomFieldDefn->GetCoordinatePrecision());
     586           2 :             oCoordPrec.dfZResolution = json_object_get_double(poZRes);
     587           2 :             poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
     588             :         }
     589             :     }
     590             : 
     591         412 :     std::set<std::string> oSetFieldNames;
     592         329 :     for (const auto &poFieldDefn : oBuildContext.apoFieldDefn)
     593         123 :         oSetFieldNames.insert(poFieldDefn->GetNameRef());
     594             : 
     595             :     auto AddTimeField =
     596          88 :         [poLayerDefn, &oSetFieldNames](const char *pszName, OGRFieldType eType)
     597             :     {
     598          22 :         if (oSetFieldNames.find(pszName) == oSetFieldNames.end())
     599             :         {
     600          42 :             OGRFieldDefn oFieldDefn(pszName, eType);
     601          21 :             poLayerDefn->AddFieldDefn(&oFieldDefn);
     602             :         }
     603             :         else
     604             :         {
     605           2 :             OGRFieldDefn oFieldDefn((std::string("jsonfg_") + pszName).c_str(),
     606           2 :                                     eType);
     607           1 :             poLayerDefn->AddFieldDefn(&oFieldDefn);
     608             :         }
     609          22 :         return poLayerDefn->GetFieldCount() - 1;
     610         206 :     };
     611             : 
     612         206 :     if (oBuildContext.bHasTimeTimestamp)
     613             :     {
     614           2 :         oBuildContext.nIdxFieldTime = AddTimeField("time", OFTDateTime);
     615             :     }
     616         204 :     else if (oBuildContext.bHasTimeDate)
     617             :     {
     618           2 :         oBuildContext.nIdxFieldTime = AddTimeField("time", OFTDate);
     619             :     }
     620             : 
     621         206 :     if (oBuildContext.bHasTimeIntervalStartDate ||
     622         203 :         oBuildContext.bHasTimeIntervalStartTimestamp ||
     623         199 :         oBuildContext.bHasTimeIntervalEndDate ||
     624         198 :         oBuildContext.bHasTimeIntervalEndTimestamp)
     625             :     {
     626             :         // Mix of Date/DateTime for start/end is not supposed to happen,
     627             :         // but be tolerant to that
     628           9 :         if (oBuildContext.bHasTimeIntervalStartTimestamp)
     629             :         {
     630           5 :             oBuildContext.nIdxFieldTimeStart =
     631           5 :                 AddTimeField("time_start", OFTDateTime);
     632             :         }
     633           4 :         else if (oBuildContext.bHasTimeIntervalStartDate)
     634             :         {
     635           2 :             oBuildContext.nIdxFieldTimeStart =
     636           2 :                 AddTimeField("time_start", OFTDate);
     637             :         }
     638           2 :         else if (oBuildContext.bHasTimeIntervalEndTimestamp)
     639             :         {
     640           1 :             oBuildContext.nIdxFieldTimeStart =
     641           1 :                 AddTimeField("time_start", OFTDateTime);
     642             :         }
     643             :         else /* if( oBuildContext.bHasTimeIntervalEndDate ) */
     644             :         {
     645           1 :             oBuildContext.nIdxFieldTimeStart =
     646           1 :                 AddTimeField("time_start", OFTDate);
     647             :         }
     648             : 
     649           9 :         if (oBuildContext.bHasTimeIntervalEndTimestamp)
     650             :         {
     651           3 :             oBuildContext.nIdxFieldTimeEnd =
     652           3 :                 AddTimeField("time_end", OFTDateTime);
     653             :         }
     654           6 :         else if (oBuildContext.bHasTimeIntervalEndDate)
     655             :         {
     656           2 :             oBuildContext.nIdxFieldTimeEnd = AddTimeField("time_end", OFTDate);
     657             :         }
     658           4 :         else if (oBuildContext.bHasTimeIntervalStartTimestamp)
     659             :         {
     660           3 :             oBuildContext.nIdxFieldTimeEnd =
     661           3 :                 AddTimeField("time_end", OFTDateTime);
     662             :         }
     663             :         else /* if( oBuildContext.bHasTimeIntervalStartDate ) */
     664             :         {
     665           1 :             oBuildContext.nIdxFieldTimeEnd = AddTimeField("time_end", OFTDate);
     666             :         }
     667             :     }
     668             : 
     669         412 :     const auto sortedFields = oBuildContext.dag.getTopologicalOrdering();
     670         206 :     CPLAssert(sortedFields.size() == oBuildContext.apoFieldDefn.size());
     671         329 :     for (int idx : sortedFields)
     672             :     {
     673         123 :         poLayerDefn->AddFieldDefn(oBuildContext.apoFieldDefn[idx].get());
     674             :     }
     675             : 
     676         206 :     if (!oBuildContext.bFeatureLevelIdAsFID)
     677             :     {
     678         174 :         const int idx = poLayerDefn->GetFieldIndexCaseSensitive("id");
     679         174 :         if (idx >= 0)
     680             :         {
     681           2 :             OGRFieldDefn *poFDefn = poLayerDefn->GetFieldDefn(idx);
     682           4 :             if (poFDefn->GetType() == OFTInteger ||
     683           2 :                 poFDefn->GetType() == OFTInteger64)
     684             :             {
     685           0 :                 if (poStreamedLayer)
     686             :                 {
     687           0 :                     poStreamedLayer->SetFIDColumn(
     688           0 :                         poLayerDefn->GetFieldDefn(idx)->GetNameRef());
     689             :                 }
     690             :                 else
     691             :                 {
     692           0 :                     poMemLayer->SetFIDColumn(
     693           0 :                         poLayerDefn->GetFieldDefn(idx)->GetNameRef());
     694             :                 }
     695             :             }
     696             :         }
     697             :     }
     698             : 
     699         206 :     if (oBuildContext.bNeedFID64)
     700           0 :         poLayer->SetMetadataItem(OLMD_FID64, "YES");
     701             : 
     702         412 :     if (oBuildContext.bSameMeasureMetadata &&
     703         206 :         (!oBuildContext.osMeasureUnit.empty() ||
     704         204 :          !oBuildContext.osMeasureDescription.empty()))
     705             :     {
     706           2 :         if (!oBuildContext.osMeasureUnit.empty())
     707             :         {
     708           2 :             poLayer->SetMetadataItem(
     709           2 :                 "UNIT", oBuildContext.osMeasureUnit.c_str(), "MEASURES");
     710             :         }
     711             : 
     712           2 :         if (!oBuildContext.osMeasureDescription.empty())
     713             :         {
     714           2 :             poLayer->SetMetadataItem("DESCRIPTION",
     715             :                                      oBuildContext.osMeasureDescription.c_str(),
     716           2 :                                      "MEASURES");
     717             :         }
     718             :     }
     719             :     else
     720             :     {
     721         204 :         if (!osMeasureUnit_.empty())
     722             :         {
     723           4 :             poLayer->SetMetadataItem("UNIT", osMeasureUnit_.c_str(),
     724           4 :                                      "MEASURES");
     725             :         }
     726             : 
     727         204 :         if (!osMeasureDescription_.empty())
     728             :         {
     729           4 :             poLayer->SetMetadataItem("DESCRIPTION",
     730           4 :                                      osMeasureDescription_.c_str(), "MEASURES");
     731             :         }
     732             :     }
     733             : 
     734         206 :     if (poStreamedLayer)
     735             :     {
     736         130 :         poStreamedLayer->SetFeatureCount(oBuildContext.nFeatureCount);
     737         130 :         oBuildContext.poStreamedLayer =
     738         130 :             poDS_->AddLayer(std::move(poStreamedLayer));
     739             :     }
     740             :     else
     741             :     {
     742          76 :         oBuildContext.poMemLayer = poDS_->AddLayer(std::move(poMemLayer));
     743             :     }
     744         206 : }
     745             : 
     746             : /************************************************************************/
     747             : /*            OGRJSONFGReader::GetLayerNameForFeature()                 */
     748             : /************************************************************************/
     749             : 
     750         950 : const char *OGRJSONFGReader::GetLayerNameForFeature(json_object *poObj) const
     751             : {
     752         950 :     const char *pszName = osDefaultLayerName_.c_str();
     753         950 :     json_object *poName = CPL_json_object_object_get(poObj, "featureType");
     754             :     // The spec allows an array of strings, but we don't support that
     755         950 :     if (poName != nullptr && json_object_get_type(poName) == json_type_string)
     756             :     {
     757         197 :         pszName = json_object_get_string(poName);
     758             :     }
     759         950 :     return pszName;
     760             : }
     761             : 
     762             : /************************************************************************/
     763             : /*                     OGRJSONFGGetOGRGeometryType()                    */
     764             : /************************************************************************/
     765             : 
     766         179 : static OGRwkbGeometryType OGRJSONFGGetOGRGeometryType(json_object *poObj,
     767             :                                                       bool bHasM)
     768             : {
     769         179 :     const auto eType = OGRGeoJSONGetOGRGeometryType(poObj, bHasM);
     770         179 :     if (eType != wkbUnknown)
     771         173 :         return eType;
     772             : 
     773           6 :     json_object *poObjType = CPL_json_object_object_get(poObj, "type");
     774           6 :     const char *pszType = json_object_get_string(poObjType);
     775           6 :     if (!pszType)
     776           0 :         return wkbNone;
     777             : 
     778           6 :     if (strcmp(pszType, "Polyhedron") == 0)
     779             :     {
     780           3 :         auto eRetType = wkbPolyhedralSurfaceZ;
     781             : 
     782           3 :         bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
     783             : 
     784           3 :         if (bHasM)
     785           0 :             eRetType = OGR_GT_SetM(eRetType);
     786           3 :         return eRetType;
     787             :     }
     788           3 :     else if (strcmp(pszType, "Prism") == 0)
     789             :     {
     790           3 :         auto poBase = CPL_json_object_object_get(poObj, "base");
     791           3 :         if (!poBase || json_object_get_type(poBase) != json_type_object)
     792             :         {
     793           0 :             return wkbNone;
     794             :         }
     795             : 
     796           3 :         bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
     797             : 
     798           3 :         const auto eBaseGeomType = OGRGeoJSONGetOGRGeometryType(poBase, bHasM);
     799           3 :         auto eRetType = wkbNone;
     800           3 :         if (eBaseGeomType == wkbPoint)
     801             :         {
     802           1 :             eRetType = wkbLineString25D;
     803             :         }
     804           2 :         else if (eBaseGeomType == wkbLineString)
     805             :         {
     806           1 :             eRetType = wkbMultiPolygon25D;
     807             :         }
     808           1 :         else if (eBaseGeomType == wkbPolygon)
     809             :         {
     810           1 :             eRetType = wkbPolyhedralSurfaceZ;
     811             :         }
     812           3 :         if (eRetType != wkbNone)
     813             :         {
     814           3 :             if (bHasM)
     815           0 :                 eRetType = OGR_GT_SetM(eRetType);
     816           3 :             return eRetType;
     817             :         }
     818             :     }
     819           0 :     return wkbNone;
     820             : }
     821             : 
     822             : /************************************************************************/
     823             : /*                   OGRJSONFGCreateNonGeoJSONGeometry()                */
     824             : /************************************************************************/
     825             : 
     826             : static std::unique_ptr<OGRGeometry>
     827           6 : OGRJSONFGCreateNonGeoJSONGeometry(json_object *poObj, bool bHasM, bool bWarn)
     828             : {
     829           6 :     json_object *poObjType = CPL_json_object_object_get(poObj, "type");
     830           6 :     const char *pszType = json_object_get_string(poObjType);
     831           6 :     if (!pszType)
     832           0 :         return nullptr;
     833             : 
     834           6 :     if (strcmp(pszType, "Polyhedron") == 0)
     835             :     {
     836           3 :         auto poCoordinates = CPL_json_object_object_get(poObj, "coordinates");
     837           6 :         if (!poCoordinates ||
     838           3 :             json_object_get_type(poCoordinates) != json_type_array)
     839             :         {
     840           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     841             :                      "Missing or invalid coordinates in Polyhedron");
     842           0 :             return nullptr;
     843             :         }
     844           3 :         if (json_object_array_length(poCoordinates) != 1)
     845             :         {
     846           0 :             if (bWarn)
     847             :             {
     848           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     849             :                          "Polyhedron with inner shells not supported");
     850             :             }
     851           0 :             return nullptr;
     852             :         }
     853             : 
     854           3 :         bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
     855             : 
     856           3 :         auto poJOuterShell = json_object_array_get_idx(poCoordinates, 0);
     857           6 :         auto poGeom = std::make_unique<OGRPolyhedralSurface>();
     858           3 :         const auto nPolys = json_object_array_length(poJOuterShell);
     859           8 :         for (auto i = decltype(nPolys){0}; i < nPolys; ++i)
     860             :         {
     861           5 :             auto poJPoly = json_object_array_get_idx(poJOuterShell, i);
     862           5 :             if (!poJPoly)
     863           0 :                 return nullptr;
     864             :             auto poPoly =
     865           5 :                 OGRGeoJSONReadPolygon(poJPoly, bHasM, /*bRaw = */ true);
     866           5 :             if (!poPoly)
     867           0 :                 return nullptr;
     868           5 :             if (poGeom->addGeometry(std::move(poPoly)) != OGRERR_NONE)
     869           0 :                 return nullptr;
     870             :         }
     871           3 :         if (nPolys == 0)
     872           1 :             poGeom->set3D(true);
     873             : 
     874           3 :         return poGeom;
     875             :     }
     876           3 :     else if (strcmp(pszType, "Prism") == 0)
     877             :     {
     878           3 :         auto poBase = CPL_json_object_object_get(poObj, "base");
     879           3 :         if (!poBase || json_object_get_type(poBase) != json_type_object)
     880             :         {
     881           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     882             :                      "Missing or invalid base in Prism");
     883           0 :             return nullptr;
     884             :         }
     885             : 
     886           3 :         bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
     887             : 
     888           3 :         json_object *poLower = CPL_json_object_object_get(poObj, "lower");
     889           3 :         const double dfLower = poLower ? json_object_get_double(poLower) : 0.0;
     890           3 :         json_object *poUpper = CPL_json_object_object_get(poObj, "upper");
     891           3 :         const double dfUpper = poUpper ? json_object_get_double(poUpper) : 0.0;
     892             : 
     893             :         auto poBaseGeom = std::unique_ptr<OGRGeometry>(OGRGeoJSONReadGeometry(
     894           6 :             poBase, bHasM, /* OGRSpatialReference* = */ nullptr));
     895           3 :         if (!poBaseGeom)
     896           0 :             return nullptr;
     897           3 :         const auto eBaseGeomType = poBaseGeom->getGeometryType();
     898           3 :         if (eBaseGeomType == wkbPoint)
     899             :         {
     900           1 :             const auto poPoint = poBaseGeom.get()->toPoint();
     901           2 :             auto poGeom = std::make_unique<OGRLineString>();
     902           1 :             if (bHasM)
     903             :             {
     904           0 :                 poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfLower,
     905             :                                  poPoint->getM());
     906           0 :                 poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfUpper,
     907             :                                  poPoint->getM());
     908             :             }
     909             :             else
     910             :             {
     911           1 :                 poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfLower);
     912           1 :                 poGeom->addPoint(poPoint->getX(), poPoint->getY(), dfUpper);
     913             :             }
     914           1 :             return poGeom;
     915             :         }
     916           2 :         else if (eBaseGeomType == wkbLineString)
     917             :         {
     918           1 :             const auto poLS = poBaseGeom.get()->toLineString();
     919           2 :             auto poGeom = std::make_unique<OGRMultiPolygon>();
     920           2 :             for (int i = 0; i < poLS->getNumPoints() - 1; ++i)
     921             :             {
     922           1 :                 auto poPoly = new OGRPolygon();
     923           1 :                 auto poRing = new OGRLinearRing();
     924           1 :                 if (bHasM)
     925             :                 {
     926           0 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
     927             :                                      poLS->getM(i));
     928           0 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
     929             :                                      dfLower, poLS->getM(i + 1));
     930           0 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
     931             :                                      dfUpper, poLS->getM(i + 1));
     932           0 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper,
     933             :                                      poLS->getM(i));
     934           0 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
     935             :                                      poLS->getM(i));
     936             :                 }
     937             :                 else
     938             :                 {
     939           1 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
     940           1 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
     941             :                                      dfLower);
     942           1 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
     943             :                                      dfUpper);
     944           1 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper);
     945           1 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
     946             :                 }
     947           1 :                 poPoly->addRingDirectly(poRing);
     948           1 :                 poGeom->addGeometryDirectly(poPoly);
     949             :             }
     950           1 :             return poGeom;
     951             :         }
     952           1 :         else if (eBaseGeomType == wkbPolygon)
     953             :         {
     954           1 :             const auto poBasePoly = poBaseGeom.get()->toPolygon();
     955           1 :             if (poBasePoly->getNumInteriorRings() > 0)
     956             :             {
     957           0 :                 if (bWarn)
     958             :                 {
     959           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     960             :                              "Polygon with holes is not supported as the base "
     961             :                              "for Prism");
     962             :                 }
     963           0 :                 return nullptr;
     964             :             }
     965           1 :             const auto poLS = poBasePoly->getExteriorRing();
     966           1 :             if (poLS == nullptr)
     967             :             {
     968           0 :                 return nullptr;
     969             :             }
     970           2 :             auto poGeom = std::make_unique<OGRPolyhedralSurface>();
     971             :             // Build lower face
     972             :             {
     973           1 :                 auto poPoly = new OGRPolygon();
     974           1 :                 auto poRing = new OGRLinearRing();
     975           5 :                 for (int i = 0; i < poLS->getNumPoints(); ++i)
     976             :                 {
     977           4 :                     if (bHasM)
     978           0 :                         poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
     979             :                                          poLS->getM(i));
     980             :                     else
     981           4 :                         poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
     982             :                 }
     983           1 :                 poPoly->addRingDirectly(poRing);
     984           1 :                 poGeom->addGeometryDirectly(poPoly);
     985             :             }
     986             :             // Build side faces
     987           4 :             for (int i = 0; i < poLS->getNumPoints() - 1; ++i)
     988             :             {
     989           3 :                 auto poPoly = new OGRPolygon();
     990           3 :                 auto poRing = new OGRLinearRing();
     991           3 :                 if (bHasM)
     992             :                 {
     993           0 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
     994             :                                      poLS->getM(i));
     995           0 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
     996             :                                      dfLower, poLS->getM(i + 1));
     997           0 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
     998             :                                      dfUpper, poLS->getM(i + 1));
     999           0 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper,
    1000             :                                      poLS->getM(i));
    1001           0 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower,
    1002             :                                      poLS->getM(i));
    1003             :                 }
    1004             :                 else
    1005             :                 {
    1006           3 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
    1007           3 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
    1008             :                                      dfLower);
    1009           3 :                     poRing->addPoint(poLS->getX(i + 1), poLS->getY(i + 1),
    1010             :                                      dfUpper);
    1011           3 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper);
    1012           3 :                     poRing->addPoint(poLS->getX(i), poLS->getY(i), dfLower);
    1013             :                 }
    1014           3 :                 poPoly->addRingDirectly(poRing);
    1015           3 :                 poGeom->addGeometryDirectly(poPoly);
    1016             :             }
    1017             :             // Build upper face
    1018             :             {
    1019           1 :                 auto poPoly = new OGRPolygon();
    1020           1 :                 auto poRing = new OGRLinearRing();
    1021           5 :                 for (int i = 0; i < poLS->getNumPoints(); ++i)
    1022             :                 {
    1023           4 :                     if (bHasM)
    1024           0 :                         poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper,
    1025             :                                          poLS->getM(i));
    1026             :                     else
    1027           4 :                         poRing->addPoint(poLS->getX(i), poLS->getY(i), dfUpper);
    1028             :                 }
    1029           1 :                 poPoly->addRingDirectly(poRing);
    1030           1 :                 poGeom->addGeometryDirectly(poPoly);
    1031             :             }
    1032           1 :             return poGeom;
    1033             :         }
    1034             :         else
    1035             :         {
    1036           0 :             if (bWarn)
    1037             :             {
    1038           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1039             :                          "Unsupported base geometry type for Prism");
    1040             :             }
    1041           0 :             return nullptr;
    1042             :         }
    1043             :     }
    1044             :     else
    1045             :     {
    1046           0 :         if (bWarn)
    1047             :         {
    1048           0 :             CPLError(CE_Warning, CPLE_AppDefined, "Unhandled place.type = %s",
    1049             :                      pszType);
    1050             :         }
    1051           0 :         return nullptr;
    1052             :     }
    1053             : }
    1054             : 
    1055             : /************************************************************************/
    1056             : /*            OGRJSONFGReader::GenerateLayerDefnFromFeature()           */
    1057             : /************************************************************************/
    1058             : 
    1059         279 : bool OGRJSONFGReader::GenerateLayerDefnFromFeature(json_object *poObj)
    1060             : {
    1061         279 :     const GeoJSONObject::Type objType = OGRGeoJSONGetType(poObj);
    1062         279 :     if (objType != GeoJSONObject::eFeature)
    1063             :     {
    1064           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Did not get a Feature");
    1065           0 :         return false;
    1066             :     }
    1067             : 
    1068             :     const bool bHasM =
    1069         279 :         OGRJSONFGHasMeasure(poObj, /* bUpperLevelMValue = */ false);
    1070             : 
    1071         279 :     const char *psLayerName = GetLayerNameForFeature(poObj);
    1072             : 
    1073         279 :     auto oBuildContextIter = oMapBuildContext_.find(psLayerName);
    1074         279 :     if (oBuildContextIter == oMapBuildContext_.end())
    1075             :     {
    1076         206 :         LayerDefnBuildContext oContext;
    1077         206 :         oMapBuildContext_[psLayerName] = std::move(oContext);
    1078         206 :         oBuildContextIter = oMapBuildContext_.find(psLayerName);
    1079             :     }
    1080         279 :     LayerDefnBuildContext *poContext = &(oBuildContextIter->second);
    1081             : 
    1082         279 :     ++poContext->nFeatureCount;
    1083             : 
    1084         279 :     json_object *poCoordRefSys = nullptr;
    1085         279 :     json_object *poPlace = nullptr;
    1086         279 :     if (eGeometryElement_ != GeometryElement::GEOMETRY)
    1087             :     {
    1088         276 :         poPlace = CPL_json_object_object_get(poObj, "place");
    1089         276 :         if (poPlace && json_object_get_type(poPlace) == json_type_object)
    1090             :         {
    1091         179 :             poCoordRefSys = CPL_json_object_object_get(poPlace, "coordRefSys");
    1092             :         }
    1093         276 :         if (!poCoordRefSys)
    1094         250 :             poCoordRefSys = CPL_json_object_object_get(poObj, "coordRefSys");
    1095             : 
    1096         276 :         if (poCoordRefSys)
    1097             :         {
    1098         140 :             std::string osVal = json_object_to_json_string(poCoordRefSys);
    1099          70 :             if (!poContext->bHasCoordRefSysAtFeatureLevel)
    1100             :             {
    1101          66 :                 poContext->bHasCoordRefSysAtFeatureLevel = true;
    1102          66 :                 poContext->osCoordRefSysAtFeatureLevel = std::move(osVal);
    1103             :                 poContext->poCRSAtFeatureLevel =
    1104          66 :                     OGRJSONFGReadCoordRefSys(poCoordRefSys);
    1105          66 :                 if (poContext->poCRSAtFeatureLevel)
    1106             :                 {
    1107          66 :                     poContext->poCRSAtFeatureLevel->SetAxisMappingStrategy(
    1108             :                         OAMS_TRADITIONAL_GIS_ORDER);
    1109             :                 }
    1110             :             }
    1111           4 :             else if (poContext->osCoordRefSysAtFeatureLevel != osVal)
    1112             :             {
    1113           2 :                 poContext->osCoordRefSysAtFeatureLevel.clear();
    1114           2 :                 poContext->poCRSAtFeatureLevel.reset();
    1115             :             }
    1116             :         }
    1117             :     }
    1118             : 
    1119         279 :     if (poContext->bSameMeasureMetadata)
    1120             :     {
    1121         279 :         json_object *poMeasures = nullptr;
    1122         294 :         if (json_object_object_get_ex(poObj, "measures", &poMeasures) &&
    1123          15 :             json_object_get_type(poMeasures) == json_type_object)
    1124             :         {
    1125          15 :             json_object *poEnabled = nullptr;
    1126          15 :             if (json_object_object_get_ex(poMeasures, "enabled", &poEnabled) &&
    1127          30 :                 json_object_get_type(poEnabled) == json_type_boolean &&
    1128          15 :                 json_object_get_boolean(poEnabled))
    1129             :             {
    1130          15 :                 json_object *poUnit = nullptr;
    1131          17 :                 if (json_object_object_get_ex(poMeasures, "unit", &poUnit) &&
    1132           2 :                     json_object_get_type(poUnit) == json_type_string)
    1133             :                 {
    1134           2 :                     if (poContext->osMeasureUnit.empty())
    1135             :                         poContext->osMeasureUnit =
    1136           2 :                             json_object_get_string(poUnit);
    1137           0 :                     else if (poContext->osMeasureUnit !=
    1138             :                              json_object_get_string(poUnit))
    1139           0 :                         poContext->bSameMeasureMetadata = false;
    1140             :                 }
    1141             : 
    1142          15 :                 json_object *poDescription = nullptr;
    1143          15 :                 if (json_object_object_get_ex(poMeasures, "description",
    1144          17 :                                               &poDescription) &&
    1145           2 :                     json_object_get_type(poDescription) == json_type_string)
    1146             :                 {
    1147           2 :                     if (poContext->osMeasureDescription.empty())
    1148             :                         poContext->osMeasureDescription =
    1149           2 :                             json_object_get_string(poDescription);
    1150           0 :                     else if (poContext->osMeasureDescription !=
    1151             :                              json_object_get_string(poDescription))
    1152           0 :                         poContext->bSameMeasureMetadata = false;
    1153             :                 }
    1154             :             }
    1155             :         }
    1156             :     }
    1157             : 
    1158             :     /* -------------------------------------------------------------------- */
    1159             :     /*      Deal with place / geometry                                      */
    1160             :     /* -------------------------------------------------------------------- */
    1161             : 
    1162         279 :     if (poContext->bDetectLayerGeomType)
    1163             :     {
    1164         279 :         bool bFallbackToGeometry =
    1165         279 :             (eGeometryElement_ != GeometryElement::PLACE);
    1166         279 :         if (poPlace && json_object_get_type(poPlace) == json_type_object)
    1167             :         {
    1168         179 :             const auto eType = OGRJSONFGGetOGRGeometryType(poPlace, bHasM);
    1169         179 :             if (eType != wkbNone)
    1170             :             {
    1171         179 :                 bFallbackToGeometry = false;
    1172         179 :                 poContext->bDetectLayerGeomType = OGRGeoJSONUpdateLayerGeomType(
    1173         179 :                     poContext->bFirstGeometry, eType,
    1174         179 :                     poContext->eLayerGeomType);
    1175             :             }
    1176             :         }
    1177             : 
    1178         279 :         if (bFallbackToGeometry)
    1179             :         {
    1180             :             json_object *poGeomObj =
    1181          99 :                 CPL_json_object_object_get(poObj, "geometry");
    1182         115 :             if (poGeomObj &&
    1183          16 :                 json_object_get_type(poGeomObj) == json_type_object)
    1184             :             {
    1185             :                 const auto eType =
    1186          16 :                     OGRGeoJSONGetOGRGeometryType(poGeomObj, bHasM);
    1187          16 :                 poContext->bDetectLayerGeomType = OGRGeoJSONUpdateLayerGeomType(
    1188          16 :                     poContext->bFirstGeometry, eType,
    1189          16 :                     poContext->eLayerGeomType);
    1190             :             }
    1191             :         }
    1192             :     }
    1193             : 
    1194             :     /* -------------------------------------------------------------------- */
    1195             :     /*      Deal with time                                                  */
    1196             :     /* -------------------------------------------------------------------- */
    1197         279 :     json_object *poTime = CPL_json_object_object_get(poObj, "time");
    1198         279 :     if (poTime)
    1199             :     {
    1200          15 :         json_object *poDate = CPL_json_object_object_get(poTime, "date");
    1201          15 :         if (poDate && json_object_get_type(poDate) == json_type_string)
    1202           3 :             poContext->bHasTimeDate = true;
    1203             : 
    1204             :         json_object *poTimestamp =
    1205          15 :             CPL_json_object_object_get(poTime, "timestamp");
    1206          17 :         if (poTimestamp &&
    1207           2 :             json_object_get_type(poTimestamp) == json_type_string)
    1208           2 :             poContext->bHasTimeTimestamp = true;
    1209             : 
    1210             :         json_object *poInterval =
    1211          15 :             CPL_json_object_object_get(poTime, "interval");
    1212          25 :         if (poInterval && json_object_get_type(poInterval) == json_type_array &&
    1213          10 :             json_object_array_length(poInterval) == 2)
    1214             :         {
    1215          10 :             json_object *poStart = json_object_array_get_idx(poInterval, 0);
    1216          10 :             if (poStart && json_object_get_type(poStart) == json_type_string)
    1217             :             {
    1218          10 :                 const char *pszStart = json_object_get_string(poStart);
    1219          10 :                 if (strchr(pszStart, 'Z'))
    1220           5 :                     poContext->bHasTimeIntervalStartTimestamp = true;
    1221           5 :                 else if (strcmp(pszStart, "..") != 0)
    1222           3 :                     poContext->bHasTimeIntervalStartDate = true;
    1223             :             }
    1224             : 
    1225          10 :             json_object *poEnd = json_object_array_get_idx(poInterval, 1);
    1226          10 :             if (poEnd && json_object_get_type(poEnd) == json_type_string)
    1227             :             {
    1228          10 :                 const char *pszEnd = json_object_get_string(poEnd);
    1229          10 :                 if (strchr(pszEnd, 'Z'))
    1230           3 :                     poContext->bHasTimeIntervalEndTimestamp = true;
    1231           7 :                 else if (strcmp(pszEnd, "..") != 0)
    1232           3 :                     poContext->bHasTimeIntervalEndDate = true;
    1233             :             }
    1234             :         }
    1235             :     }
    1236             : 
    1237             :     /* -------------------------------------------------------------------- */
    1238             :     /*      Read collection of properties.                                  */
    1239             :     /* -------------------------------------------------------------------- */
    1240         279 :     json_object *poObjProps = CPL_json_object_object_get(poObj, "properties");
    1241             : 
    1242         279 :     int nPrevFieldIdx = -1;
    1243             : 
    1244             :     // First deal with id, either at top level or in properties["id"]
    1245         279 :     OGRGeoJSONGenerateFeatureDefnDealWithID(
    1246         279 :         poObj, poObjProps, nPrevFieldIdx, poContext->oMapFieldNameToIdx,
    1247         279 :         poContext->apoFieldDefn, poContext->dag,
    1248         279 :         poContext->bFeatureLevelIdAsFID, poContext->bFeatureLevelIdAsAttribute,
    1249         279 :         poContext->bNeedFID64);
    1250             : 
    1251         530 :     if (nullptr != poObjProps &&
    1252         251 :         json_object_get_type(poObjProps) == json_type_object)
    1253             :     {
    1254             :         json_object_iter it;
    1255         251 :         it.key = nullptr;
    1256         251 :         it.val = nullptr;
    1257         251 :         it.entry = nullptr;
    1258         502 :         std::vector<int> anCurFieldIndices;
    1259         589 :         json_object_object_foreachC(poObjProps, it)
    1260             :         {
    1261         338 :             anCurFieldIndices.clear();
    1262         338 :             OGRGeoJSONReaderAddOrUpdateField(
    1263         338 :                 anCurFieldIndices, poContext->oMapFieldNameToIdx,
    1264         338 :                 poContext->apoFieldDefn, it.key, it.val,
    1265         338 :                 bFlattenNestedAttributes_, chNestedAttributeSeparator_,
    1266         338 :                 bArrayAsString_, bDateAsString_,
    1267         338 :                 poContext->aoSetUndeterminedTypeFields);
    1268         676 :             for (int idx : anCurFieldIndices)
    1269             :             {
    1270         676 :                 poContext->dag.addNode(
    1271         338 :                     idx, poContext->apoFieldDefn[idx]->GetNameRef());
    1272         338 :                 if (nPrevFieldIdx != -1)
    1273             :                 {
    1274         241 :                     poContext->dag.addEdge(nPrevFieldIdx, idx);
    1275             :                 }
    1276         338 :                 nPrevFieldIdx = idx;
    1277             :             }
    1278             :         }
    1279             :     }
    1280             : 
    1281         279 :     return true;
    1282             : }
    1283             : 
    1284             : /************************************************************************/
    1285             : /*                  OGRJSONFGReader::ReadFeature()                      */
    1286             : /************************************************************************/
    1287             : 
    1288             : std::unique_ptr<OGRFeature>
    1289         671 : OGRJSONFGReader::ReadFeature(json_object *poObj, const char *pszRequestedLayer,
    1290             :                              bool bHasM, OGRJSONFGMemLayer **pOutMemLayer,
    1291             :                              OGRJSONFGStreamedLayer **pOutStreamedLayer)
    1292             : {
    1293         671 :     const char *pszLayerName = GetLayerNameForFeature(poObj);
    1294         671 :     if (pszRequestedLayer && strcmp(pszLayerName, pszRequestedLayer) != 0)
    1295           3 :         return nullptr;
    1296             : 
    1297         668 :     bHasM = OGRJSONFGHasMeasure(poObj, bHasM);
    1298             : 
    1299         668 :     auto oBuildContextIter = oMapBuildContext_.find(pszLayerName);
    1300         668 :     CPLAssert(oBuildContextIter != oMapBuildContext_.end());
    1301         668 :     auto &oBuildContext = oBuildContextIter->second;
    1302         668 :     OGRLayer *poLayer =
    1303         668 :         oBuildContext.poStreamedLayer
    1304         668 :             ? static_cast<OGRLayer *>(oBuildContext.poStreamedLayer)
    1305             :             : static_cast<OGRLayer *>(oBuildContext.poMemLayer);
    1306             : 
    1307         668 :     if (pOutMemLayer)
    1308          78 :         *pOutMemLayer = oBuildContext.poMemLayer;
    1309         590 :     else if (pOutStreamedLayer)
    1310         590 :         *pOutStreamedLayer = oBuildContext.poStreamedLayer;
    1311             : 
    1312         668 :     OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
    1313        1336 :     auto poFeature = std::make_unique<OGRFeature>(poFDefn);
    1314             : 
    1315             :     /* -------------------------------------------------------------------- */
    1316             :     /*      Translate GeoJSON "properties" object to feature attributes.    */
    1317             :     /* -------------------------------------------------------------------- */
    1318             : 
    1319         668 :     json_object *poObjProps = CPL_json_object_object_get(poObj, "properties");
    1320        1308 :     if (nullptr != poObjProps &&
    1321         640 :         json_object_get_type(poObjProps) == json_type_object)
    1322             :     {
    1323             :         json_object_iter it;
    1324         640 :         it.key = nullptr;
    1325         640 :         it.val = nullptr;
    1326         640 :         it.entry = nullptr;
    1327        2204 :         json_object_object_foreachC(poObjProps, it)
    1328             :         {
    1329        1564 :             const int nField = poFDefn->GetFieldIndexCaseSensitive(it.key);
    1330        1564 :             if (nField < 0 &&
    1331           0 :                 !(bFlattenNestedAttributes_ && it.val != nullptr &&
    1332           0 :                   json_object_get_type(it.val) == json_type_object))
    1333             :             {
    1334           0 :                 CPLDebug("JSONFG", "Cannot find field %s", it.key);
    1335             :             }
    1336             :             else
    1337             :             {
    1338        1564 :                 OGRGeoJSONReaderSetField(
    1339        1564 :                     poLayer, poFeature.get(), nField, it.key, it.val,
    1340        1564 :                     bFlattenNestedAttributes_, chNestedAttributeSeparator_);
    1341             :             }
    1342             :         }
    1343             :     }
    1344             : 
    1345             :     /* -------------------------------------------------------------------- */
    1346             :     /*      Try to use feature-level ID if available                        */
    1347             :     /*      and of integral type. Otherwise, leave unset (-1) then index    */
    1348             :     /*      in features sequence will be used as FID.                       */
    1349             :     /* -------------------------------------------------------------------- */
    1350         668 :     json_object *poObjId = CPL_json_object_object_get(poObj, "id");
    1351         668 :     if (nullptr != poObjId && oBuildContext.bFeatureLevelIdAsFID)
    1352             :     {
    1353          32 :         poFeature->SetFID(static_cast<GIntBig>(json_object_get_int64(poObjId)));
    1354             :     }
    1355             : 
    1356             :     /* -------------------------------------------------------------------- */
    1357             :     /*      Handle the case where the special id is in a regular field.     */
    1358             :     /* -------------------------------------------------------------------- */
    1359         636 :     else if (nullptr != poObjId)
    1360             :     {
    1361           2 :         const int nIdx = poFDefn->GetFieldIndexCaseSensitive("id");
    1362           2 :         if (nIdx >= 0 && !poFeature->IsFieldSet(nIdx))
    1363             :         {
    1364           2 :             poFeature->SetField(nIdx, json_object_get_string(poObjId));
    1365             :         }
    1366             :     }
    1367             : 
    1368             :     /* -------------------------------------------------------------------- */
    1369             :     /*      Deal with time                                                  */
    1370             :     /* -------------------------------------------------------------------- */
    1371         668 :     json_object *poTime = CPL_json_object_object_get(poObj, "time");
    1372         668 :     if (poTime)
    1373             :     {
    1374          15 :         json_object *poDate = CPL_json_object_object_get(poTime, "date");
    1375          15 :         if (poDate && json_object_get_type(poDate) == json_type_string)
    1376             :         {
    1377           3 :             poFeature->SetField(oBuildContext.nIdxFieldTime,
    1378             :                                 json_object_get_string(poDate));
    1379             :         }
    1380             : 
    1381             :         json_object *poTimestamp =
    1382          15 :             CPL_json_object_object_get(poTime, "timestamp");
    1383          17 :         if (poTimestamp &&
    1384           2 :             json_object_get_type(poTimestamp) == json_type_string)
    1385             :         {
    1386           2 :             poFeature->SetField(oBuildContext.nIdxFieldTime,
    1387             :                                 json_object_get_string(poTimestamp));
    1388             :         }
    1389             : 
    1390             :         json_object *poInterval =
    1391          15 :             CPL_json_object_object_get(poTime, "interval");
    1392          25 :         if (poInterval && json_object_get_type(poInterval) == json_type_array &&
    1393          10 :             json_object_array_length(poInterval) == 2)
    1394             :         {
    1395          10 :             json_object *poStart = json_object_array_get_idx(poInterval, 0);
    1396          10 :             if (poStart && json_object_get_type(poStart) == json_type_string)
    1397             :             {
    1398          10 :                 const char *pszStart = json_object_get_string(poStart);
    1399          10 :                 if (strcmp(pszStart, "..") != 0)
    1400           8 :                     poFeature->SetField(oBuildContext.nIdxFieldTimeStart,
    1401             :                                         pszStart);
    1402             :             }
    1403             : 
    1404          10 :             json_object *poEnd = json_object_array_get_idx(poInterval, 1);
    1405          10 :             if (poEnd && json_object_get_type(poEnd) == json_type_string)
    1406             :             {
    1407          10 :                 const char *pszEnd = json_object_get_string(poEnd);
    1408          10 :                 if (strcmp(pszEnd, "..") != 0)
    1409           6 :                     poFeature->SetField(oBuildContext.nIdxFieldTimeEnd, pszEnd);
    1410             :             }
    1411             :         }
    1412             :     }
    1413             : 
    1414             :     /* -------------------------------------------------------------------- */
    1415             :     /*      Translate "place" (and fallback to "geometry") sub-object       */
    1416             :     /* -------------------------------------------------------------------- */
    1417         668 :     json_object *poPlace = nullptr;
    1418         668 :     bool bFallbackToGeometry = (eGeometryElement_ != GeometryElement::PLACE);
    1419             : 
    1420         668 :     if (eGeometryElement_ != GeometryElement::GEOMETRY)
    1421             :     {
    1422         665 :         poPlace = CPL_json_object_object_get(poObj, "place");
    1423             :     }
    1424         668 :     if (poPlace && json_object_get_type(poPlace) == json_type_object)
    1425             :     {
    1426         608 :         bHasM = OGRJSONFGHasMeasure(poPlace, bHasM);
    1427         608 :         json_object *poCoordRefSys = nullptr;
    1428         608 :         if (!oBuildContext.poCRSAtFeatureLevel)
    1429             :         {
    1430         551 :             poCoordRefSys = CPL_json_object_object_get(poPlace, "coordRefSys");
    1431         551 :             if (!poCoordRefSys)
    1432             :             {
    1433             :                 poCoordRefSys =
    1434         551 :                     CPL_json_object_object_get(poObj, "coordRefSys");
    1435             :             }
    1436             :         }
    1437             : 
    1438         608 :         std::unique_ptr<OGRGeometry> poGeometry;
    1439         608 :         json_object *poObjType = CPL_json_object_object_get(poPlace, "type");
    1440         608 :         const char *pszType = json_object_get_string(poObjType);
    1441         608 :         if (pszType && (strcmp(pszType, "Polyhedron") == 0 ||
    1442         605 :                         strcmp(pszType, "Prism") == 0))
    1443             :         {
    1444          12 :             poGeometry = OGRJSONFGCreateNonGeoJSONGeometry(poPlace, bHasM,
    1445           6 :                                                            /* bWarn=*/false);
    1446             :         }
    1447             :         else
    1448             :         {
    1449         602 :             poGeometry = OGRGeoJSONReadGeometry(poPlace, bHasM, nullptr);
    1450             :         }
    1451         608 :         if (poGeometry)
    1452         604 :             bFallbackToGeometry = false;
    1453             : 
    1454         608 :         auto poLayerSRS = poLayer->GetSpatialRef();
    1455         608 :         if (!poGeometry)
    1456             :         {
    1457             :             // nothing to do
    1458             :         }
    1459         604 :         else if (poCoordRefSys)
    1460             :         {
    1461           8 :             auto poFeatureCRS = OGRJSONFGReadCoordRefSys(poCoordRefSys);
    1462           4 :             if (poFeatureCRS)
    1463             :             {
    1464           4 :                 poFeatureCRS->SetAxisMappingStrategy(
    1465             :                     OAMS_TRADITIONAL_GIS_ORDER);
    1466             :                 const bool bFeatureCRSNeedSwapXY =
    1467           4 :                     OGRJSONFGMustSwapXY(poFeatureCRS.get());
    1468           4 :                 if (poLayerSRS)
    1469             :                 {
    1470             :                     // Both feature and layer-level CRS. Reproject if needed
    1471           2 :                     if (!poFeatureCRS->IsSame(poLayerSRS))
    1472             :                     {
    1473             :                         auto poCT =
    1474             :                             std::unique_ptr<OGRCoordinateTransformation>(
    1475             :                                 OGRCreateCoordinateTransformation(
    1476           4 :                                     poFeatureCRS.get(), poLayerSRS));
    1477           2 :                         if (poCT)
    1478             :                         {
    1479           2 :                             if (bFeatureCRSNeedSwapXY)
    1480           1 :                                 poGeometry->swapXY();
    1481           2 :                             if (poGeometry->transform(poCT.get()) ==
    1482             :                                 OGRERR_NONE)
    1483             :                             {
    1484           2 :                                 poGeometry->assignSpatialReference(poLayerSRS);
    1485           2 :                                 poFeature->SetGeometryDirectly(
    1486             :                                     poGeometry.release());
    1487             :                             }
    1488             :                         }
    1489             :                     }
    1490             :                     else
    1491             :                     {
    1492           0 :                         poGeometry->assignSpatialReference(poLayerSRS);
    1493           0 :                         if (oBuildContext.bSwapPlacesXY)
    1494           0 :                             poGeometry->swapXY();
    1495           0 :                         poFeature->SetGeometryDirectly(poGeometry.release());
    1496             :                     }
    1497             :                 }
    1498             :                 else
    1499             :                 {
    1500             :                     // No layer-level CRS
    1501           2 :                     auto poFeatureCRSBorrowed = poFeatureCRS.release();
    1502           2 :                     poGeometry->assignSpatialReference(poFeatureCRSBorrowed);
    1503           2 :                     poFeatureCRSBorrowed->Release();
    1504           2 :                     if (bFeatureCRSNeedSwapXY)
    1505           1 :                         poGeometry->swapXY();
    1506           2 :                     poFeature->SetGeometryDirectly(poGeometry.release());
    1507             :                 }
    1508             :             }
    1509             :         }
    1510             :         else
    1511             :         {
    1512         600 :             poGeometry->assignSpatialReference(poLayerSRS);
    1513         600 :             if (oBuildContext.bSwapPlacesXY)
    1514           5 :                 poGeometry->swapXY();
    1515         600 :             poFeature->SetGeometryDirectly(poGeometry.release());
    1516             :         }
    1517             :     }
    1518             : 
    1519         778 :     if (bFallbackToGeometry &&
    1520         110 :         (oBuildContext.poCTWGS84ToLayerCRS || oBuildContext.bLayerCRSIsWGS84))
    1521             :     {
    1522          41 :         json_object *poGeomObj = CPL_json_object_object_get(poObj, "geometry");
    1523          41 :         if (nullptr != poGeomObj)
    1524             :         {
    1525             :             auto poGeometry =
    1526             :                 std::unique_ptr<OGRGeometry>(OGRGeoJSONReadGeometry(
    1527          22 :                     poGeomObj, /* bHasM = */ false, nullptr));
    1528          11 :             if (poGeometry)
    1529             :             {
    1530          11 :                 if (oBuildContext.poCTWGS84ToLayerCRS)
    1531             :                 {
    1532           2 :                     if (poGeometry->transform(
    1533           2 :                             oBuildContext.poCTWGS84ToLayerCRS.get()) ==
    1534             :                         OGRERR_NONE)
    1535             :                     {
    1536           2 :                         poGeometry->assignSpatialReference(
    1537           1 :                             poLayer->GetSpatialRef());
    1538           1 :                         poFeature->SetGeometryDirectly(poGeometry.release());
    1539             :                     }
    1540             :                 }
    1541             :                 else /* if (oBuildContext.bLayerCRSIsWGS84) */
    1542             :                 {
    1543          20 :                     poGeometry->assignSpatialReference(
    1544          10 :                         poLayer->GetSpatialRef());
    1545          10 :                     poFeature->SetGeometryDirectly(poGeometry.release());
    1546             :                 }
    1547             :             }
    1548             :         }
    1549             :     }
    1550             : 
    1551         668 :     return poFeature;
    1552             : }

Generated by: LCOV version 1.14