From 17f4c30dbf3f5e2c59c0857ddd59839ba06b370e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 15:54:13 +0200 Subject: [PATCH 1/7] JSONFG identification: recognize more JSON-FG specification version --- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index 2006604795e9..24f411f971c2 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -508,17 +508,24 @@ bool JSONFGIsObject(const char *pszText) const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText)); + // In theory, conformsTo should be required, but let be lax... { const auto nPos = osWithoutSpace.find("\"conformsTo\":["); if (nPos != std::string::npos) { - if (osWithoutSpace.find("\"[ogc-json-fg-1-0.1:core]\"", nPos) != - std::string::npos || - osWithoutSpace.find( - "\"http://www.opengis.net/spec/json-fg-1/0.1\"", nPos) != - std::string::npos) + for (const char *pszVersion : {"0.1", "0.2", "0.3"}) { - return true; + if (osWithoutSpace.find( + CPLSPrintf("\"[ogc-json-fg-1-%s:core]\"", pszVersion), + nPos) != std::string::npos || + osWithoutSpace.find( + CPLSPrintf( + "\"http://www.opengis.net/spec/json-fg-1/%s\"", + pszVersion), + nPos) != std::string::npos) + { + return true; + } } } } From b89aa342abcfe4333ae5eea9ce450621fb12b074 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 16:23:40 +0200 Subject: [PATCH 2/7] Test opening a JSONFG file with just coordRefSys as a hint it is one --- .../jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json | 12 ++++++++++++ autotest/ogr/ogr_jsonfg.py | 9 +++++++++ 2 files changed, 21 insertions(+) create mode 100644 autotest/ogr/data/jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json diff --git a/autotest/ogr/data/jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json b/autotest/ogr/data/jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json new file mode 100644 index 000000000000..7f1089f35cb5 --- /dev/null +++ b/autotest/ogr/data/jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json @@ -0,0 +1,12 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": 1, + "coordRefSys": "[EPSG:4326]", + "geometry": { "type": "Point", "coordinates": [2, 49] }, + "properties": {} + } + ] +} diff --git a/autotest/ogr/ogr_jsonfg.py b/autotest/ogr/ogr_jsonfg.py index b2d5aecbbcc2..55db4212ce79 100755 --- a/autotest/ogr/ogr_jsonfg.py +++ b/autotest/ogr/ogr_jsonfg.py @@ -296,6 +296,15 @@ def test_jsonfg_read_coordRefSys_invalid(coordRefSys): ), ("data/jsonfg/crs_4326_feat_only.json", 4326, [2, 1], 2, 49, None, None), ("data/jsonfg/crs_none.json", 4326, [2, 1], 2, 49, None, None), + ( + "data/jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json", + 4326, + [2, 1], + 2, + 49, + None, + None, + ), ], ) def test_jsonfg_read_crs( From 241ef936aff83ea91a2763c775b0a66612e0fd60 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 15:43:09 +0200 Subject: [PATCH 3/7] GeoJSON/JSONFG: do not recognize GeoJSON files with a featureType feature property as JSONFG Fixes https://github.com/OSGeo/gdal/issues/9946 --- autotest/ogr/data/geojson/featuretype.json | 30 +++++++++ autotest/ogr/ogr_geojson.py | 10 +++ ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 68 ++++++++++++++++++++- 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 autotest/ogr/data/geojson/featuretype.json diff --git a/autotest/ogr/data/geojson/featuretype.json b/autotest/ogr/data/geojson/featuretype.json new file mode 100644 index 000000000000..0cd898c0af60 --- /dev/null +++ b/autotest/ogr/data/geojson/featuretype.json @@ -0,0 +1,30 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "featureType": "Pipe" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 10.618902721, + 59.949950678 + ], + [ + 10.619008536, + 59.949828304 + ] + ] + } + } + ], + "crs": { + "type": "name", + "properties": { + "name": "urn:ogc:def:crs:EPSG::4326" + } + } +} diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 2b5e0837e68d..7e14d2e13e51 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -5151,3 +5151,13 @@ def test_ogr_geojson_geom_coord_precision_RFC7946(tmp_vsimem): prec = geom_fld.GetCoordinatePrecision() assert prec.GetXYResolution() == pytest.approx(8.983152841195214e-09) assert prec.GetZResolution() == 1e-3 + + +############################################################################### +# Test opening a file that has a featureType property, but is not JSONFG. + + +def test_ogr_geojson_open_with_featureType_non_jsonfg(): + + ds = gdal.OpenEx("data/geojson/featuretype.json") + assert ds.GetDriver().GetDescription() == "GeoJSON" diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index 24f411f971c2..a7373b0e0ec3 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -31,6 +31,7 @@ #include #include "cpl_port.h" #include "cpl_conv.h" +#include "cpl_json_streaming_parser.h" #include "ogr_geometry.h" #include // JSON-C @@ -530,9 +531,7 @@ bool JSONFGIsObject(const char *pszText) } } - if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos || - osWithoutSpace.find("\"featureType\":\"") != std::string::npos || - osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos || + if (osWithoutSpace.find("\"place\":{\"type\":") != std::string::npos || osWithoutSpace.find("\"place\":{\"coordinates\":") != std::string::npos || osWithoutSpace.find("\"time\":{\"date\":") != std::string::npos || @@ -542,6 +541,69 @@ bool JSONFGIsObject(const char *pszText) return true; } + if (osWithoutSpace.find("\"coordRefSys\":") != std::string::npos || + osWithoutSpace.find("\"featureType\":") != std::string::npos) + { + // Check that coordRefSys and/or featureType are either at the + // FeatureCollection or Feature level + struct MyParser : public CPLJSonStreamingParser + { + bool m_bFoundJSONFGFeatureType = false; + bool m_bFoundJSONFGCoordrefSys = false; + std::string m_osLevel{}; + + void StartObjectMember(const char *pszKey, size_t nLength) override + { + if (!m_bFoundJSONFGFeatureType && + nLength == strlen("featureType") && + strcmp(pszKey, "featureType") == 0) + { + m_bFoundJSONFGFeatureType = + (m_osLevel == "{" || // At FeatureCollection level + m_osLevel == "{[{"); // At Feature level + } + else if (!m_bFoundJSONFGCoordrefSys && + nLength == strlen("coordRefSys") && + strcmp(pszKey, "coordRefSys") == 0) + { + m_bFoundJSONFGCoordrefSys = + (m_osLevel == "{" || // At FeatureCollection level + m_osLevel == "{[{"); // At Feature level + } + } + + void StartObject() override + { + m_osLevel += '{'; + } + + void EndObject() override + { + if (!m_osLevel.empty()) + m_osLevel.pop_back(); + } + + void StartArray() override + { + m_osLevel += '['; + } + + void EndArray() override + { + if (!m_osLevel.empty()) + m_osLevel.pop_back(); + } + }; + + MyParser oParser; + oParser.Parse(pszText, strlen(pszText), true); + if (oParser.m_bFoundJSONFGFeatureType || + oParser.m_bFoundJSONFGCoordrefSys) + { + return true; + } + } + return false; } From 2ebbf7a17aa73e7c116a60dc3f85b4136ded3839 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 15:48:02 +0200 Subject: [PATCH 4/7] GeoJSON/JSONFG: make it possible with -if/papszAllowedDrivers to force opening a JSONFG file with the GeoJSON driver Refs #9946 --- autotest/ogr/ogr_geojson.py | 19 ++++++++++ .../geojson/ogrgeojsondatasource.cpp | 4 +-- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 36 ++++++++++++------- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h | 2 +- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 7e14d2e13e51..048c8b6548f5 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -5161,3 +5161,22 @@ def test_ogr_geojson_open_with_featureType_non_jsonfg(): ds = gdal.OpenEx("data/geojson/featuretype.json") assert ds.GetDriver().GetDescription() == "GeoJSON" + + +############################################################################### +# Test force opening a JSONFG file with the GeoJSON driver + + +def test_ogr_geojson_open_jsonfg_with_geojson(): + + ds = gdal.OpenEx("data/jsonfg/crs_none.json", allowed_drivers=["GeoJSON"]) + assert ds.GetDriver().GetDescription() == "GeoJSON" + + if gdal.GetDriverByName("JSONFG"): + ds = gdal.OpenEx("data/jsonfg/crs_none.json") + assert ds.GetDriver().GetDescription() == "JSONFG" + + ds = gdal.OpenEx( + "data/jsonfg/crs_none.json", allowed_drivers=["GeoJSON", "JSONFG"] + ) + assert ds.GetDriver().GetDescription() == "JSONFG" diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp index 5a2d259d4dbf..ad83a8672983 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp @@ -882,7 +882,7 @@ int OGRGeoJSONDataSource::ReadFromService(GDALOpenInfo *poOpenInfo, /* -------------------------------------------------------------------- */ if (EQUAL(pszSource, poOpenInfo->pszFilename) && osJSonFlavor_ == "GeoJSON") { - if (!GeoJSONIsObject(pszGeoData_)) + if (!GeoJSONIsObject(pszGeoData_, poOpenInfo->papszAllowedDrivers)) { if (ESRIJSONIsObject(pszGeoData_) || TopoJSONIsObject(pszGeoData_) || @@ -1003,7 +1003,7 @@ void OGRGeoJSONDataSource::LoadLayers(GDALOpenInfo *poOpenInfo, oOpenInfo.fpL = nullptr; } - if (!GeoJSONIsObject(pszGeoData_)) + if (!GeoJSONIsObject(pszGeoData_, poOpenInfo->papszAllowedDrivers)) { CPLDebug(pszJSonFlavor, "No valid %s data found in source '%s'", pszJSonFlavor, pszName_); diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index a7373b0e0ec3..86eed7535833 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -157,7 +157,8 @@ static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize) /************************************************************************/ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, - bool &bReadMoreBytes) + bool &bReadMoreBytes, + CSLConstList papszAllowedDrivers) { bMightBeSequence = false; bReadMoreBytes = false; @@ -168,8 +169,12 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, if (IsTypeSomething(pszText, "Topology")) return false; - if (JSONFGIsObject(pszText) && GDALGetDriverByName("JSONFG")) + if ((!papszAllowedDrivers || + CSLFindString(papszAllowedDrivers, "JSONFG") >= 0) && + GDALGetDriverByName("JSONFG") && JSONFGIsObject(pszText)) + { return false; + } if (IsTypeSomething(pszText, "FeatureCollection")) { @@ -230,7 +235,8 @@ static bool IsGeoJSONLikeObject(const char *pszText) { bool bMightBeSequence; bool bReadMoreBytes; - return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes); + return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, + nullptr); } /************************************************************************/ @@ -396,13 +402,14 @@ static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo) bool bReadMoreBytes = false; if (!IsGeoJSONLikeObject( reinterpret_cast(poOpenInfo->pabyHeader), - bMightBeSequence, bReadMoreBytes)) + bMightBeSequence, bReadMoreBytes, poOpenInfo->papszAllowedDrivers)) { if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 && poOpenInfo->TryToIngest(1000 * 1000) && !IsGeoJSONLikeObject( reinterpret_cast(poOpenInfo->pabyHeader), - bMightBeSequence, bReadMoreBytes))) + bMightBeSequence, bReadMoreBytes, + poOpenInfo->papszAllowedDrivers))) { return false; } @@ -417,11 +424,12 @@ static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo) /* GeoJSONIsObject() */ /************************************************************************/ -bool GeoJSONIsObject(const char *pszText) +bool GeoJSONIsObject(const char *pszText, CSLConstList papszAllowedDrivers) { bool bMightBeSequence = false; bool bReadMoreBytes = false; - if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes)) + if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, + papszAllowedDrivers)) { return false; } @@ -452,13 +460,15 @@ static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo) bool bMightBeSequence = false; bool bReadMoreBytes = false; - if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes)) + if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, + poOpenInfo->papszAllowedDrivers)) { if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 && poOpenInfo->TryToIngest(1000 * 1000) && IsGeoJSONLikeObject( reinterpret_cast(poOpenInfo->pabyHeader), - bMightBeSequence, bReadMoreBytes))) + bMightBeSequence, bReadMoreBytes, + poOpenInfo->papszAllowedDrivers))) { return false; } @@ -476,7 +486,8 @@ bool GeoJSONSeqIsObject(const char *pszText) bool bMightBeSequence = false; bool bReadMoreBytes = false; - if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes)) + if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, + nullptr)) { return false; } @@ -661,11 +672,12 @@ GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo) return eGeoJSONSourceFile; } const char *pszText = poOpenInfo->pszFilename + strlen("GeoJSON:"); - if (GeoJSONIsObject(pszText)) + if (GeoJSONIsObject(pszText, poOpenInfo->papszAllowedDrivers)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } - else if (GeoJSONIsObject(poOpenInfo->pszFilename)) + else if (GeoJSONIsObject(poOpenInfo->pszFilename, + poOpenInfo->papszAllowedDrivers)) { srcType = eGeoJSONSourceText; } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h index d0988eb450e8..37940f161447 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h @@ -59,7 +59,7 @@ GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo); /* GeoJSONIsObject */ /************************************************************************/ -bool GeoJSONIsObject(const char *pszText); +bool GeoJSONIsObject(const char *pszText, CSLConstList papszAllowedDrivers); bool GeoJSONSeqIsObject(const char *pszText); bool ESRIJSONIsObject(const char *pszText); bool TopoJSONIsObject(const char *pszText); From d1a7d21159a7c6a41f74cab5ab31c376ddd62056 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 15:51:15 +0200 Subject: [PATCH 5/7] GDALIdentifyDriverEx(): transmit papszAllowedDrivers to Identify(), to for example make it possible with -if/papszAllowedDrivers to force identifying a JSONFG file with the GeoJSON driver Refs #9946 --- autotest/ogr/ogr_geojson.py | 21 +++++++++++++++++++++ gcore/gdaldriver.cpp | 1 + 2 files changed, 22 insertions(+) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 048c8b6548f5..f905273b2939 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -5180,3 +5180,24 @@ def test_ogr_geojson_open_jsonfg_with_geojson(): "data/jsonfg/crs_none.json", allowed_drivers=["GeoJSON", "JSONFG"] ) assert ds.GetDriver().GetDescription() == "JSONFG" + + +############################################################################### +# Test force identifying a JSONFG file with the GeoJSON driver + + +def test_ogr_geojson_identify_jsonfg_with_geojson(): + + drv = gdal.IdentifyDriverEx( + "data/jsonfg/crs_none.json", allowed_drivers=["GeoJSON"] + ) + assert drv.GetDescription() == "GeoJSON" + + if gdal.GetDriverByName("JSONFG"): + drv = gdal.IdentifyDriverEx("data/jsonfg/crs_none.json") + assert drv.GetDescription() == "JSONFG" + + drv = gdal.IdentifyDriverEx( + "data/jsonfg/crs_none.json", allowed_drivers=["GeoJSON", "JSONFG"] + ) + assert drv.GetDescription() == "JSONFG" diff --git a/gcore/gdaldriver.cpp b/gcore/gdaldriver.cpp index 0496c3408ff8..e44cdeae2904 100644 --- a/gcore/gdaldriver.cpp +++ b/gcore/gdaldriver.cpp @@ -2665,6 +2665,7 @@ GDALDriverH CPL_STDCALL GDALIdentifyDriverEx( nIdentifyFlags |= GDAL_OF_KIND_MASK & ~GDAL_OF_MULTIDIM_RASTER; GDALOpenInfo oOpenInfo(pszFilename, nIdentifyFlags, papszFileList); + oOpenInfo.papszAllowedDrivers = papszAllowedDrivers; CPLErrorStateBackuper oBackuper; CPLErrorSetState(CE_None, CPLE_AppDefined, ""); From 66d2e2108452c3f7636c88fc76c9ead6716bbcf5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 16:17:49 +0200 Subject: [PATCH 6/7] Add CPLJSonStreamingParser::StopParsing() --- port/cpl_json_streaming_parser.cpp | 14 +++++++++++--- port/cpl_json_streaming_parser.h | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/port/cpl_json_streaming_parser.cpp b/port/cpl_json_streaming_parser.cpp index b22277ccfdcf..e5e23481e3ae 100644 --- a/port/cpl_json_streaming_parser.cpp +++ b/port/cpl_json_streaming_parser.cpp @@ -144,6 +144,15 @@ bool CPLJSonStreamingParser::EmitException(const char *pszMessage) return false; } +/************************************************************************/ +/* StopParsing() */ +/************************************************************************/ + +void CPLJSonStreamingParser::StopParsing() +{ + m_bStopParsing = true; +} + /************************************************************************/ /* EmitUnexpectedChar() */ /************************************************************************/ @@ -433,11 +442,10 @@ void CPLJSonStreamingParser::DecodeUnicode() bool CPLJSonStreamingParser::Parse(const char *pStr, size_t nLength, bool bFinished) { - if (m_bExceptionOccurred) - return false; - while (true) { + if (m_bExceptionOccurred || m_bStopParsing) + return false; State eCurState = currentState(); if (eCurState == INIT) { diff --git a/port/cpl_json_streaming_parser.h b/port/cpl_json_streaming_parser.h index 50e422deedad..62c569e8b679 100644 --- a/port/cpl_json_streaming_parser.h +++ b/port/cpl_json_streaming_parser.h @@ -55,6 +55,7 @@ class CPL_DLL CPLJSonStreamingParser bool m_bExceptionOccurred = false; bool m_bElementFound = false; + bool m_bStopParsing = false; int m_nLastChar = 0; int m_nLineCounter = 1; int m_nCharCounter = 1; @@ -98,6 +99,7 @@ class CPL_DLL CPLJSonStreamingParser protected: bool EmitException(const char *pszMessage); + void StopParsing(); public: CPLJSonStreamingParser(); From 420da570d40f13b4fe7c383ca4e3bb7799c1f6b8 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 16 May 2024 16:18:10 +0200 Subject: [PATCH 7/7] JSONFGIsObject: use CPLJSonStreamingParser::StopParsing() --- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index 86eed7535833..4ec6ba2b3ae6 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -565,21 +565,23 @@ bool JSONFGIsObject(const char *pszText) void StartObjectMember(const char *pszKey, size_t nLength) override { - if (!m_bFoundJSONFGFeatureType && - nLength == strlen("featureType") && + if (nLength == strlen("featureType") && strcmp(pszKey, "featureType") == 0) { m_bFoundJSONFGFeatureType = (m_osLevel == "{" || // At FeatureCollection level m_osLevel == "{[{"); // At Feature level + if (m_bFoundJSONFGFeatureType) + StopParsing(); } - else if (!m_bFoundJSONFGCoordrefSys && - nLength == strlen("coordRefSys") && + else if (nLength == strlen("coordRefSys") && strcmp(pszKey, "coordRefSys") == 0) { m_bFoundJSONFGCoordrefSys = (m_osLevel == "{" || // At FeatureCollection level m_osLevel == "{[{"); // At Feature level + if (m_bFoundJSONFGCoordrefSys) + StopParsing(); } }