diff --git a/autotest/ogr/data/gml/datetime.gml b/autotest/ogr/data/gml/datetime.gml new file mode 100644 index 000000000000..2db2e4037bba --- /dev/null +++ b/autotest/ogr/data/gml/datetime.gml @@ -0,0 +1,24 @@ + + + + + 23:59:60 + 9999-12-31 + 9999-12-31T23:59:60.999 + 9999-12-31T23:59:60.999 + + + + + 23:59:60.999 + 9999-12-31 + 9999-12-31T23:59:60Z + 9999-12-31T23:59:60+12:30 + + + diff --git a/autotest/ogr/data/gml/datetime.xsd b/autotest/ogr/data/gml/datetime.xsd new file mode 100644 index 000000000000..ac07c165c35b --- /dev/null +++ b/autotest/ogr/data/gml/datetime.xsd @@ -0,0 +1,54 @@ + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index 83dd1cefe429..9a7372b9a2ec 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -5214,3 +5214,38 @@ def test_ogr_gml_citygml3(tmp_vsimem, skip_resolve_as_open_option): f.GetGeometryRef().ExportToWkt() == "POLYHEDRALSURFACE Z (((0 0 0,1 1 1,1 0 2,0 0 0)))" ) + + +############################################################################### + + +def test_ogr_gml_datetime(tmp_vsimem): + + ds = ogr.Open("data/gml/datetime.gml") + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTTime + assert f["time"] == "23:59:60" + assert lyr.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTDate + assert f["date"] == "9999/12/31" + assert lyr.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTDateTime + assert f["datetime"] == "9999/12/31 23:59:60.999" + assert lyr.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTDateTime + assert f["dtInTimePosition"] == "9999/12/31 23:59:60.999" + + tmp_filename = tmp_vsimem / "out.gml" + with gdal.VSIFile("data/gml/datetime.gml", "rb") as fin: + with gdal.VSIFile(tmp_filename, "wb") as fout: + fout.write(fin.read()) + + ds = ogr.Open(tmp_filename) + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTTime + assert f["time"] == "23:59:60" + assert lyr.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTDate + assert f["date"] == "9999/12/31" + assert lyr.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTDateTime + assert f["datetime"] == "9999/12/31 23:59:60.999" + assert lyr.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTDateTime + assert f["timePosition"] == "9999/12/31 23:59:60.999" diff --git a/autotest/ogr/ogr_gmlas.py b/autotest/ogr/ogr_gmlas.py index f29ad01023e5..61d4e7bd3e9e 100755 --- a/autotest/ogr/ogr_gmlas.py +++ b/autotest/ogr/ogr_gmlas.py @@ -3534,3 +3534,35 @@ def test_ogr_gmlas_citygml_lod2_no_schema_location(): pytest.skip(f"cannot open {url}") assert ds.GetLayerCount() == 1537 + + +############################################################################### + + +@pytest.mark.require_curl() +def test_ogr_gmlas_datetime(tmp_vsimem): + + ds = ogr.Open("GMLAS:data/gml/datetime.gml") + lyr = ds.GetLayer("test") + f = lyr.GetNextFeature() + lyr_defn = lyr.GetLayerDefn() + assert ( + lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("time")).GetType() == ogr.OFTTime + ) + assert f["time"] == "23:59:60" + assert ( + lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("date")).GetType() == ogr.OFTDate + ) + assert f["date"] == "9999/12/31" + assert ( + lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("datetime")).GetType() + == ogr.OFTDateTime + ) + assert f["datetime"] == "9999/12/31 23:59:60.999" + assert ( + lyr_defn.GetFieldDefn( + lyr_defn.GetFieldIndex("dtintimeposition_timeposition") + ).GetType() + == ogr.OFTDateTime + ) + assert f["dtintimeposition_timeposition"] == "9999/12/31 23:59:60.999" diff --git a/autotest/ogr/ogr_wfs.py b/autotest/ogr/ogr_wfs.py index db893c9e7d39..914246d1e01d 100755 --- a/autotest/ogr/ogr_wfs.py +++ b/autotest/ogr/ogr_wfs.py @@ -3455,7 +3455,7 @@ def test_ogr_wfs_vsimem_wfs110_schema_not_understood(with_and_without_streaming) or f.int != 123456789 or f.float != 1.2 or f.double != 1.23 - or f.dt != "2015-04-17T12:34:56Z" + or f.dt != "2015/04/17 12:34:56+00" or f.GetGeometryRef().ExportToWkt() != "POINT (2 49)" ) @@ -5342,3 +5342,88 @@ def test_ogr_wfs_no_gml_driver( lyr.GetNextFeature() finally: gml_drv.Register() + + +############################################################################### +# Test fix for https://github.com/OSGeo/gdal/issues/13120 + + +def test_ogr_wfs_does_not_understand_schema(): + + getfeatures_response = """ + + + foo + + + 41.5 0.5 + + + + +""" + + schema = """ + + + + + + + + + + + + + +""" + + with gdaltest.tempfile( + "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&REQUEST=GetCapabilities", + """ + + + foo:lyr + urn:ogc:def:crs:EPSG::4326 + + -10 40 + 15 50 + + + + +""", + ), gdaltest.tempfile( + "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=DescribeFeatureType&TYPENAME=foo:lyr", + schema, + ), gdaltest.tempfile( + "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=foo:lyr&COUNT=1", + getfeatures_response, + ), gdaltest.tempfile( + "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=foo:lyr", + getfeatures_response, + ): + ds = ogr.Open("WFS:/vsimem/test_ogr_wfs_does_not_understand_schema") + lyr = ds.GetLayer(0) + assert lyr.GetGeometryColumn() == "shape" + + lyr.SetSpatialFilterRect(0, 41, 1, 42) + + with gdaltest.tempfile( + "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=DescribeFeatureType&TYPENAME=foo:lyr", + schema, + ), gdaltest.tempfile( + "/vsimem/test_ogr_wfs_does_not_understand_schema?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=foo:lyr&FILTER=%3CFilter%20xmlns%3D%22http:%2F%2Fwww.opengis.net%2Ffes%2F2.0%22%20xmlns:gml%3D%22http:%2F%2Fwww.opengis.net%2Fgml%2F3.2%22%3E%3CBBOX%3E%3CValueReference%3Eshape%3C%2FValueReference%3E%3Cgml:Envelope%3E%3Cgml:lowerCorner%3E41.0000000000000000%200.0000000000000000%3C%2Fgml:lowerCorner%3E%3Cgml:upperCorner%3E42.0000000000000000%201.0000000000000000%3C%2Fgml:upperCorner%3E%3C%2Fgml:Envelope%3E%3C%2FBBOX%3E%3C%2FFilter%3E", + getfeatures_response, + ): + f = lyr.GetNextFeature() + assert f diff --git a/ogr/ogrsf_frmts/gml/gmlhandler.cpp b/ogr/ogrsf_frmts/gml/gmlhandler.cpp index c122e9e7c7c6..5828d7b7bcc4 100644 --- a/ogr/ogrsf_frmts/gml/gmlhandler.cpp +++ b/ogr/ogrsf_frmts/gml/gmlhandler.cpp @@ -1085,11 +1085,19 @@ int GMLHandler::FindRealPropertyByCheckingConditions(int nIdx, void *attr) OGRErr GMLHandler::startElementFeatureAttribute(const char *pszName, int nLenName, void *attr) { - /* Reset flag */ - m_bInCurField = false; GMLReadState *poState = m_poReader->GetState(); + if (m_bInCurField && m_nAttributeIndex >= 0 && + std::string_view(pszName, nLenName) == "timePosition") + { + poState->PushPath(pszName, nLenName); + return OGRERR_NONE; + } + + /* Reset flag */ + m_bInCurField = false; + /* -------------------------------------------------------------------- */ /* If we are collecting geometry, or if we determine this is a */ /* geometry element then append to the geometry info. */ diff --git a/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h b/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h index 68b8e9f050ed..284cd1a0f50c 100644 --- a/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h +++ b/ogr/ogrsf_frmts/gmlas/ogr_gmlas_consts.h @@ -268,6 +268,7 @@ STRING_CONST(szXS_GYEAR, "gYear"); STRING_CONST(szXS_GYEAR_MONTH, "gYearMonth"); STRING_CONST(szXS_TIME, "time"); STRING_CONST(szXS_DATETIME, "dateTime"); +STRING_CONST(szXS_TIME_INSTANT_TYPE, "TimeInstantType"); STRING_CONST(szXS_ANY_URI, "anyURI"); STRING_CONST(szXS_ANY_TYPE, "anyType"); STRING_CONST(szXS_ANY_SIMPLE_TYPE, "anySimpleType"); diff --git a/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp b/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp index d8f0000d508a..c34eb9ec06a7 100644 --- a/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp +++ b/ogr/ogrsf_frmts/gmlas/ogrgmlasschemaanalyzer.cpp @@ -3344,9 +3344,19 @@ bool GMLASSchemaAnalyzer::ExploreModelGroup( const std::vector &osNestedClassFields = oNestedClass.GetFields(); + const bool bIsTimeInstantType = + transcode(poTypeDef->getName()) == + szXS_TIME_INSTANT_TYPE; for (size_t j = 0; j < osNestedClassFields.size(); j++) { GMLASField oField(osNestedClassFields[j]); + if (bIsTimeInstantType && + oField.GetType() == GMLAS_FT_ANYSIMPLETYPE && + oField.GetName() == "timePosition") + { + oField.SetType(GMLAS_FT_DATETIME, + szXS_DATETIME); + } oField.SetName(osPrefixedEltName + "_" + oField.GetName()); if (nMinOccurs == 0 || @@ -3356,6 +3366,7 @@ bool GMLASSchemaAnalyzer::ExploreModelGroup( oField.SetMinOccurs(0); oField.SetNotNullable(false); } + aoFields.push_back(std::move(oField)); } diff --git a/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp b/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp index 778901b0c870..22b6e820e1c4 100644 --- a/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp +++ b/ogr/ogrsf_frmts/gmlutils/gmlpropertydefn.cpp @@ -148,7 +148,62 @@ void GMLPropertyDefn::AnalysePropertyValue(const GMLProperty *psGMLProperty, } else { - m_eType = GMLPT_String; + const auto IsDigitLowerOrEqual = [](char c, int max) + { return c >= '0' && c <= '0' + max; }; + + if ((m_eType == GMLPT_Untyped || m_eType == GMLPT_DateTime || + m_eType == GMLPT_Date) && + IsDigitLowerOrEqual(pszValue[0], 9) && + IsDigitLowerOrEqual(pszValue[1], 9) && + IsDigitLowerOrEqual(pszValue[2], 9) && + IsDigitLowerOrEqual(pszValue[3], 9) && pszValue[4] == '-' && + IsDigitLowerOrEqual(pszValue[5], 1) && + IsDigitLowerOrEqual(pszValue[6], 9) && pszValue[7] == '-' && + IsDigitLowerOrEqual(pszValue[8], 3) && + IsDigitLowerOrEqual(pszValue[9], 9)) + { + if (pszValue[10] == 'T' && + IsDigitLowerOrEqual(pszValue[11], 2) && + IsDigitLowerOrEqual(pszValue[12], 9) && + pszValue[13] == ':' && + IsDigitLowerOrEqual(pszValue[14], 5) && + IsDigitLowerOrEqual(pszValue[15], 9) && + pszValue[16] == ':' && + IsDigitLowerOrEqual(pszValue[17], 6) && + IsDigitLowerOrEqual(pszValue[18], 9) && + (pszValue[19] == '\0' || pszValue[19] == '.' || + pszValue[19] == 'Z' || pszValue[19] == '+' || + pszValue[19] == '-')) + { + m_eType = GMLPT_DateTime; + } + else if (pszValue[10] == '\0') + { + if (m_eType != GMLPT_DateTime) + m_eType = GMLPT_Date; + } + else + { + m_eType = GMLPT_String; + } + } + else if ((m_eType == GMLPT_Untyped || m_eType == GMLPT_Time) && + IsDigitLowerOrEqual(pszValue[0], 2) && + IsDigitLowerOrEqual(pszValue[1], 9) && + pszValue[2] == ':' && + IsDigitLowerOrEqual(pszValue[3], 5) && + IsDigitLowerOrEqual(pszValue[4], 9) && + pszValue[5] == ':' && + IsDigitLowerOrEqual(pszValue[6], 6) && + IsDigitLowerOrEqual(pszValue[7], 9) && + (pszValue[8] == '\0' || pszValue[8] == '.')) + { + m_eType = GMLPT_Time; + } + else + { + m_eType = GMLPT_String; + } } } else diff --git a/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp b/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp index e1770e4bdd94..9463d99d0c91 100644 --- a/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp +++ b/ogr/ogrsf_frmts/gmlutils/parsexsd.cpp @@ -129,7 +129,7 @@ static bool GetSimpleTypeProperties(CPLXMLNode *psTypeNode, return true; } - else if (EQUAL(pszBase, "dateTime")) + else if (EQUAL(pszBase, "dateTime") || EQUAL(pszBase, "TimeInstantType")) { *pGMLType = GMLPT_DateTime; return true; @@ -425,7 +425,8 @@ static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode, gmlType = GMLPT_Date; else if (EQUAL(pszStrippedNSType, "time")) gmlType = GMLPT_Time; - else if (EQUAL(pszStrippedNSType, "dateTime")) + else if (EQUAL(pszStrippedNSType, "dateTime") || + EQUAL(pszStrippedNSType, "TimeInstantType")) gmlType = GMLPT_DateTime; else if (EQUAL(pszStrippedNSType, "real") || EQUAL(pszStrippedNSType, "double") || diff --git a/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp b/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp index 9a1118f02646..f9f4ca138627 100644 --- a/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp +++ b/ogr/ogrsf_frmts/wfs/ogrwfslayer.cpp @@ -1185,6 +1185,7 @@ OGRFeatureDefn *OGRWFSLayer::BuildLayerDefn(OGRFeatureDefn *poSrcFDefn) return poFeatureDefn; } poSrcFDefn = l_poLayer->GetLayerDefn(); + osGeometryColumnName = l_poLayer->GetGeometryColumn(); bGotApproximateLayerDefn = true; /* We cannot trust width and precision based on a single feature */ bUnsetWidthPrecision = true; diff --git a/ogr/ogrutils.cpp b/ogr/ogrutils.cpp index d4232c0f98f1..c5fc0de7f74c 100644 --- a/ogr/ogrutils.cpp +++ b/ogr/ogrutils.cpp @@ -1261,7 +1261,7 @@ int OGRParseDate(const char *pszInput, OGRField *psField, int nOptions) return FALSE; const double dfSeconds = CPLAtof(pszInput); // We accept second=60 for leap seconds - if (dfSeconds > 60.0) + if (dfSeconds >= 61.0) return FALSE; psField->Date.Second = static_cast(dfSeconds);