LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogresrijsonreader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 228 252 90.5 %
Date: 2024-11-21 22:18:42 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 :     const char *pszName = "ESRIJSON";
     100          21 :     if (eSourceType == eGeoJSONSourceFile)
     101             :     {
     102          19 :         pszName = poDS->GetDescription();
     103          19 :         if (STARTS_WITH_CI(pszName, "ESRIJSON:"))
     104           1 :             pszName += strlen("ESRIJSON:");
     105          19 :         pszName = CPLGetBasename(pszName);
     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_ = new OGRGeoJSONLayer(pszName, poSRS, eGeomType, poDS, nullptr);
     145          21 :     if (poSRS != nullptr)
     146          10 :         poSRS->Release();
     147             : 
     148          21 :     if (!GenerateLayerDefn())
     149             :     {
     150           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     151             :                  "Layer schema generation failed.");
     152             : 
     153           0 :         delete poLayer_;
     154           0 :         return;
     155             :     }
     156             : 
     157          21 :     OGRGeoJSONLayer *poThisLayer = ReadFeatureCollection(poGJObject_);
     158          21 :     if (poThisLayer == nullptr)
     159             :     {
     160           0 :         delete poLayer_;
     161           0 :         return;
     162             :     }
     163             : 
     164          21 :     CPLErrorReset();
     165             : 
     166          21 :     poLayer_->DetectGeometryType();
     167          21 :     poDS->AddLayer(poLayer_);
     168             : }
     169             : 
     170             : /************************************************************************/
     171             : /*                        GenerateFeatureDefn()                         */
     172             : /************************************************************************/
     173             : 
     174          21 : bool OGRESRIJSONReader::GenerateLayerDefn()
     175             : {
     176          21 :     CPLAssert(nullptr != poGJObject_);
     177             : 
     178          21 :     bool bSuccess = true;
     179             : 
     180          21 :     OGRFeatureDefn *poDefn = poLayer_->GetLayerDefn();
     181          21 :     CPLAssert(nullptr != poDefn);
     182          21 :     CPLAssert(0 == poDefn->GetFieldCount());
     183          21 :     auto oTemporaryUnsealer(poDefn->GetTemporaryUnsealer());
     184             : 
     185             :     /* -------------------------------------------------------------------- */
     186             :     /*      Scan all features and generate layer definition.                */
     187             :     /* -------------------------------------------------------------------- */
     188          21 :     json_object *poFields = OGRGeoJSONFindMemberByName(poGJObject_, "fields");
     189          41 :     if (nullptr != poFields &&
     190          20 :         json_type_array == json_object_get_type(poFields))
     191             :     {
     192          20 :         const auto nFeatures = json_object_array_length(poFields);
     193          62 :         for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     194             :         {
     195          42 :             json_object *poField = json_object_array_get_idx(poFields, i);
     196          42 :             if (!ParseField(poField))
     197             :             {
     198           0 :                 CPLDebug("GeoJSON", "Create feature schema failure.");
     199           0 :                 bSuccess = false;
     200             :             }
     201             :         }
     202             :     }
     203           1 :     else if ((poFields = OGRGeoJSONFindMemberByName(
     204           1 :                   poGJObject_, "fieldAliases")) != nullptr &&
     205           0 :              json_object_get_type(poFields) == json_type_object)
     206             :     {
     207             :         json_object_iter it;
     208           0 :         it.key = nullptr;
     209           0 :         it.val = nullptr;
     210           0 :         it.entry = nullptr;
     211           0 :         json_object_object_foreachC(poFields, it)
     212             :         {
     213           0 :             OGRFieldDefn fldDefn(it.key, OFTString);
     214           0 :             poDefn->AddFieldDefn(&fldDefn);
     215             :         }
     216             :     }
     217             :     else
     218             :     {
     219             :         // Guess the fields' schema from the content of the features' "attributes"
     220             :         // element
     221             :         json_object *poObjFeatures =
     222           1 :             OGRGeoJSONFindMemberByName(poGJObject_, "features");
     223           2 :         if (poObjFeatures &&
     224           1 :             json_type_array == json_object_get_type(poObjFeatures))
     225             :         {
     226           2 :             gdal::DirectedAcyclicGraph<int, std::string> dag;
     227           2 :             std::vector<std::unique_ptr<OGRFieldDefn>> apoFieldDefn{};
     228           2 :             std::map<std::string, int> oMapFieldNameToIdx{};
     229           2 :             std::vector<int> anCurFieldIndices;
     230           2 :             std::set<int> aoSetUndeterminedTypeFields;
     231             : 
     232           1 :             const auto nFeatures = json_object_array_length(poObjFeatures);
     233           2 :             for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     234             :             {
     235             :                 json_object *poObjFeature =
     236           1 :                     json_object_array_get_idx(poObjFeatures, i);
     237           2 :                 if (poObjFeature != nullptr &&
     238           1 :                     json_object_get_type(poObjFeature) == json_type_object)
     239             :                 {
     240           1 :                     int nPrevFieldIdx = -1;
     241             : 
     242             :                     json_object *poObjProps =
     243           1 :                         OGRGeoJSONFindMemberByName(poObjFeature, "attributes");
     244           2 :                     if (nullptr != poObjProps &&
     245           1 :                         json_object_get_type(poObjProps) == json_type_object)
     246             :                     {
     247             :                         json_object_iter it;
     248           1 :                         it.key = nullptr;
     249           1 :                         it.val = nullptr;
     250           1 :                         it.entry = nullptr;
     251           2 :                         json_object_object_foreachC(poObjProps, it)
     252             :                         {
     253           1 :                             anCurFieldIndices.clear();
     254           1 :                             OGRGeoJSONReaderAddOrUpdateField(
     255             :                                 anCurFieldIndices, oMapFieldNameToIdx,
     256           1 :                                 apoFieldDefn, it.key, it.val,
     257             :                                 /*bFlattenNestedAttributes = */ true,
     258             :                                 /* chNestedAttributeSeparator = */ '.',
     259             :                                 /* bArrayAsString =*/false,
     260             :                                 /* bDateAsString = */ false,
     261             :                                 aoSetUndeterminedTypeFields);
     262           2 :                             for (int idx : anCurFieldIndices)
     263             :                             {
     264           2 :                                 dag.addNode(idx,
     265           1 :                                             apoFieldDefn[idx]->GetNameRef());
     266           1 :                                 if (nPrevFieldIdx != -1)
     267             :                                 {
     268           0 :                                     dag.addEdge(nPrevFieldIdx, idx);
     269             :                                 }
     270           1 :                                 nPrevFieldIdx = idx;
     271             :                             }
     272             :                         }
     273             :                     }
     274             :                 }
     275             :             }
     276             : 
     277           2 :             const auto sortedFields = dag.getTopologicalOrdering();
     278           1 :             CPLAssert(sortedFields.size() == apoFieldDefn.size());
     279           2 :             for (int idx : sortedFields)
     280             :             {
     281             :                 // cppcheck-suppress containerOutOfBounds
     282           1 :                 poDefn->AddFieldDefn(apoFieldDefn[idx].get());
     283             :             }
     284             :         }
     285             :     }
     286             : 
     287          42 :     return bSuccess;
     288             : }
     289             : 
     290             : /************************************************************************/
     291             : /*                             ParseField()                             */
     292             : /************************************************************************/
     293             : 
     294          42 : bool OGRESRIJSONReader::ParseField(json_object *poObj)
     295             : {
     296          42 :     OGRFeatureDefn *poDefn = poLayer_->GetLayerDefn();
     297          42 :     CPLAssert(nullptr != poDefn);
     298             : 
     299          42 :     bool bSuccess = false;
     300             : 
     301             :     /* -------------------------------------------------------------------- */
     302             :     /*      Read collection of properties.                                  */
     303             :     /* -------------------------------------------------------------------- */
     304          42 :     json_object *poObjName = OGRGeoJSONFindMemberByName(poObj, "name");
     305          42 :     json_object *poObjType = OGRGeoJSONFindMemberByName(poObj, "type");
     306          42 :     if (nullptr != poObjName && nullptr != poObjType)
     307             :     {
     308          42 :         OGRFieldType eFieldType = OFTString;
     309          42 :         OGRFieldSubType eFieldSubType = OFSTNone;
     310          42 :         const char *pszObjName = json_object_get_string(poObjName);
     311          42 :         const char *pszObjType = json_object_get_string(poObjType);
     312          42 :         if (EQUAL(pszObjType, "esriFieldTypeString"))
     313             :         {
     314             :             // do nothing
     315             :         }
     316          34 :         else if (EQUAL(pszObjType, "esriFieldTypeOID"))
     317             :         {
     318          12 :             eFieldType = OFTInteger;
     319          12 :             poLayer_->SetFIDColumn(pszObjName);
     320             :         }
     321          22 :         else if (EQUAL(pszObjType, "esriFieldTypeSingle"))
     322             :         {
     323           2 :             eFieldType = OFTReal;
     324           2 :             eFieldSubType = OFSTFloat32;
     325             :         }
     326          20 :         else if (EQUAL(pszObjType, "esriFieldTypeDouble"))
     327             :         {
     328           8 :             eFieldType = OFTReal;
     329             :         }
     330          12 :         else if (EQUAL(pszObjType, "esriFieldTypeSmallInteger"))
     331             :         {
     332           2 :             eFieldType = OFTInteger;
     333           2 :             eFieldSubType = OFSTInt16;
     334             :         }
     335          10 :         else if (EQUAL(pszObjType, "esriFieldTypeInteger"))
     336             :         {
     337           8 :             eFieldType = OFTInteger;
     338             :         }
     339           2 :         else if (EQUAL(pszObjType, "esriFieldTypeDate"))
     340             :         {
     341           2 :             eFieldType = OFTDateTime;
     342             :         }
     343             :         else
     344             :         {
     345           0 :             CPLDebug("ESRIJSON",
     346             :                      "Unhandled fields[\"%s\"].type = %s. "
     347             :                      "Processing it as a String",
     348             :                      pszObjName, pszObjType);
     349             :         }
     350          42 :         OGRFieldDefn fldDefn(pszObjName, eFieldType);
     351          42 :         fldDefn.SetSubType(eFieldSubType);
     352             : 
     353          42 :         if (eFieldType != OFTDateTime)
     354             :         {
     355             :             json_object *const poObjLength =
     356          40 :                 OGRGeoJSONFindMemberByName(poObj, "length");
     357          58 :             if (poObjLength != nullptr &&
     358          18 :                 json_object_get_type(poObjLength) == json_type_int)
     359             :             {
     360          18 :                 const int nWidth = json_object_get_int(poObjLength);
     361             :                 // A dummy width of 2147483647 seems to indicate no known field with
     362             :                 // which in the OGR world is better modelled as 0 field width.
     363             :                 // (#6529)
     364          18 :                 if (nWidth != INT_MAX)
     365          18 :                     fldDefn.SetWidth(nWidth);
     366             :             }
     367             :         }
     368             : 
     369          42 :         json_object *poObjAlias = OGRGeoJSONFindMemberByName(poObj, "alias");
     370          42 :         if (poObjAlias && json_object_get_type(poObjAlias) == json_type_string)
     371             :         {
     372          42 :             const char *pszAlias = json_object_get_string(poObjAlias);
     373          42 :             if (strcmp(pszObjName, pszAlias) != 0)
     374          12 :                 fldDefn.SetAlternativeName(pszAlias);
     375             :         }
     376             : 
     377          42 :         poDefn->AddFieldDefn(&fldDefn);
     378             : 
     379          42 :         bSuccess = true;
     380             :     }
     381          42 :     return bSuccess;
     382             : }
     383             : 
     384             : /************************************************************************/
     385             : /*                           AddFeature                                 */
     386             : /************************************************************************/
     387             : 
     388          20 : bool OGRESRIJSONReader::AddFeature(OGRFeature *poFeature)
     389             : {
     390          20 :     if (nullptr == poFeature)
     391           0 :         return false;
     392             : 
     393          20 :     poLayer_->AddFeature(poFeature);
     394          20 :     delete poFeature;
     395             : 
     396          20 :     return true;
     397             : }
     398             : 
     399             : /************************************************************************/
     400             : /*                           EsriDateToOGRDate()                        */
     401             : /************************************************************************/
     402             : 
     403           2 : static void EsriDateToOGRDate(int64_t nVal, OGRField *psField)
     404             : {
     405           2 :     const auto nSeconds = nVal / 1000;
     406           2 :     const auto nMillisec = static_cast<int>(nVal % 1000);
     407             : 
     408             :     struct tm brokendowntime;
     409           2 :     CPLUnixTimeToYMDHMS(nSeconds, &brokendowntime);
     410             : 
     411           2 :     psField->Date.Year = static_cast<GInt16>(brokendowntime.tm_year + 1900);
     412           2 :     psField->Date.Month = static_cast<GByte>(brokendowntime.tm_mon + 1);
     413           2 :     psField->Date.Day = static_cast<GByte>(brokendowntime.tm_mday);
     414           2 :     psField->Date.Hour = static_cast<GByte>(brokendowntime.tm_hour);
     415           2 :     psField->Date.Minute = static_cast<GByte>(brokendowntime.tm_min);
     416           2 :     psField->Date.Second =
     417           2 :         static_cast<float>(brokendowntime.tm_sec + nMillisec / 1000.0);
     418           2 :     psField->Date.TZFlag = 100;
     419           2 :     psField->Date.Reserved = 0;
     420           2 : }
     421             : 
     422             : /************************************************************************/
     423             : /*                           ReadFeature()                              */
     424             : /************************************************************************/
     425             : 
     426          20 : OGRFeature *OGRESRIJSONReader::ReadFeature(json_object *poObj)
     427             : {
     428          20 :     CPLAssert(nullptr != poObj);
     429          20 :     CPLAssert(nullptr != poLayer_);
     430             : 
     431          20 :     OGRFeature *poFeature = new OGRFeature(poLayer_->GetLayerDefn());
     432             : 
     433             :     /* -------------------------------------------------------------------- */
     434             :     /*      Translate ESRIJSON "attributes" object to feature attributes.   */
     435             :     /* -------------------------------------------------------------------- */
     436          20 :     CPLAssert(nullptr != poFeature);
     437             : 
     438          20 :     json_object *poObjProps = OGRGeoJSONFindMemberByName(poObj, "attributes");
     439          39 :     if (nullptr != poObjProps &&
     440          19 :         json_object_get_type(poObjProps) == json_type_object)
     441             :     {
     442          19 :         OGRFieldDefn *poFieldDefn = nullptr;
     443             :         json_object_iter it;
     444          19 :         it.key = nullptr;
     445          19 :         it.val = nullptr;
     446          19 :         it.entry = nullptr;
     447          62 :         json_object_object_foreachC(poObjProps, it)
     448             :         {
     449          43 :             const int nField = poFeature->GetFieldIndex(it.key);
     450          43 :             if (nField >= 0)
     451             :             {
     452          43 :                 poFieldDefn = poFeature->GetFieldDefnRef(nField);
     453          43 :                 if (poFieldDefn && it.val != nullptr)
     454             :                 {
     455          43 :                     if (EQUAL(it.key, poLayer_->GetFIDColumn()))
     456          12 :                         poFeature->SetFID(json_object_get_int(it.val));
     457          43 :                     switch (poLayer_->GetLayerDefn()
     458          43 :                                 ->GetFieldDefn(nField)
     459          43 :                                 ->GetType())
     460             :                     {
     461          22 :                         case OFTInteger:
     462             :                         {
     463          22 :                             poFeature->SetField(nField,
     464          22 :                                                 json_object_get_int(it.val));
     465          22 :                             break;
     466             :                         }
     467          10 :                         case OFTReal:
     468             :                         {
     469          10 :                             poFeature->SetField(nField,
     470          10 :                                                 json_object_get_double(it.val));
     471          10 :                             break;
     472             :                         }
     473           2 :                         case OFTDateTime:
     474             :                         {
     475           2 :                             const auto nVal = json_object_get_int64(it.val);
     476           2 :                             EsriDateToOGRDate(
     477             :                                 nVal, poFeature->GetRawFieldRef(nField));
     478           2 :                             break;
     479             :                         }
     480           9 :                         default:
     481             :                         {
     482           9 :                             poFeature->SetField(nField,
     483             :                                                 json_object_get_string(it.val));
     484           9 :                             break;
     485             :                         }
     486             :                     }
     487             :                 }
     488             :             }
     489             :         }
     490             :     }
     491             : 
     492          20 :     const OGRwkbGeometryType eType = poLayer_->GetGeomType();
     493          20 :     if (eType == wkbNone)
     494           0 :         return poFeature;
     495             : 
     496             :     /* -------------------------------------------------------------------- */
     497             :     /*      Translate geometry sub-object of ESRIJSON Feature.               */
     498             :     /* -------------------------------------------------------------------- */
     499          20 :     json_object *poObjGeom = nullptr;
     500          20 :     json_object *poTmp = poObj;
     501             :     json_object_iter it;
     502          20 :     it.key = nullptr;
     503          20 :     it.val = nullptr;
     504          20 :     it.entry = nullptr;
     505          55 :     json_object_object_foreachC(poTmp, it)
     506             :     {
     507          37 :         if (EQUAL(it.key, "geometry"))
     508             :         {
     509          20 :             if (it.val != nullptr)
     510          18 :                 poObjGeom = it.val;
     511             :             // We're done.  They had 'geometry':null.
     512             :             else
     513           2 :                 return poFeature;
     514             :         }
     515             :     }
     516             : 
     517          18 :     if (nullptr != poObjGeom)
     518             :     {
     519          18 :         OGRGeometry *poGeometry = OGRESRIJSONReadGeometry(poObjGeom);
     520          18 :         if (nullptr != poGeometry)
     521             :         {
     522          18 :             poFeature->SetGeometryDirectly(poGeometry);
     523             :         }
     524             :     }
     525             : 
     526          18 :     return poFeature;
     527             : }
     528             : 
     529             : /************************************************************************/
     530             : /*                           ReadFeatureCollection()                    */
     531             : /************************************************************************/
     532             : 
     533          21 : OGRGeoJSONLayer *OGRESRIJSONReader::ReadFeatureCollection(json_object *poObj)
     534             : {
     535          21 :     CPLAssert(nullptr != poLayer_);
     536             : 
     537          21 :     json_object *poObjFeatures = OGRGeoJSONFindMemberByName(poObj, "features");
     538          21 :     if (nullptr == poObjFeatures)
     539             :     {
     540           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     541             :                  "Invalid FeatureCollection object. "
     542             :                  "Missing \'features\' member.");
     543           0 :         return nullptr;
     544             :     }
     545             : 
     546          21 :     if (json_type_array == json_object_get_type(poObjFeatures))
     547             :     {
     548          21 :         const auto nFeatures = json_object_array_length(poObjFeatures);
     549          41 :         for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i)
     550             :         {
     551             :             json_object *poObjFeature =
     552          20 :                 json_object_array_get_idx(poObjFeatures, i);
     553          40 :             if (poObjFeature != nullptr &&
     554          20 :                 json_object_get_type(poObjFeature) == json_type_object)
     555             :             {
     556             :                 OGRFeature *poFeature =
     557          20 :                     OGRESRIJSONReader::ReadFeature(poObjFeature);
     558          20 :                 AddFeature(poFeature);
     559             :             }
     560             :         }
     561             :     }
     562             : 
     563             :     // We're returning class member to follow the same pattern of
     564             :     // Read* functions call convention.
     565          21 :     CPLAssert(nullptr != poLayer_);
     566          21 :     return poLayer_;
     567             : }

Generated by: LCOV version 1.14