LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogrgeojsonutils.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 487 547 89.0 %
Date: 2025-05-31 00:00:17 Functions: 34 34 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implementation of private utilities used within OGR GeoJSON Driver.
       5             :  * Author:   Mateusz Loskot, mateusz@loskot.net
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2007, Mateusz Loskot
       9             :  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "ogrgeojsonutils.h"
      15             : #include <assert.h>
      16             : #include "cpl_port.h"
      17             : #include "cpl_conv.h"
      18             : #include "cpl_json_streaming_parser.h"
      19             : #include "ogr_geometry.h"
      20             : #include <json.h>  // JSON-C
      21             : 
      22             : #include <algorithm>
      23             : #include <memory>
      24             : 
      25             : const char szESRIJSonFeaturesGeometryRings[] =
      26             :     "{\"features\":[{\"geometry\":{\"rings\":[";
      27             : 
      28             : // Cf https://github.com/OSGeo/gdal/issues/9996#issuecomment-2129845692
      29             : const char szESRIJSonFeaturesAttributes[] = "{\"features\":[{\"attributes\":{";
      30             : 
      31             : /************************************************************************/
      32             : /*                           SkipUTF8BOM()                              */
      33             : /************************************************************************/
      34             : 
      35      278315 : static void SkipUTF8BOM(const char *&pszText)
      36             : {
      37             :     /* Skip UTF-8 BOM (#5630) */
      38      278315 :     const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
      39      278315 :     if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
      40           9 :         pszText += 3;
      41      278315 : }
      42             : 
      43             : /************************************************************************/
      44             : /*                           IsJSONObject()                             */
      45             : /************************************************************************/
      46             : 
      47      276240 : static bool IsJSONObject(const char *pszText)
      48             : {
      49      276240 :     if (nullptr == pszText)
      50           0 :         return false;
      51             : 
      52      276240 :     SkipUTF8BOM(pszText);
      53             : 
      54             :     /* -------------------------------------------------------------------- */
      55             :     /*      This is a primitive test, but we need to perform it fast.       */
      56             :     /* -------------------------------------------------------------------- */
      57      278044 :     while (*pszText != '\0' && isspace(static_cast<unsigned char>(*pszText)))
      58        1804 :         pszText++;
      59             : 
      60      276240 :     const char *const apszPrefix[] = {"loadGeoJSON(", "jsonp("};
      61      828690 :     for (size_t iP = 0; iP < sizeof(apszPrefix) / sizeof(apszPrefix[0]); iP++)
      62             :     {
      63      552465 :         if (strncmp(pszText, apszPrefix[iP], strlen(apszPrefix[iP])) == 0)
      64             :         {
      65          15 :             pszText += strlen(apszPrefix[iP]);
      66          15 :             break;
      67             :         }
      68             :     }
      69             : 
      70      276240 :     if (*pszText != '{')
      71      271542 :         return false;
      72             : 
      73        4698 :     return true;
      74             : }
      75             : 
      76             : /************************************************************************/
      77             : /*                           GetTopLevelType()                          */
      78             : /************************************************************************/
      79             : 
      80        2181 : static std::string GetTopLevelType(const char *pszText)
      81             : {
      82        2181 :     if (!strstr(pszText, "\"type\""))
      83         106 :         return std::string();
      84             : 
      85        2075 :     SkipUTF8BOM(pszText);
      86             : 
      87             :     struct MyParser : public CPLJSonStreamingParser
      88             :     {
      89             :         std::string m_osLevel{};
      90             :         bool m_bInTopLevelType = false;
      91             :         std::string m_osTopLevelTypeValue{};
      92             : 
      93        2629 :         void StartObjectMember(const char *pszKey, size_t nLength) override
      94             :         {
      95        2629 :             m_bInTopLevelType = false;
      96        4766 :             if (nLength == strlen("type") && strcmp(pszKey, "type") == 0 &&
      97        2137 :                 m_osLevel == "{")
      98             :             {
      99        2032 :                 m_bInTopLevelType = true;
     100             :             }
     101        2629 :         }
     102             : 
     103        2352 :         void String(const char *pszValue, size_t nLength) override
     104             :         {
     105        2352 :             if (m_bInTopLevelType)
     106             :             {
     107        2032 :                 m_osTopLevelTypeValue.assign(pszValue, nLength);
     108        2032 :                 StopParsing();
     109             :             }
     110        2352 :         }
     111             : 
     112        2244 :         void StartObject() override
     113             :         {
     114        2244 :             m_osLevel += '{';
     115        2244 :             m_bInTopLevelType = false;
     116        2244 :         }
     117             : 
     118         168 :         void EndObject() override
     119             :         {
     120         168 :             if (!m_osLevel.empty())
     121         168 :                 m_osLevel.pop_back();
     122         168 :             m_bInTopLevelType = false;
     123         168 :         }
     124             : 
     125         120 :         void StartArray() override
     126             :         {
     127         120 :             m_osLevel += '[';
     128         120 :             m_bInTopLevelType = false;
     129         120 :         }
     130             : 
     131          70 :         void EndArray() override
     132             :         {
     133          70 :             if (!m_osLevel.empty())
     134          70 :                 m_osLevel.pop_back();
     135          70 :             m_bInTopLevelType = false;
     136          70 :         }
     137             :     };
     138             : 
     139        4150 :     MyParser oParser;
     140        2075 :     oParser.Parse(pszText, strlen(pszText), true);
     141        2075 :     return oParser.m_osTopLevelTypeValue;
     142             : }
     143             : 
     144             : /************************************************************************/
     145             : /*                           GetCompactJSon()                           */
     146             : /************************************************************************/
     147             : 
     148        3024 : static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize)
     149             : {
     150             :     /* Skip UTF-8 BOM (#5630) */
     151        3024 :     const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
     152        3024 :     if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
     153           6 :         pszText += 3;
     154             : 
     155        3024 :     CPLString osWithoutSpace;
     156        3024 :     bool bInString = false;
     157     2550230 :     for (int i = 0; pszText[i] != '\0' && osWithoutSpace.size() < nMaxSize; i++)
     158             :     {
     159     2547210 :         if (bInString)
     160             :         {
     161      840630 :             if (pszText[i] == '\\')
     162             :             {
     163        2060 :                 osWithoutSpace += pszText[i];
     164        2060 :                 if (pszText[i + 1] == '\0')
     165           0 :                     break;
     166        2060 :                 osWithoutSpace += pszText[i + 1];
     167        2060 :                 i++;
     168             :             }
     169      838570 :             else if (pszText[i] == '"')
     170             :             {
     171       60963 :                 bInString = false;
     172       60963 :                 osWithoutSpace += '"';
     173             :             }
     174             :             else
     175             :             {
     176      777607 :                 osWithoutSpace += pszText[i];
     177             :             }
     178             :         }
     179     1706580 :         else if (pszText[i] == '"')
     180             :         {
     181       60970 :             bInString = true;
     182       60970 :             osWithoutSpace += '"';
     183             :         }
     184     1645610 :         else if (!isspace(static_cast<unsigned char>(pszText[i])))
     185             :         {
     186      739376 :             osWithoutSpace += pszText[i];
     187             :         }
     188             :     }
     189        3024 :     return osWithoutSpace;
     190             : }
     191             : 
     192             : /************************************************************************/
     193             : /*                          IsGeoJSONLikeObject()                       */
     194             : /************************************************************************/
     195             : 
     196      117499 : static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence,
     197             :                                 bool &bReadMoreBytes, GDALOpenInfo *poOpenInfo,
     198             :                                 const char *pszExpectedDriverName)
     199             : {
     200      117499 :     bMightBeSequence = false;
     201      117499 :     bReadMoreBytes = false;
     202             : 
     203      117499 :     if (!IsJSONObject(pszText))
     204      115492 :         return false;
     205             : 
     206        4014 :     const std::string osTopLevelType = GetTopLevelType(pszText);
     207        2007 :     if (osTopLevelType == "Topology")
     208           8 :         return false;
     209             : 
     210        2038 :     if (poOpenInfo->IsSingleAllowedDriver(pszExpectedDriverName) &&
     211          39 :         GDALGetDriverByName(pszExpectedDriverName))
     212             :     {
     213          39 :         return true;
     214             :     }
     215             : 
     216        3922 :     if ((!poOpenInfo->papszAllowedDrivers ||
     217           2 :          CSLFindString(poOpenInfo->papszAllowedDrivers, "JSONFG") >= 0) &&
     218        1962 :         GDALGetDriverByName("JSONFG") && JSONFGIsObject(pszText, poOpenInfo))
     219             :     {
     220         284 :         return false;
     221             :     }
     222             : 
     223        1676 :     if (osTopLevelType == "FeatureCollection")
     224             :     {
     225        1101 :         return true;
     226             :     }
     227             : 
     228        1150 :     const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
     229         580 :     if (osWithoutSpace.find("{\"features\":[") == 0 &&
     230         580 :         osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) != 0 &&
     231           3 :         osWithoutSpace.find(szESRIJSonFeaturesAttributes) != 0)
     232             :     {
     233           3 :         return true;
     234             :     }
     235             : 
     236             :     // See
     237             :     // https://raw.githubusercontent.com/icepack/icepack-data/master/meshes/larsen/larsen_inflow.geojson
     238             :     // "{"crs":...,"features":[..."
     239             :     // or
     240             :     // https://gist.githubusercontent.com/NiklasDallmann/27e339dd78d1942d524fbcd179f9fdcf/raw/527a8319d75a9e29446a32a19e4c902213b0d668/42XR9nLAh8Poh9Xmniqh3Bs9iisNm74mYMC56v3Wfyo=_isochrones_fails.geojson
     241             :     // "{"bbox":...,"features":[..."
     242         572 :     if (osWithoutSpace.find(",\"features\":[") != std::string::npos)
     243             :     {
     244          38 :         return !ESRIJSONIsObject(pszText, poOpenInfo);
     245             :     }
     246             : 
     247             :     // See https://github.com/OSGeo/gdal/issues/2720
     248        1065 :     if (osWithoutSpace.find("{\"coordinates\":[") == 0 ||
     249             :         // and https://github.com/OSGeo/gdal/issues/2787
     250        1059 :         osWithoutSpace.find("{\"geometry\":{\"coordinates\":[") == 0 ||
     251             :         // and https://github.com/qgis/QGIS/issues/61266
     252         528 :         osWithoutSpace.find(
     253         522 :             "{\"geometry\":{\"type\":\"Point\",\"coordinates\":[") == 0 ||
     254         522 :         osWithoutSpace.find(
     255         519 :             "{\"geometry\":{\"type\":\"LineString\",\"coordinates\":[") == 0 ||
     256         519 :         osWithoutSpace.find(
     257         516 :             "{\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[") == 0 ||
     258         516 :         osWithoutSpace.find(
     259         513 :             "{\"geometry\":{\"type\":\"MultiPoint\",\"coordinates\":[") == 0 ||
     260         513 :         osWithoutSpace.find(
     261             :             "{\"geometry\":{\"type\":\"MultiLineString\",\"coordinates\":[") ==
     262         510 :             0 ||
     263         510 :         osWithoutSpace.find(
     264             :             "{\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[") ==
     265        1065 :             0 ||
     266         507 :         osWithoutSpace.find("{\"geometry\":{\"type\":\"GeometryCollection\","
     267             :                             "\"geometries\":[") == 0)
     268             :     {
     269          30 :         return true;
     270             :     }
     271             : 
     272        1140 :     if (osTopLevelType == "Feature" || osTopLevelType == "Point" ||
     273         825 :         osTopLevelType == "LineString" || osTopLevelType == "Polygon" ||
     274         465 :         osTopLevelType == "MultiPoint" || osTopLevelType == "MultiLineString" ||
     275         939 :         osTopLevelType == "MultiPolygon" ||
     276          60 :         osTopLevelType == "GeometryCollection")
     277             :     {
     278         450 :         bMightBeSequence = true;
     279         450 :         return true;
     280             :     }
     281             : 
     282             :     // See https://github.com/OSGeo/gdal/issues/3280
     283          54 :     if (osWithoutSpace.find("{\"properties\":{") == 0)
     284             :     {
     285           2 :         bMightBeSequence = true;
     286           2 :         bReadMoreBytes = true;
     287           2 :         return false;
     288             :     }
     289             : 
     290          52 :     return false;
     291             : }
     292             : 
     293          26 : static bool IsGeoJSONLikeObject(const char *pszText, GDALOpenInfo *poOpenInfo,
     294             :                                 const char *pszExpectedDriverName)
     295             : {
     296             :     bool bMightBeSequence;
     297             :     bool bReadMoreBytes;
     298          26 :     return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
     299          52 :                                poOpenInfo, pszExpectedDriverName);
     300             : }
     301             : 
     302             : /************************************************************************/
     303             : /*                       ESRIJSONIsObject()                             */
     304             : /************************************************************************/
     305             : 
     306       53297 : bool ESRIJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
     307             : {
     308       53297 :     if (!IsJSONObject(pszText))
     309       53053 :         return false;
     310             : 
     311         246 :     if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON") &&
     312           2 :         GDALGetDriverByName("ESRIJSON"))
     313             :     {
     314           2 :         return true;
     315             :     }
     316             : 
     317         242 :     if (  // ESRI Json geometry
     318         242 :         (strstr(pszText, "\"geometryType\"") != nullptr &&
     319          62 :          strstr(pszText, "\"esriGeometry") != nullptr)
     320             : 
     321             :         // ESRI Json "FeatureCollection"
     322         180 :         || strstr(pszText, "\"fieldAliases\"") != nullptr
     323             : 
     324             :         // ESRI Json "FeatureCollection"
     325         180 :         || (strstr(pszText, "\"fields\"") != nullptr &&
     326           0 :             strstr(pszText, "\"esriFieldType") != nullptr))
     327             :     {
     328          62 :         return true;
     329             :     }
     330             : 
     331         360 :     const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
     332         360 :     if (osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) == 0 ||
     333         360 :         osWithoutSpace.find(szESRIJSonFeaturesAttributes) == 0 ||
     334         180 :         osWithoutSpace.find("\"spatialReference\":{\"wkid\":") !=
     335             :             std::string::npos)
     336             :     {
     337           4 :         return true;
     338             :     }
     339             : 
     340         176 :     return false;
     341             : }
     342             : 
     343             : /************************************************************************/
     344             : /*                       TopoJSONIsObject()                             */
     345             : /************************************************************************/
     346             : 
     347       53229 : bool TopoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
     348             : {
     349       53229 :     if (!IsJSONObject(pszText))
     350       53053 :         return false;
     351             : 
     352         178 :     if (poOpenInfo->IsSingleAllowedDriver("TopoJSON") &&
     353           2 :         GDALGetDriverByName("TopoJSON"))
     354             :     {
     355           2 :         return true;
     356             :     }
     357             : 
     358         174 :     return GetTopLevelType(pszText) == "Topology";
     359             : }
     360             : 
     361             : /************************************************************************/
     362             : /*                      IsLikelyNewlineSequenceGeoJSON()                */
     363             : /************************************************************************/
     364             : 
     365             : static GDALIdentifyEnum
     366         424 : IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, const GByte *pabyHeader,
     367             :                                const char *pszFileContent)
     368             : {
     369         424 :     const size_t nBufferSize = 4096 * 10;
     370         848 :     std::vector<GByte> abyBuffer;
     371         424 :     abyBuffer.resize(nBufferSize + 1);
     372             : 
     373         424 :     int nCurlLevel = 0;
     374         424 :     bool bInString = false;
     375         424 :     bool bLastIsEscape = false;
     376         424 :     bool bFirstIter = true;
     377         424 :     bool bEOLFound = false;
     378         424 :     int nCountObject = 0;
     379             :     while (true)
     380             :     {
     381             :         size_t nRead;
     382         556 :         bool bEnd = false;
     383         556 :         if (bFirstIter)
     384             :         {
     385         424 :             const char *pszText =
     386         424 :                 pszFileContent ? pszFileContent
     387             :                                : reinterpret_cast<const char *>(pabyHeader);
     388         424 :             assert(pszText);
     389         424 :             nRead = std::min(strlen(pszText), nBufferSize);
     390         424 :             memcpy(abyBuffer.data(), pszText, nRead);
     391         424 :             bFirstIter = false;
     392         424 :             if (fpL)
     393             :             {
     394         143 :                 VSIFSeekL(fpL, nRead, SEEK_SET);
     395             :             }
     396             :         }
     397             :         else
     398             :         {
     399         132 :             nRead = VSIFReadL(abyBuffer.data(), 1, nBufferSize, fpL);
     400         132 :             bEnd = nRead < nBufferSize;
     401             :         }
     402     3256870 :         for (size_t i = 0; i < nRead; i++)
     403             :         {
     404     3256410 :             if (nCurlLevel == 0)
     405             :             {
     406         696 :                 if (abyBuffer[i] == '{')
     407             :                 {
     408         517 :                     nCountObject++;
     409         517 :                     if (nCountObject == 2)
     410             :                     {
     411          93 :                         break;
     412             :                     }
     413         424 :                     nCurlLevel++;
     414             :                 }
     415         179 :                 else if (nCountObject == 1 && abyBuffer[i] == '\n')
     416             :                 {
     417         128 :                     bEOLFound = true;
     418             :                 }
     419          51 :                 else if (!isspace(static_cast<unsigned char>(abyBuffer[i])))
     420             :                 {
     421           3 :                     return GDAL_IDENTIFY_FALSE;
     422             :                 }
     423             :             }
     424     3255720 :             else if (bInString)
     425             :             {
     426       94065 :                 if (bLastIsEscape)
     427             :                 {
     428         798 :                     bLastIsEscape = false;
     429             :                 }
     430       93267 :                 else if (abyBuffer[i] == '\\')
     431             :                 {
     432         798 :                     bLastIsEscape = true;
     433             :                 }
     434       92469 :                 else if (abyBuffer[i] == '"')
     435             :                 {
     436        3080 :                     bInString = false;
     437             :                 }
     438             :             }
     439     3161650 :             else if (abyBuffer[i] == '"')
     440             :             {
     441        3080 :                 bInString = true;
     442             :             }
     443     3158570 :             else if (abyBuffer[i] == '{')
     444             :             {
     445         307 :                 nCurlLevel++;
     446             :             }
     447     3158260 :             else if (abyBuffer[i] == '}')
     448             :             {
     449         731 :                 nCurlLevel--;
     450             :             }
     451             :         }
     452         553 :         if (!fpL || bEnd || nCountObject == 2)
     453             :             break;
     454         132 :     }
     455         421 :     if (bEOLFound && nCountObject == 2)
     456          93 :         return GDAL_IDENTIFY_TRUE;
     457         328 :     return GDAL_IDENTIFY_UNKNOWN;
     458             : }
     459             : 
     460             : /************************************************************************/
     461             : /*                           GeoJSONFileIsObject()                      */
     462             : /************************************************************************/
     463             : 
     464       53927 : static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo)
     465             : {
     466             :     // By default read first 6000 bytes.
     467             :     // 6000 was chosen as enough bytes to
     468             :     // enable all current tests to pass.
     469             : 
     470       53927 :     if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
     471             :     {
     472       48944 :         return false;
     473             :     }
     474             : 
     475        4983 :     bool bMightBeSequence = false;
     476        4983 :     bool bReadMoreBytes = false;
     477        4983 :     if (!IsGeoJSONLikeObject(
     478        4983 :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     479             :             bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON"))
     480             :     {
     481        4322 :         if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
     482           2 :               poOpenInfo->TryToIngest(1000 * 1000) &&
     483           2 :               !IsGeoJSONLikeObject(
     484           2 :                   reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     485             :                   bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON")))
     486             :         {
     487        4320 :             return false;
     488             :         }
     489             :     }
     490             : 
     491         748 :     return !(bMightBeSequence && IsLikelyNewlineSequenceGeoJSON(
     492          85 :                                      poOpenInfo->fpL, poOpenInfo->pabyHeader,
     493         663 :                                      nullptr) == GDAL_IDENTIFY_TRUE);
     494             : }
     495             : 
     496             : /************************************************************************/
     497             : /*                           GeoJSONIsObject()                          */
     498             : /************************************************************************/
     499             : 
     500       54801 : bool GeoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
     501             : {
     502       54801 :     bool bMightBeSequence = false;
     503       54801 :     bool bReadMoreBytes = false;
     504       54801 :     if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
     505             :                              poOpenInfo, "GeoJSON"))
     506             :     {
     507       53927 :         return false;
     508             :     }
     509             : 
     510        1149 :     return !(bMightBeSequence &&
     511         275 :              IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) ==
     512         874 :                  GDAL_IDENTIFY_TRUE);
     513             : }
     514             : 
     515             : /************************************************************************/
     516             : /*                        GeoJSONSeqFileIsObject()                      */
     517             : /************************************************************************/
     518             : 
     519       53322 : static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo)
     520             : {
     521             :     // By default read first 6000 bytes.
     522             :     // 6000 was chosen as enough bytes to
     523             :     // enable all current tests to pass.
     524             : 
     525       53322 :     if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
     526             :     {
     527       48940 :         return false;
     528             :     }
     529             : 
     530        4382 :     const char *pszText =
     531             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     532        4382 :     if (pszText[0] == '\x1e')
     533          26 :         return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq");
     534             : 
     535        4356 :     bool bMightBeSequence = false;
     536        4356 :     bool bReadMoreBytes = false;
     537        4356 :     if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
     538             :                              poOpenInfo, "GeoJSONSeq"))
     539             :     {
     540        4298 :         if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
     541           0 :               poOpenInfo->TryToIngest(1000 * 1000) &&
     542           0 :               IsGeoJSONLikeObject(
     543           0 :                   reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     544             :                   bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSONSeq")))
     545             :         {
     546        4298 :             return false;
     547             :         }
     548             :     }
     549             : 
     550          58 :     if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq") &&
     551           2 :         IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL, poOpenInfo->pabyHeader,
     552          60 :                                        nullptr) != GDAL_IDENTIFY_FALSE &&
     553           2 :         GDALGetDriverByName("GeoJSONSeq"))
     554             :     {
     555           2 :         return true;
     556             :     }
     557             : 
     558         112 :     return bMightBeSequence && IsLikelyNewlineSequenceGeoJSON(
     559          56 :                                    poOpenInfo->fpL, poOpenInfo->pabyHeader,
     560          56 :                                    nullptr) == GDAL_IDENTIFY_TRUE;
     561             : }
     562             : 
     563       53331 : bool GeoJSONSeqIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
     564             : {
     565       53331 :     if (pszText[0] == '\x1e')
     566           0 :         return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq");
     567             : 
     568       53331 :     bool bMightBeSequence = false;
     569       53331 :     bool bReadMoreBytes = false;
     570       53331 :     if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
     571             :                              poOpenInfo, "GeoJSONSeq"))
     572             :     {
     573       53325 :         return false;
     574             :     }
     575             : 
     576           6 :     if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq") &&
     577           0 :         IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) !=
     578           6 :             GDAL_IDENTIFY_FALSE &&
     579           0 :         GDALGetDriverByName("GeoJSONSeq"))
     580             :     {
     581           0 :         return true;
     582             :     }
     583             : 
     584          12 :     return bMightBeSequence &&
     585           6 :            IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) ==
     586           6 :                GDAL_IDENTIFY_TRUE;
     587             : }
     588             : 
     589             : /************************************************************************/
     590             : /*                        JSONFGFileIsObject()                          */
     591             : /************************************************************************/
     592             : 
     593       48754 : static bool JSONFGFileIsObject(GDALOpenInfo *poOpenInfo)
     594             : {
     595             :     // 6000 somewhat arbitrary. Based on other JSON-like drivers
     596       48754 :     if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
     597             :     {
     598       47355 :         return false;
     599             :     }
     600             : 
     601        1399 :     const char *pszText =
     602             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     603        1399 :     return JSONFGIsObject(pszText, poOpenInfo);
     604             : }
     605             : 
     606       52215 : bool JSONFGIsObject(const char *pszText, GDALOpenInfo *poOpenInfo)
     607             : {
     608       52215 :     if (!IsJSONObject(pszText))
     609       49944 :         return false;
     610             : 
     611        2273 :     if (poOpenInfo->IsSingleAllowedDriver("JSONFG") &&
     612           2 :         GDALGetDriverByName("JSONFG"))
     613             :     {
     614           2 :         return true;
     615             :     }
     616             : 
     617        4538 :     const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
     618             : 
     619             :     // In theory, conformsTo should be required, but let be lax...
     620             :     {
     621        2269 :         const auto nPos = osWithoutSpace.find("\"conformsTo\":[");
     622        2269 :         if (nPos != std::string::npos)
     623             :         {
     624         564 :             for (const char *pszVersion : {"0.1", "0.2", "0.3"})
     625             :             {
     626         564 :                 if (osWithoutSpace.find(
     627             :                         CPLSPrintf("\"[ogc-json-fg-1-%s:core]\"", pszVersion),
     628         564 :                         nPos) != std::string::npos ||
     629           0 :                     osWithoutSpace.find(
     630             :                         CPLSPrintf(
     631             :                             "\"http://www.opengis.net/spec/json-fg-1/%s\"",
     632             :                             pszVersion),
     633             :                         nPos) != std::string::npos)
     634             :                 {
     635         564 :                     return true;
     636             :                 }
     637             :             }
     638             :         }
     639             :     }
     640             : 
     641        3410 :     if (osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos ||
     642        1705 :         osWithoutSpace.find("\"place\":{\"coordinates\":") !=
     643        1705 :             std::string::npos ||
     644        3410 :         osWithoutSpace.find("\"time\":{\"date\":") != std::string::npos ||
     645        5115 :         osWithoutSpace.find("\"time\":{\"timestamp\":") != std::string::npos ||
     646        1705 :         osWithoutSpace.find("\"time\":{\"interval\":") != std::string::npos)
     647             :     {
     648           0 :         return true;
     649             :     }
     650             : 
     651        3406 :     if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos ||
     652        1701 :         osWithoutSpace.find("\"featureType\":") != std::string::npos)
     653             :     {
     654             :         // Check that coordRefSys and/or featureType are either at the
     655             :         // FeatureCollection or Feature level
     656             :         struct MyParser : public CPLJSonStreamingParser
     657             :         {
     658             :             bool m_bFoundJSONFGFeatureType = false;
     659             :             bool m_bFoundJSONFGCoordrefSys = false;
     660             :             std::string m_osLevel{};
     661             : 
     662          92 :             void StartObjectMember(const char *pszKey, size_t nLength) override
     663             :             {
     664          92 :                 if (nLength == strlen("featureType") &&
     665          16 :                     strcmp(pszKey, "featureType") == 0)
     666             :                 {
     667           6 :                     m_bFoundJSONFGFeatureType =
     668          12 :                         (m_osLevel == "{" ||   // At FeatureCollection level
     669           6 :                          m_osLevel == "{[{");  // At Feature level
     670           6 :                     if (m_bFoundJSONFGFeatureType)
     671           0 :                         StopParsing();
     672             :                 }
     673          86 :                 else if (nLength == strlen("coordRefSys") &&
     674          10 :                          strcmp(pszKey, "coordRefSys") == 0)
     675             :                 {
     676           4 :                     m_bFoundJSONFGCoordrefSys =
     677           8 :                         (m_osLevel == "{" ||   // At FeatureCollection level
     678           4 :                          m_osLevel == "{[{");  // At Feature level
     679           4 :                     if (m_bFoundJSONFGCoordrefSys)
     680           4 :                         StopParsing();
     681             :                 }
     682          92 :             }
     683             : 
     684          44 :             void StartObject() override
     685             :             {
     686          44 :                 m_osLevel += '{';
     687          44 :             }
     688             : 
     689          36 :             void EndObject() override
     690             :             {
     691          36 :                 if (!m_osLevel.empty())
     692          36 :                     m_osLevel.pop_back();
     693          36 :             }
     694             : 
     695          28 :             void StartArray() override
     696             :             {
     697          28 :                 m_osLevel += '[';
     698          28 :             }
     699             : 
     700          24 :             void EndArray() override
     701             :             {
     702          24 :                 if (!m_osLevel.empty())
     703          24 :                     m_osLevel.pop_back();
     704          24 :             }
     705             :         };
     706             : 
     707          10 :         MyParser oParser;
     708          10 :         oParser.Parse(pszText, strlen(pszText), true);
     709          10 :         if (oParser.m_bFoundJSONFGFeatureType ||
     710          10 :             oParser.m_bFoundJSONFGCoordrefSys)
     711             :         {
     712           4 :             return true;
     713             :         }
     714             :     }
     715             : 
     716        1701 :     return false;
     717             : }
     718             : 
     719             : /************************************************************************/
     720             : /*                           IsLikelyESRIJSONURL()                      */
     721             : /************************************************************************/
     722             : 
     723         131 : static bool IsLikelyESRIJSONURL(const char *pszURL)
     724             : {
     725             :     // URLs with f=json are strong candidates for ESRI JSON services
     726             :     // except if they have "/items?", in which case they are likely OAPIF
     727         262 :     return (strstr(pszURL, "f=json") != nullptr ||
     728         131 :             strstr(pszURL, "f=pjson") != nullptr ||
     729         262 :             strstr(pszURL, "resultRecordCount=") != nullptr) &&
     730         131 :            strstr(pszURL, "/items?") == nullptr;
     731             : }
     732             : 
     733             : /************************************************************************/
     734             : /*                           GeoJSONGetSourceType()                     */
     735             : /************************************************************************/
     736             : 
     737       54324 : GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo)
     738             : {
     739       54324 :     GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
     740             : 
     741             :     // NOTE: Sometimes URL ends with .geojson token, for example
     742             :     //       http://example/path/2232.geojson
     743             :     //       It's important to test beginning of source first.
     744       54324 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:http://") ||
     745       54324 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:https://") ||
     746       54324 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:ftp://"))
     747             :     {
     748           0 :         srcType = eGeoJSONSourceService;
     749             :     }
     750       54324 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
     751       54289 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
     752       54287 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
     753             :     {
     754          37 :         if (poOpenInfo->IsSingleAllowedDriver("GeoJSON"))
     755             :         {
     756           1 :             return eGeoJSONSourceService;
     757             :         }
     758          36 :         if ((strstr(poOpenInfo->pszFilename, "SERVICE=WFS") ||
     759          36 :              strstr(poOpenInfo->pszFilename, "service=WFS") ||
     760          36 :              strstr(poOpenInfo->pszFilename, "service=wfs")) &&
     761           0 :             !strstr(poOpenInfo->pszFilename, "json"))
     762             :         {
     763           0 :             return eGeoJSONSourceUnknown;
     764             :         }
     765          36 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     766             :         {
     767           0 :             return eGeoJSONSourceUnknown;
     768             :         }
     769          36 :         srcType = eGeoJSONSourceService;
     770             :     }
     771       54287 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSON:"))
     772             :     {
     773             :         VSIStatBufL sStat;
     774           0 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("GeoJSON:"), &sStat) == 0)
     775             :         {
     776           0 :             return eGeoJSONSourceFile;
     777             :         }
     778           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("GeoJSON:");
     779           0 :         if (GeoJSONIsObject(pszText, poOpenInfo))
     780           0 :             return eGeoJSONSourceText;
     781           0 :         return eGeoJSONSourceUnknown;
     782             :     }
     783       54287 :     else if (GeoJSONIsObject(poOpenInfo->pszFilename, poOpenInfo))
     784             :     {
     785         360 :         srcType = eGeoJSONSourceText;
     786             :     }
     787       53927 :     else if (GeoJSONFileIsObject(poOpenInfo))
     788             :     {
     789         635 :         srcType = eGeoJSONSourceFile;
     790             :     }
     791             : 
     792       54323 :     return srcType;
     793             : }
     794             : 
     795             : /************************************************************************/
     796             : /*                     ESRIJSONDriverGetSourceType()                    */
     797             : /************************************************************************/
     798             : 
     799       53276 : GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
     800             : {
     801       53276 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:http://") ||
     802       53276 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:https://") ||
     803       53276 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:ftp://"))
     804             :     {
     805           0 :         return eGeoJSONSourceService;
     806             :     }
     807       53276 :     else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
     808       53259 :              STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
     809       53258 :              STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
     810             :     {
     811          18 :         if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON"))
     812             :         {
     813           1 :             return eGeoJSONSourceService;
     814             :         }
     815          17 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     816             :         {
     817           0 :             return eGeoJSONSourceService;
     818             :         }
     819          17 :         return eGeoJSONSourceUnknown;
     820             :     }
     821             : 
     822       53258 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:"))
     823             :     {
     824             :         VSIStatBufL sStat;
     825           2 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("ESRIJSON:"), &sStat) ==
     826             :             0)
     827             :         {
     828           2 :             return eGeoJSONSourceFile;
     829             :         }
     830           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("ESRIJSON:");
     831           0 :         if (ESRIJSONIsObject(pszText, poOpenInfo))
     832           0 :             return eGeoJSONSourceText;
     833           0 :         return eGeoJSONSourceUnknown;
     834             :     }
     835             : 
     836       53256 :     if (poOpenInfo->fpL == nullptr)
     837             :     {
     838       48941 :         const char *pszText = poOpenInfo->pszFilename;
     839       48941 :         if (ESRIJSONIsObject(pszText, poOpenInfo))
     840           4 :             return eGeoJSONSourceText;
     841       48937 :         return eGeoJSONSourceUnknown;
     842             :     }
     843             : 
     844             :     // By default read first 6000 bytes.
     845             :     // 6000 was chosen as enough bytes to
     846             :     // enable all current tests to pass.
     847        4315 :     if (!poOpenInfo->TryToIngest(6000))
     848             :     {
     849           0 :         return eGeoJSONSourceUnknown;
     850             :     }
     851             : 
     852        8630 :     if (poOpenInfo->pabyHeader != nullptr &&
     853        4315 :         ESRIJSONIsObject(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     854             :                          poOpenInfo))
     855             :     {
     856          32 :         return eGeoJSONSourceFile;
     857             :     }
     858        4283 :     return eGeoJSONSourceUnknown;
     859             : }
     860             : 
     861             : /************************************************************************/
     862             : /*                     TopoJSONDriverGetSourceType()                    */
     863             : /************************************************************************/
     864             : 
     865       53253 : GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
     866             : {
     867       53253 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:http://") ||
     868       53253 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:https://") ||
     869       53253 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:ftp://"))
     870             :     {
     871           0 :         return eGeoJSONSourceService;
     872             :     }
     873       53253 :     else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
     874       53228 :              STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
     875       53226 :              STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
     876             :     {
     877          27 :         if (poOpenInfo->IsSingleAllowedDriver("TOPOJSON"))
     878             :         {
     879           1 :             return eGeoJSONSourceService;
     880             :         }
     881          26 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     882             :         {
     883           0 :             return eGeoJSONSourceUnknown;
     884             :         }
     885          26 :         return eGeoJSONSourceService;
     886             :     }
     887             : 
     888       53226 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:"))
     889             :     {
     890             :         VSIStatBufL sStat;
     891           0 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("TopoJSON:"), &sStat) ==
     892             :             0)
     893             :         {
     894           0 :             return eGeoJSONSourceFile;
     895             :         }
     896           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("TopoJSON:");
     897           0 :         if (TopoJSONIsObject(pszText, poOpenInfo))
     898           0 :             return eGeoJSONSourceText;
     899           0 :         return eGeoJSONSourceUnknown;
     900             :     }
     901             : 
     902       53226 :     if (poOpenInfo->fpL == nullptr)
     903             :     {
     904       48937 :         const char *pszText = poOpenInfo->pszFilename;
     905       48937 :         if (TopoJSONIsObject(pszText, poOpenInfo))
     906           0 :             return eGeoJSONSourceText;
     907       48937 :         return eGeoJSONSourceUnknown;
     908             :     }
     909             : 
     910             :     // By default read first 6000 bytes.
     911             :     // 6000 was chosen as enough bytes to
     912             :     // enable all current tests to pass.
     913        4289 :     if (!poOpenInfo->TryToIngest(6000))
     914             :     {
     915           0 :         return eGeoJSONSourceUnknown;
     916             :     }
     917             : 
     918        8578 :     if (poOpenInfo->pabyHeader != nullptr &&
     919        4289 :         TopoJSONIsObject(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     920             :                          poOpenInfo))
     921             :     {
     922          10 :         return eGeoJSONSourceFile;
     923             :     }
     924        4279 :     return eGeoJSONSourceUnknown;
     925             : }
     926             : 
     927             : /************************************************************************/
     928             : /*                          GeoJSONSeqGetSourceType()                   */
     929             : /************************************************************************/
     930             : 
     931       53357 : GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo)
     932             : {
     933       53357 :     GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
     934             : 
     935       53357 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:http://") ||
     936       53357 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:https://") ||
     937       53357 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:ftp://"))
     938             :     {
     939           0 :         srcType = eGeoJSONSourceService;
     940             :     }
     941       53357 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
     942       53332 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
     943       53330 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
     944             :     {
     945          27 :         if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq"))
     946             :         {
     947           1 :             return eGeoJSONSourceService;
     948             :         }
     949          26 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     950             :         {
     951           0 :             return eGeoJSONSourceUnknown;
     952             :         }
     953          26 :         srcType = eGeoJSONSourceService;
     954             :     }
     955       53330 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:"))
     956             :     {
     957             :         VSIStatBufL sStat;
     958           2 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("GEOJSONSeq:"), &sStat) ==
     959             :             0)
     960             :         {
     961           2 :             return eGeoJSONSourceFile;
     962             :         }
     963           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("GEOJSONSeq:");
     964           0 :         if (GeoJSONSeqIsObject(pszText, poOpenInfo))
     965           0 :             return eGeoJSONSourceText;
     966           0 :         return eGeoJSONSourceUnknown;
     967             :     }
     968       53328 :     else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename, poOpenInfo))
     969             :     {
     970           6 :         srcType = eGeoJSONSourceText;
     971             :     }
     972       53322 :     else if (GeoJSONSeqFileIsObject(poOpenInfo))
     973             :     {
     974          84 :         srcType = eGeoJSONSourceFile;
     975             :     }
     976             : 
     977       53354 :     return srcType;
     978             : }
     979             : 
     980             : /************************************************************************/
     981             : /*                      JSONFGDriverGetSourceType()                     */
     982             : /************************************************************************/
     983             : 
     984       48871 : GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo)
     985             : {
     986       48871 :     GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
     987             : 
     988       48871 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:http://") ||
     989       48871 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:https://") ||
     990       48871 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:ftp://"))
     991             :     {
     992           0 :         srcType = eGeoJSONSourceService;
     993             :     }
     994       48871 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
     995       48846 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
     996       48844 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
     997             :     {
     998          27 :         if (poOpenInfo->IsSingleAllowedDriver("JSONFG"))
     999             :         {
    1000           1 :             return eGeoJSONSourceService;
    1001             :         }
    1002          26 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
    1003             :         {
    1004           0 :             return eGeoJSONSourceUnknown;
    1005             :         }
    1006          26 :         srcType = eGeoJSONSourceService;
    1007             :     }
    1008       48844 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:"))
    1009             :     {
    1010             :         VSIStatBufL sStat;
    1011           0 :         const size_t nJSONFGPrefixLen = strlen("JSONFG:");
    1012           0 :         if (VSIStatL(poOpenInfo->pszFilename + nJSONFGPrefixLen, &sStat) == 0)
    1013             :         {
    1014           0 :             return eGeoJSONSourceFile;
    1015             :         }
    1016           0 :         const char *pszText = poOpenInfo->pszFilename + nJSONFGPrefixLen;
    1017           0 :         if (JSONFGIsObject(pszText, poOpenInfo))
    1018           0 :             return eGeoJSONSourceText;
    1019           0 :         return eGeoJSONSourceUnknown;
    1020             :     }
    1021       48844 :     else if (JSONFGIsObject(poOpenInfo->pszFilename, poOpenInfo))
    1022             :     {
    1023          90 :         srcType = eGeoJSONSourceText;
    1024             :     }
    1025       48754 :     else if (JSONFGFileIsObject(poOpenInfo))
    1026             :     {
    1027         196 :         srcType = eGeoJSONSourceFile;
    1028             :     }
    1029             : 
    1030       48870 :     return srcType;
    1031             : }
    1032             : 
    1033             : /************************************************************************/
    1034             : /*                        GeoJSONStringPropertyToFieldType()            */
    1035             : /************************************************************************/
    1036             : 
    1037         844 : OGRFieldType GeoJSONStringPropertyToFieldType(json_object *poObject,
    1038             :                                               int &nTZFlag)
    1039             : {
    1040         844 :     if (poObject == nullptr)
    1041             :     {
    1042          13 :         return OFTString;
    1043             :     }
    1044         831 :     const char *pszStr = json_object_get_string(poObject);
    1045             : 
    1046         831 :     nTZFlag = 0;
    1047             :     OGRField sWrkField;
    1048         831 :     CPLPushErrorHandler(CPLQuietErrorHandler);
    1049         831 :     const bool bSuccess = CPL_TO_BOOL(OGRParseDate(pszStr, &sWrkField, 0));
    1050         831 :     CPLPopErrorHandler();
    1051         831 :     CPLErrorReset();
    1052         831 :     if (bSuccess)
    1053             :     {
    1054         250 :         const bool bHasDate =
    1055         250 :             strchr(pszStr, '/') != nullptr || strchr(pszStr, '-') != nullptr;
    1056         250 :         const bool bHasTime = strchr(pszStr, ':') != nullptr;
    1057         250 :         nTZFlag = sWrkField.Date.TZFlag;
    1058         250 :         if (bHasDate && bHasTime)
    1059         148 :             return OFTDateTime;
    1060         102 :         else if (bHasDate)
    1061         100 :             return OFTDate;
    1062             :         else
    1063           2 :             return OFTTime;
    1064             :         // TODO: What if both are false?
    1065             :     }
    1066         581 :     return OFTString;
    1067             : }
    1068             : 
    1069             : /************************************************************************/
    1070             : /*                   GeoJSONHTTPFetchWithContentTypeHeader()            */
    1071             : /************************************************************************/
    1072             : 
    1073           7 : CPLHTTPResult *GeoJSONHTTPFetchWithContentTypeHeader(const char *pszURL)
    1074             : {
    1075          14 :     std::string osHeaders;
    1076             :     const char *pszGDAL_HTTP_HEADERS =
    1077           7 :         CPLGetConfigOption("GDAL_HTTP_HEADERS", nullptr);
    1078           7 :     bool bFoundAcceptHeader = false;
    1079           7 :     if (pszGDAL_HTTP_HEADERS)
    1080             :     {
    1081           3 :         bool bHeadersDone = false;
    1082             :         // Compatibility hack for "HEADERS=Accept: text/plain, application/json"
    1083           3 :         if (strstr(pszGDAL_HTTP_HEADERS, "\r\n") == nullptr)
    1084             :         {
    1085           2 :             const char *pszComma = strchr(pszGDAL_HTTP_HEADERS, ',');
    1086           2 :             if (pszComma != nullptr && strchr(pszComma, ':') == nullptr)
    1087             :             {
    1088           1 :                 osHeaders = pszGDAL_HTTP_HEADERS;
    1089           1 :                 bFoundAcceptHeader =
    1090           1 :                     STARTS_WITH_CI(pszGDAL_HTTP_HEADERS, "Accept:");
    1091           1 :                 bHeadersDone = true;
    1092             :             }
    1093             :         }
    1094           3 :         if (!bHeadersDone)
    1095             :         {
    1096             :             // We accept both raw headers with \r\n as a separator, or as
    1097             :             // a comma separated list of foo: bar values.
    1098             :             const CPLStringList aosTokens(
    1099           2 :                 strstr(pszGDAL_HTTP_HEADERS, "\r\n")
    1100           1 :                     ? CSLTokenizeString2(pszGDAL_HTTP_HEADERS, "\r\n", 0)
    1101           1 :                     : CSLTokenizeString2(pszGDAL_HTTP_HEADERS, ",",
    1102           6 :                                          CSLT_HONOURSTRINGS));
    1103           7 :             for (int i = 0; i < aosTokens.size(); ++i)
    1104             :             {
    1105           5 :                 if (!osHeaders.empty())
    1106           3 :                     osHeaders += "\r\n";
    1107           5 :                 if (!bFoundAcceptHeader)
    1108           5 :                     bFoundAcceptHeader =
    1109           5 :                         STARTS_WITH_CI(aosTokens[i], "Accept:");
    1110           5 :                 osHeaders += aosTokens[i];
    1111             :             }
    1112             :         }
    1113             :     }
    1114           7 :     if (!bFoundAcceptHeader)
    1115             :     {
    1116           5 :         if (!osHeaders.empty())
    1117           1 :             osHeaders += "\r\n";
    1118           5 :         osHeaders += "Accept: text/plain, application/json";
    1119             :     }
    1120             : 
    1121          14 :     CPLStringList aosOptions;
    1122           7 :     aosOptions.SetNameValue("HEADERS", osHeaders.c_str());
    1123           7 :     CPLHTTPResult *pResult = CPLHTTPFetch(pszURL, aosOptions.List());
    1124             : 
    1125          14 :     if (nullptr == pResult || 0 == pResult->nDataLen ||
    1126           7 :         0 != CPLGetLastErrorNo())
    1127             :     {
    1128           0 :         CPLHTTPDestroyResult(pResult);
    1129           0 :         return nullptr;
    1130             :     }
    1131             : 
    1132           7 :     if (0 != pResult->nStatus)
    1133             :     {
    1134           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Curl reports error: %d: %s",
    1135             :                  pResult->nStatus, pResult->pszErrBuf);
    1136           0 :         CPLHTTPDestroyResult(pResult);
    1137           0 :         return nullptr;
    1138             :     }
    1139             : 
    1140           7 :     return pResult;
    1141             : }

Generated by: LCOV version 1.14