diff --git a/python/server/auto_generated/qgsserverapiutils.sip.in b/python/server/auto_generated/qgsserverapiutils.sip.in index d894ccdafb1..4e2e9aa9866 100644 --- a/python/server/auto_generated/qgsserverapiutils.sip.in +++ b/python/server/auto_generated/qgsserverapiutils.sip.in @@ -49,11 +49,17 @@ Parses a comma separated ``bbox`` into a (possibly empty) :py:class:`QgsRectangl static TemporalDateInterval parseTemporalDateInterval( const QString &interval ) throw( QgsServerApiBadRequestException ); %Docstring -Parse a date time ``interval`` and returns a TemporalInterval +Parse a date ``interval`` and returns a TemporalDateInterval :raises QgsServerApiBadRequestException: if interval cannot be parsed %End + static TemporalDateTimeInterval parseTemporalDateTimeInterval( const QString &interval ) throw( QgsServerApiBadRequestException ); +%Docstring +Parse a datetime ``interval`` and returns a TemporalDateTimeInterval + +:raises QgsServerApiBadRequestException: if interval cannot be parsed +%End diff --git a/resources/server/api/ogc/schema.json b/resources/server/api/ogc/schema.json index 7e894972814..d811b704afb 100644 --- a/resources/server/api/ogc/schema.json +++ b/resources/server/api/ogc/schema.json @@ -306,10 +306,10 @@ } } }, - "time" : { - "name" : "time", + "datetime" : { + "name" : "datetime", "in" : "query", - "description" : "Either a date-time or a period string that adheres to RFC 3339. Examples:\n\n* A date-time: \"2018-02-12T23:20:50Z\"\n* A period: \"2018-02-12T00:00:00Z/2018-03-18T12:31:12Z\" or \"2018-02-12T00:00:00Z/P1M6DT12H31M12S\"\n\nOnly features that have a temporal property that intersects the value of\n`time` are selected.\n\nIf a feature has multiple temporal properties, it is the decision of the\nserver whether only a single temporal property is used to determine\nthe extent or all relevant temporal properties.", + "description" : "Either a date-time or an interval, open or closed. Date and time expressions\nadhere to RFC 3339. Open intervals are expressed using double-dots.\n\nExamples:\n * A date-time: \"2018-02-12T23:20:50Z\"\n * A closed interval: \"2018-02-12T00:00:00Z/2018-03-18T12:31:12Z\"\n * Open intervals: \"2018-02-12T00:00:00Z/..\" or \"../2018-03-18T12:31:12Z\"\nOnly features that have a temporal property that intersects the value of\n`datetime` are selected.\nIf a feature has multiple temporal properties, it is the decision of the\nserver whether only a single temporal property is used to determine\nthe extent or all relevant temporal properties.", "required" : false, "style" : "form", "explode" : false, diff --git a/src/app/qgssourcefieldsproperties.cpp b/src/app/qgssourcefieldsproperties.cpp index 767da27e9b0..baada8b4a6b 100644 --- a/src/app/qgssourcefieldsproperties.cpp +++ b/src/app/qgssourcefieldsproperties.cpp @@ -279,7 +279,7 @@ void QgsSourceFieldsProperties::setRow( int row, int idx, const QgsField &field // ok, in theory, we could support any field type that // can contain something convertible to a date/datetime, but // let's keep it simple for now - if ( field.isDateOrTime() ) + if ( field.isDateOrTime() || field.type() == QVariant::String ) { oapifAttrItem->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable ); oapifAttrItem->setCheckState( mLayer->includeAttributesOapifTemporalFilters().contains( field.name() ) ? Qt::Checked : Qt::Unchecked ); diff --git a/src/server/qgsserverapiutils.cpp b/src/server/qgsserverapiutils.cpp index 9ca47e25946..fba96daa672 100644 --- a/src/server/qgsserverapiutils.cpp +++ b/src/server/qgsserverapiutils.cpp @@ -83,6 +83,10 @@ template T QgsServerApiUtils::parseTemporalInterval( const } }; const QStringList parts { interval.split( '/' ) }; + if ( parts.length() != 2 ) + { + throw QgsServerApiBadRequestException( QStringLiteral( "%1 is not a valid datetime interval." ).arg( interval ), QStringLiteral( "Server" ), Qgis::Critical ); + } return { parseDate( parts[0] ), parseDate( parts[1] ) }; } @@ -103,6 +107,25 @@ QgsExpression QgsServerApiUtils::temporalFilterExpression( const QgsVectorLayer QgsExpression expression; QStringList conditions; + QSet temporalFields { layer->includeAttributesOapifTemporalFilters() }; + // Automatically pick any date/datetime fields if empty + if ( temporalFields.isEmpty() ) + { + const auto constFields { layer->fields() }; + for ( const auto &f : constFields ) + { + if ( f.isDateOrTime() ) + { + temporalFields.insert( f.name() ); + } + } + } + + if ( temporalFields.isEmpty() ) + { + return expression; + } + // Is it an interval? if ( interval.contains( '/' ) ) { @@ -110,7 +133,7 @@ QgsExpression QgsServerApiUtils::temporalFilterExpression( const QgsVectorLayer try { TemporalDateInterval dateInterval { QgsServerApiUtils::parseTemporalDateInterval( interval ) }; - for ( const auto &fieldName : layer->includeAttributesOapifTemporalFilters() ) + for ( const auto &fieldName : temporalFields ) { int fieldIdx { layer->fields().lookupField( fieldName ) }; if ( fieldIdx < 0 ) @@ -155,7 +178,7 @@ QgsExpression QgsServerApiUtils::temporalFilterExpression( const QgsVectorLayer catch ( QgsServerApiBadRequestException & ) // try datetime { TemporalDateTimeInterval dateTimeInterval { QgsServerApiUtils::parseTemporalDateTimeInterval( interval ) }; - for ( const auto &fieldName : layer->includeAttributesOapifTemporalFilters() ) + for ( const auto &fieldName : qgis::as_const( temporalFields ) ) { int fieldIdx { layer->fields().lookupField( fieldName ) }; if ( fieldIdx < 0 ) @@ -199,7 +222,7 @@ QgsExpression QgsServerApiUtils::temporalFilterExpression( const QgsVectorLayer } else // plain value { - for ( const auto &fieldName : layer->includeAttributesOapifTemporalFilters() ) + for ( const auto &fieldName : qgis::as_const( temporalFields ) ) { int fieldIdx { layer->fields().lookupField( fieldName ) }; if ( fieldIdx < 0 ) diff --git a/src/server/qgsserverapiutils.h b/src/server/qgsserverapiutils.h index ed49b1a9e64..160002949df 100644 --- a/src/server/qgsserverapiutils.h +++ b/src/server/qgsserverapiutils.h @@ -59,12 +59,20 @@ class SERVER_EXPORT QgsServerApiUtils */ static QgsRectangle parseBbox( const QString &bbox ); + /** + * A temporal date interval, if only one of "begin" or "end" are valid, the interval is open. + * If both "begin" and "end"a re invalid the interval is invalid. + */ struct TemporalDateInterval { QDate begin; QDate end; }; + /** + * A temporal datetime interval, if only one of "begin" or "end" are valid, the interval is open. + * If both "begin" and "end"a re invalid the interval is invalid. + */ struct TemporalDateTimeInterval { QDateTime begin; @@ -72,14 +80,21 @@ class SERVER_EXPORT QgsServerApiUtils }; /** - * Parse a date time \a interval and returns a TemporalInterval + * Parse a date \a interval and returns a TemporalDateInterval * * \throws QgsServerApiBadRequestException if interval cannot be parsed */ static TemporalDateInterval parseTemporalDateInterval( const QString &interval ) SIP_THROW( QgsServerApiBadRequestException ); + + /** + * Parse a datetime \a interval and returns a TemporalDateTimeInterval + * + * \throws QgsServerApiBadRequestException if interval cannot be parsed + */ static TemporalDateTimeInterval parseTemporalDateTimeInterval( const QString &interval ) SIP_THROW( QgsServerApiBadRequestException ); ///@cond PRIVATE + // T is TemporalDateInterval|TemporalDateTimeInterval, T2 is QDate|QdateTime template static T parseTemporalInterval( const QString &interval ) SIP_SKIP; /// @endcond @@ -99,9 +114,6 @@ class SERVER_EXPORT QgsServerApiUtils /** * layerExtent returns json array with [xMin,yMin,xMax,yMax] CRS84 extent for the given \a layer - * FIXME: the OpenAPI swagger docs say that it is inverted axis order: West, north, east, south edges of the spatial extent. - * but current example implementations and GDAL assume it's not. - * TODO: maybe consider advertised extent instead? */ static json layerExtent( const QgsVectorLayer *layer ) SIP_SKIP; diff --git a/src/server/services/wfs3/qgswfs3handlers.cpp b/src/server/services/wfs3/qgswfs3handlers.cpp index 60b4489ea8e..b598ec141d7 100644 --- a/src/server/services/wfs3/qgswfs3handlers.cpp +++ b/src/server/services/wfs3/qgswfs3handlers.cpp @@ -30,6 +30,7 @@ #include "qgsserverinterface.h" #include "qgsexpressioncontext.h" #include "qgsexpressioncontextutils.h" +#include "qgslogger.h" #ifdef HAVE_SERVER_PYTHON_PLUGINS #include "qgsfilterrestorer.h" @@ -759,12 +760,38 @@ QList QgsWfs3CollectionsItemsHandler::parameters( // datetime QgsServerQueryStringParameter datetime { QStringLiteral( "datetime" ), false, - QgsServerQueryStringParameter::Type::Integer, - QStringLiteral( "Date time filter" ), + QgsServerQueryStringParameter::Type::String, + QStringLiteral( "Datetime filter" ), }; - datetime.setCustomValidator( [ = ]( const QgsServerApiContext &, QVariant & value ) -> bool + datetime.setCustomValidator( [ ]( const QgsServerApiContext &, QVariant & value ) -> bool { - // TODO + const QString stringValue { value.toString() }; + if ( stringValue.contains( '/' ) ) + { + try + { + QgsServerApiUtils::parseTemporalDateInterval( stringValue ); + } + catch ( QgsServerException & ) + { + try + { + QgsServerApiUtils::parseTemporalDateTimeInterval( stringValue ); + } + catch ( QgsServerException & ) + { + return false; + } + } + } + else + { + if ( ! QDate::fromString( stringValue, Qt::DateFormat::ISODate ).isValid( ) && + ! QDateTime::fromString( stringValue, Qt::DateFormat::ISODate ).isValid( ) ) + { + return false; + } + } return true; } ); params.push_back( datetime ); @@ -870,7 +897,7 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context { "$ref", "#/components/parameters/resultType" }, { "$ref", "#/components/parameters/bbox" }, { "$ref", "#/components/parameters/bbox-crs" }, - { "$ref", "#/components/parameters/time" }, + { "$ref", "#/components/parameters/datetime" }, } }; @@ -1044,7 +1071,7 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c QString filterExpression; QStringList expressions; - /*/ datetime + // datetime const QString datetime { context.request()->queryParameter( QStringLiteral( "datetime" ) ) }; if ( ! datetime.isEmpty() ) { @@ -1057,7 +1084,7 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c { expressions.push_back( timeExpression.expression() ); } - }*/ + } // Inputs are valid, process request QgsFeatureRequest featureRequest = filteredRequest( mapLayer, context ); @@ -1092,8 +1119,13 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c } // Join all expression filters - filterExpression = expressions.join( QStringLiteral( " AND " ) ); - featureRequest.setFilterExpression( filterExpression ); + if ( ! expressions.isEmpty() ) + { + filterExpression = expressions.join( QStringLiteral( " AND " ) ); + featureRequest.setFilterExpression( filterExpression ); + QgsDebugMsgLevel( QStringLiteral( "Filter expression: %1" ).arg( featureRequest.filterExpression()->expression() ), 4 ); + } + // WFS3 core specs only serves 4326 featureRequest.setDestinationCrs( crs, context.project()->transformContext() ); diff --git a/tests/src/python/test_qgsserver_api.py b/tests/src/python/test_qgsserver_api.py index 8f0681fd74e..041ed22205b 100644 --- a/tests/src/python/test_qgsserver_api.py +++ b/tests/src/python/test_qgsserver_api.py @@ -471,14 +471,19 @@ class QgsServerAPITest(QgsServerAPITestBase): response = self.compareApi(request, project, 'test_wfs3_collections_items_layer1_with_short_name_eq_tw_star.json') self.assertEqual(response.statusCode(), 200) + def test_wfs3_excluded_attributes(self): + """Test excluded attributes""" + project = QgsProject() + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/exclude_attribute/items/0.geojson') + response = self.compareApi(request, project, 'test_wfs3_collections_items_exclude_attribute_0.json') + self.assertEqual(response.statusCode(), 200) + def test_wfs3_time_filters(self): """Test datetime filters""" project = QgsProject() project.read(unitTestDataPath('qgis_server') + '/test_project_api_timefilters.qgs') - request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/points/items?name=lus*') - response = self.compareApi(request, project, 'test_wfs3_collections_items_points_eq_lus.json') - self.assertEqual(response.statusCode(), 200) # Prepare 3 projects with all three options: "created", "updated", both. tmpDir = QtCore.QTemporaryDir() @@ -493,10 +498,14 @@ class QgsServerAPITest(QgsServerAPITestBase): both_path = os.path.join(tmpDir.path(), 'test_project_api_timefilters_both.qgs') project.write(both_path) - # Test data: - #wkt_geom fid name created updated - #Point (7.30355493642693343 44.82162158126364915) 2 bricherasio 2019-05-05 2020-05-05T05:05:05.000 - #Point (7.2500747591236081 44.81342128741047048) 1 luserna 2019-01-01 2020-01-01T10:10:10.000 + ''' + Test data: + wkt_geom fid name created updated + Point (7.2500747591236081 44.81342128741047048) 1 luserna 2019-01-01 2020-01-01T10:10:10.000 + Point (7.30355493642693343 44.82162158126364915) 2 bricherasio 2019-05-05 2020-05-05T05:05:05.000 + Point (7.28848021144956881 44.79768920192042714) 3 bibiana + Point (7.22555186948937145 44.82015087638781381) 4 torre 2019-10-10 2019-10-10T10:10:10.000 + ''' # What to test: #interval-closed = date-time "/" date-time @@ -505,9 +514,9 @@ class QgsServerAPITest(QgsServerAPITestBase): #interval = interval-closed / interval-open-start / interval-open-end #datetime = date-time / interval - def _date_tester(project_path, expected, unexpected): + def _date_tester(project_path, datetime, expected, unexpected): # Test "created" date field exact - request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/points/items?datetime=2019-05-05') + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/points/items?datetime=%s' % datetime) response = QgsBufferServerResponse() project.read(project_path) self.server.handleRequest(request, response, project) @@ -517,16 +526,89 @@ class QgsServerAPITest(QgsServerAPITestBase): for unexp in unexpected: self.assertFalse(unexp in body) - # Test "created" date field exact - _date_tester(created_path, ['bricherasio'], ['luserna']) + # Test single values - def test_wfs3_excluded_attributes(self): - """Test excluded attributes""" - project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') - request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/exclude_attribute/items/0.geojson') - response = self.compareApi(request, project, 'test_wfs3_collections_items_exclude_attribute_0.json') - self.assertEqual(response.statusCode(), 200) + ################################################################################## + # Test "created" date field + # Test exact + _date_tester(created_path, '2019-05-05', ['bricherasio'], ['luserna', 'torre']) + # Test datetime field exact (test that we can use a time on a date type field) + _date_tester(created_path, '2019-05-05T01:01:01', ['bricherasio'], ['luserna', 'torre']) + # Test exact no match + _date_tester(created_path, '2019-05-06', [], ['luserna', 'bricherasio', 'torre']) + + ################################################################################## + # Test "updated" datetime field + # Test exact + _date_tester(updated_path, '2020-05-05T05:05:05', ['bricherasio'], ['luserna', 'torre']) + # Test date field exact (test that we can NOT use a date on a datetime type field) + _date_tester(updated_path, '2020-05-05', [], ['luserna', 'bricherasio', 'torre']) + # Test exact no match + _date_tester(updated_path, '2020-05-06T05:05:05', [], ['luserna', 'bricherasio', 'torre']) + + ################################################################################## + # Test both + # Test exact + _date_tester(both_path, '2019-10-10T10:10:10', ['torre'], ['bricherasio', 'luserna']) + # Test date field exact (test that we can NOT use a date on a datetime type field) + _date_tester(both_path, '2020-05-05', [], ['luserna', 'bricherasio', 'torre']) + # Test exact no match + _date_tester(both_path, '2020-05-06T05:05:05', [], ['luserna', 'bricherasio', 'torre']) + + # Test intervals + + ################################################################################## + # Test "created" date field + _date_tester(created_path, '2019-05-04/2019-05-06', ['bricherasio'], ['luserna', 'torre']) + _date_tester(created_path, '2019-05-04/..', ['bricherasio', 'torre'], ['luserna']) + _date_tester(created_path, '2019-05-04/', ['bricherasio', 'torre'], ['luserna']) + _date_tester(created_path, '2100-05-04/', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(created_path, '2100-05-04/..', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(created_path, '/2019-05-06', ['bricherasio', 'luserna'], ['torre']) + _date_tester(created_path, '/2019-01-02', ['luserna'], ['torre', 'bricherasio']) + _date_tester(created_path, '../2019-05-06', ['bricherasio', 'luserna'], ['torre']) + _date_tester(created_path, '../2019-01-02', ['luserna'], ['torre', 'bricherasio']) + + # Test datetimes on "created" date field + _date_tester(created_path, '2019-05-04T01:01:01/2019-05-06T01:01:01', ['bricherasio'], ['luserna', 'torre']) + _date_tester(created_path, '2019-05-04T01:01:01/..', ['bricherasio', 'torre'], ['luserna']) + _date_tester(created_path, '2019-05-04T01:01:01/', ['bricherasio', 'torre'], ['luserna']) + _date_tester(created_path, '2100-05-04T01:01:01/', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(created_path, '2100-05-04T01:01:01/..', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(created_path, '/2019-05-06T01:01:01', ['bricherasio', 'luserna'], ['torre']) + _date_tester(created_path, '/2019-01-02T01:01:01', ['luserna'], ['torre', 'bricherasio']) + _date_tester(created_path, '../2019-05-06T01:01:01', ['bricherasio', 'luserna'], ['torre']) + _date_tester(created_path, '../2019-01-02T01:01:01', ['luserna'], ['torre', 'bricherasio']) + + ################################################################################## + # Test "updated" date field + _date_tester(updated_path, '2020-05-04/2020-05-06', ['bricherasio'], ['luserna', 'torre']) + _date_tester(updated_path, '2020-05-04/..', ['bricherasio'], ['luserna', 'torre']) + _date_tester(updated_path, '2020-05-04/', ['bricherasio'], ['luserna', 'torre']) + _date_tester(updated_path, '2100-05-04/', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(updated_path, '2100-05-04/..', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(updated_path, '/2020-02-02', ['torre', 'luserna'], ['bricherasio']) + _date_tester(updated_path, '/2020-01-01', ['luserna', 'torre'], ['bricherasio']) + _date_tester(updated_path, '../2020-02-02', ['torre', 'luserna'], ['bricherasio']) + _date_tester(updated_path, '../2020-01-01', ['luserna', 'torre'], ['bricherasio']) + + # Test datetimes on "updated" datetime field + _date_tester(updated_path, '2020-05-04/2020-05-06', ['bricherasio'], ['luserna', 'torre']) + _date_tester(updated_path, '2020-05-04/..', ['bricherasio'], ['luserna', 'torre']) + _date_tester(updated_path, '2020-05-04/', ['bricherasio'], ['luserna', 'torre']) + _date_tester(updated_path, '2100-05-04/', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(updated_path, '2100-05-04/..', [], ['luserna', 'bricherasio', 'torre']) + _date_tester(updated_path, '/2020-02-02', ['torre', 'luserna'], ['bricherasio']) + _date_tester(updated_path, '/2020-01-01', ['luserna', 'torre'], ['bricherasio']) + _date_tester(updated_path, '../2020-02-02', ['torre', 'luserna'], ['bricherasio']) + _date_tester(updated_path, '../2020-01-01', ['luserna', 'torre'], ['bricherasio']) + + ################################################################################## + # Test both + _date_tester(both_path, '2019-10-09/2019-10-11', ['torre'], ['luserna', 'bricherasio']) + _date_tester(both_path, '2019-05-04/2020-05-06', ['torre', 'bricherasio'], ['luserna']) + _date_tester(both_path, '../2020-01-01', ['luserna', 'torre'], ['bricherasio']) + _date_tester(both_path, '/2020-01-01', ['luserna', 'torre'], ['bricherasio']) class Handler1(QgsServerOgcApiHandler): diff --git a/tests/testdata/qgis_server/api/test_wfs3_api_project.json b/tests/testdata/qgis_server/api/test_wfs3_api_project.json index 600624ad340..5c594f44725 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_api_project.json +++ b/tests/testdata/qgis_server/api/test_wfs3_api_project.json @@ -53,6 +53,17 @@ Content-Type: application/openapi+json;version=3.0 }, "style": "form" }, + "datetime": { + "description": "Either a date-time or an interval, open or closed. Date and time expressions\nadhere to RFC 3339. Open intervals are expressed using double-dots.\n\nExamples:\n * A date-time: \"2018-02-12T23:20:50Z\"\n * A closed interval: \"2018-02-12T00:00:00Z/2018-03-18T12:31:12Z\"\n * Open intervals: \"2018-02-12T00:00:00Z/..\" or \"../2018-03-18T12:31:12Z\"\nOnly features that have a temporal property that intersects the value of\n`datetime` are selected.\nIf a feature has multiple temporal properties, it is the decision of the\nserver whether only a single temporal property is used to determine\nthe extent or all relevant temporal properties.", + "explode": false, + "in": "query", + "name": "datetime", + "required": false, + "schema": { + "type": "string" + }, + "style": "form" + }, "featureId": { "description": "Local identifier of a specific feature", "in": "path", @@ -121,17 +132,6 @@ Content-Type: application/openapi+json;version=3.0 "type": "string" }, "style": "form" - }, - "time": { - "description": "Either a date-time or a period string that adheres to RFC 3339. Examples:\n\n* A date-time: \"2018-02-12T23:20:50Z\"\n* A period: \"2018-02-12T00:00:00Z/2018-03-18T12:31:12Z\" or \"2018-02-12T00:00:00Z/P1M6DT12H31M12S\"\n\nOnly features that have a temporal property that intersects the value of\n`time` are selected.\n\nIf a feature has multiple temporal properties, it is the decision of the\nserver whether only a single temporal property is used to determine\nthe extent or all relevant temporal properties.", - "explode": false, - "in": "query", - "name": "time", - "required": false, - "schema": { - "type": "string" - }, - "style": "form" } }, "schemas": { diff --git a/tests/testdata/qgis_server/test_project_api_timefilters.gpkg b/tests/testdata/qgis_server/test_project_api_timefilters.gpkg new file mode 100644 index 00000000000..b66ccd99025 Binary files /dev/null and b/tests/testdata/qgis_server/test_project_api_timefilters.gpkg differ diff --git a/tests/testdata/qgis_server/test_project_api_timefilters.qgs b/tests/testdata/qgis_server/test_project_api_timefilters.qgs new file mode 100644 index 00000000000..32adf97a742 --- /dev/null +++ b/tests/testdata/qgis_server/test_project_api_timefilters.qgs @@ -0,0 +1,765 @@ + + + + QGIS Test Project API + + + + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + points_47ad3bc8_35bd_4392_8994_2dc5ff04be60 + + + + + + + + + + degrees + + 7.25418797910206159 + 44.7955945616427087 + 7.3179146610253607 + 44.85094654515160784 + + 0 + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + 0 + + + + + + + + + + + + + 7.25007390975952148 + 44.79768753051757813 + 7.30355501174926758 + 44.82162857055664063 + + points_47ad3bc8_35bd_4392_8994_2dc5ff04be60 + ./test_project_api_timefilters.gpkg|layername=points + + + + points + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + dataset + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + + + name + + + + + + + + + 255 + + + + + 1 + true + + + + 0 + 2 + off + + + + + + current_layer + + + 255 + 255 + 255 + 255 + 0 + 255 + 255 + + + + testlayer_0b835118_a5d5_4255_b5dd_f42253c0a4a0 + + + + false + + + + + + WGS84 + + + m2 + meters + + + 50 + 16 + 30 + true + false + false + 0 + false + false + true + 0 + 255,0,0,255 + + + false + + + true + 2 + D + + + + + + 3452 + +proj=longlat +datum=WGS84 +no_defs + EPSG:4326 + 1 + + + + + + + + + points_47ad3bc8_35bd_4392_8994_2dc5ff04be60 + + + 8 + 8 + 8 + 8 + 8 + + + + + + + + None + true + elpaso@itopen.it + QGIS dev team + Alessandro Pasotti + + + 1 + + 8.20315414376310059 + 44.901236559338642 + 8.204164917965862 + 44.90159838674664172 + + conditions unknown + 90 + + + + 1 + + 4 + false + + + + false + Some UTF8 text èòù + true + QGIS TestProject + 0 + + false + + + + + + + + false + + + + + false + + 5000 + + + + false + + + + + + + + + + + QGIS Test Project API + + + + + + + + + + + + + 2000-01-01T00:00:00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +