Skip to content

Commit 2ac20c6

Browse files
committed
[WFS Provider] Implement workarounds to better behave when extent reported by capabilities is wrong
Some servers like http://geodata.nationaalgeoregister.nl/bag/wfs report wrong layer extent in their GetCapabilities response. This commit implements a work around : - in the 'Request only features intersecting extent' mode, if no feature is returned in a BBOX enclosing the GetCapabilities extent, then query a single feature to initialize the extent. The user will then to zoom again on layer and zoom out. - in the other mode, the extent is updated with the feature geometry extent as soon as features come from the server, and the user can zoom on layer regularly to se it updated.
1 parent 3d95712 commit 2ac20c6

6 files changed

+333
-41
lines changed

src/providers/wfs/qgswfsfeatureiterator.cpp

+1-32
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,6 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
389389
connect( &mFeatureHitsAsyncRequest, SIGNAL( downloadFinished() ), &loop, SLOT( quit() ) );
390390
}
391391

392-
QgsGmlStreamingParser::AxisOrientationLogic axisOrientationLogic( QgsGmlStreamingParser::Honour_EPSG_if_urn );
393-
if ( mShared->mURI.ignoreAxisOrientation() )
394-
{
395-
axisOrientationLogic = QgsGmlStreamingParser::Ignore_EPSG;
396-
}
397-
398392
bool interrupted = false;
399393
bool truncatedResponse = false;
400394
QSettings s;
@@ -404,32 +398,7 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
404398
while ( true )
405399
{
406400
success = true;
407-
QgsGmlStreamingParser* parser;
408-
if ( mShared->mLayerPropertiesList.size() )
409-
{
410-
QList< QgsGmlStreamingParser::LayerProperties > layerPropertiesList;
411-
Q_FOREACH ( QgsOgcUtils::LayerProperties layerProperties, mShared->mLayerPropertiesList )
412-
{
413-
QgsGmlStreamingParser::LayerProperties layerPropertiesOut;
414-
layerPropertiesOut.mName = layerProperties.mName;
415-
layerPropertiesOut.mGeometryAttribute = layerProperties.mGeometryAttribute;
416-
layerPropertiesList << layerPropertiesOut;
417-
}
418-
419-
parser = new QgsGmlStreamingParser( layerPropertiesList,
420-
mShared->mFields,
421-
mShared->mMapFieldNameToSrcLayerNameFieldName,
422-
axisOrientationLogic,
423-
mShared->mURI.invertAxisOrientation() );
424-
}
425-
else
426-
{
427-
parser = new QgsGmlStreamingParser( mShared->mURI.typeName(),
428-
mShared->mGeometryAttribute,
429-
mShared->mFields,
430-
axisOrientationLogic,
431-
mShared->mURI.invertAxisOrientation() );
432-
}
401+
QgsGmlStreamingParser* parser = mShared->createParser();
433402

434403
QUrl url( buildURL( mTotalDownloadedFeatureCount,
435404
maxFeatures ? maxFeatures : mShared->mMaxFeatures, false ) );

src/providers/wfs/qgswfsprovider.cpp

+26-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ QgsWFSProvider::QgsWFSProvider( const QString& uri, const QgsWFSCapabilities::Ca
5959
{
6060
mShared->mCaps = caps;
6161
connect( mShared.data(), SIGNAL( raiseError( const QString& ) ), this, SLOT( pushErrorSlot( const QString& ) ) );
62+
connect( mShared.data(), SIGNAL( extentUpdated() ), this, SIGNAL( fullExtentCalculated() ) );
6263

6364
if ( uri.isEmpty() )
6465
{
@@ -684,7 +685,29 @@ QgsCoordinateReferenceSystem QgsWFSProvider::crs()
684685

685686
QgsRectangle QgsWFSProvider::extent()
686687
{
687-
return mExtent;
688+
// Some servers return completely buggy extent in their capabilities response
689+
// so mix it with the extent actually got from the downloaded features
690+
QgsRectangle computedExtent( mShared->computedExtent() );
691+
QgsDebugMsg( "computedExtent: " + computedExtent.toString() );
692+
QgsDebugMsg( "mCapabilityExtent: " + mShared->mCapabilityExtent.toString() );
693+
694+
// If we didn't get any feature, then return capabilities extent.
695+
if ( computedExtent.isNull() )
696+
return mShared->mCapabilityExtent;
697+
698+
// If the capabilities extent is completely off from the features, then
699+
// use feature extent.
700+
// Case of standplaats layer of http://geodata.nationaalgeoregister.nl/bag/wfs
701+
if ( !computedExtent.intersects( mShared->mCapabilityExtent ) )
702+
return computedExtent;
703+
704+
if ( mShared->downloadFinished() )
705+
{
706+
return computedExtent;
707+
}
708+
709+
computedExtent.combineExtentWith( &mShared->mCapabilityExtent );
710+
return computedExtent;
688711
}
689712

690713
bool QgsWFSProvider::isValid()
@@ -1378,9 +1401,9 @@ bool QgsWFSProvider::getCapabilities()
13781401
QgsDebugMsg( "src:" + src.authid() );
13791402
QgsDebugMsg( "dst:" + mShared->mSourceCRS.authid() );
13801403

1381-
mExtent = ct.transformBoundingBox( r, QgsCoordinateTransform::ForwardTransform );
1404+
mShared->mCapabilityExtent = ct.transformBoundingBox( r, QgsCoordinateTransform::ForwardTransform );
13821405

1383-
QgsDebugMsg( "layer ext:" + mExtent.toString() );
1406+
QgsDebugMsg( "layer ext:" + mShared->mCapabilityExtent.toString() );
13841407
}
13851408
if ( mShared->mCaps.featureTypes[i].insertCap )
13861409
{

src/providers/wfs/qgswfsprovider.h

-2
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,6 @@ class QgsWFSProvider : public QgsVectorDataProvider
153153
//! String used to define a subset of the layer
154154
QString mSubsetString;
155155

156-
/** Bounding box for the layer*/
157-
QgsRectangle mExtent;
158156
/** Geometry type of the features in this layer*/
159157
mutable QGis::WkbType mWKBType;
160158
/** Flag if provider is valid*/

src/providers/wfs/qgswfsshareddata.cpp

+158-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* *
1414
***************************************************************************/
1515

16+
#include <math.h> // M_PI
17+
1618
#include "qgswfsconstants.h"
1719
#include "qgswfsshareddata.h"
1820
#include "qgswfsutils.h"
@@ -34,6 +36,7 @@
3436

3537
#include <QCryptographicHash>
3638

39+
3740
QgsWFSSharedData::QgsWFSSharedData( const QString& uri )
3841
: mURI( uri )
3942
, mSourceCRS( 0 )
@@ -48,6 +51,7 @@ QgsWFSSharedData::QgsWFSSharedData( const QString& uri )
4851
, mFeatureCountExact( false )
4952
, mGetFeatureHitsIssued( false )
5053
, mTotalFeaturesAttemptedToBeCached( 0 )
54+
, mTryFetchingOneFeature( false )
5155
{
5256
// Needed because used by a signal
5357
qRegisterMetaType< QVector<QgsWFSFeatureGmlIdPair> >( "QVector<QgsWFSFeatureGmlIdPair>" );
@@ -514,6 +518,7 @@ int QgsWFSSharedData::registerToCache( QgsWFSFeatureIterator* iterator, QgsRecta
514518
delete mDownloader;
515519
mMutex.lock();
516520
mDownloadFinished = false;
521+
mComputedExtent = QgsRectangle();
517522
mDownloader = new QgsWFSThreadedFeatureDownloader( this );
518523
QEventLoop loop;
519524
connect( mDownloader, SIGNAL( ready() ), &loop, SLOT( quit() ) );
@@ -826,6 +831,7 @@ void QgsWFSSharedData::serializeFeatures( QVector<QgsWFSFeatureGmlIdPair>& featu
826831
existingGmlIds = getExistingCachedGmlIds( featureList );
827832
QVector<QgsWFSFeatureGmlIdPair> updatedFeatureList;
828833

834+
QgsRectangle localComputedExtent( mComputedExtent );
829835
Q_FOREACH ( QgsWFSFeatureGmlIdPair featPair, featureList )
830836
{
831837
const QgsFeature& gmlFeature = featPair.first;
@@ -874,8 +880,13 @@ void QgsWFSSharedData::serializeFeatures( QVector<QgsWFSFeatureGmlIdPair>& featu
874880

875881
cachedFeature.setAttribute( hexwkbGeomIdx, QVariant( QString( array.toHex().data() ) ) );
876882

877-
QgsGeometry* polyBoudingBox = QgsGeometry::fromRect( geometry->boundingBox() );
878-
cachedFeature.setGeometry( polyBoudingBox );
883+
QgsRectangle bBox( geometry->boundingBox() );
884+
if ( localComputedExtent.isNull() )
885+
localComputedExtent = bBox;
886+
else
887+
localComputedExtent.combineExtentWith( &bBox );
888+
QgsGeometry* polyBoundingBox = QgsGeometry::fromRect( bBox );
889+
cachedFeature.setGeometry( polyBoundingBox );
879890
}
880891
else
881892
{
@@ -936,15 +947,30 @@ void QgsWFSSharedData::serializeFeatures( QVector<QgsWFSFeatureGmlIdPair>& featu
936947
if ( !mFeatureCountExact )
937948
mFeatureCount += featureListToCache.size();
938949
mTotalFeaturesAttemptedToBeCached += featureListToCache.size();
950+
if ( !localComputedExtent.isNull() && mComputedExtent.isNull() && !mTryFetchingOneFeature &&
951+
!localComputedExtent.intersects( mCapabilityExtent ) )
952+
{
953+
QgsMessageLog::logMessage( tr( "Layer extent reported by the server is not correct. "
954+
"You may need to zoom again on layer while features are being downloaded" ), tr( "WFS" ) );
955+
}
956+
mComputedExtent = localComputedExtent;
939957
}
940958
}
941959

942960
featureList = updatedFeatureList;
943961

962+
emit extentUpdated();
963+
944964
QgsDebugMsg( QString( "end %1" ).arg( featureList.size() ) );
945965

946966
}
947967

968+
QgsRectangle QgsWFSSharedData::computedExtent()
969+
{
970+
QMutexLocker locker( &mMutex );
971+
return mComputedExtent;
972+
}
973+
948974
void QgsWFSSharedData::pushError( const QString& errorMsg )
949975
{
950976
QgsMessageLog::logMessage( errorMsg, tr( "WFS" ) );
@@ -970,6 +996,36 @@ void QgsWFSSharedData::endOfDownload( bool success, int featureCount,
970996
mDownloadFinished = true;
971997
if ( success && !mRect.isEmpty() )
972998
{
999+
// In the case we requested an extent that includes the extent reported by GetCapabilities response,
1000+
// that we have no filter and we got no features, then it is not unlikely that the capabilities
1001+
// might be wrong. In which case, query one feature so that we got a beginning of extent from
1002+
// which the user will be able to zoom out. This is far from being ideal...
1003+
if ( featureCount == 0 && mRect.contains( mCapabilityExtent ) && mWFSFilter.isEmpty() &&
1004+
mCaps.supportsHits && !mGeometryAttribute.isEmpty() && !mTryFetchingOneFeature )
1005+
{
1006+
QgsDebugMsg( "Capability extent is probably wrong. Starting a new request with one feature limit to get at least one feature" );
1007+
mTryFetchingOneFeature = true;
1008+
QgsWFSSingleFeatureRequest request( this );
1009+
mComputedExtent = request.getExtent();
1010+
if ( !mComputedExtent.isNull() )
1011+
{
1012+
// Grow the extent by ~ 50 km (completely arbitrary number if you wonder!)
1013+
// so that it is sufficiently zoomed out
1014+
if ( mSourceCRS.mapUnits() == QGis::Meters )
1015+
mComputedExtent.grow( 50. * 1000. );
1016+
else if ( mSourceCRS.mapUnits() == QGis::Degrees )
1017+
mComputedExtent.grow( 50. / 110 );
1018+
QgsMessageLog::logMessage( tr( "Layer extent reported by the server is not correct. You may need to zoom on layer and then zoom out to see all fetchures" ), tr( "WFS" ) );
1019+
}
1020+
mMutex.unlock();
1021+
if ( !mComputedExtent.isNull() )
1022+
{
1023+
emit extentUpdated();
1024+
}
1025+
mMutex.lock();
1026+
return;
1027+
}
1028+
9731029
// Arbitrary threshold to avoid the cache of BBOX to grow out of control.
9741030
// Note: we could be smarter and keep some BBOXes, but the saturation is
9751031
// unlikely to happen in practice, so just clear everything.
@@ -1099,6 +1155,41 @@ int QgsWFSSharedData::getFeatureCount( bool issueRequestIfNeeded )
10991155
return mFeatureCount;
11001156
}
11011157

1158+
QgsGmlStreamingParser* QgsWFSSharedData::createParser()
1159+
{
1160+
QgsGmlStreamingParser::AxisOrientationLogic axisOrientationLogic( QgsGmlStreamingParser::Honour_EPSG_if_urn );
1161+
if ( mURI.ignoreAxisOrientation() )
1162+
{
1163+
axisOrientationLogic = QgsGmlStreamingParser::Ignore_EPSG;
1164+
}
1165+
1166+
if ( mLayerPropertiesList.size() )
1167+
{
1168+
QList< QgsGmlStreamingParser::LayerProperties > layerPropertiesList;
1169+
Q_FOREACH ( QgsOgcUtils::LayerProperties layerProperties, mLayerPropertiesList )
1170+
{
1171+
QgsGmlStreamingParser::LayerProperties layerPropertiesOut;
1172+
layerPropertiesOut.mName = layerProperties.mName;
1173+
layerPropertiesOut.mGeometryAttribute = layerProperties.mGeometryAttribute;
1174+
layerPropertiesList << layerPropertiesOut;
1175+
}
1176+
1177+
return new QgsGmlStreamingParser( layerPropertiesList,
1178+
mFields,
1179+
mMapFieldNameToSrcLayerNameFieldName,
1180+
axisOrientationLogic,
1181+
mURI.invertAxisOrientation() );
1182+
}
1183+
else
1184+
{
1185+
return new QgsGmlStreamingParser( mURI.typeName(),
1186+
mGeometryAttribute,
1187+
mFields,
1188+
axisOrientationLogic,
1189+
mURI.invertAxisOrientation() );
1190+
}
1191+
}
1192+
11021193

11031194
// -------------------------
11041195

@@ -1165,3 +1256,68 @@ QString QgsWFSFeatureHitsRequest::errorMessageWithReason( const QString& reason
11651256
return tr( "Download of feature count failed: %1" ).arg( reason );
11661257
}
11671258

1259+
1260+
// -------------------------
1261+
1262+
1263+
QgsWFSSingleFeatureRequest::QgsWFSSingleFeatureRequest( QgsWFSSharedData* shared )
1264+
: QgsWFSRequest( shared->mURI.uri() ), mShared( shared )
1265+
{
1266+
}
1267+
1268+
QgsWFSSingleFeatureRequest::~QgsWFSSingleFeatureRequest()
1269+
{
1270+
}
1271+
1272+
QgsRectangle QgsWFSSingleFeatureRequest::getExtent()
1273+
{
1274+
QUrl getFeatureUrl( mUri.baseURL() );
1275+
getFeatureUrl.addQueryItem( "REQUEST", "GetFeature" );
1276+
getFeatureUrl.addQueryItem( "VERSION", mShared->mWFSVersion );
1277+
if ( mShared->mWFSVersion .startsWith( "2.0" ) )
1278+
getFeatureUrl.addQueryItem( "TYPENAMES", mUri.typeName() );
1279+
else
1280+
getFeatureUrl.addQueryItem( "TYPENAME", mUri.typeName() );
1281+
if ( mShared->mWFSVersion .startsWith( "2.0" ) )
1282+
getFeatureUrl.addQueryItem( "COUNT", QString::number( 1 ) );
1283+
else
1284+
getFeatureUrl.addQueryItem( "MAXFEATURES", QString::number( 1 ) );
1285+
1286+
if ( !sendGET( getFeatureUrl, true ) )
1287+
return -1;
1288+
1289+
const QByteArray& buffer = response();
1290+
1291+
QgsDebugMsg( "parsing QgsWFSSingleFeatureRequest: " + buffer );
1292+
1293+
// parse XML
1294+
QgsGmlStreamingParser* parser = mShared->createParser();
1295+
QString gmlProcessErrorMsg;
1296+
QgsRectangle extent;
1297+
if ( parser->processData( buffer, true, gmlProcessErrorMsg ) )
1298+
{
1299+
QVector<QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair> featurePtrList =
1300+
parser->getAndStealReadyFeatures();
1301+
QVector<QgsWFSFeatureGmlIdPair> featureList;
1302+
for ( int i = 0;i < featurePtrList.size();i++ )
1303+
{
1304+
QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair& featPair = featurePtrList[i];
1305+
QgsFeature f( *( featPair.first ) );
1306+
const QgsGeometry* geometry = f.constGeometry();
1307+
if ( geometry )
1308+
{
1309+
extent = geometry->boundingBox();
1310+
}
1311+
delete featPair.first;
1312+
}
1313+
}
1314+
delete parser;
1315+
return extent;
1316+
}
1317+
1318+
QString QgsWFSSingleFeatureRequest::errorMessageWithReason( const QString& reason )
1319+
{
1320+
return tr( "Download of feature failed: %1" ).arg( reason );
1321+
}
1322+
1323+

0 commit comments

Comments
 (0)