LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/geojson - ogrgeojsonutils.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 428 486 88.1 %
Date: 2024-05-03 15:49:35 Functions: 23 23 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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      12             :  * copy of this software and associated documentation files (the "Software"),
      13             :  * to deal in the Software without restriction, including without limitation
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15             :  * and/or sell copies of the Software, and to permit persons to whom the
      16             :  * Software is furnished to do so, subject to the following conditions:
      17             :  *
      18             :  * The above copyright notice and this permission notice shall be included
      19             :  * in all copies or substantial portions of the Software.
      20             :  *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27             :  * DEALINGS IN THE SOFTWARE.
      28             :  ****************************************************************************/
      29             : 
      30             : #include "ogrgeojsonutils.h"
      31             : #include <assert.h>
      32             : #include "cpl_port.h"
      33             : #include "cpl_conv.h"
      34             : #include "ogr_geometry.h"
      35             : #include <json.h>  // JSON-C
      36             : 
      37             : #include <algorithm>
      38             : #include <memory>
      39             : 
      40             : const char szESRIJSonPotentialStart1[] =
      41             :     "{\"features\":[{\"geometry\":{\"rings\":[";
      42             : 
      43             : /************************************************************************/
      44             : /*                           IsJSONObject()                             */
      45             : /************************************************************************/
      46             : 
      47      229229 : static bool IsJSONObject(const char *pszText)
      48             : {
      49      229229 :     if (nullptr == pszText)
      50           0 :         return false;
      51             : 
      52             :     /* Skip UTF-8 BOM (#5630) */
      53      229229 :     const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
      54      229229 :     if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
      55           6 :         pszText += 3;
      56             : 
      57             :     /* -------------------------------------------------------------------- */
      58             :     /*      This is a primitive test, but we need to perform it fast.       */
      59             :     /* -------------------------------------------------------------------- */
      60      230991 :     while (*pszText != '\0' && isspace(static_cast<unsigned char>(*pszText)))
      61        1762 :         pszText++;
      62             : 
      63      229229 :     const char *const apszPrefix[] = {"loadGeoJSON(", "jsonp("};
      64      687666 :     for (size_t iP = 0; iP < sizeof(apszPrefix) / sizeof(apszPrefix[0]); iP++)
      65             :     {
      66      458447 :         if (strncmp(pszText, apszPrefix[iP], strlen(apszPrefix[iP])) == 0)
      67             :         {
      68          10 :             pszText += strlen(apszPrefix[iP]);
      69          10 :             break;
      70             :         }
      71             :     }
      72             : 
      73      229229 :     if (*pszText != '{')
      74      225187 :         return false;
      75             : 
      76        4042 :     return true;
      77             : }
      78             : 
      79             : /************************************************************************/
      80             : /*                           IsTypeSomething()                          */
      81             : /************************************************************************/
      82             : 
      83        4532 : static bool IsTypeSomething(const char *pszText, const char *pszTypeValue)
      84             : {
      85        4532 :     const char *pszIter = pszText;
      86             :     while (true)
      87             :     {
      88       14022 :         pszIter = strstr(pszIter, "\"type\"");
      89       14022 :         if (pszIter == nullptr)
      90        4532 :             return false;
      91       10833 :         pszIter += strlen("\"type\"");
      92       10999 :         while (isspace(static_cast<unsigned char>(*pszIter)))
      93         166 :             pszIter++;
      94       10833 :         if (*pszIter != ':')
      95           0 :             return false;
      96       10833 :         pszIter++;
      97       20165 :         while (isspace(static_cast<unsigned char>(*pszIter)))
      98        9332 :             pszIter++;
      99       10833 :         CPLString osValue;
     100       10833 :         osValue.Printf("\"%s\"", pszTypeValue);
     101       10833 :         if (STARTS_WITH(pszIter, osValue.c_str()))
     102        1343 :             return true;
     103        9490 :     }
     104             : }
     105             : 
     106             : /************************************************************************/
     107             : /*                           GetCompactJSon()                           */
     108             : /************************************************************************/
     109             : 
     110        2534 : static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize)
     111             : {
     112             :     /* Skip UTF-8 BOM (#5630) */
     113        2534 :     const GByte *pabyData = reinterpret_cast<const GByte *>(pszText);
     114        2534 :     if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF)
     115           6 :         pszText += 3;
     116             : 
     117        2534 :     CPLString osWithoutSpace;
     118        2534 :     bool bInString = false;
     119     1729220 :     for (int i = 0; pszText[i] != '\0' && osWithoutSpace.size() < nMaxSize; i++)
     120             :     {
     121     1726680 :         if (bInString)
     122             :         {
     123      696653 :             if (pszText[i] == '\\')
     124             :             {
     125         239 :                 osWithoutSpace += pszText[i];
     126         239 :                 if (pszText[i + 1] == '\0')
     127           0 :                     break;
     128         239 :                 osWithoutSpace += pszText[i + 1];
     129         239 :                 i++;
     130             :             }
     131      696414 :             else if (pszText[i] == '"')
     132             :             {
     133       49966 :                 bInString = false;
     134       49966 :                 osWithoutSpace += '"';
     135             :             }
     136             :             else
     137             :             {
     138      646448 :                 osWithoutSpace += pszText[i];
     139             :             }
     140             :         }
     141     1030030 :         else if (pszText[i] == '"')
     142             :         {
     143       50110 :             bInString = true;
     144       50110 :             osWithoutSpace += '"';
     145             :         }
     146      979919 :         else if (!isspace(static_cast<unsigned char>(pszText[i])))
     147             :         {
     148      634393 :             osWithoutSpace += pszText[i];
     149             :         }
     150             :     }
     151        2534 :     return osWithoutSpace;
     152             : }
     153             : 
     154             : /************************************************************************/
     155             : /*                          IsGeoJSONLikeObject()                       */
     156             : /************************************************************************/
     157             : 
     158       97523 : static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence,
     159             :                                 bool &bReadMoreBytes)
     160             : {
     161       97523 :     bMightBeSequence = false;
     162       97523 :     bReadMoreBytes = false;
     163             : 
     164       97523 :     if (!IsJSONObject(pszText))
     165       95835 :         return false;
     166             : 
     167        1688 :     if (IsTypeSomething(pszText, "Topology"))
     168           6 :         return false;
     169             : 
     170        1682 :     if (JSONFGIsObject(pszText) && GDALGetDriverByName("JSONFG"))
     171         274 :         return false;
     172             : 
     173        1408 :     if (IsTypeSomething(pszText, "FeatureCollection"))
     174             :     {
     175        1004 :         return true;
     176             :     }
     177             : 
     178         808 :     CPLString osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
     179         409 :     if (osWithoutSpace.find("{\"features\":[") == 0 &&
     180           5 :         osWithoutSpace.find(szESRIJSonPotentialStart1) != 0)
     181             :     {
     182           3 :         return true;
     183             :     }
     184             : 
     185             :     // See
     186             :     // https://raw.githubusercontent.com/icepack/icepack-data/master/meshes/larsen/larsen_inflow.geojson
     187             :     // "{"crs":...,"features":[..."
     188             :     // or
     189             :     // https://gist.githubusercontent.com/NiklasDallmann/27e339dd78d1942d524fbcd179f9fdcf/raw/527a8319d75a9e29446a32a19e4c902213b0d668/42XR9nLAh8Poh9Xmniqh3Bs9iisNm74mYMC56v3Wfyo=_isochrones_fails.geojson
     190             :     // "{"bbox":...,"features":[..."
     191         401 :     if (osWithoutSpace.find(",\"features\":[") != std::string::npos)
     192             :     {
     193          30 :         return !ESRIJSONIsObject(pszText);
     194             :     }
     195             : 
     196             :     // See https://github.com/OSGeo/gdal/issues/2720
     197         739 :     if (osWithoutSpace.find("{\"coordinates\":[") == 0 ||
     198             :         // and https://github.com/OSGeo/gdal/issues/2787
     199         368 :         osWithoutSpace.find("{\"geometry\":{\"coordinates\":[") == 0)
     200             :     {
     201           6 :         return true;
     202             :     }
     203             : 
     204         365 :     if (IsTypeSomething(pszText, "Feature") ||
     205         218 :         IsTypeSomething(pszText, "Point") ||
     206         188 :         IsTypeSomething(pszText, "LineString") ||
     207         182 :         IsTypeSomething(pszText, "Polygon") ||
     208         176 :         IsTypeSomething(pszText, "MultiPoint") ||
     209          56 :         IsTypeSomething(pszText, "MultiLineString") ||
     210         621 :         IsTypeSomething(pszText, "MultiPolygon") ||
     211          38 :         IsTypeSomething(pszText, "GeometryCollection"))
     212             :     {
     213         327 :         bMightBeSequence = true;
     214         327 :         return true;
     215             :     }
     216             : 
     217             :     // See https://github.com/OSGeo/gdal/issues/3280
     218          38 :     if (osWithoutSpace.find("{\"properties\":{") == 0)
     219             :     {
     220           2 :         bMightBeSequence = true;
     221           2 :         bReadMoreBytes = true;
     222           2 :         return false;
     223             :     }
     224             : 
     225          36 :     return false;
     226             : }
     227             : 
     228          26 : static bool IsGeoJSONLikeObject(const char *pszText)
     229             : {
     230             :     bool bMightBeSequence;
     231             :     bool bReadMoreBytes;
     232          52 :     return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes);
     233             : }
     234             : 
     235             : /************************************************************************/
     236             : /*                       ESRIJSONIsObject()                             */
     237             : /************************************************************************/
     238             : 
     239       44336 : bool ESRIJSONIsObject(const char *pszText)
     240             : {
     241       44336 :     if (!IsJSONObject(pszText))
     242       44115 :         return false;
     243             : 
     244         221 :     if (  // ESRI Json geometry
     245         221 :         (strstr(pszText, "\"geometryType\"") != nullptr &&
     246          64 :          strstr(pszText, "\"esriGeometry") != nullptr)
     247             : 
     248             :         // ESRI Json "FeatureCollection"
     249         157 :         || strstr(pszText, "\"fieldAliases\"") != nullptr
     250             : 
     251             :         // ESRI Json "FeatureCollection"
     252         157 :         || (strstr(pszText, "\"fields\"") != nullptr &&
     253           0 :             strstr(pszText, "\"esriFieldType") != nullptr))
     254             :     {
     255          64 :         return true;
     256             :     }
     257             : 
     258             :     CPLString osWithoutSpace =
     259         314 :         GetCompactJSon(pszText, strlen(szESRIJSonPotentialStart1));
     260         157 :     if (osWithoutSpace.find(szESRIJSonPotentialStart1) == 0)
     261             :     {
     262           0 :         return true;
     263             :     }
     264             : 
     265         157 :     return false;
     266             : }
     267             : 
     268             : /************************************************************************/
     269             : /*                       TopoJSONIsObject()                             */
     270             : /************************************************************************/
     271             : 
     272       44274 : bool TopoJSONIsObject(const char *pszText)
     273             : {
     274       44274 :     if (!IsJSONObject(pszText))
     275       44114 :         return false;
     276             : 
     277         160 :     return IsTypeSomething(pszText, "Topology");
     278             : }
     279             : 
     280             : /************************************************************************/
     281             : /*                      IsLikelyNewlineSequenceGeoJSON()                */
     282             : /************************************************************************/
     283             : 
     284         299 : static bool IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL,
     285             :                                            const GByte *pabyHeader,
     286             :                                            const char *pszFileContent)
     287             : {
     288         299 :     const size_t nBufferSize = 4096 * 10;
     289         299 :     std::vector<GByte> abyBuffer;
     290         299 :     abyBuffer.resize(nBufferSize + 1);
     291             : 
     292         299 :     int nCurlLevel = 0;
     293         299 :     bool bInString = false;
     294         299 :     bool bLastIsEscape = false;
     295         299 :     bool bCompatibleOfSequence = true;
     296         299 :     bool bFirstIter = true;
     297         299 :     bool bEOLFound = false;
     298         299 :     int nCountObject = 0;
     299             :     while (true)
     300             :     {
     301             :         size_t nRead;
     302         369 :         bool bEnd = false;
     303         369 :         if (bFirstIter)
     304             :         {
     305         299 :             const char *pszText =
     306         299 :                 pszFileContent ? pszFileContent
     307             :                                : reinterpret_cast<const char *>(pabyHeader);
     308         299 :             assert(pszText);
     309         299 :             nRead = std::min(strlen(pszText), nBufferSize);
     310         299 :             memcpy(abyBuffer.data(), pszText, nRead);
     311         299 :             bFirstIter = false;
     312         299 :             if (fpL)
     313             :             {
     314         131 :                 VSIFSeekL(fpL, nRead, SEEK_SET);
     315             :             }
     316             :         }
     317             :         else
     318             :         {
     319          70 :             nRead = VSIFReadL(abyBuffer.data(), 1, nBufferSize, fpL);
     320          70 :             bEnd = nRead < nBufferSize;
     321             :         }
     322     1198580 :         for (size_t i = 0; i < nRead; i++)
     323             :         {
     324     1198310 :             if (nCurlLevel == 0)
     325             :             {
     326         554 :                 if (abyBuffer[i] == '{')
     327             :                 {
     328         389 :                     nCountObject++;
     329         389 :                     if (nCountObject == 2)
     330             :                     {
     331          93 :                         break;
     332             :                     }
     333         296 :                     nCurlLevel++;
     334             :                 }
     335         165 :                 else if (nCountObject == 1 && abyBuffer[i] == '\n')
     336             :                 {
     337         111 :                     bEOLFound = true;
     338             :                 }
     339          54 :                 else if (!isspace(static_cast<unsigned char>(abyBuffer[i])))
     340             :                 {
     341           6 :                     bCompatibleOfSequence = false;
     342           6 :                     break;
     343             :                 }
     344             :             }
     345     1197760 :             else if (bInString)
     346             :             {
     347       61104 :                 if (bLastIsEscape)
     348             :                 {
     349          18 :                     bLastIsEscape = false;
     350             :                 }
     351       61086 :                 else if (abyBuffer[i] == '\\')
     352             :                 {
     353          18 :                     bLastIsEscape = true;
     354             :                 }
     355       61068 :                 else if (abyBuffer[i] == '"')
     356             :                 {
     357        1335 :                     bInString = false;
     358             :                 }
     359             :             }
     360     1136650 :             else if (abyBuffer[i] == '"')
     361             :             {
     362        1335 :                 bInString = true;
     363             :             }
     364     1135320 :             else if (abyBuffer[i] == '{')
     365             :             {
     366         194 :                 nCurlLevel++;
     367             :             }
     368     1135120 :             else if (abyBuffer[i] == '}')
     369             :             {
     370         490 :                 nCurlLevel--;
     371             :             }
     372             :         }
     373         369 :         if (!fpL || bEnd || !bCompatibleOfSequence || nCountObject == 2)
     374             :             break;
     375          70 :     }
     376         598 :     return bCompatibleOfSequence && bEOLFound && nCountObject == 2;
     377             : }
     378             : 
     379             : /************************************************************************/
     380             : /*                           GeoJSONFileIsObject()                      */
     381             : /************************************************************************/
     382             : 
     383       44869 : static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo)
     384             : {
     385             :     // By default read first 6000 bytes.
     386             :     // 6000 was chosen as enough bytes to
     387             :     // enable all current tests to pass.
     388             : 
     389       44869 :     if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
     390             :     {
     391       40827 :         return false;
     392             :     }
     393             : 
     394        4042 :     bool bMightBeSequence = false;
     395        4042 :     bool bReadMoreBytes = false;
     396        4042 :     if (!IsGeoJSONLikeObject(
     397        4042 :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     398             :             bMightBeSequence, bReadMoreBytes))
     399             :     {
     400        3476 :         if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
     401           2 :               poOpenInfo->TryToIngest(1000 * 1000) &&
     402           2 :               !IsGeoJSONLikeObject(
     403           2 :                   reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     404             :                   bMightBeSequence, bReadMoreBytes)))
     405             :         {
     406        3474 :             return false;
     407             :         }
     408             :     }
     409             : 
     410         643 :     return !(bMightBeSequence &&
     411          75 :              IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL,
     412         643 :                                             poOpenInfo->pabyHeader, nullptr));
     413             : }
     414             : 
     415             : /************************************************************************/
     416             : /*                           GeoJSONIsObject()                          */
     417             : /************************************************************************/
     418             : 
     419       45565 : bool GeoJSONIsObject(const char *pszText)
     420             : {
     421       45565 :     bool bMightBeSequence = false;
     422       45565 :     bool bReadMoreBytes = false;
     423       45565 :     if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes))
     424             :     {
     425       44883 :         return false;
     426             :     }
     427             : 
     428         844 :     return !(bMightBeSequence &&
     429         844 :              IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText));
     430             : }
     431             : 
     432             : /************************************************************************/
     433             : /*                        GeoJSONSeqFileIsObject()                      */
     434             : /************************************************************************/
     435             : 
     436       44354 : static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo)
     437             : {
     438             :     // By default read first 6000 bytes.
     439             :     // 6000 was chosen as enough bytes to
     440             :     // enable all current tests to pass.
     441             : 
     442       44354 :     if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
     443             :     {
     444       40817 :         return false;
     445             :     }
     446             : 
     447        3537 :     const char *pszText =
     448             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     449        3537 :     if (pszText[0] == '\x1e')
     450          26 :         return IsGeoJSONLikeObject(pszText + 1);
     451             : 
     452        3511 :     bool bMightBeSequence = false;
     453        3511 :     bool bReadMoreBytes = false;
     454        3511 :     if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes))
     455             :     {
     456        3455 :         if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
     457           0 :               poOpenInfo->TryToIngest(1000 * 1000) &&
     458           0 :               IsGeoJSONLikeObject(
     459           0 :                   reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     460             :                   bMightBeSequence, bReadMoreBytes)))
     461             :         {
     462        3455 :             return false;
     463             :         }
     464             :     }
     465             : 
     466         112 :     return bMightBeSequence &&
     467          56 :            IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL,
     468         112 :                                           poOpenInfo->pabyHeader, nullptr);
     469             : }
     470             : 
     471       44377 : bool GeoJSONSeqIsObject(const char *pszText)
     472             : {
     473       44377 :     if (pszText[0] == '\x1e')
     474           0 :         return IsGeoJSONLikeObject(pszText + 1);
     475             : 
     476       44377 :     bool bMightBeSequence = false;
     477       44377 :     bool bReadMoreBytes = false;
     478       44377 :     if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes))
     479             :     {
     480       44371 :         return false;
     481             :     }
     482             : 
     483          12 :     return bMightBeSequence &&
     484          12 :            IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText);
     485             : }
     486             : 
     487             : /************************************************************************/
     488             : /*                        JSONFGFileIsObject()                          */
     489             : /************************************************************************/
     490             : 
     491       40429 : static bool JSONFGFileIsObject(GDALOpenInfo *poOpenInfo)
     492             : {
     493             :     // 6000 somewhat arbitrary. Based on other JSON-like drivers
     494       40429 :     if (poOpenInfo->fpL == nullptr || !poOpenInfo->TryToIngest(6000))
     495             :     {
     496       39552 :         return false;
     497             :     }
     498             : 
     499         877 :     const char *pszText =
     500             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     501         877 :     return JSONFGIsObject(pszText);
     502             : }
     503             : 
     504       43096 : bool JSONFGIsObject(const char *pszText)
     505             : {
     506       43096 :     if (!IsJSONObject(pszText))
     507       41123 :         return false;
     508             : 
     509        3946 :     const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText));
     510             : 
     511             :     {
     512        1973 :         const auto nPos = osWithoutSpace.find("\"conformsTo\":[");
     513        1973 :         if (nPos != std::string::npos)
     514             :         {
     515         548 :             if (osWithoutSpace.find("\"[ogc-json-fg-1-0.1:core]\"", nPos) !=
     516         548 :                     std::string::npos ||
     517           0 :                 osWithoutSpace.find(
     518             :                     "\"http://www.opengis.net/spec/json-fg-1/0.1\"", nPos) !=
     519             :                     std::string::npos)
     520             :             {
     521         548 :                 return true;
     522             :             }
     523             :         }
     524             :     }
     525             : 
     526        2850 :     if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos ||
     527        2850 :         osWithoutSpace.find("\"featureType\":\"") != std::string::npos ||
     528        2850 :         osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos ||
     529        1425 :         osWithoutSpace.find("\"place\":{\"coordinates\":") !=
     530        1425 :             std::string::npos ||
     531        2850 :         osWithoutSpace.find("\"time\":{\"date\":") != std::string::npos ||
     532        4275 :         osWithoutSpace.find("\"time\":{\"timestamp\":") != std::string::npos ||
     533        1425 :         osWithoutSpace.find("\"time\":{\"interval\":") != std::string::npos)
     534             :     {
     535           0 :         return true;
     536             :     }
     537             : 
     538        1425 :     return false;
     539             : }
     540             : 
     541             : /************************************************************************/
     542             : /*                           IsLikelyESRIJSONURL()                      */
     543             : /************************************************************************/
     544             : 
     545         115 : static bool IsLikelyESRIJSONURL(const char *pszURL)
     546             : {
     547             :     // URLs with f=json are strong candidates for ESRI JSON services
     548             :     // except if they have "/items?", in which case they are likely OAPIF
     549         115 :     return strstr(pszURL, "f=json") != nullptr &&
     550         115 :            strstr(pszURL, "/items?") == nullptr;
     551             : }
     552             : 
     553             : /************************************************************************/
     554             : /*                           GeoJSONGetSourceType()                     */
     555             : /************************************************************************/
     556             : 
     557       45184 : GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo)
     558             : {
     559       45184 :     GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
     560             : 
     561             :     // NOTE: Sometimes URL ends with .geojson token, for example
     562             :     //       http://example/path/2232.geojson
     563             :     //       It's important to test beginning of source first.
     564       45184 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:http://") ||
     565       45185 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:https://") ||
     566       45185 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSON:ftp://"))
     567             :     {
     568           0 :         srcType = eGeoJSONSourceService;
     569             :     }
     570       45185 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
     571       45142 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
     572       45141 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
     573             :     {
     574          44 :         if ((strstr(poOpenInfo->pszFilename, "SERVICE=WFS") ||
     575          44 :              strstr(poOpenInfo->pszFilename, "service=WFS") ||
     576          44 :              strstr(poOpenInfo->pszFilename, "service=wfs")) &&
     577           0 :             !strstr(poOpenInfo->pszFilename, "json"))
     578             :         {
     579           0 :             return eGeoJSONSourceUnknown;
     580             :         }
     581          44 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     582             :         {
     583           0 :             return eGeoJSONSourceUnknown;
     584             :         }
     585          44 :         srcType = eGeoJSONSourceService;
     586             :     }
     587       45141 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSON:"))
     588             :     {
     589             :         VSIStatBufL sStat;
     590           0 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("GeoJSON:"), &sStat) == 0)
     591             :         {
     592           0 :             return eGeoJSONSourceFile;
     593             :         }
     594           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("GeoJSON:");
     595           0 :         if (GeoJSONIsObject(pszText))
     596           0 :             return eGeoJSONSourceText;
     597           0 :         return eGeoJSONSourceUnknown;
     598             :     }
     599       45141 :     else if (GeoJSONIsObject(poOpenInfo->pszFilename))
     600             :     {
     601         272 :         srcType = eGeoJSONSourceText;
     602             :     }
     603       44869 :     else if (GeoJSONFileIsObject(poOpenInfo))
     604             :     {
     605         540 :         srcType = eGeoJSONSourceFile;
     606             :     }
     607             : 
     608       45183 :     return srcType;
     609             : }
     610             : 
     611             : /************************************************************************/
     612             : /*                     ESRIJSONDriverGetSourceType()                    */
     613             : /************************************************************************/
     614             : 
     615       44299 : GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
     616             : {
     617       44299 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:http://") ||
     618       44299 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:https://") ||
     619       44300 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:ftp://"))
     620             :     {
     621           0 :         return eGeoJSONSourceService;
     622             :     }
     623       44300 :     else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
     624       44292 :              STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
     625       44291 :              STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
     626             :     {
     627           9 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     628             :         {
     629           0 :             return eGeoJSONSourceService;
     630             :         }
     631           9 :         return eGeoJSONSourceUnknown;
     632             :     }
     633             : 
     634       44291 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:"))
     635             :     {
     636             :         VSIStatBufL sStat;
     637           2 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("ESRIJSON:"), &sStat) ==
     638             :             0)
     639             :         {
     640           2 :             return eGeoJSONSourceFile;
     641             :         }
     642           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("ESRIJSON:");
     643           0 :         if (ESRIJSONIsObject(pszText))
     644           0 :             return eGeoJSONSourceText;
     645           0 :         return eGeoJSONSourceUnknown;
     646             :     }
     647             : 
     648       44289 :     if (poOpenInfo->fpL == nullptr)
     649             :     {
     650       40818 :         const char *pszText = poOpenInfo->pszFilename;
     651       40818 :         if (ESRIJSONIsObject(pszText))
     652           4 :             return eGeoJSONSourceText;
     653       40814 :         return eGeoJSONSourceUnknown;
     654             :     }
     655             : 
     656             :     // By default read first 6000 bytes.
     657             :     // 6000 was chosen as enough bytes to
     658             :     // enable all current tests to pass.
     659        3471 :     if (!poOpenInfo->TryToIngest(6000))
     660             :     {
     661           0 :         return eGeoJSONSourceUnknown;
     662             :     }
     663             : 
     664        6942 :     if (poOpenInfo->pabyHeader != nullptr &&
     665        3471 :         ESRIJSONIsObject(
     666        3471 :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader)))
     667             :     {
     668          30 :         return eGeoJSONSourceFile;
     669             :     }
     670        3441 :     return eGeoJSONSourceUnknown;
     671             : }
     672             : 
     673             : /************************************************************************/
     674             : /*                     TopoJSONDriverGetSourceType()                    */
     675             : /************************************************************************/
     676             : 
     677       44268 : GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo)
     678             : {
     679       44268 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:http://") ||
     680       44268 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:https://") ||
     681       44268 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:ftp://"))
     682             :     {
     683           0 :         return eGeoJSONSourceService;
     684             :     }
     685       44268 :     else if (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
     686       44259 :              STARTS_WITH(poOpenInfo->pszFilename, "https://") ||
     687       44258 :              STARTS_WITH(poOpenInfo->pszFilename, "ftp://"))
     688             :     {
     689          10 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     690             :         {
     691           0 :             return eGeoJSONSourceUnknown;
     692             :         }
     693          10 :         return eGeoJSONSourceService;
     694             :     }
     695             : 
     696       44258 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:"))
     697             :     {
     698             :         VSIStatBufL sStat;
     699           0 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("TopoJSON:"), &sStat) ==
     700             :             0)
     701             :         {
     702           0 :             return eGeoJSONSourceFile;
     703             :         }
     704           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("TopoJSON:");
     705           0 :         if (TopoJSONIsObject(pszText))
     706           0 :             return eGeoJSONSourceText;
     707           0 :         return eGeoJSONSourceUnknown;
     708             :     }
     709             : 
     710       44258 :     if (poOpenInfo->fpL == nullptr)
     711             :     {
     712       40813 :         const char *pszText = poOpenInfo->pszFilename;
     713       40813 :         if (TopoJSONIsObject(pszText))
     714           0 :             return eGeoJSONSourceText;
     715       40813 :         return eGeoJSONSourceUnknown;
     716             :     }
     717             : 
     718             :     // By default read first 6000 bytes.
     719             :     // 6000 was chosen as enough bytes to
     720             :     // enable all current tests to pass.
     721        3445 :     if (!poOpenInfo->TryToIngest(6000))
     722             :     {
     723           0 :         return eGeoJSONSourceUnknown;
     724             :     }
     725             : 
     726        6888 :     if (poOpenInfo->pabyHeader != nullptr &&
     727        3444 :         TopoJSONIsObject(
     728        3444 :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader)))
     729             :     {
     730           6 :         return eGeoJSONSourceFile;
     731             :     }
     732        3438 :     return eGeoJSONSourceUnknown;
     733             : }
     734             : 
     735             : /************************************************************************/
     736             : /*                          GeoJSONSeqGetSourceType()                   */
     737             : /************************************************************************/
     738             : 
     739       44404 : GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo)
     740             : {
     741       44404 :     GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
     742             : 
     743       44404 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:http://") ||
     744       44404 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:https://") ||
     745       44403 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:ftp://"))
     746             :     {
     747           0 :         srcType = eGeoJSONSourceService;
     748             :     }
     749       44404 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
     750       44364 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
     751       44362 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
     752             :     {
     753          42 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     754             :         {
     755           0 :             return eGeoJSONSourceUnknown;
     756             :         }
     757          42 :         srcType = eGeoJSONSourceService;
     758             :     }
     759       44362 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GEOJSONSeq:"))
     760             :     {
     761             :         VSIStatBufL sStat;
     762           2 :         if (VSIStatL(poOpenInfo->pszFilename + strlen("GEOJSONSeq:"), &sStat) ==
     763             :             0)
     764             :         {
     765           2 :             return eGeoJSONSourceFile;
     766             :         }
     767           0 :         const char *pszText = poOpenInfo->pszFilename + strlen("GEOJSONSeq:");
     768           0 :         if (GeoJSONSeqIsObject(pszText))
     769           0 :             return eGeoJSONSourceText;
     770           0 :         return eGeoJSONSourceUnknown;
     771             :     }
     772       44360 :     else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename))
     773             :     {
     774           6 :         srcType = eGeoJSONSourceText;
     775             :     }
     776       44354 :     else if (GeoJSONSeqFileIsObject(poOpenInfo))
     777             :     {
     778          82 :         srcType = eGeoJSONSourceFile;
     779             :     }
     780             : 
     781       44402 :     return srcType;
     782             : }
     783             : 
     784             : /************************************************************************/
     785             : /*                      JSONFGDriverGetSourceType()                     */
     786             : /************************************************************************/
     787             : 
     788       40529 : GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo)
     789             : {
     790       40529 :     GeoJSONSourceType srcType = eGeoJSONSourceUnknown;
     791             : 
     792       40529 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:http://") ||
     793       40529 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:https://") ||
     794       40529 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:ftp://"))
     795             :     {
     796           0 :         srcType = eGeoJSONSourceService;
     797             :     }
     798       40529 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "http://") ||
     799       40521 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") ||
     800       40519 :              STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://"))
     801             :     {
     802          10 :         if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename))
     803             :         {
     804           0 :             return eGeoJSONSourceUnknown;
     805             :         }
     806          10 :         srcType = eGeoJSONSourceService;
     807             :     }
     808       40519 :     else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:"))
     809             :     {
     810             :         VSIStatBufL sStat;
     811           0 :         const size_t nJSONFGPrefixLen = strlen("JSONFG:");
     812           0 :         if (VSIStatL(poOpenInfo->pszFilename + nJSONFGPrefixLen, &sStat) == 0)
     813             :         {
     814           0 :             return eGeoJSONSourceFile;
     815             :         }
     816           0 :         const char *pszText = poOpenInfo->pszFilename + nJSONFGPrefixLen;
     817           0 :         if (JSONFGIsObject(pszText))
     818           0 :             return eGeoJSONSourceText;
     819           0 :         return eGeoJSONSourceUnknown;
     820             :     }
     821       40519 :     else if (JSONFGIsObject(poOpenInfo->pszFilename))
     822             :     {
     823          90 :         srcType = eGeoJSONSourceText;
     824             :     }
     825       40429 :     else if (JSONFGFileIsObject(poOpenInfo))
     826             :     {
     827         184 :         srcType = eGeoJSONSourceFile;
     828             :     }
     829             : 
     830       40529 :     return srcType;
     831             : }
     832             : 
     833             : /************************************************************************/
     834             : /*                           GeoJSONPropertyToFieldType()               */
     835             : /************************************************************************/
     836             : 
     837             : constexpr GIntBig MY_INT64_MAX = (((GIntBig)0x7FFFFFFF) << 32) | 0xFFFFFFFF;
     838             : constexpr GIntBig MY_INT64_MIN = ((GIntBig)0x80000000) << 32;
     839             : 
     840        4649 : OGRFieldType GeoJSONPropertyToFieldType(json_object *poObject,
     841             :                                         OGRFieldSubType &eSubType,
     842             :                                         bool bArrayAsString)
     843             : {
     844        4649 :     eSubType = OFSTNone;
     845             : 
     846        4649 :     if (poObject == nullptr)
     847             :     {
     848           7 :         return OFTString;
     849             :     }
     850             : 
     851        4642 :     json_type type = json_object_get_type(poObject);
     852             : 
     853        4642 :     if (json_type_boolean == type)
     854             :     {
     855          31 :         eSubType = OFSTBoolean;
     856          31 :         return OFTInteger;
     857             :     }
     858        4611 :     else if (json_type_double == type)
     859         489 :         return OFTReal;
     860        4122 :     else if (json_type_int == type)
     861             :     {
     862        2437 :         GIntBig nVal = json_object_get_int64(poObject);
     863        2437 :         if (!CPL_INT64_FITS_ON_INT32(nVal))
     864             :         {
     865          34 :             if (nVal == MY_INT64_MIN || nVal == MY_INT64_MAX)
     866             :             {
     867             :                 static bool bWarned = false;
     868           1 :                 if (!bWarned)
     869             :                 {
     870           1 :                     bWarned = true;
     871           1 :                     CPLError(
     872             :                         CE_Warning, CPLE_AppDefined,
     873             :                         "Integer values probably ranging out of 64bit integer "
     874             :                         "range have been found. Will be clamped to "
     875             :                         "INT64_MIN/INT64_MAX");
     876             :                 }
     877             :             }
     878          34 :             return OFTInteger64;
     879             :         }
     880             :         else
     881             :         {
     882        2403 :             return OFTInteger;
     883             :         }
     884             :     }
     885        1685 :     else if (json_type_string == type)
     886        1356 :         return OFTString;
     887         329 :     else if (json_type_array == type)
     888             :     {
     889         300 :         if (bArrayAsString)
     890             :         {
     891           1 :             eSubType = OFSTJSON;
     892           1 :             return OFTString;
     893             :         }
     894         299 :         const auto nSize = json_object_array_length(poObject);
     895         299 :         if (nSize == 0)
     896             :         {
     897           1 :             eSubType = OFSTJSON;
     898           1 :             return OFTString;
     899             :         }
     900         298 :         OGRFieldType eType = OFTIntegerList;
     901         617 :         for (auto i = decltype(nSize){0}; i < nSize; i++)
     902             :         {
     903         332 :             json_object *poRow = json_object_array_get_idx(poObject, i);
     904         332 :             if (poRow != nullptr)
     905             :             {
     906         332 :                 type = json_object_get_type(poRow);
     907         332 :                 if (type == json_type_string)
     908             :                 {
     909          70 :                     if (i == 0 || eType == OFTStringList)
     910             :                     {
     911          67 :                         eType = OFTStringList;
     912             :                     }
     913             :                     else
     914             :                     {
     915           3 :                         eSubType = OFSTJSON;
     916           3 :                         return OFTString;
     917             :                     }
     918             :                 }
     919         262 :                 else if (type == json_type_double)
     920             :                 {
     921          62 :                     if (eSubType == OFSTNone &&
     922           6 :                         (i == 0 || eType == OFTRealList ||
     923           2 :                          eType == OFTIntegerList || eType == OFTInteger64List))
     924             :                     {
     925          62 :                         eType = OFTRealList;
     926             :                     }
     927             :                     else
     928             :                     {
     929           0 :                         eSubType = OFSTJSON;
     930           0 :                         return OFTString;
     931             :                     }
     932             :                 }
     933         200 :                 else if (type == json_type_int)
     934             :                 {
     935         155 :                     if (eSubType == OFSTNone && eType == OFTIntegerList)
     936             :                     {
     937         144 :                         GIntBig nVal = json_object_get_int64(poRow);
     938         144 :                         if (!CPL_INT64_FITS_ON_INT32(nVal))
     939         144 :                             eType = OFTInteger64List;
     940             :                     }
     941          11 :                     else if (eSubType == OFSTNone &&
     942           5 :                              (eType == OFTInteger64List ||
     943             :                               eType == OFTRealList))
     944             :                     {
     945             :                         // ok
     946             :                     }
     947             :                     else
     948             :                     {
     949           3 :                         eSubType = OFSTJSON;
     950           3 :                         return OFTString;
     951             :                     }
     952             :                 }
     953          45 :                 else if (type == json_type_boolean)
     954             :                 {
     955          39 :                     if (i == 0 ||
     956           4 :                         (eType == OFTIntegerList && eSubType == OFSTBoolean))
     957             :                     {
     958          38 :                         eSubType = OFSTBoolean;
     959             :                     }
     960             :                     else
     961             :                     {
     962           1 :                         eSubType = OFSTJSON;
     963           1 :                         return OFTString;
     964             :                     }
     965             :                 }
     966             :                 else
     967             :                 {
     968           6 :                     eSubType = OFSTJSON;
     969           6 :                     return OFTString;
     970             :                 }
     971             :             }
     972             :             else
     973             :             {
     974           0 :                 eSubType = OFSTJSON;
     975           0 :                 return OFTString;
     976             :             }
     977             :         }
     978             : 
     979         285 :         return eType;
     980             :     }
     981          29 :     else if (json_type_object == type)
     982             :     {
     983          29 :         eSubType = OFSTJSON;
     984          29 :         return OFTString;
     985             :     }
     986             : 
     987           0 :     return OFTString;  // null
     988             : }
     989             : 
     990             : /************************************************************************/
     991             : /*                        GeoJSONStringPropertyToFieldType()            */
     992             : /************************************************************************/
     993             : 
     994         615 : OGRFieldType GeoJSONStringPropertyToFieldType(json_object *poObject,
     995             :                                               int &nTZFlag)
     996             : {
     997         615 :     if (poObject == nullptr)
     998             :     {
     999           7 :         return OFTString;
    1000             :     }
    1001         608 :     const char *pszStr = json_object_get_string(poObject);
    1002             : 
    1003         608 :     nTZFlag = 0;
    1004             :     OGRField sWrkField;
    1005         608 :     CPLPushErrorHandler(CPLQuietErrorHandler);
    1006         608 :     const bool bSuccess = CPL_TO_BOOL(OGRParseDate(pszStr, &sWrkField, 0));
    1007         608 :     CPLPopErrorHandler();
    1008         608 :     CPLErrorReset();
    1009         608 :     if (bSuccess)
    1010             :     {
    1011         225 :         const bool bHasDate =
    1012         225 :             strchr(pszStr, '/') != nullptr || strchr(pszStr, '-') != nullptr;
    1013         225 :         const bool bHasTime = strchr(pszStr, ':') != nullptr;
    1014         225 :         nTZFlag = sWrkField.Date.TZFlag;
    1015         225 :         if (bHasDate && bHasTime)
    1016         123 :             return OFTDateTime;
    1017         102 :         else if (bHasDate)
    1018         100 :             return OFTDate;
    1019             :         else
    1020           2 :             return OFTTime;
    1021             :         // TODO: What if both are false?
    1022             :     }
    1023         383 :     return OFTString;
    1024             : }
    1025             : 
    1026             : /************************************************************************/
    1027             : /*                           OGRGeoJSONGetGeometryName()                */
    1028             : /************************************************************************/
    1029             : 
    1030        1646 : const char *OGRGeoJSONGetGeometryName(OGRGeometry const *poGeometry)
    1031             : {
    1032        1646 :     CPLAssert(nullptr != poGeometry);
    1033             : 
    1034        1646 :     const OGRwkbGeometryType eType = wkbFlatten(poGeometry->getGeometryType());
    1035             : 
    1036        1646 :     if (wkbPoint == eType)
    1037         161 :         return "Point";
    1038        1485 :     else if (wkbLineString == eType)
    1039          54 :         return "LineString";
    1040        1431 :     else if (wkbPolygon == eType)
    1041        1263 :         return "Polygon";
    1042         168 :     else if (wkbMultiPoint == eType)
    1043          66 :         return "MultiPoint";
    1044         102 :     else if (wkbMultiLineString == eType)
    1045          34 :         return "MultiLineString";
    1046          68 :     else if (wkbMultiPolygon == eType)
    1047          42 :         return "MultiPolygon";
    1048          26 :     else if (wkbGeometryCollection == eType)
    1049          25 :         return "GeometryCollection";
    1050             : 
    1051           1 :     return "Unknown";
    1052             : }

Generated by: LCOV version 1.14