Skip to content

Commit

Permalink
Merge pull request #39368 from elpaso/wfs-t-1.1
Browse files Browse the repository at this point in the history
Fix WFS-T 1.1.0 support
  • Loading branch information
elpaso committed Oct 17, 2020
2 parents 0a5321a + 9edf482 commit a0711d7
Show file tree
Hide file tree
Showing 23 changed files with 910 additions and 57 deletions.
1 change: 1 addition & 0 deletions python/gui/auto_generated/qgsnewhttpconnection.sip.in
Expand Up @@ -108,6 +108,7 @@ Returns the "test connection" button.




virtual QString wfsSettingsKey( const QString &base, const QString &connectionName ) const;
%Docstring
Returns the QSettings key for WFS related settings for the connection.
Expand Down
3 changes: 2 additions & 1 deletion src/core/qgsogcutils.cpp
Expand Up @@ -1173,7 +1173,8 @@ QDomElement QgsOgcUtils::geometryToGML( const QgsGeometry &geometry, QDomDocumen
return geometryToGML( geometry, doc, ( format == QLatin1String( "GML2" ) ) ? GML_2_1_2 : GML_3_2_1, QString(), false, QString(), precision );
}

QDomElement QgsOgcUtils::geometryToGML( const QgsGeometry &geometry, QDomDocument &doc,
QDomElement QgsOgcUtils::geometryToGML( const QgsGeometry &geometry,
QDomDocument &doc,
GMLVersion gmlVersion,
const QString &srsName,
bool invertAxisOrientation,
Expand Down
9 changes: 9 additions & 0 deletions src/gui/qgsnewhttpconnection.cpp
Expand Up @@ -166,6 +166,7 @@ void QgsNewHttpConnection::wfsVersionCurrentIndexChanged( int index )
txtPageSize->setEnabled( cbxWfsFeaturePaging->isChecked() && ( index == WFS_VERSION_MAX || index >= WFS_VERSION_1_1 ) );
cbxWfsIgnoreAxisOrientation->setEnabled( index != WFS_VERSION_1_0 && index != WFS_VERSION_API_FEATURES_1_0 );
cbxWfsInvertAxisOrientation->setEnabled( index != WFS_VERSION_API_FEATURES_1_0 );
wfsUseGml2EncodingForTransactions()->setEnabled( index == WFS_VERSION_1_1 );
}

void QgsNewHttpConnection::wfsFeaturePagingStateChanged( int state )
Expand Down Expand Up @@ -256,6 +257,11 @@ QCheckBox *QgsNewHttpConnection::wfsPagingEnabledCheckBox()
return cbxWfsFeaturePaging;
}

QCheckBox *QgsNewHttpConnection::wfsUseGml2EncodingForTransactions()
{
return cbxWfsUseGml2EncodingForTransactions;
}

QLineEdit *QgsNewHttpConnection::wfsPageSizeLineEdit()
{
return txtPageSize;
Expand All @@ -281,6 +287,8 @@ void QgsNewHttpConnection::updateServiceSpecificSettings()
cbxWmsIgnoreReportedLayerExtents->setChecked( settings.value( wmsKey + QStringLiteral( "/ignoreReportedLayerExtents" ), false ).toBool() );
cbxWfsIgnoreAxisOrientation->setChecked( settings.value( wfsKey + "/ignoreAxisOrientation", false ).toBool() );
cbxWfsInvertAxisOrientation->setChecked( settings.value( wfsKey + "/invertAxisOrientation", false ).toBool() );
cbxWfsUseGml2EncodingForTransactions->setChecked( settings.value( wfsKey + "/preferCoordinatesForWfsT11", false ).toBool() );

cbxWmsIgnoreAxisOrientation->setChecked( settings.value( wmsKey + "/ignoreAxisOrientation", false ).toBool() );
cbxWmsInvertAxisOrientation->setChecked( settings.value( wmsKey + "/invertAxisOrientation", false ).toBool() );
cbxIgnoreGetFeatureInfoURI->setChecked( settings.value( wmsKey + "/ignoreGetFeatureInfoURI", false ).toBool() );
Expand Down Expand Up @@ -463,6 +471,7 @@ void QgsNewHttpConnection::accept()
{
settings.setValue( wfsKey + "/ignoreAxisOrientation", cbxWfsIgnoreAxisOrientation->isChecked() );
settings.setValue( wfsKey + "/invertAxisOrientation", cbxWfsInvertAxisOrientation->isChecked() );
settings.setValue( wfsKey + "/preferCoordinatesForWfsT11", cbxWfsUseGml2EncodingForTransactions->isChecked() );
}
if ( mTypes & ConnectionWms || mTypes & ConnectionWcs )
{
Expand Down
6 changes: 6 additions & 0 deletions src/gui/qgsnewhttpconnection.h
Expand Up @@ -150,6 +150,12 @@ class GUI_EXPORT QgsNewHttpConnection : public QDialog, private Ui::QgsNewHttpCo
*/
QCheckBox *wfsPagingEnabledCheckBox() SIP_SKIP;

/**
* Returns the "Use GML2 encoding for transactions" checkbox
* \since QGIS 3.16
*/
QCheckBox *wfsUseGml2EncodingForTransactions() SIP_SKIP;

/**
* Returns the "WFS page size" edit
* \since QGIS 3.2
Expand Down
7 changes: 7 additions & 0 deletions src/providers/wfs/qgswfsconnection.cpp
Expand Up @@ -53,6 +53,13 @@ QgsWfsConnection::QgsWfsConnection( const QString &connName )
settings.value( key + "/" + QgsWFSConstants::SETTINGS_PAGING_ENABLED, true ).toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
}

if ( settings.contains( key + "/" + QgsWFSConstants::SETTINGS_WFST_1_1_PREFER_COORDINATES ) )
{
mUri.removeParam( QgsWFSConstants::URI_PARAM_WFST_1_1_PREFER_COORDINATES ); // setParam allow for duplicates!
mUri.setParam( QgsWFSConstants::URI_PARAM_WFST_1_1_PREFER_COORDINATES,
settings.value( key + "/" + QgsWFSConstants::SETTINGS_WFST_1_1_PREFER_COORDINATES, true ).toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
}

QgsDebugMsgLevel( QStringLiteral( "WFS full uri: '%1'." ).arg( QString( mUri.uri() ) ), 4 );
}

Expand Down
2 changes: 2 additions & 0 deletions src/providers/wfs/qgswfsconstants.cpp
Expand Up @@ -40,6 +40,7 @@ const QString QgsWFSConstants::URI_PARAM_VALIDATESQLFUNCTIONS( QStringLiteral( "
const QString QgsWFSConstants::URI_PARAM_HIDEDOWNLOADPROGRESSDIALOG( QStringLiteral( "hideDownloadProgressDialog" ) );
const QString QgsWFSConstants::URI_PARAM_PAGING_ENABLED( "pagingEnabled" );
const QString QgsWFSConstants::URI_PARAM_PAGE_SIZE( "pageSize" );
const QString QgsWFSConstants::URI_PARAM_WFST_1_1_PREFER_COORDINATES( "preferCoordinatesForWfsT11" );

const QString QgsWFSConstants::VERSION_AUTO( QStringLiteral( "auto" ) );

Expand All @@ -48,3 +49,4 @@ const QString QgsWFSConstants::SETTINGS_VERSION( QStringLiteral( "version" ) );
const QString QgsWFSConstants::SETTINGS_MAXNUMFEATURES( QStringLiteral( "maxnumfeatures" ) );
const QString QgsWFSConstants::SETTINGS_PAGING_ENABLED( QStringLiteral( "pagingenabled" ) );
const QString QgsWFSConstants::SETTINGS_PAGE_SIZE( QStringLiteral( "pagesize" ) );
const QString QgsWFSConstants::SETTINGS_WFST_1_1_PREFER_COORDINATES( QStringLiteral( "preferCoordinatesForWfsT11" ) );
2 changes: 2 additions & 0 deletions src/providers/wfs/qgswfsconstants.h
Expand Up @@ -48,6 +48,7 @@ struct QgsWFSConstants
static const QString URI_PARAM_HIDEDOWNLOADPROGRESSDIALOG;
static const QString URI_PARAM_PAGING_ENABLED;
static const QString URI_PARAM_PAGE_SIZE;
static const QString URI_PARAM_WFST_1_1_PREFER_COORDINATES;

//
static const QString VERSION_AUTO;
Expand All @@ -58,6 +59,7 @@ struct QgsWFSConstants
static const QString SETTINGS_MAXNUMFEATURES;
static const QString SETTINGS_PAGING_ENABLED;
static const QString SETTINGS_PAGE_SIZE;
static const QString SETTINGS_WFST_1_1_PREFER_COORDINATES;
};

#endif // QGSWFSCONSTANTS_H
7 changes: 7 additions & 0 deletions src/providers/wfs/qgswfsdatasourceuri.cpp
Expand Up @@ -373,6 +373,13 @@ bool QgsWFSDataSourceURI::hideDownloadProgressDialog() const
return mURI.hasParam( QgsWFSConstants::URI_PARAM_HIDEDOWNLOADPROGRESSDIALOG );
}


bool QgsWFSDataSourceURI::preferCoordinatesForWfst11() const
{
return mURI.hasParam( QgsWFSConstants::URI_PARAM_WFST_1_1_PREFER_COORDINATES ) &&
mURI.param( QgsWFSConstants::URI_PARAM_WFST_1_1_PREFER_COORDINATES ).toUpper() == QLatin1String( "TRUE" );
}

QString QgsWFSDataSourceURI::build( const QString &baseUri,
const QString &typeName,
const QString &crsString,
Expand Down
3 changes: 3 additions & 0 deletions src/providers/wfs/qgswfsdatasourceuri.h
Expand Up @@ -113,6 +113,9 @@ class QgsWFSDataSourceURI
//! Whether to hide download progress dialog in QGIS main app. Defaults to false
bool hideDownloadProgressDialog() const;

//! Whether to use "coordinates" instead of "pos" and "posList" for WFS-T 1.1 transactions (ESRI mapserver)
bool preferCoordinatesForWfst11() const;

//! Returns authorization parameters
const QgsAuthorizationSettings &auth() const { return mAuth; }

Expand Down
158 changes: 140 additions & 18 deletions src/providers/wfs/qgswfsprovider.cpp
Expand Up @@ -787,6 +787,49 @@ void QgsWFSProvider::reloadProviderData()
mShared->invalidateCache();
}

QDomElement QgsWFSProvider::geometryElement( const QgsGeometry &geometry, QDomDocument &transactionDoc )
{
QDomElement gmlElem;

// Determine axis orientation and gml version
bool applyAxisInversion;
QgsOgcUtils::GMLVersion gmlVersion;

if ( mShared->mWFSVersion.startsWith( QLatin1String( "1.1" ) ) )
{
// WFS 1.1.0 uses preferably GML 3, but ESRI mapserver in 2020 doesn't like it so we stick to GML2
if ( ! mShared->mServerPrefersCoordinatesForTransactions_1_1 )
{
gmlVersion = QgsOgcUtils::GML_3_1_0;
}
else
{
gmlVersion = QgsOgcUtils::GML_2_1_2;
}
// For servers like Geomedia and QGIS Server that advertise EPSG:XXXX in capabilities even in WFS 1.1 or 2.0
// cpabilities useEPSGColumnFormat is set.
// We follow GeoServer convention here which is to treat EPSG:4326 as lon/lat
applyAxisInversion = ( crs().hasAxisInverted() && ! mShared->mURI.ignoreAxisOrientation() && ! mShared->mCaps.useEPSGColumnFormat )
|| mShared->mURI.invertAxisOrientation();
}
else // 1.0
{
gmlVersion = QgsOgcUtils::GML_2_1_2;
applyAxisInversion = mShared->mURI.invertAxisOrientation();
}

gmlElem = QgsOgcUtils::geometryToGML(
geometry,
transactionDoc,
gmlVersion,
mShared->srsName(),
applyAxisInversion,
QString()
);

return gmlElem;
}

QgsWkbTypes::Type QgsWFSProvider::wkbType() const
{
return mShared->mWKBType;
Expand Down Expand Up @@ -877,10 +920,10 @@ bool QgsWFSProvider::addFeatures( QgsFeatureList &flist, Flags flags )
{
the_geom.convertToMultiType();
}
QDomElement gmlElem = QgsOgcUtils::geometryToGML( the_geom, transactionDoc );
if ( !gmlElem.isNull() )

const QDomElement gmlElem { geometryElement( the_geom, transactionDoc ) };
if ( ! gmlElem.isNull() )
{
gmlElem.setAttribute( QStringLiteral( "srsName" ), crs().authid() );
geomElem.appendChild( gmlElem );
featureElem.appendChild( geomElem );
}
Expand Down Expand Up @@ -1045,9 +1088,9 @@ bool QgsWFSProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
nameElem.appendChild( nameText );
propertyElem.appendChild( nameElem );
QDomElement valueElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Value" ) );
QDomElement gmlElem = QgsOgcUtils::geometryToGML( geomIt.value(), transactionDoc );
gmlElem.setAttribute( QStringLiteral( "srsName" ), crs().authid() );
valueElem.appendChild( gmlElem );

valueElem.appendChild( geometryElement( geomIt.value(), transactionDoc ) );

propertyElem.appendChild( valueElem );
updateElem.appendChild( propertyElem );

Expand Down Expand Up @@ -1264,6 +1307,8 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields

QByteArray response = describeFeatureType.response();

QgsDebugMsgLevel( response, 4 );

QDomDocument describeFeatureDocument;
QString errorMsg;
if ( !describeFeatureDocument.setContent( response, true, &errorMsg ) )
Expand Down Expand Up @@ -1580,17 +1625,25 @@ bool QgsWFSProvider::sendTransactionDocument( const QDomDocument &doc, QDomDocum
return false;
}

QgsDebugMsgLevel( doc.toString(), 4 );

QgsWFSTransactionRequest request( mShared->mURI );
return request.send( doc, serverResponse );
}

QDomElement QgsWFSProvider::createTransactionElement( QDomDocument &doc ) const
{
QDomElement transactionElem = doc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Transaction" ) );
// QString WfsVersion = mShared->mWFSVersion;
// For now: hardcoded to 1.0.0
QString WfsVersion = QStringLiteral( "1.0.0" );
transactionElem.setAttribute( QStringLiteral( "version" ), WfsVersion );
const QString WfsVersion = mShared->mWFSVersion;
// only 1.1.0 and 1.0.0 are supported
if ( WfsVersion == QStringLiteral( "1.1.0" ) )
{
transactionElem.setAttribute( QStringLiteral( "version" ), WfsVersion );
}
else
{
transactionElem.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0.0" ) );
}
transactionElem.setAttribute( QStringLiteral( "service" ), QStringLiteral( "WFS" ) );
transactionElem.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );

Expand Down Expand Up @@ -1634,19 +1687,70 @@ bool QgsWFSProvider::transactionSuccess( const QDomDocument &serverResponse ) co
return false;
}

QDomNodeList transactionResultList = documentElem.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "TransactionResult" ) );
if ( transactionResultList.size() < 1 )
const QString WfsVersion = mShared->mWFSVersion;

if ( WfsVersion == QStringLiteral( "1.1.0" ) )
{
const QDomNodeList transactionSummaryList = documentElem.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "TransactionSummary" ) );
if ( transactionSummaryList.size() < 1 )
{
return false;
}

QDomElement transactionElement { transactionSummaryList.at( 0 ).toElement() };
QDomNodeList totalInserted = transactionElement.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "totalInserted" ) );
QDomNodeList totalUpdated = transactionElement.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "totalUpdated" ) );
QDomNodeList totalDeleted = transactionElement.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "totalDeleted" ) );
if ( totalInserted.size() > 0 && totalInserted.at( 0 ).toElement().text().toInt() > 0 )
{
return true;
}
if ( totalUpdated.size() > 0 && totalUpdated.at( 0 ).toElement().text().toInt() > 0 )
{
return true;
}
if ( totalDeleted.size() > 0 && totalDeleted.at( 0 ).toElement().text().toInt() > 0 )
{
return true;
}

// Handle wrong QGIS server response (capital initial letter)
totalInserted = transactionElement.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "TotalInserted" ) );
totalUpdated = transactionElement.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "TotalUpdated" ) );
totalDeleted = transactionElement.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "TotalDeleted" ) );
if ( totalInserted.size() > 0 && totalInserted.at( 0 ).toElement().text().toInt() > 0 )
{
return true;
}
if ( totalUpdated.size() > 0 && totalUpdated.at( 0 ).toElement().text().toInt() > 0 )
{
return true;
}
if ( totalDeleted.size() > 0 && totalDeleted.at( 0 ).toElement().text().toInt() > 0 )
{
return true;
}

return false;
}

QDomNodeList statusList = transactionResultList.at( 0 ).toElement().elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Status" ) );
if ( statusList.size() < 1 )
}
else
{
return false;
const QDomNodeList transactionResultList = documentElem.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "TransactionResult" ) );
if ( transactionResultList.size() < 1 )
{
return false;
}

const QDomNodeList statusList = transactionResultList.at( 0 ).toElement().elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Status" ) );
if ( statusList.size() < 1 )
{
return false;
}

return statusList.at( 0 ).firstChildElement().localName() == QLatin1String( "SUCCESS" );
}

return statusList.at( 0 ).firstChildElement().localName() == QLatin1String( "SUCCESS" );
}

QStringList QgsWFSProvider::insertedFeatureIds( const QDomDocument &serverResponse ) const
Expand All @@ -1663,7 +1767,17 @@ QStringList QgsWFSProvider::insertedFeatureIds( const QDomDocument &serverRespon
return ids;
}

QDomNodeList insertResultList = rootElem.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "InsertResult" ) );
// Handles WFS 1.1.0
QString insertResultTagName;
if ( mShared->mWFSVersion == QStringLiteral( "1.1.0" ) )
{
insertResultTagName = QStringLiteral( "InsertResults" );
}
else
{
insertResultTagName = QStringLiteral( "InsertResult" );
}
QDomNodeList insertResultList = rootElem.elementsByTagNameNS( QgsWFSConstants::WFS_NAMESPACE, insertResultTagName );
for ( int i = 0; i < insertResultList.size(); ++i )
{
QDomNodeList featureIdList = insertResultList.at( i ).toElement().elementsByTagNameNS( QgsWFSConstants::OGC_NAMESPACE, QStringLiteral( "FeatureId" ) );
Expand Down Expand Up @@ -1850,6 +1964,14 @@ void QgsWFSProvider::handleException( const QDomDocument &serverResponse )
return;
}

// WFS 1.1.0
if ( exceptionElem.tagName() == QLatin1String( "TransactionResponse" ) )
{
pushError( tr( "Unsuccessful service response: no features were added, deleted or changed." ) );
return;
}


if ( exceptionElem.tagName() == QLatin1String( "ExceptionReport" ) )
{
QDomElement exception = exceptionElem.firstChildElement( QStringLiteral( "Exception" ) );
Expand Down
5 changes: 5 additions & 0 deletions src/providers/wfs/qgswfsprovider.h
Expand Up @@ -140,6 +140,11 @@ class QgsWFSProvider final: public QgsVectorDataProvider

friend class QgsWFSFeatureSource;

/**
* Create the geometry element
*/
QDomElement geometryElement( const QgsGeometry &geometry, QDomDocument &transactionDoc );

protected:

//! String used to define a subset of the layer
Expand Down
1 change: 1 addition & 0 deletions src/providers/wfs/qgswfsshareddata.cpp
Expand Up @@ -27,6 +27,7 @@ QgsWFSSharedData::QgsWFSSharedData( const QString &uri )
, mURI( uri )
{
mHideProgressDialog = mURI.hideDownloadProgressDialog();
mServerPrefersCoordinatesForTransactions_1_1 = mURI.preferCoordinatesForWfst11();
}

QgsWFSSharedData::~QgsWFSSharedData()
Expand Down
5 changes: 5 additions & 0 deletions src/providers/wfs/qgswfsshareddata.h
Expand Up @@ -89,6 +89,11 @@ class QgsWFSSharedData : public QObject, public QgsBackgroundCachedSharedData
*/
bool mGetFeatureEPSGDotHonoursEPSGOrder = false;

/**
* If the server (typically ESRI with WFS-T 1.1 in 2020) does not like "pos" and "posList", and requires "coordinates" for WFS 1.1 transactions
*/
bool mServerPrefersCoordinatesForTransactions_1_1 = false;

//! Geometry type of the features in this layer
QgsWkbTypes::Type mWKBType = QgsWkbTypes::Unknown;

Expand Down

0 comments on commit a0711d7

Please sign in to comment.