Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GeoJSON/JSONFG: do not recognize GeoJSON files with a featureType feature property as JSONFG #9950

Merged
merged 7 commits into from
May 21, 2024
30 changes: 30 additions & 0 deletions autotest/ogr/data/geojson/featuretype.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
12 changes: 12 additions & 0 deletions autotest/ogr/data/jsonfg/crs_none_fc_mixed_feat_no_conformsTo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 1,
"coordRefSys": "[EPSG:4326]",
"geometry": { "type": "Point", "coordinates": [2, 49] },
"properties": {}
}
]
}
50 changes: 50 additions & 0 deletions autotest/ogr/ogr_geojson.py
Original file line number Diff line number Diff line change
Expand Up @@ -5151,3 +5151,53 @@ 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"


###############################################################################
# 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"


###############################################################################
# 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"
9 changes: 9 additions & 0 deletions autotest/ogr/ogr_jsonfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions gcore/gdaldriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, "");
Expand Down
4 changes: 2 additions & 2 deletions ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_) ||
Expand Down Expand Up @@ -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_);
Expand Down
125 changes: 104 additions & 21 deletions ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <assert.h>
#include "cpl_port.h"
#include "cpl_conv.h"
#include "cpl_json_streaming_parser.h"
#include "ogr_geometry.h"
#include <json.h> // JSON-C

Expand Down Expand Up @@ -156,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;
Expand All @@ -167,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"))
{
Expand Down Expand Up @@ -229,7 +235,8 @@ static bool IsGeoJSONLikeObject(const char *pszText)
{
bool bMightBeSequence;
bool bReadMoreBytes;
return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes);
return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes,
nullptr);
}

/************************************************************************/
Expand Down Expand Up @@ -395,13 +402,14 @@ static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo)
bool bReadMoreBytes = false;
if (!IsGeoJSONLikeObject(
reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
bMightBeSequence, bReadMoreBytes))
bMightBeSequence, bReadMoreBytes, poOpenInfo->papszAllowedDrivers))
{
if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 &&
poOpenInfo->TryToIngest(1000 * 1000) &&
!IsGeoJSONLikeObject(
reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
bMightBeSequence, bReadMoreBytes)))
bMightBeSequence, bReadMoreBytes,
poOpenInfo->papszAllowedDrivers)))
{
return false;
}
Expand All @@ -416,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;
}
Expand Down Expand Up @@ -451,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<const char *>(poOpenInfo->pabyHeader),
bMightBeSequence, bReadMoreBytes)))
bMightBeSequence, bReadMoreBytes,
poOpenInfo->papszAllowedDrivers)))
{
return false;
}
Expand All @@ -475,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;
}
Expand Down Expand Up @@ -508,24 +520,29 @@ 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;
}
}
}
}

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 ||
Expand All @@ -535,6 +552,71 @@ 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 (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 (nLength == strlen("coordRefSys") &&
strcmp(pszKey, "coordRefSys") == 0)
{
m_bFoundJSONFGCoordrefSys =
(m_osLevel == "{" || // At FeatureCollection level
m_osLevel == "{[{"); // At Feature level
if (m_bFoundJSONFGCoordrefSys)
StopParsing();
}
}

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;
}

Expand Down Expand Up @@ -592,11 +674,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;
}
Expand Down
2 changes: 1 addition & 1 deletion ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading