[WFS provider] Handle documents with Complex Feature schemas (using OGR GMLAS driver), and JSON'ify content of complex properties

Funded by QGIS user group Germany (QGIS Anwendergruppe Deutschland e.V.)
This commit is contained in:
Even Rouault 2024-01-15 16:09:09 +01:00
parent be57a36ffd
commit 83fe2fea87
No known key found for this signature in database
GPG Key ID: 33EBBFC47B3DD87D
12 changed files with 1383 additions and 36 deletions

View File

@ -63,6 +63,7 @@ target_include_directories(provider_wfs_a PUBLIC
target_link_libraries(provider_wfs_a
qgis_core
GDAL::GDAL
)
# require c++17
@ -120,6 +121,7 @@ else()
target_link_libraries (provider_wfs
qgis_core
GDAL::GDAL
)
if (WITH_GUI)

View File

@ -213,12 +213,24 @@ QUrl QgsWFSFeatureDownloaderImpl::buildURL( qint64 startIndex, long long maxFeat
arg( minx ).arg( miny ).arg( maxx ).arg( maxy ) );
QgsExpression bboxExp( filterBbox );
QDomDocument bboxDoc;
QMap<QString, QString> fieldNameToXPathMap;
if ( !mShared->mFieldNameToXPathAndIsNestedContentMap.isEmpty() )
{
for ( auto iterFieldName = mShared->mFieldNameToXPathAndIsNestedContentMap.constBegin(); iterFieldName != mShared->mFieldNameToXPathAndIsNestedContentMap.constEnd(); ++iterFieldName )
{
const QString &fieldName = iterFieldName.key();
const auto &value = iterFieldName.value();
fieldNameToXPathMap[fieldName] = value.first;
}
}
QDomElement bboxElem = QgsOgcUtils::expressionToOgcFilter( bboxExp, bboxDoc,
gmlVersion, filterVersion,
mShared->mLayerPropertiesList.size() == 1 ? mShared->mLayerPropertiesList[0].mNamespacePrefix : QString(),
mShared->mLayerPropertiesList.size() == 1 ? mShared->mLayerPropertiesList[0].mNamespaceURI : QString(),
geometryAttribute, mShared->srsName(),
honourAxisOrientation, mShared->mURI.invertAxisOrientation() );
honourAxisOrientation, mShared->mURI.invertAxisOrientation(), nullptr, fieldNameToXPathMap, mShared->mNamespacePrefixToURIMap );
bboxDoc.appendChild( bboxElem );
filters.push_back( bboxDoc.toString() );

View File

@ -16,13 +16,17 @@
***************************************************************************/
#include "qgis.h"
#include "qgscplhttpfetchoverrider.h"
#include "qgsfeature.h"
#include "qgsfeedback.h"
#include "qgsfields.h"
#include "qgsgeometry.h"
#include "qgscoordinatereferencesystem.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include "qgsogcutils.h"
#include "qgsogrutils.h"
#include "qgssqliteutils.h"
#include "qgswfsconstants.h"
#include "qgswfsfeatureiterator.h"
#include "qgswfsprovider.h"
@ -33,6 +37,10 @@
#include "qgswfsutils.h"
#include "qgssettings.h"
#include "cpl_string.h"
#include "gdal.h"
#include <QAbstractButton>
#include <QApplication>
#include <QDateTime>
#include <QDomDocument>
@ -605,6 +613,8 @@ bool QgsWFSProvider::processSQL( const QString &sqlString, QString &errorMsg, QS
QgsFields fields;
Qgis::WkbType geomType;
if ( !readAttributesFromSchema( describeFeatureDocument,
response,
/* singleLayerContext = */ typenameList.size() == 1,
typeName,
geometryAttribute, fields, geomType, errorMsg ) )
{
@ -624,7 +634,7 @@ bool QgsWFSProvider::processSQL( const QString &sqlString, QString &errorMsg, QS
}
}
setLayerPropertiesListFromDescribeFeature( describeFeatureDocument, typenameList, errorMsg );
setLayerPropertiesListFromDescribeFeature( describeFeatureDocument, response, typenameList, errorMsg );
const QString &defaultTypeName = mShared->mURI.typeName();
QgsWFSProviderSQLColumnRefValidator oColumnValidator(
@ -789,7 +799,7 @@ bool QgsWFSProvider::processSQL( const QString &sqlString, QString &errorMsg, QS
return true;
}
bool QgsWFSProvider::setLayerPropertiesListFromDescribeFeature( QDomDocument &describeFeatureDocument, const QStringList &typenameList, QString &errorMsg )
bool QgsWFSProvider::setLayerPropertiesListFromDescribeFeature( QDomDocument &describeFeatureDocument, const QByteArray &response, const QStringList &typenameList, QString &errorMsg )
{
mShared->mLayerPropertiesList.clear();
for ( const QString &typeName : typenameList )
@ -798,6 +808,8 @@ bool QgsWFSProvider::setLayerPropertiesListFromDescribeFeature( QDomDocument &de
QgsFields fields;
Qgis::WkbType geomType;
if ( !readAttributesFromSchema( describeFeatureDocument,
response,
/* singleLayerContext = */ typenameList.size() == 1,
typeName,
geometryAttribute, fields, geomType, errorMsg ) )
{
@ -1513,6 +1525,8 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields
}
if ( !readAttributesFromSchema( describeFeatureDocument,
response,
/* singleLayerContext = */ true,
mShared->mURI.typeName(),
geometryAttribute, fields, geomType, errorMsg ) )
{
@ -1522,18 +1536,478 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields
return false;
}
setLayerPropertiesListFromDescribeFeature( describeFeatureDocument, {mShared->mURI.typeName()}, errorMsg );
setLayerPropertiesListFromDescribeFeature( describeFeatureDocument, response, {mShared->mURI.typeName()}, errorMsg );
return true;
}
bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
const QByteArray &response,
bool singleLayerContext,
const QString &prefixedTypename,
QString &geometryAttribute,
QgsFields &fields,
Qgis::WkbType &geomType,
QString &errorMsg )
{
bool mayTryWithGMLAS = false;
bool ret = readAttributesFromSchemaWithoutGMLAS( schemaDoc, prefixedTypename, geometryAttribute, fields, geomType, errorMsg, mayTryWithGMLAS );
if ( singleLayerContext &&
mayTryWithGMLAS &&
GDALGetDriverByName( "GMLAS" ) )
{
QgsFields fieldsGMLAS;
Qgis::WkbType geomTypeGMLAS;
QString errorMsgGMLAS;
if ( readAttributesFromSchemaWithGMLAS( response, prefixedTypename, geometryAttribute, fieldsGMLAS, geomTypeGMLAS, errorMsgGMLAS ) )
{
fields = fieldsGMLAS;
geomType = geomTypeGMLAS;
ret = true;
}
else if ( !ret )
{
errorMsg = errorMsgGMLAS;
}
}
return ret;
}
static QVariant::Type getVariantTypeFromXML( const QString &xmlType )
{
QVariant::Type attributeType = QVariant::Invalid;
const QString type = QString( xmlType )
.replace( QLatin1String( "xs:" ), QString() )
.replace( QLatin1String( "xsd:" ), QString() );
if ( type.compare( QLatin1String( "string" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "token" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "NMTOKEN" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "NCName" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "QName" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "ID" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "IDREF" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "anyURI" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "anySimpleType" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::String;
}
else if ( type.compare( QLatin1String( "boolean" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::Bool;
}
else if ( type.compare( QLatin1String( "double" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "float" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "decimal" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::Double;
}
else if ( type.compare( QLatin1String( "byte" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "unsignedByte" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "int" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "short" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "unsignedShort" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::Int;
}
else if ( type.compare( QLatin1String( "long" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "unsignedLong" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "integer" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "negativeInteger" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "nonNegativeInteger" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "positiveInteger" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::LongLong;
}
else if ( type.compare( QLatin1String( "date" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "gYear" ), Qt::CaseInsensitive ) == 0 ||
type.compare( QLatin1String( "gYearMonth" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::Date;
}
else if ( type.compare( QLatin1String( "time" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::Time;
}
else if ( type.compare( QLatin1String( "dateTime" ), Qt::CaseInsensitive ) == 0 )
{
attributeType = QVariant::DateTime;
}
return attributeType;
}
bool QgsWFSProvider::readAttributesFromSchemaWithGMLAS( const QByteArray &response,
const QString &prefixedTypename,
QString &geometryAttribute,
QgsFields &fields,
Qgis::WkbType &geomType,
QString &errorMsg )
{
QUrl url( mShared->mURI.requestUrl( QStringLiteral( "DescribeFeatureType" ) ) );
QUrlQuery query( url );
query.addQueryItem( QStringLiteral( "TYPENAME" ), prefixedTypename );
url.setQuery( query );
// If a previous attempt with the same URL failed because of cancellation
// in the past second, do not retry.
// The main use case for that is when QgsWfsProviderMetadata::querySublayers()
// is called when adding a layer, and several QgsWFSProvider instances are
// quickly created.
static QMutex mutex;
static QUrl lastCanceledURL;
static QDateTime lastCanceledDateTime;
{
QMutexLocker lock( &mutex );
if ( lastCanceledURL == url && lastCanceledDateTime + 1 > QDateTime::currentDateTime() )
{
mMetadataRetrievalCanceled = true;
return false;
}
}
// Create a unique /vsimem/ filename
constexpr int TEMP_FILENAME_SIZE = 128;
void *p = malloc( TEMP_FILENAME_SIZE );
char *pszSchemaTempFilename = static_cast<char *>( p );
snprintf( pszSchemaTempFilename, TEMP_FILENAME_SIZE, "/vsimem/schema_%p.xsd", p );
// Serialize the main schema into a temporary /vsimem/ filename
char *pszSchema = VSIStrdup( response.constData() );
VSILFILE *fp = VSIFileFromMemBuffer( pszSchemaTempFilename,
reinterpret_cast<GByte *>( pszSchema ), strlen( pszSchema ), /* bTakeOwnership=*/ true );
if ( fp )
VSIFCloseL( fp );
QgsFeedback feedback;
GDALDatasetH hDS = nullptr;
// Analyze the DescribeFeatureType response schema with the OGR GMLAS driver
// in a thread, so it can get interrupted (with GDAL 3.9: https://github.com/OSGeo/gdal/pull/9019)
const auto downloaderLambda = [pszSchemaTempFilename, &feedback, &hDS]()
{
QgsCPLHTTPFetchOverrider cplHTTPFetchOverrider( QString(), &feedback );
QgsSetCPLHTTPFetchOverriderInitiatorClass( cplHTTPFetchOverrider, QStringLiteral( "WFSProviderDownloadSchema" ) )
char **papszOpenOptions = nullptr;
papszOpenOptions = CSLSetNameValue( papszOpenOptions, "XSD", pszSchemaTempFilename );
hDS = GDALOpenEx( "GMLAS:", GDAL_OF_VECTOR, nullptr, papszOpenOptions, nullptr );
CSLDestroy( papszOpenOptions );
};
std::unique_ptr<_DownloaderThread> downloaderThread =
std::make_unique<_DownloaderThread>( downloaderLambda );
downloaderThread->start();
QTimer timerForHits;
QMessageBox *box = nullptr;
QWidget *parentWidget = nullptr;
if ( qApp->thread() == QThread::currentThread() )
{
parentWidget = QApplication::activeWindow();
if ( !parentWidget )
{
const QWidgetList widgets = QgsApplication::topLevelWidgets();
for ( QWidget *widget : widgets )
{
if ( widget->objectName() == QLatin1String( "QgisApp" ) )
{
parentWidget = widget;
break;
}
}
}
}
if ( parentWidget )
{
// Display an information box if within 2 seconds, the schema has not
// been analyzed.
box = new QMessageBox(
QMessageBox::Information, tr( "Information" ), tr( "Download of schemas in progress..." ),
QMessageBox::Cancel,
parentWidget );
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,9,0)
connect( box, &QDialog::rejected, &feedback, &QgsFeedback::cancel );
#else
box->button( QMessageBox::Cancel )->setEnabled( false );
#endif
QgsSettings s;
const double settingDefaultValue = 2.0;
const QString settingName = QStringLiteral( "qgis/wfsDownloadSchemasPopupTimeout" );
if ( !s.contains( settingName ) )
{
s.setValue( settingName, settingDefaultValue );
}
const double timeout = s.value( settingName, settingDefaultValue ).toDouble();
if ( timeout > 0 )
{
timerForHits.setInterval( static_cast<int>( 1000 * timeout ) );
timerForHits.setSingleShot( true );
timerForHits.start();
connect( &timerForHits, &QTimer::timeout, box, &QDialog::exec );
}
// Close dialog when download theread finishes.
// Will actually trigger the QDialog::rejected signal...
connect( downloaderThread.get(), &QThread::finished, box, &QDialog::accept );
}
// Run an event loop until download thread finishes
QEventLoop loop;
connect( downloaderThread.get(), &QThread::finished, &loop, &QEventLoop::quit );
loop.exec( QEventLoop::ExcludeUserInputEvents );
downloaderThread->wait();
VSIUnlink( pszSchemaTempFilename );
VSIFree( pszSchemaTempFilename );
bool ret = hDS != nullptr;
if ( feedback.isCanceled() && !ret )
{
QMutexLocker lock( &mutex );
mMetadataRetrievalCanceled = true;
lastCanceledURL = url;
lastCanceledDateTime = QDateTime::currentDateTime();
errorMsg = tr( "Schema analysis interrupted by user." );
return false;
}
if ( !ret )
{
errorMsg = tr( "Cannot analyze schema indicated in DescribeFeatureType response." );
return false;
}
gdal::dataset_unique_ptr oDSCloser( hDS );
// Retrieve namespace prefix and URIs
OGRLayerH hOtherMetadataLayer = GDALDatasetGetLayerByName( hDS, "_ogr_other_metadata" );
if ( !hOtherMetadataLayer )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find _ogr_other_metadata layer" ), 4 );
return false;
}
auto hOtherMetadataLayerDefn = OGR_L_GetLayerDefn( hOtherMetadataLayer );
const int keyIdx = OGR_FD_GetFieldIndex( hOtherMetadataLayerDefn, "key" );
if ( keyIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find key field in _ogr_other_metadata" ), 4 );
return false;
}
const int valueIdx = OGR_FD_GetFieldIndex( hOtherMetadataLayerDefn, "value" );
if ( valueIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find value field in _ogr_other_metadata" ), 4 );
return false;
}
std::map<int, QPair<QString, QString>> mapPrefixIdxToPrefixAndUri;
while ( true )
{
gdal::ogr_feature_unique_ptr hFeatureOtherMD(
OGR_L_GetNextFeature( hOtherMetadataLayer ) );
if ( !hFeatureOtherMD )
break;
const QString key = QString::fromUtf8(
OGR_F_GetFieldAsString( hFeatureOtherMD.get(), keyIdx ) );
const QString value = QString::fromUtf8(
OGR_F_GetFieldAsString( hFeatureOtherMD.get(), valueIdx ) );
if ( key.startsWith( QLatin1String( "namespace_prefix_" ) ) )
{
mapPrefixIdxToPrefixAndUri[key.mid( int( strlen( "namespace_prefix_" ) ) ).toInt()].first = value;
}
else if ( key.startsWith( QLatin1String( "namespace_uri_" ) ) )
{
mapPrefixIdxToPrefixAndUri[key.mid( int( strlen( "namespace_uri_" ) ) ).toInt()].second = value;
}
}
for ( const auto &kv : mapPrefixIdxToPrefixAndUri )
{
if ( !kv.second.first.isEmpty() && !kv.second.second.isEmpty() )
{
mShared->mNamespacePrefixToURIMap[kv.second.first] = kv.second.second;
QgsDebugMsgLevel( QStringLiteral( "%1 -> %2" ).arg( kv.second.first ).arg( kv.second.second ), 4 );
}
}
// Find the layer of interest
OGRLayerH hLayersMetadata = GDALDatasetGetLayerByName( hDS, "_ogr_layers_metadata" );
if ( !hLayersMetadata )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find _ogr_layers_metadata layer" ), 4 );
return false;
}
OGR_L_SetAttributeFilter( hLayersMetadata,
( "layer_xpath = " + QgsSqliteUtils::quotedString( prefixedTypename ).toStdString() ).c_str() );
gdal::ogr_feature_unique_ptr hFeatureLayersMD( OGR_L_GetNextFeature( hLayersMetadata ) );
if ( !hFeatureLayersMD )
{
QgsDebugMsgLevel(
QStringLiteral( "Cannot find feature with layer_xpath = %1 in _ogr_layers_metadata" ).arg( prefixedTypename ), 4 );
return false;
}
const int fldIdx = OGR_F_GetFieldIndex( hFeatureLayersMD.get(), "layer_name" );
if ( fldIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find layer_name field in _ogr_layers_metadata" ), 4 );
return false;
}
const QString layerName = QString::fromUtf8(
OGR_F_GetFieldAsString( hFeatureLayersMD.get(), fldIdx ) );
OGRLayerH hLayer = GDALDatasetGetLayerByName(
hDS, layerName.toStdString().c_str() );
if ( !hLayer )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find %& layer" ).arg( layerName ), 4 );
return false;
}
// Get field information
OGRLayerH hFieldsMetadata = GDALDatasetGetLayerByName( hDS, "_ogr_fields_metadata" );
if ( !hFieldsMetadata )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find _ogr_fields_metadata layer" ), 4 );
return false;
}
OGR_L_SetAttributeFilter( hFieldsMetadata,
( "layer_name = " + QgsSqliteUtils::quotedString( layerName ).toStdString() ).c_str() );
auto hFieldsMetadataDefn = OGR_L_GetLayerDefn( hFieldsMetadata );
const int fieldNameIdx = OGR_FD_GetFieldIndex( hFieldsMetadataDefn, "field_name" );
if ( fieldNameIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find field_name field in _ogr_fields_metadata" ), 4 );
return false;
}
const int fieldXPathIdx = OGR_FD_GetFieldIndex( hFieldsMetadataDefn, "field_xpath" );
if ( fieldXPathIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find field_xpath field in _ogr_fields_metadata" ), 4 );
return false;
}
const int fieldIsListIdx = OGR_FD_GetFieldIndex( hFieldsMetadataDefn, "field_is_list" );
if ( fieldIsListIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find field_is_list field in _ogr_fields_metadata" ), 4 );
return false;
}
const int fieldTypeIdx = OGR_FD_GetFieldIndex( hFieldsMetadataDefn, "field_type" );
if ( fieldTypeIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find field_type field in _ogr_fields_metadata" ), 4 );
return false;
}
const int fieldCategoryIdx = OGR_FD_GetFieldIndex( hFieldsMetadataDefn, "field_category" );
if ( fieldCategoryIdx < 0 )
{
// should not happen
QgsDebugMsgLevel( QStringLiteral( "Cannot find field_category field in _ogr_fields_metadata" ), 4 );
return false;
}
mShared->mFieldNameToXPathAndIsNestedContentMap.clear();
while ( true )
{
gdal::ogr_feature_unique_ptr hFeatureFieldsMD( OGR_L_GetNextFeature( hFieldsMetadata ) );
if ( !hFeatureFieldsMD )
break;
QString fieldName = QString::fromUtf8( OGR_F_GetFieldAsString( hFeatureFieldsMD.get(), fieldNameIdx ) );
const char *fieldXPath = OGR_F_GetFieldAsString( hFeatureFieldsMD.get(), fieldXPathIdx );
// The xpath includes the one of the feature itself. We can strip it off
const char *slash = strchr( fieldXPath, '/' );
if ( slash )
fieldXPath = slash + 1;
const bool fieldIsList = OGR_F_GetFieldAsInteger( hFeatureFieldsMD.get(), fieldIsListIdx ) == 1;
const char *fieldType = OGR_F_GetFieldAsString( hFeatureFieldsMD.get(), fieldTypeIdx );
const char *fieldCategory = OGR_F_GetFieldAsString( hFeatureFieldsMD.get(), fieldCategoryIdx );
// For fields that should be linked to other tables and that we will
// get as JSON, remove the "_pkid" suffix from the name created by GMLAS.
if ( EQUAL( fieldCategory, "PATH_TO_CHILD_ELEMENT_WITH_LINK" ) &&
fieldName.endsWith( QLatin1String( "_pkid" ) ) )
{
fieldName.resize( fieldName.size() - int( strlen( "_pkid" ) ) );
}
QgsDebugMsgLevel(
QStringLiteral( "field %1: xpath=%2 is_list=%3 type=%4 category=%5" ).
arg( fieldName ).arg( fieldXPath ).arg( fieldIsList ).arg( fieldType ).arg( fieldCategory ), 5 );
if ( EQUAL( fieldCategory, "REGULAR" ) && EQUAL( fieldType, "geometry" ) )
{
if ( geometryAttribute.isEmpty() )
{
mShared->mFieldNameToXPathAndIsNestedContentMap[fieldName] =
QPair<QString, bool>( fieldXPath, false );
geometryAttribute = fieldName;
geomType = QgsOgrUtils::ogrGeometryTypeToQgsWkbType(
OGR_L_GetGeomType( hLayer ) );
}
}
else if ( EQUAL( fieldCategory, "REGULAR" ) && !fieldIsList )
{
QVariant::Type type = getVariantTypeFromXML( QString::fromUtf8( fieldType ) );
if ( type != QVariant::Invalid )
{
fields.append( QgsField( fieldName, type, fieldType ) );
}
else
{
// unhandled:duration, base64Binary, hexBinary, anyType
QgsDebugMsgLevel(
QStringLiteral( "unhandled type for field %1: xpath=%2 is_list=%3 type=%4 category=%5" ).
arg( fieldName ).arg( fieldXPath ).arg( fieldIsList ).arg( fieldType ).arg( fieldCategory ), 3 );
fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
}
mShared->mFieldNameToXPathAndIsNestedContentMap[fieldName] =
QPair<QString, bool>( fieldXPath, false );
}
else
{
QgsField field( fieldName, QVariant::String );
field.setEditorWidgetSetup( QgsEditorWidgetSetup( QStringLiteral( "JsonEdit" ), QVariantMap() ) );
fields.append( field );
mShared->mFieldNameToXPathAndIsNestedContentMap[fieldName] =
QPair<QString, bool>( fieldXPath, true );
}
}
return true;
}
bool QgsWFSProvider::readAttributesFromSchemaWithoutGMLAS( QDomDocument &schemaDoc,
const QString &prefixedTypename,
QString &geometryAttribute,
QgsFields &fields,
Qgis::WkbType &geomType,
QString &errorMsg, bool &mayTryWithGMLAS )
{
mayTryWithGMLAS = false;
//get the <schema> root element
QDomNodeList schemaNodeList = schemaDoc.elementsByTagNameNS( QgsWFSConstants::XMLSCHEMA_NAMESPACE, QStringLiteral( "schema" ) );
if ( schemaNodeList.length() < 1 )
@ -1615,6 +2089,7 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
if ( foundImport && onlyIncludeOrImport )
{
errorMsg = tr( "It is probably a schema for Complex Features." );
mayTryWithGMLAS = true;
}
// e.g http://services.cuzk.cz/wfs/inspire-CP-wfs.asp?SERVICE=WFS&VERSION=2.0.0&REQUEST=DescribeFeatureType
// which has a single <include schemaLocation="http://inspire.ec.europa.eu/schemas/cp/4.0/CadastralParcels.xsd"/>
@ -1643,17 +2118,18 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
arg( schemaLocation, errorMsg );
}
return readAttributesFromSchema( describeFeatureDocument,
prefixedTypename,
geometryAttribute,
fields,
geomType,
errorMsg );
return readAttributesFromSchemaWithoutGMLAS( describeFeatureDocument,
prefixedTypename,
geometryAttribute,
fields,
geomType,
errorMsg, mayTryWithGMLAS );
}
else
{
errorMsg = tr( "Cannot find element '%1'" ).arg( unprefixedTypename );
mayTryWithGMLAS = true;
}
return false;
}
@ -1772,27 +2248,18 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
propertyType = propertyType.at( 0 ).toUpper() + propertyType.mid( 1 );
geomType = geomTypeFromPropertyType( geometryAttribute, propertyType );
}
else if ( !name.isEmpty() ) //todo: distinguish between numerical and non-numerical types
else if ( !name.isEmpty() )
{
QVariant::Type attributeType = QVariant::String; //string is default type
if ( type.contains( QLatin1String( "double" ), Qt::CaseInsensitive ) || type.contains( QLatin1String( "float" ), Qt::CaseInsensitive ) || type.contains( QLatin1String( "decimal" ), Qt::CaseInsensitive ) )
const QVariant::Type attributeType = getVariantTypeFromXML( type );
if ( attributeType != QVariant::Invalid )
{
attributeType = QVariant::Double;
fields.append( QgsField( name, attributeType, type ) );
}
else if ( type.contains( QLatin1String( "int" ), Qt::CaseInsensitive ) ||
type.contains( QLatin1String( "short" ), Qt::CaseInsensitive ) )
else
{
attributeType = QVariant::Int;
mayTryWithGMLAS = true;
fields.append( QgsField( name, QVariant::String, type ) );
}
else if ( type.contains( QLatin1String( "long" ), Qt::CaseInsensitive ) )
{
attributeType = QVariant::LongLong;
}
else if ( type.contains( QLatin1String( "dateTime" ), Qt::CaseInsensitive ) )
{
attributeType = QVariant::DateTime;
}
fields.append( QgsField( name, attributeType, type ) );
}
}
if ( !foundGeometryAttribute )

View File

@ -138,6 +138,9 @@ class QgsWFSProvider final: public QgsVectorDataProvider
//! Perform an initial GetFeature request with a 1-feature limit.
void issueInitialGetFeature();
//! Return whether metadata retrieval has been canceled (typically download of the schema)
bool metadataRetrievalCanceled() const { return mMetadataRetrievalCanceled; }
private slots:
void featureReceivedAnalyzeOneFeature( QVector<QgsFeatureUniqueIdPair> );
@ -171,11 +174,24 @@ class QgsWFSProvider final: public QgsVectorDataProvider
QDomElement geometryElement( const QgsGeometry &geometry, QDomDocument &transactionDoc );
//! Set mShared->mLayerPropertiesList from describeFeatureDocument
bool setLayerPropertiesListFromDescribeFeature( QDomDocument &describeFeatureDocument, const QStringList &typenameList, QString &errorMsg );
bool setLayerPropertiesListFromDescribeFeature( QDomDocument &describeFeatureDocument, const QByteArray &response, const QStringList &typenameList, QString &errorMsg );
//! backup of mShared->mLayerPropertiesList on the feature type when there is no sql request
QList< QgsOgcUtils::LayerProperties > mLayerPropertiesListWhenNoSqlRequest;
//! Set if metadata retrieval has been canceled (typically download of the schema)
bool mMetadataRetrievalCanceled = false;
bool readAttributesFromSchemaWithoutGMLAS( QDomDocument &schemaDoc,
const QString &prefixedTypename,
QString &geometryAttribute,
QgsFields &fields, Qgis::WkbType &geomType, QString &errorMsg, bool &mayTryWithGMLAS );
bool readAttributesFromSchemaWithGMLAS( const QByteArray &response,
const QString &prefixedTypename,
QString &geometryAttribute,
QgsFields &fields, Qgis::WkbType &geomType, QString &errorMsg );
protected:
//! String used to define a subset of the layer
@ -206,6 +222,8 @@ class QgsWFSProvider final: public QgsVectorDataProvider
* thematic attributes and their types from a dom document. Returns true in case of success.
*/
bool readAttributesFromSchema( QDomDocument &schemaDoc,
const QByteArray &response,
bool singleLayerContext,
const QString &prefixedTypename,
QString &geometryAttribute,
QgsFields &fields, Qgis::WkbType &geomType, QString &errorMsg );

View File

@ -236,6 +236,8 @@ QList<QgsProviderSublayerDetails> QgsWfsProviderMetadata::querySublayers( const
QgsWFSProvider provider(
uri + " " + QgsWFSConstants::URI_PARAM_SKIP_INITIAL_GET_FEATURE + "='true'",
QgsDataProvider::ProviderOptions(), caps );
if ( provider.metadataRetrievalCanceled() )
return res;
QgsProviderSublayerDetails details;
details.setType( Qgis::LayerType::Vector );
details.setProviderKey( QgsWFSProvider::WFS_PROVIDER_KEY );

View File

@ -54,6 +54,8 @@ QgsWFSSharedData *QgsWFSSharedData::clone() const
copy->mGeometryAttribute = mGeometryAttribute;
copy->mLayerPropertiesList = mLayerPropertiesList;
copy->mMapFieldNameToSrcLayerNameFieldName = mMapFieldNameToSrcLayerNameFieldName;
copy->mFieldNameToXPathAndIsNestedContentMap = mFieldNameToXPathAndIsNestedContentMap;
copy->mNamespacePrefixToURIMap = mNamespacePrefixToURIMap;
copy->mPageSize = mPageSize;
copy->mCaps = mCaps;
copy->mHasWarnedAboutMissingFeatureId = mHasWarnedAboutMissingFeatureId;
@ -105,8 +107,23 @@ QString QgsWFSSharedData::computedExpression( const QgsExpression &expression )
bool honourAxisOrientation = false;
getVersionValues( gmlVersion, filterVersion, honourAxisOrientation );
QMap<QString, QString> fieldNameToXPathMap;
if ( !mFieldNameToXPathAndIsNestedContentMap.isEmpty() )
{
for ( auto iterFieldName = mFieldNameToXPathAndIsNestedContentMap.constBegin(); iterFieldName != mFieldNameToXPathAndIsNestedContentMap.constEnd(); ++iterFieldName )
{
const QString &fieldName = iterFieldName.key();
const auto &value = iterFieldName.value();
fieldNameToXPathMap[fieldName] = value.first;
}
}
QDomDocument expressionDoc;
QDomElement expressionElem = QgsOgcUtils::expressionToOgcExpression( expression, expressionDoc, gmlVersion, filterVersion, mGeometryAttribute, srsName(), honourAxisOrientation, mURI.invertAxisOrientation(), nullptr, true );
QDomElement expressionElem = QgsOgcUtils::expressionToOgcExpression(
expression, expressionDoc, gmlVersion, filterVersion, mGeometryAttribute,
srsName(), honourAxisOrientation, mURI.invertAxisOrientation(), nullptr,
true,
fieldNameToXPathMap, mNamespacePrefixToURIMap );
if ( !expressionElem.isNull() )
{
@ -155,12 +172,23 @@ bool QgsWFSSharedData::computeFilter( QString &errorMsg )
}
}
QMap<QString, QString> fieldNameToXPathMap;
if ( !mFieldNameToXPathAndIsNestedContentMap.isEmpty() )
{
for ( auto iterFieldName = mFieldNameToXPathAndIsNestedContentMap.constBegin(); iterFieldName != mFieldNameToXPathAndIsNestedContentMap.constEnd(); ++iterFieldName )
{
const QString &fieldName = iterFieldName.key();
const auto &value = iterFieldName.value();
fieldNameToXPathMap[fieldName] = value.first;
}
}
QDomDocument filterDoc;
const QDomElement filterElem = QgsOgcUtils::SQLStatementToOgcFilter(
sql, filterDoc, gmlVersion, filterVersion, mLayerPropertiesList,
honourAxisOrientation, mURI.invertAxisOrientation(),
mCaps.mapUnprefixedTypenameToPrefixedTypename,
&errorMsg );
&errorMsg, fieldNameToXPathMap, mNamespacePrefixToURIMap );
if ( !errorMsg.isEmpty() )
{
errorMsg = tr( "SQL statement to OGC Filter error: " ) + errorMsg;
@ -188,13 +216,24 @@ bool QgsWFSSharedData::computeFilter( QString &errorMsg )
//if not, if must be a QGIS expression
const QgsExpression filterExpression( filter );
QMap<QString, QString> fieldNameToXPathMap;
if ( !mFieldNameToXPathAndIsNestedContentMap.isEmpty() )
{
for ( auto iterFieldName = mFieldNameToXPathAndIsNestedContentMap.constBegin(); iterFieldName != mFieldNameToXPathAndIsNestedContentMap.constEnd(); ++iterFieldName )
{
const QString &fieldName = iterFieldName.key();
const auto &value = iterFieldName.value();
fieldNameToXPathMap[fieldName] = value.first;
}
}
const QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter(
filterExpression, filterDoc, gmlVersion, filterVersion,
mLayerPropertiesList.size() == 1 ? mLayerPropertiesList[0].mNamespacePrefix : QString(),
mLayerPropertiesList.size() == 1 ? mLayerPropertiesList[0].mNamespaceURI : QString(),
mGeometryAttribute,
srsName(), honourAxisOrientation, mURI.invertAxisOrientation(),
&errorMsg );
&errorMsg, fieldNameToXPathMap, mNamespacePrefixToURIMap );
if ( !errorMsg.isEmpty() )
{
@ -261,11 +300,16 @@ QgsGmlStreamingParser *QgsWFSSharedData::createParser() const
}
else
{
return new QgsGmlStreamingParser( mURI.typeName(),
mGeometryAttribute,
mFields,
axisOrientationLogic,
mURI.invertAxisOrientation() );
auto parser = new QgsGmlStreamingParser( mURI.typeName(),
mGeometryAttribute,
mFields,
axisOrientationLogic,
mURI.invertAxisOrientation() );
if ( !mFieldNameToXPathAndIsNestedContentMap.isEmpty() )
{
parser->setFieldsXPath( mFieldNameToXPathAndIsNestedContentMap, mNamespacePrefixToURIMap );
}
return parser;
}
}
@ -329,6 +373,29 @@ QString QgsWFSSharedData::combineWFSFilters( const std::vector<QString> &filters
}
envelopeFilterDoc.firstChildElement().appendChild( andElem );
QSet<QString> setNamespaceURI;
for ( auto iterFieldName = mFieldNameToXPathAndIsNestedContentMap.constBegin(); iterFieldName != mFieldNameToXPathAndIsNestedContentMap.constEnd(); ++iterFieldName )
{
const auto &value = iterFieldName.value();
const QStringList parts = value.first.split( '/' );
for ( const QString &part : parts )
{
const QStringList subparts = part.split( ':' );
if ( subparts.size() == 2 && subparts[0] != QLatin1String( "gml" ) )
{
const auto iter = mNamespacePrefixToURIMap.constFind( subparts[0] );
if ( iter != mNamespacePrefixToURIMap.constEnd() &&
!setNamespaceURI.contains( *iter ) )
{
setNamespaceURI.insert( *iter );
QDomAttr attr = envelopeFilterDoc.createAttribute( QStringLiteral( "xmlns:" ) + subparts[0] );
attr.setValue( *iter );
envelopeFilterDoc.firstChildElement().setAttributeNode( attr );
}
}
}
}
if ( mLayerPropertiesList.size() == 1 &&
envelopeFilterDoc.firstChildElement().hasAttribute( QStringLiteral( "xmlns:" ) + mLayerPropertiesList[0].mNamespacePrefix ) )
{
@ -337,7 +404,6 @@ QString QgsWFSSharedData::combineWFSFilters( const std::vector<QString> &filters
else
{
// add xmls:PREFIX=URI attributes to top element
QSet<QString> setNamespaceURI;
for ( const QgsOgcUtils::LayerProperties &props : std::as_const( mLayerPropertiesList ) )
{
if ( !props.mNamespacePrefix.isEmpty() && !props.mNamespaceURI.isEmpty() &&

View File

@ -97,6 +97,12 @@ class QgsWFSSharedData : public QObject, public QgsBackgroundCachedSharedData
//! Map a field name to the pair (typename, fieldname) that describes its source field
QMap< QString, QPair<QString, QString> > mMapFieldNameToSrcLayerNameFieldName;
//! Map a field name to the pair (xpath, isNestedContent)
QMap<QString, QPair<QString, bool> > mFieldNameToXPathAndIsNestedContentMap;
//! Map a namespace prefix to its URI
QMap<QString, QString> mNamespacePrefixToURIMap;
//! Page size for WFS 2.0. 0 = disabled
long long mPageSize = 0;

View File

@ -51,11 +51,18 @@ from qgis.PyQt.QtCore import (
QObject,
Qt,
QTime,
QVariant,
)
import unittest
from qgis.testing import start_app, QgisTestCase
from utilities import compareWkt, unitTestDataPath
from osgeo import gdal
# Default value is 2 second, which is too short when run under Valgrind
gdal.SetConfigOption('OGR_GMLAS_XERCES_MAX_TIME', '20')
TEST_DATA_DIR = unitTestDataPath()
@ -6427,6 +6434,53 @@ Can't recognize service requested.
self.assertEqual(sublayers[3].featureCount(), -1)
self.assertEqual(sublayers[4].featureCount(), -1)
@unittest.skipIf(gdal.GetDriverByName("GMLAS") is None, "OGR GMLAS driver required")
def testWFSComplexFeatures(self):
"""Test reading complex features"""
endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS_complex_features'
shutil.copy(os.path.join(TEST_DATA_DIR, 'provider', 'wfs', 'inspire_complexfeatures', 'getcapabilities.xml'), sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities&VERSION=2.0.0'))
shutil.copy(os.path.join(TEST_DATA_DIR, 'provider', 'wfs', 'inspire_complexfeatures', 'describefeaturetype.xml'), sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=ps:ProtectedSite&TYPENAME=ps:ProtectedSite'))
shutil.copy(os.path.join(TEST_DATA_DIR, 'provider', 'wfs', 'inspire_complexfeatures', 'getfeature_hits.xml'), sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=ps:ProtectedSite&RESULTTYPE=hits'))
shutil.copy(os.path.join(TEST_DATA_DIR, 'provider', 'wfs', 'inspire_complexfeatures', 'getfeature.xml'), sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=ps:ProtectedSite&SRSNAME=urn:ogc:def:crs:EPSG::25833'))
vl = QgsVectorLayer("url='http://" + endpoint + "' typename='ps:ProtectedSite' version='2.0.0' skipInitialGetFeature='true'", 'test', 'WFS')
self.assertTrue(vl.isValid())
self.assertEqual(vl.featureCount(), 1228)
self.assertEqual(len(vl.fields()), 26)
self.assertEqual([field.name() for field in vl.fields()], ['id', 'metadataproperty', 'description_href', 'description_title', 'description_nilreason', 'description', 'descriptionreference_href', 'descriptionreference_title', 'descriptionreference_nilreason', 'identifier_codespace', 'identifier', 'name', 'location_location', 'inspireid_identifier_localid', 'inspireid_identifier_namespace', 'inspireid_identifier_versionid_nilreason', 'inspireid_identifier_versionid_nil', 'inspireid_identifier_versionid', 'legalfoundationdate_nilreason', 'legalfoundationdate', 'legalfoundationdocument_nilreason', 'legalfoundationdocument_owns', 'legalfoundationdocument_ci_citation', 'sitedesignation', 'sitename', 'siteprotectionclassification'])
self.assertEqual(vl.fields()["sitedesignation"].type(), QVariant.String)
got_f = [f for f in vl.getFeatures()]
self.assertEqual(len(got_f), 1)
geom = got_f[0].geometry()
self.assertFalse(geom.isNull())
self.assertEqual(got_f[0]["id"], 'ProtectedSite_FFH_553_DE4546-303')
self.assertEqual(got_f[0]["sitedesignation"], '{"ps:DesignationType":{"ps:designation":{"@xlink:href":"http://inspire.ec.europa.eu/codelist/Natura2000DesignationValue/specialAreaOfConservation"},"ps:designationScheme":{"@xlink:href":"http://inspire.ec.europa.eu/codelist/DesignationSchemeValue/natura2000"}}}')
vl = QgsVectorLayer("url='http://" + endpoint + "' typename='ps:ProtectedSite' version='2.0.0' skipInitialGetFeature='true'", 'test', 'WFS')
vl.setSubsetString("inspireid_identifier_localid = 'ProtectedSite_FFH_553_DE4546'")
if int(QT_VERSION_STR.split('.')[0]) >= 6:
attrs = 'xmlns:base="http://inspire.ec.europa.eu/schemas/base/3.3" xmlns:ps="http://inspire.ec.europa.eu/schemas/ps/4.0"'
else:
attrs = 'xmlns:ps="http://inspire.ec.europa.eu/schemas/ps/4.0" xmlns:base="http://inspire.ec.europa.eu/schemas/base/3.3"'
with open(sanitize(endpoint,
f"""?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=ps:ProtectedSite&SRSNAME=urn:ogc:def:crs:EPSG::25833&FILTER=<fes:Filter xmlns:fes="http://www.opengis.net/fes/2.0">
<fes:PropertyIsEqualTo>
<fes:ValueReference {attrs}>ps:inspireID/base:Identifier/base:localId</fes:ValueReference>
<fes:Literal>ProtectedSite_FFH_553_DE4546</fes:Literal>
</fes:PropertyIsEqualTo>
</fes:Filter>
"""),
'wb') as f:
with open(os.path.join(TEST_DATA_DIR, 'provider', 'wfs', 'inspire_complexfeatures', 'getfeature.xml'), "rb") as f_source:
f.write(f_source.read())
got_f = [f for f in vl.getFeatures()]
self.assertEqual(len(got_f), 1)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://inspire.ec.europa.eu/schemas/ps/4.0">
<import namespace="http://inspire.ec.europa.eu/schemas/base/3.3" schemaLocation="https://inspire.ec.europa.eu/schemas/base/3.3/BaseTypes.xsd"/>
<import namespace="http://inspire.ec.europa.eu/schemas/gn/4.0" schemaLocation="https://inspire.ec.europa.eu/schemas/gn/4.0/GeographicalNames.xsd"/>
<include schemaLocation="https://inspire.ec.europa.eu/schemas/ps/4.0/ProtectedSites.xsd"/>
</schema>

View File

@ -0,0 +1,652 @@
<?xml version='1.0' encoding='UTF-8'?>
<WFS_Capabilities version="2.0.0" xmlns="http://www.opengis.net/wfs/2.0" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:ogc="http://www.opengis.net/ogc" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd">
<ows:ServiceIdentification>
<ows:Title>INSPIRE Download Service: Protected Sites / Schutzgebiete des Landes Brandenburg (WFS-PS-SCHUTZG)</ows:Title>
<ows:Abstract>Der interoperable INSPIRE-Downloaddienst (WFS) Protected Sites (Schutzgebiete des Landes Brandenburg) beinhaltet Informationen zu den Schutzgebieten nach Naturschutzrecht des Landes Brandenburg und Europäische Schutzgebiete. Zu den Schutzgebieten nach Naturschutzrecht des Landes Brandenburg zählen Naturschutzgebiete, Landschaftsschutzgebiete, Biosphärenreservate, Naturparks und Nationalparke. Zum europäischen Schutzgebietssystem Natura 2000 zählen Vogelschutzgebiete (Special Protection Area (SPA)) und Fauna-Flora-Habitat-Gebiete (FFH-Gebiete). Gemäß der INSPIRE-Datenspezifikation Protected Sites (D2.8.I.9_v3.2) liegen die Inhalte der Schutzgebiete INSPIRE-konform vor. Der WFS beinhaltet den FeatureType ProtectedSite. --- The compliant INSPIRE download service (WFS) Protected Sites (Schutzgebiete des Landes Brandenburg) provides information on protected sites in the state of Brandenburg that are legally protected by state environmental law or according to the European Natura 2000 protected areas network. Areas legally protected by state environmental law include nature reserves, protected landscapes, biosphere reserves, nature parks and national parks. Areas according to the European Natura 2000 network include Special Protection Areas (SPA) for birds and Special Areas of Conservation (SAC) defined by the European Union's Habitats Directive (92/43/EEC). The content of the protected areas is compliant to the INSPIRE data specification for the annex theme Protected Sites (D2.8.I.9_v3.2). The WFS consists of the FeatureType ProtectedSite.</ows:Abstract>
<ows:Keywords>
<ows:Keyword>Schutzgebiet</ows:Keyword>
<ows:Keyword>Naturschutz</ows:Keyword>
<ows:Keyword>Natur</ows:Keyword>
<ows:Keyword>Umwelt</ows:Keyword>
<ows:Keyword>Biosphärenreservat</ows:Keyword>
<ows:Keyword>Landschaft</ows:Keyword>
<ows:Keyword>Landschaftsschutzgebiet</ows:Keyword>
<ows:Keyword>Nationalpark</ows:Keyword>
<ows:Keyword>Naturpark</ows:Keyword>
<ows:Keyword>Naturschutzgebiet</ows:Keyword>
<ows:Keyword>Naturschutzrecht</ows:Keyword>
<ows:Keyword>Vogelschutzgebiet</ows:Keyword>
<ows:Keyword>Schutzgebiete</ows:Keyword>
<ows:Keyword>Großschutzgebiet</ows:Keyword>
<ows:Keyword>ProtectedSite</ows:Keyword>
<ows:Keyword>Brandenburg</ows:Keyword>
<ows:Keyword>WFS</ows:Keyword>
<ows:Keyword>interoperabel</ows:Keyword>
<ows:Keyword>interoperability</ows:Keyword>
<ows:Keyword>inspireidentifiziert</ows:Keyword>
</ows:Keywords>
<ows:ServiceType codeSpace="http://www.opengeospatial.org/">WFS</ows:ServiceType>
<ows:ServiceTypeVersion>2.0.0</ows:ServiceTypeVersion>
<ows:Fees>Nutzung erfolgt derzeit kostenfrei unter Beachtung des Urheberrechts.</ows:Fees>
<ows:AccessConstraints>Es gelten die Bedingungen der Datenlizenz Deutschland Namensnennung Version 2.0: https://www.govdata.de/dl-de/by-2-0. Als Bezeichnung des Bereitstellers ist „© Landesamt für Umwelt Brandenburg“ anzugeben.</ows:AccessConstraints>
</ows:ServiceIdentification>
<ows:ServiceProvider>
<ows:ProviderName>Landesvermessung und Geobasisinformation Brandenburg (LGB)</ows:ProviderName>
<ows:ProviderSite xlink:href="https://inspire.brandenburg.de"/>
<ows:ServiceContact>
<ows:IndividualName>INSPIRE-Zentrale im Land Brandenburg</ows:IndividualName>
<ows:PositionName>Kundenservice</ows:PositionName>
<ows:ContactInfo>
<ows:Phone>
<ows:Voice>+49-331-8844-123</ows:Voice>
<ows:Facsimile>+49-331-8844-16123</ows:Facsimile>
</ows:Phone>
<ows:Address>
<ows:DeliveryPoint>Heinrich-Mann-Allee 104 B</ows:DeliveryPoint>
<ows:City>Potsdam</ows:City>
<ows:AdministrativeArea>Brandenburg</ows:AdministrativeArea>
<ows:PostalCode>14473</ows:PostalCode>
<ows:Country>Deutschland</ows:Country>
<ows:ElectronicMailAddress>kundenservice@inspire.brandenburg.de</ows:ElectronicMailAddress>
</ows:Address>
<ows:OnlineResource xlink:href="https://isk.geobasis-bb.de/produktinformation/Produktinformation-INSPIRE-WFS-PS-SCHUTZG.pdf"/>
<ows:HoursOfService>24/7</ows:HoursOfService>
<ows:ContactInstructions></ows:ContactInstructions>
</ows:ContactInfo>
<ows:Role>ServiceCenter</ows:Role>
</ows:ServiceContact>
</ows:ServiceProvider>
<!-- commented because of fake urls
<ows:OperationsMetadata>
<ows:Operation name="GetCapabilities">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs?"/>
<ows:Post xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs"/>
</ows:HTTP>
</ows:DCP>
<ows:Parameter name="AcceptVersions">
<ows:AllowedValues>
<ows:Value>2.0.0</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
<ows:Parameter name="AcceptFormats">
<ows:AllowedValues>
<ows:Value>text/xml</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
<ows:Parameter name="Sections">
<ows:AllowedValues>
<ows:Value>ServiceIdentification</ows:Value>
<ows:Value>ServiceProvider</ows:Value>
<ows:Value>OperationsMetadata</ows:Value>
<ows:Value>FeatureTypeList</ows:Value>
<ows:Value>Filter_Capabilities</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
</ows:Operation>
<ows:Operation name="DescribeFeatureType">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs?"/>
<ows:Post xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="ListStoredQueries">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs?"/>
<ows:Post xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="DescribeStoredQueries">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs?"/>
<ows:Post xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="GetFeature">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs?"/>
<ows:Post xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="GetPropertyValue">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs?"/>
<ows:Post xlink:href="https://inspire.brandenburg.de/services/ps_schutzg_wfs"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Parameter name="version">
<ows:AllowedValues>
<ows:Value>2.0.0</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
<ows:Parameter name="srsName">
<ows:AllowedValues>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/25833</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/25832</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/4326</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/4258</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/3034</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/3035</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/3044</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/3045</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/3857</ows:Value>
<ows:Value>http://www.opengis.net/def/crs/EPSG/0/4839</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
<ows:Parameter name="outputFormat">
<ows:AllowedValues>
<ows:Value>application/gml+xml; version=3.2</ows:Value>
<ows:Value>text/xml; subtype=gml/3.2.1</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
<ows:Parameter name="resolve">
<ows:AllowedValues>
<ows:Value>none</ows:Value>
<ows:Value>local</ows:Value>
<ows:Value>remote</ows:Value>
<ows:Value>all</ows:Value>
</ows:AllowedValues>
</ows:Parameter>
<ows:Constraint name="ImplementsSimpleWFS">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsBasicWFS">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsTransactionalWFS">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsLockingWFS">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="KVPEncoding">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="XMLEncoding">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="SOAPEncoding">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsInheritance">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsRemoteResolve">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsResultPaging">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsStandardJoins">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsSpatialJoins">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsTemporalJoins">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ImplementsFeatureVersioning">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ManageStoredQueries">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="CountDefault">
<ows:NoValues/>
<ows:DefaultValue>15000</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="ResolveLocalScope">
<ows:NoValues/>
<ows:DefaultValue>*</ows:DefaultValue>
</ows:Constraint>
<ows:Constraint name="QueryExpressions">
<ows:AllowedValues>
<ows:Value>wfs:Query</ows:Value>
<ows:Value>wfs:StoredQuery</ows:Value>
</ows:AllowedValues>
</ows:Constraint>
<ows:ExtendedCapabilities>
<inspire_dls:ExtendedCapabilities xmlns="http://www.deegree.org/services/metadata" xmlns:inspire_common="http://inspire.ec.europa.eu/schemas/common/1.0" xmlns:inspire_dls="http://inspire.ec.europa.eu/schemas/inspire_dls/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://inspire.ec.europa.eu/schemas/common/1.0 http://inspire.ec.europa.eu/schemas/common/1.0/common.xsd http://inspire.ec.europa.eu/schemas/inspire_dls/1.0 http://inspire.ec.europa.eu/schemas/inspire_dls/1.0/inspire_dls.xsd">
<inspire_common:MetadataUrl>
<inspire_common:URL>https://www.metaver.de/csw?REQUEST=GetRecordById&amp;SERVICE=CSW&amp;VERSION=2.0.2&amp;id=2CB45BFD-63E5-436A-B64B-AB9799CE0088</inspire_common:URL>
<inspire_common:MediaType>application/vnd.iso.19139+xml</inspire_common:MediaType>
</inspire_common:MetadataUrl>
<inspire_common:SupportedLanguages>
<inspire_common:DefaultLanguage>
<inspire_common:Language>ger</inspire_common:Language>
</inspire_common:DefaultLanguage>
</inspire_common:SupportedLanguages>
<inspire_common:ResponseLanguage>
<inspire_common:Language>ger</inspire_common:Language>
</inspire_common:ResponseLanguage>
<inspire_dls:SpatialDataSetIdentifier>
<inspire_common:Code>9e0092a4-c397-41e9-a11d-6b09e67fae13</inspire_common:Code>
<inspire_common:Namespace>https://registry.gdi-de.org/id/de.bb.metadata</inspire_common:Namespace>
</inspire_dls:SpatialDataSetIdentifier>
</inspire_dls:ExtendedCapabilities>
</ows:ExtendedCapabilities>
</ows:OperationsMetadata>
-->
<FeatureTypeList>
<FeatureType>
<Name xmlns:ps="http://inspire.ec.europa.eu/schemas/ps/4.0">ps:ProtectedSite</Name>
<Title>ProtectedSite</Title>
<Abstract>Naturschutzgebiete (NSG), Landschaftsschutzgebiete (LSG), Nationalparke (NatP), Naturparke (NP), Biospärenreservate (BR), Fauna-Flora-Habitat-Gebiete (FFH) und Vogelschutzgebiete (SPA, Special Protection Area) des Landes Brandenburg</Abstract>
<DefaultCRS>http://www.opengis.net/def/crs/EPSG/0/25833</DefaultCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/25832</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/4326</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/4258</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/3034</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/3035</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/3044</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/3045</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/3857</OtherCRS>
<OtherCRS>http://www.opengis.net/def/crs/EPSG/0/4839</OtherCRS>
<OutputFormats>
<Format>application/gml+xml; version=3.2</Format>
<Format>text/xml; subtype=gml/3.2.1</Format>
</OutputFormats>
<ows:WGS84BoundingBox>
<ows:LowerCorner>11.071522 51.292131</ows:LowerCorner>
<ows:UpperCorner>14.777398 53.617170</ows:UpperCorner>
</ows:WGS84BoundingBox>
<MetadataURL xlink:href="https://www.metaver.de/csw?REQUEST=GetRecordById&amp;SERVICE=CSW&amp;VERSION=2.0.2&amp;id=DE8B2AC4-6507-4391-AA33-D6A61996806B"/>
</FeatureType>
</FeatureTypeList>
<fes:Filter_Capabilities>
<fes:Conformance>
<fes:Constraint name="ImplementsQuery">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsAdHocQuery">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsFunctions">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsResourceId">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsMinStandardFilter">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsStandardFilter">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsMinSpatialFilter">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsSpatialFilter">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsMinTemporalFilter">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsTemporalFilter">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsVersionNav">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsSorting">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsExtendedOperators">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsMinimumXPath">
<ows:NoValues/>
<ows:DefaultValue>TRUE</ows:DefaultValue>
</fes:Constraint>
<fes:Constraint name="ImplementsSchemaElementFunc">
<ows:NoValues/>
<ows:DefaultValue>FALSE</ows:DefaultValue>
</fes:Constraint>
</fes:Conformance>
<fes:Id_Capabilities>
<fes:ResourceIdentifier name="fes:ResourceId"/>
</fes:Id_Capabilities>
<fes:Scalar_Capabilities>
<fes:LogicalOperators/>
<fes:ComparisonOperators>
<fes:ComparisonOperator name="PropertyIsEqualTo"/>
<fes:ComparisonOperator name="PropertyIsNotEqualTo"/>
<fes:ComparisonOperator name="PropertyIsLessThan"/>
<fes:ComparisonOperator name="PropertyIsGreaterThan"/>
<fes:ComparisonOperator name="PropertyIsLessThanOrEqualTo"/>
<fes:ComparisonOperator name="PropertyIsGreaterThanOrEqualTo"/>
<fes:ComparisonOperator name="PropertyIsLike"/>
<fes:ComparisonOperator name="PropertyIsNull"/>
<fes:ComparisonOperator name="PropertyIsNil"/>
<fes:ComparisonOperator name="PropertyIsBetween"/>
</fes:ComparisonOperators>
</fes:Scalar_Capabilities>
<fes:Spatial_Capabilities>
<fes:GeometryOperands xmlns:gml="http://www.opengis.net/gml" xmlns:gml32="http://www.opengis.net/gml/3.2">
<fes:GeometryOperand name="gml:Box"/>
<fes:GeometryOperand name="gml:Envelope"/>
<fes:GeometryOperand name="gml:Point"/>
<fes:GeometryOperand name="gml:LineString"/>
<fes:GeometryOperand name="gml:Curve"/>
<fes:GeometryOperand name="gml:Polygon"/>
<fes:GeometryOperand name="gml:Surface"/>
<fes:GeometryOperand name="gml:MultiPoint"/>
<fes:GeometryOperand name="gml:MultiLineString"/>
<fes:GeometryOperand name="gml:MultiCurve"/>
<fes:GeometryOperand name="gml:MultiPolygon"/>
<fes:GeometryOperand name="gml:MultiSurface"/>
<fes:GeometryOperand name="gml:CompositeCurve"/>
<fes:GeometryOperand name="gml:CompositeSurface"/>
<fes:GeometryOperand name="gml32:Envelope"/>
<fes:GeometryOperand name="gml32:Point"/>
<fes:GeometryOperand name="gml32:LineString"/>
<fes:GeometryOperand name="gml32:Curve"/>
<fes:GeometryOperand name="gml32:Polygon"/>
<fes:GeometryOperand name="gml32:Surface"/>
<fes:GeometryOperand name="gml32:MultiPoint"/>
<fes:GeometryOperand name="gml32:MultiLineString"/>
<fes:GeometryOperand name="gml32:MultiCurve"/>
<fes:GeometryOperand name="gml32:MultiPolygon"/>
<fes:GeometryOperand name="gml32:MultiSurface"/>
<fes:GeometryOperand name="gml32:CompositeCurve"/>
<fes:GeometryOperand name="gml32:CompositeSurface"/>
</fes:GeometryOperands>
<fes:SpatialOperators>
<fes:SpatialOperator name="BBOX"/>
<fes:SpatialOperator name="Equals"/>
<fes:SpatialOperator name="Disjoint"/>
<fes:SpatialOperator name="Intersects"/>
<fes:SpatialOperator name="Touches"/>
<fes:SpatialOperator name="Crosses"/>
<fes:SpatialOperator name="Within"/>
<fes:SpatialOperator name="Contains"/>
<fes:SpatialOperator name="Overlaps"/>
<fes:SpatialOperator name="Beyond"/>
<fes:SpatialOperator name="DWithin"/>
</fes:SpatialOperators>
</fes:Spatial_Capabilities>
<fes:Temporal_Capabilities>
<fes:TemporalOperands xmlns:gml="http://www.opengis.net/gml" xmlns:gml32="http://www.opengis.net/gml/3.2">
<fes:TemporalOperand name="gml:TimeInstant"/>
<fes:TemporalOperand name="gml:TimePeriod"/>
<fes:TemporalOperand name="gml32:TimeInstant"/>
<fes:TemporalOperand name="gml32:TimePeriod"/>
</fes:TemporalOperands>
<fes:TemporalOperators>
<fes:TemporalOperator name="After"/>
<fes:TemporalOperator name="Before"/>
<fes:TemporalOperator name="During"/>
<fes:TemporalOperator name="TEquals"/>
</fes:TemporalOperators>
</fes:Temporal_Capabilities>
<fes:Functions>
<fes:Function name="Area">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Area">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Centroid">
<fes:Returns xmlns:gml="http://www.opengis.net/gml">gml:Point</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Centroid">
<fes:Returns xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:Point</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="env">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:anyType</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:string</fes:Type>
</fes:Argument>
<fes:Argument name="arg2">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:anyType</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="ExtraProp">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:anyType</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:string</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="GeometryFromWKT">
<fes:Returns xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:string</fes:Type>
</fes:Argument>
<fes:Argument name="arg2">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:string</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="GeometryFromWKT">
<fes:Returns xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:string</fes:Type>
</fes:Argument>
<fes:Argument name="arg2">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:string</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="GetCurrentScale">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
</fes:Function>
<fes:Function name="HatchingDistance">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Type>
</fes:Argument>
<fes:Argument name="arg2">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IDiv">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Type>
</fes:Argument>
<fes:Argument name="arg2">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IMod">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Type>
</fes:Argument>
<fes:Argument name="arg2">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="InteriorPoint">
<fes:Returns xmlns:gml="http://www.opengis.net/gml">gml:Point</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="InteriorPoint">
<fes:Returns xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:Point</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IsCurve">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:boolean</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IsCurve">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:boolean</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IsPoint">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:boolean</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IsPoint">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:boolean</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IsSurface">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:boolean</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="IsSurface">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:boolean</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Length">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Length">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Lower">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="MoveGeometry">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml="http://www.opengis.net/gml">gml:_Geometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="MoveGeometry">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:gml32="http://www.opengis.net/gml/3.2">gml32:AbstractGeometry</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
<fes:Function name="Upper">
<fes:Returns xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:integer</fes:Returns>
<fes:Arguments>
<fes:Argument name="arg1">
<fes:Type xmlns:xsd="http://www.w3.org/2001/XMLSchema">xsd:double</fes:Type>
</fes:Argument>
</fes:Arguments>
</fes:Function>
</fes:Functions>
</fes:Filter_Capabilities>
</WFS_Capabilities>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
<?xml version='1.0' encoding='UTF-8'?>
<wfs:FeatureCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd" xmlns:wfs="http://www.opengis.net/wfs/2.0" timeStamp="2024-01-05T15:47:23Z" numberMatched="1228" numberReturned="0">
</wfs:FeatureCollection>