LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogresrijsonreader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 229 253 90.5 %
Date: 2025-01-18 12:42:00 Functions: 10 10 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implementation of OGRESRIJSONReader class (OGR ESRIJSON Driver)
       5             :  *           to read ESRI Feature Service REST data
       6             :  * Author:   Even Rouault, even dot rouault at spatialys.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  * Copyright (c) 2007, Mateusz Loskot
      11             :  * Copyright (c) 2013, Kyle Shannon <kyle at pobox dot com>
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include "cpl_port.h"
      17             : #include "ogrlibjsonutils.h"
      18             : 
      19             : #include <limits.h>
      20             : #include <stddef.h>
      21             : 
      22             : #include "cpl_conv.h"
      23             : #include "cpl_error.h"
      24             : #include "cpl_time.h"
      25             : #include "json.h"
      26             : // #include "json_object.h"
      27             : // #include "json_tokener.h"
      28             : #include "ogr_api.h"
      29             : #include "ogr_core.h"
      30             : #include "ogr_feature.h"
      31             : #include "ogr_geometry.h"
      32             : #include "ogr_spatialref.h"
      33             : #include "ogr_geojson.h"
      34             : #include "ogrgeojsonreader.h"
      35             : #include "ogrgeojsonutils.h"
      36             : #include "ogresrijsongeometry.h"
      37             : 
      38             : // #include "symbol_renames.h"
      39             : 
      40             : /************************************************************************/
      41             : /*                          OGRESRIJSONReader()                         */
      42             : /************************************************************************/
      43             : 
      44          21 : OGRESRIJSONReader::OGRESRIJSONReader() : poGJObject_(nullptr), poLayer_(nullptr)
      45             : {
      46          21 : }
      47             : 
      48             : /************************************************************************/
      49             : /*                         ~OGRESRIJSONReader()                         */
      50             : /************************************************************************/
      51             : 
      52          42 : OGRESRIJSONReader::~OGRESRIJSONReader()
      53             : {
      54          21 :     if (nullptr != poGJObject_)
      55             :     {
      56          21 :         json_object_put(poGJObject_);
      57             :     }
      58             : 
      59          21 :     poGJObject_ = nullptr;
      60          21 :     poLayer_ = nullptr;
      61          21 : }
      62             : 
      63             : /************************************************************************/
      64             : /*                           Parse()                                    */
      65             : /************************************************************************/
      66             : 
      67          21 : OGRErr OGRESRIJSONReader::Parse(const char *pszText)
      68             : {
      69          21 :     json_object *jsobj = nullptr;
      70          21 :     if (nullptr != pszText && !OGRJSonParse(pszText, &jsobj, true))
      71             :     {
      72           0 :         return OGRERR_CORRUPT_DATA;
      73             :     }
      74             : 
      75             :     // JSON tree is shared for while lifetime of the reader object
      76             :     // and will be released in the destructor.
      77          21 :     poGJObject_ = jsobj;
      78          21 :     return OGRERR_NONE;
      79             : }
      80             : 
      81             : /************************************************************************/
      82             : /*                           ReadLayers()                               */
      83             : /************************************************************************/
      84             : 
      85          21 : void OGRESRIJSONReader::ReadLayers(OGRGeoJSONDataSource *poDS,
      86             :                                    GeoJSONSourceType eSourceType)
      87             : {
      88          21 :     CPLAssert(nullptr == poLayer_);
      89             : 
      90          21 :     if (nullptr == poGJObject_)
      91             :     {
      92           0 :         CPLDebug("ESRIJSON",
      93             :                  "Missing parsed ESRIJSON data. Forgot to call Parse()?");
      94           0 :         return;
      95             :     }
      96             : 
      97          21 :     OGRSpatialReference *poSRS = OGRESRIJSONReadSpatialReference(poGJObject_);
      98             : 
      99          21 :     std::string osName = "ESRIJSON";
     100          21 :     if (eSourceType == eGeoJSONSourceFile)
     101             :     {
     102          19 :         osName = poDS->GetDescription();
     103          19 :         if (STARTS_WITH_CI(osName.c_str(), "ESRIJSON:"))
     104           1 :             osName = osName.substr(strlen("ESRIJSON:"));
     105          19 :         osName = CPLGetBasenameSafe(osName.c_str());
     106             :     }
     107             : 
     108          21 :     auto eGeomType = OGRESRIJSONGetGeometryType(poGJObject_);
     109          21 :     if (eGeomType == wkbNone)
     110             :     {
     111           1 :         if (poSRS)
     112             :         {
     113           0 :             eGeomType = wkbUnknown;
     114             :         }
     115             :         else
     116             :         {
     117             :             json_object *poObjFeatures =
     118           1 :                 OGRGeoJSONFindMemberByName(poGJObject_, "features");
     119           2 :             if (poObjFeatures &&
     120           1 :                 json_type_array == json_object_get_type(poObjFeatures))
     121             :             {
     122           1 :                 const auto nFeatures = json_object_array_length(poObjFeatures);
     123           1 :                 for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     124             :                 {
     125             :                     json_object *poObjFeature =
     126           1 :                         json_object_array_get_idx(poObjFeatures, i);
     127           2 :                     if (poObjFeature != nullptr &&
     128           1 :                         json_object_get_type(poObjFeature) == json_type_object)
     129             :                     {
     130           1 :                         if (auto poObjGeometry = OGRGeoJSONFindMemberByName(
     131             :                                 poObjFeature, "geometry"))
     132             :                         {
     133           1 :                             eGeomType = wkbUnknown;
     134             :                             poSRS =
     135           1 :                                 OGRESRIJSONReadSpatialReference(poObjGeometry);
     136           1 :                             break;
     137             :                         }
     138             :                     }
     139             :                 }
     140             :             }
     141             :         }
     142             :     }
     143             : 
     144          21 :     poLayer_ =
     145          21 :         new OGRGeoJSONLayer(osName.c_str(), poSRS, eGeomType, poDS, nullptr);
     146          21 :     if (poSRS != nullptr)
     147          10 :         poSRS->Release();
     148             : 
     149          21 :     if (!GenerateLayerDefn())
     150             :     {
     151           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     152             :                  "Layer schema generation failed.");
     153             : 
     154           0 :         delete poLayer_;
     155           0 :         return;
     156             :     }
     157             : 
     158          21 :     OGRGeoJSONLayer *poThisLayer = ReadFeatureCollection(poGJObject_);
     159          21 :     if (poThisLayer == nullptr)
     160             :     {
     161           0 :         delete poLayer_;
     162           0 :         return;
     163             :     }
     164             : 
     165          21 :     CPLErrorReset();
     166             : 
     167          21 :     poLayer_->DetectGeometryType();
     168          21 :     poDS->AddLayer(poLayer_);
     169             : }
     170             : 
     171             : /************************************************************************/
     172             : /*                        GenerateFeatureDefn()                         */
     173             : /************************************************************************/
     174             : 
     175          21 : bool OGRESRIJSONReader::GenerateLayerDefn()
     176             : {
     177          21 :     CPLAssert(nullptr != poGJObject_);
     178             : 
     179          21 :     bool bSuccess = true;
     180             : 
     181          21 :     OGRFeatureDefn *poDefn = poLayer_->GetLayerDefn();
     182          21 :     CPLAssert(nullptr != poDefn);
     183          21 :     CPLAssert(0 == poDefn->GetFieldCount());
     184          21 :     auto oTemporaryUnsealer(poDefn->GetTemporaryUnsealer());
     185             : 
     186             :     /* -------------------------------------------------------------------- */
     187             :     /*      Scan all features and generate layer definition.                */
     188             :     /* -------------------------------------------------------------------- */
     189          21 :     json_object *poFields = OGRGeoJSONFindMemberByName(poGJObject_, "fields");
     190          41 :     if (nullptr != poFields &&
     191          20 :         json_type_array == json_object_get_type(poFields))
     192             :     {
     193          20 :         const auto nFeatures = json_object_array_length(poFields);
     194          62 :         for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     195             :         {
     196          42 :             json_object *poField = json_object_array_get_idx(poFields, i);
     197          42 :             if (!ParseField(poField))
     198             :             {
     199           0 :                 CPLDebug("GeoJSON", "Create feature schema failure.");
     200           0 :                 bSuccess = false;
     201             :             }
     202             :         }
     203             :     }
     204           1 :     else if ((poFields = OGRGeoJSONFindMemberByName(
     205           1 :                   poGJObject_, "fieldAliases")) != nullptr &&
     206           0 :              json_object_get_type(poFields) == json_type_object)
     207             :     {
     208             :         json_object_iter it;
     209           0 :         it.key = nullptr;
     210           0 :         it.val = nullptr;
     211           0 :         it.entry = nullptr;
     212           0 :         json_object_object_foreachC(poFields, it)
     213             :         {
     214           0 :             OGRFieldDefn fldDefn(it.key, OFTString);
     215           0 :             poDefn->AddFieldDefn(&fldDefn);
     216             :         }
     217             :     }
     218             :     else
     219             :     {
     220             :         // Guess the fields' schema from the content of the features' "attributes"
     221             :         // element
     222             :         json_object *poObjFeatures =
     223           1 :             OGRGeoJSONFindMemberByName(poGJObject_, "features");
     224           2 :         if (poObjFeatures &&
     225           1 :             json_type_array == json_object_get_type(poObjFeatures))
     226             :         {
     227           2 :             gdal::DirectedAcyclicGraph<int, std::string> dag;
     228           2 :             std::vector<std::unique_ptr<OGRFieldDefn>> apoFieldDefn{};
     229           2 :             std::map<std::string, int> oMapFieldNameToIdx{};
     230           2 :             std::vector<int> anCurFieldIndices;
     231           2 :             std::set<int> aoSetUndeterminedTypeFields;
     232             : 
     233           1 :             const auto nFeatures = json_object_array_length(poObjFeatures);
     234           2 :             for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     235             :             {
     236             :                 json_object *poObjFeature =
     237           1 :                     json_object_array_get_idx(poObjFeatures, i);
     238           2 :                 if (poObjFeature != nullptr &&
     239           1 :                     json_object_get_type(poObjFeature) == json_type_object)
     240             :                 {
     241           1 :                     int nPrevFieldIdx = -1;
     242             : 
     243             :                     json_object *poObjProps =
     244           1 :                         OGRGeoJSONFindMemberByName(poObjFeature, "attributes");
     245           2 :                     if (nullptr != poObjProps &&
     246           1 :                         json_object_get_type(poObjProps) == json_type_object)
     247             :                     {
     248             :                         json_object_iter it;
     249           1 :                         it.key = nullptr;
     250           1 :                         it.val = nullptr;
     251           1 :                         it.entry = nullptr;
     252           2 :                         json_object_object_foreachC(poObjProps, it)
     253             :                         {
     254           1 :                             anCurFieldIndices.clear();
     255           1 :                             OGRGeoJSONReaderAddOrUpdateField(
     256             :                                 anCurFieldIndices, oMapFieldNameToIdx,
     257           1 :                                 apoFieldDefn, it.key, it.val,
     258             :                                 /*bFlattenNestedAttributes = */ true,
     259             :                                 /* chNestedAttributeSeparator = */ '.',
     260             :                                 /* bArrayAsString =*/false,
     261             :                                 /* bDateAsString = */ false,
     262             :                                 aoSetUndeterminedTypeFields);
     263           2 :                             for (int idx : anCurFieldIndices)
     264             :                             {
     265           2 :                                 dag.addNode(idx,
     266           1 :                                             apoFieldDefn[idx]->GetNameRef());
     267           1 :                                 if (nPrevFieldIdx != -1)
     268             :                                 {
     269           0 :                                     dag.addEdge(nPrevFieldIdx, idx);
     270             :                                 }
     271           1 :                                 nPrevFieldIdx = idx;
     272             :                             }
     273             :                         }
     274             :                     }
     275             :                 }
     276             :             }
     277             : 
     278           2 :             const auto sortedFields = dag.getTopologicalOrdering();
     279           1 :             CPLAssert(sortedFields.size() == apoFieldDefn.size());
     280           2 :             for (int idx : sortedFields)
     281             :             {
     282             :                 // cppcheck-suppress containerOutOfBounds
     283           1 :                 poDefn->AddFieldDefn(apoFieldDefn[idx].get());
     284             :             }
     285             :         }
     286             :     }
     287             : 
     288          42 :     return bSuccess;
     289             : }
     290             : 
     291             : /************************************************************************/
     292             : /*                             ParseField()                             */
     293             : /************************************************************************/
     294             : 
     295          42 : bool OGRESRIJSONReader::ParseField(json_object *poObj)
     296             : {
     297          42 :     OGRFeatureDefn *poDefn = poLayer_->GetLayerDefn();
     298          42 :     CPLAssert(nullptr != poDefn);
     299             : 
     300          42 :     bool bSuccess = false;
     301             : 
     302             :     /* -------------------------------------------------------------------- */
     303             :     /*      Read collection of properties.                                  */
     304             :     /* -------------------------------------------------------------------- */
     305          42 :     json_object *poObjName = OGRGeoJSONFindMemberByName(poObj, "name");
     306          42 :     json_object *poObjType = OGRGeoJSONFindMemberByName(poObj, "type");
     307          42 :     if (nullptr != poObjName && nullptr != poObjType)
     308             :     {
     309          42 :         OGRFieldType eFieldType = OFTString;
     310          42 :         OGRFieldSubType eFieldSubType = OFSTNone;
     311          42 :         const char *pszObjName = json_object_get_string(poObjName);
     312          42 :         const char *pszObjType = json_object_get_string(poObjType);
     313          42 :         if (EQUAL(pszObjType, "esriFieldTypeString"))
     314             :         {
     315             :             // do nothing
     316             :         }
     317          34 :         else if (EQUAL(pszObjType, "esriFieldTypeOID"))
     318             :         {
     319          12 :             eFieldType = OFTInteger;
     320          12 :             poLayer_->SetFIDColumn(pszObjName);
     321             :         }
     322          22 :         else if (EQUAL(pszObjType, "esriFieldTypeSingle"))
     323             :         {
     324           2 :             eFieldType = OFTReal;
     325           2 :             eFieldSubType = OFSTFloat32;
     326             :         }
     327          20 :         else if (EQUAL(pszObjType, "esriFieldTypeDouble"))
     328             :         {
     329           8 :             eFieldType = OFTReal;
     330             :         }
     331          12 :         else if (EQUAL(pszObjType, "esriFieldTypeSmallInteger"))
     332             :         {
     333           2 :             eFieldType = OFTInteger;
     334           2 :             eFieldSubType = OFSTInt16;
     335             :         }
     336          10 :         else if (EQUAL(pszObjType, "esriFieldTypeInteger"))
     337             :         {
     338           8 :             eFieldType = OFTInteger;
     339             :         }
     340           2 :         else if (EQUAL(pszObjType, "esriFieldTypeDate"))
     341             :         {
     342           2 :             eFieldType = OFTDateTime;
     343             :         }
     344             :         else
     345             :         {
     346           0 :             CPLDebug("ESRIJSON",
     347             :                      "Unhandled fields[\"%s\"].type = %s. "
     348             :                      "Processing it as a String",
     349             :                      pszObjName, pszObjType);
     350             :         }
     351          42 :         OGRFieldDefn fldDefn(pszObjName, eFieldType);
     352          42 :         fldDefn.SetSubType(eFieldSubType);
     353             : 
     354          42 :         if (eFieldType != OFTDateTime)
     355             :         {
     356             :             json_object *const poObjLength =
     357          40 :                 OGRGeoJSONFindMemberByName(poObj, "length");
     358          58 :             if (poObjLength != nullptr &&
     359          18 :                 json_object_get_type(poObjLength) == json_type_int)
     360             :             {
     361          18 :                 const int nWidth = json_object_get_int(poObjLength);
     362             :                 // A dummy width of 2147483647 seems to indicate no known field with
     363             :                 // which in the OGR world is better modelled as 0 field width.
     364             :                 // (#6529)
     365          18 :                 if (nWidth != INT_MAX)
     366          18 :                     fldDefn.SetWidth(nWidth);
     367             :             }
     368             :         }
     369             : 
     370          42 :         json_object *poObjAlias = OGRGeoJSONFindMemberByName(poObj, "alias");
     371          42 :         if (poObjAlias && json_object_get_type(poObjAlias) == json_type_string)
     372             :         {
     373          42 :             const char *pszAlias = json_object_get_string(poObjAlias);
     374          42 :             if (strcmp(pszObjName, pszAlias) != 0)
     375          12 :                 fldDefn.SetAlternativeName(pszAlias);
     376             :         }
     377             : 
     378          42 :         poDefn->AddFieldDefn(&fldDefn);
     379             : 
     380          42 :         bSuccess = true;
     381             :     }
     382          42 :     return bSuccess;
     383             : }
     384             : 
     385             : /************************************************************************/
     386             : /*                           AddFeature                                 */
     387             : /************************************************************************/
     388             : 
     389          20 : bool OGRESRIJSONReader::AddFeature(OGRFeature *poFeature)
     390             : {
     391          20 :     if (nullptr == poFeature)
     392           0 :         return false;
     393             : 
     394          20 :     poLayer_->AddFeature(poFeature);
     395          20 :     delete poFeature;
     396             : 
     397          20 :     return true;
     398             : }
     399             : 
     400             : /************************************************************************/
     401             : /*                           EsriDateToOGRDate()                        */
     402             : /************************************************************************/
     403             : 
     404           2 : static void EsriDateToOGRDate(int64_t nVal, OGRField *psField)
     405             : {
     406           2 :     const auto nSeconds = nVal / 1000;
     407           2 :     const auto nMillisec = static_cast<int>(nVal % 1000);
     408             : 
     409             :     struct tm brokendowntime;
     410           2 :     CPLUnixTimeToYMDHMS(nSeconds, &brokendowntime);
     411             : 
     412           2 :     psField->Date.Year = static_cast<GInt16>(brokendowntime.tm_year + 1900);
     413           2 :     psField->Date.Month = static_cast<GByte>(brokendowntime.tm_mon + 1);
     414           2 :     psField->Date.Day = static_cast<GByte>(brokendowntime.tm_mday);
     415           2 :     psField->Date.Hour = static_cast<GByte>(brokendowntime.tm_hour);
     416           2 :     psField->Date.Minute = static_cast<GByte>(brokendowntime.tm_min);
     417           2 :     psField->Date.Second =
     418           2 :         static_cast<float>(brokendowntime.tm_sec + nMillisec / 1000.0);
     419           2 :     psField->Date.TZFlag = 100;
     420           2 :     psField->Date.Reserved = 0;
     421           2 : }
     422             : 
     423             : /************************************************************************/
     424             : /*                           ReadFeature()                              */
     425             : /************************************************************************/
     426             : 
     427          20 : OGRFeature *OGRESRIJSONReader::ReadFeature(json_object *poObj)
     428             : {
     429          20 :     CPLAssert(nullptr != poObj);
     430          20 :     CPLAssert(nullptr != poLayer_);
     431             : 
     432          20 :     OGRFeature *poFeature = new OGRFeature(poLayer_->GetLayerDefn());
     433             : 
     434             :     /* -------------------------------------------------------------------- */
     435             :     /*      Translate ESRIJSON "attributes" object to feature attributes.   */
     436             :     /* -------------------------------------------------------------------- */
     437          20 :     CPLAssert(nullptr != poFeature);
     438             : 
     439          20 :     json_object *poObjProps = OGRGeoJSONFindMemberByName(poObj, "attributes");
     440          39 :     if (nullptr != poObjProps &&
     441          19 :         json_object_get_type(poObjProps) == json_type_object)
     442             :     {
     443          19 :         OGRFieldDefn *poFieldDefn = nullptr;
     444             :         json_object_iter it;
     445          19 :         it.key = nullptr;
     446          19 :         it.val = nullptr;
     447          19 :         it.entry = nullptr;
     448          62 :         json_object_object_foreachC(poObjProps, it)
     449             :         {
     450          43 :             const int nField = poFeature->GetFieldIndex(it.key);
     451          43 :             if (nField >= 0)
     452             :             {
     453          43 :                 poFieldDefn = poFeature->GetFieldDefnRef(nField);
     454          43 :                 if (poFieldDefn && it.val != nullptr)
     455             :                 {
     456          43 :                     if (EQUAL(it.key, poLayer_->GetFIDColumn()))
     457          12 :                         poFeature->SetFID(json_object_get_int(it.val));
     458          43 :                     switch (poLayer_->GetLayerDefn()
     459          43 :                                 ->GetFieldDefn(nField)
     460          43 :                                 ->GetType())
     461             :                     {
     462          22 :                         case OFTInteger:
     463             :                         {
     464          22 :                             poFeature->SetField(nField,
     465          22 :                                                 json_object_get_int(it.val));
     466          22 :                             break;
     467             :                         }
     468          10 :                         case OFTReal:
     469             :                         {
     470          10 :                             poFeature->SetField(nField,
     471          10 :                                                 json_object_get_double(it.val));
     472          10 :                             break;
     473             :                         }
     474           2 :                         case OFTDateTime:
     475             :                         {
     476           2 :                             const auto nVal = json_object_get_int64(it.val);
     477           2 :                             EsriDateToOGRDate(
     478             :                                 nVal, poFeature->GetRawFieldRef(nField));
     479           2 :                             break;
     480             :                         }
     481           9 :                         default:
     482             :                         {
     483           9 :                             poFeature->SetField(nField,
     484             :                                                 json_object_get_string(it.val));
     485           9 :                             break;
     486             :                         }
     487             :                     }
     488             :                 }
     489             :             }
     490             :         }
     491             :     }
     492             : 
     493          20 :     const OGRwkbGeometryType eType = poLayer_->GetGeomType();
     494          20 :     if (eType == wkbNone)
     495           0 :         return poFeature;
     496             : 
     497             :     /* -------------------------------------------------------------------- */
     498             :     /*      Translate geometry sub-object of ESRIJSON Feature.               */
     499             :     /* -------------------------------------------------------------------- */
     500          20 :     json_object *poObjGeom = nullptr;
     501          20 :     json_object *poTmp = poObj;
     502             :     json_object_iter it;
     503          20 :     it.key = nullptr;
     504          20 :     it.val = nullptr;
     505          20 :     it.entry = nullptr;
     506          55 :     json_object_object_foreachC(poTmp, it)
     507             :     {
     508          37 :         if (EQUAL(it.key, "geometry"))
     509             :         {
     510          20 :             if (it.val != nullptr)
     511          18 :                 poObjGeom = it.val;
     512             :             // We're done.  They had 'geometry':null.
     513             :             else
     514           2 :                 return poFeature;
     515             :         }
     516             :     }
     517             : 
     518          18 :     if (nullptr != poObjGeom)
     519             :     {
     520          18 :         OGRGeometry *poGeometry = OGRESRIJSONReadGeometry(poObjGeom);
     521          18 :         if (nullptr != poGeometry)
     522             :         {
     523          18 :             poFeature->SetGeometryDirectly(poGeometry);
     524             :         }
     525             :     }
     526             : 
     527          18 :     return poFeature;
     528             : }
     529             : 
     530             : /************************************************************************/
     531             : /*                           ReadFeatureCollection()                    */
     532             : /************************************************************************/
     533             : 
     534          21 : OGRGeoJSONLayer *OGRESRIJSONReader::ReadFeatureCollection(json_object *poObj)
     535             : {
     536          21 :     CPLAssert(nullptr != poLayer_);
     537             : 
     538          21 :     json_object *poObjFeatures = OGRGeoJSONFindMemberByName(poObj, "features");
     539          21 :     if (nullptr == poObjFeatures)
     540             :     {
     541           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     542             :                  "Invalid FeatureCollection object. "
     543             :                  "Missing \'features\' member.");
     544           0 :         return nullptr;
     545             :     }
     546             : 
     547          21 :     if (json_type_array == json_object_get_type(poObjFeatures))
     548             :     {
     549          21 :         const auto nFeatures = json_object_array_length(poObjFeatures);
     550          41 :         for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     551             :         {
     552             :             json_object *poObjFeature =
     553          20 :                 json_object_array_get_idx(poObjFeatures, i);
     554          40 :             if (poObjFeature != nullptr &&
     555          20 :                 json_object_get_type(poObjFeature) == json_type_object)
     556             :             {
     557             :                 OGRFeature *poFeature =
     558          20 :                     OGRESRIJSONReader::ReadFeature(poObjFeature);
     559          20 :                 AddFeature(poFeature);
     560             :             }
     561             :         }
     562             :     }
     563             : 
     564             :     // We're returning class member to follow the same pattern of
     565             :     // Read* functions call convention.
     566          21 :     CPLAssert(nullptr != poLayer_);
     567          21 :     return poLayer_;
     568             : }

Generated by: LCOV version 1.14