Skip to content
Permalink
Browse files

[afs] Support interruption requests on feature iterator

Results in a huge increase in responsiveness when working with
large AFS layers
  • Loading branch information
nyalldawson committed Feb 20, 2018
1 parent 1c8c461 commit 3aaf35ad30a973853ab98c149b96c0380759ebbe
@@ -19,6 +19,7 @@
#include "geometry/qgsgeometry.h"
#include "qgsexception.h"
#include "qgsarcgisrestutils.h"
#include "qgsfeedback.h"

QgsAfsFeatureSource::QgsAfsFeatureSource( const std::shared_ptr<QgsAfsSharedData> &sharedData )
: mSharedData( sharedData )
@@ -72,20 +73,11 @@ QgsAfsFeatureIterator::QgsAfsFeatureIterator( QgsAfsFeatureSource *source, bool

if ( !mFilterRect.isNull() )
{
QgsFeatureIds featuresInRect = mSource->sharedData()->getFeatureIdsInExtent( mFilterRect );
if ( !requestIds.isEmpty() )
{
requestIds.intersect( featuresInRect );
}
else
{
requestIds = featuresInRect;
}
if ( requestIds.empty() )
{
close();
return;
}
// defer request to find features in filter rect until first feature is requested
// this allows time for a interruption checker to be installed on the iterator
// and avoids performing this expensive check in the main thread when just
// preparing iterators
mDeferredFeaturesInFilterRectCheck = true;
}

mFeatureIdList = requestIds.toList();
@@ -107,9 +99,38 @@ bool QgsAfsFeatureIterator::fetchFeature( QgsFeature &f )
if ( mClosed )
return false;

if ( mInterruptionChecker && mInterruptionChecker->isCanceled() )
return false;

if ( mFeatureIterator >= mSource->sharedData()->featureCount() )
return false;

if ( mDeferredFeaturesInFilterRectCheck )
{
QgsFeatureIds featuresInRect = mSource->sharedData()->getFeatureIdsInExtent( mFilterRect, mInterruptionChecker );
if ( !mFeatureIdList.isEmpty() )
{
QgsFeatureIds requestIds = mFeatureIdList.toSet();
requestIds.intersect( featuresInRect );
mFeatureIdList = requestIds.toList();
}
else
{
mFeatureIdList = featuresInRect.toList();
}
if ( mFeatureIdList.empty() )
{
return false;
}

std::sort( mFeatureIdList.begin(), mFeatureIdList.end() );
mRemainingFeatureIds = mFeatureIdList;
if ( !mRemainingFeatureIds.empty() )
mFeatureIterator = mRemainingFeatureIds.at( 0 );

mDeferredFeaturesInFilterRectCheck = false;
}

if ( !mFeatureIdList.empty() && mRemainingFeatureIds.empty() )
return false;

@@ -133,10 +154,13 @@ bool QgsAfsFeatureIterator::fetchFeature( QgsFeature &f )
{
while ( mFeatureIterator < mSource->sharedData()->featureCount() )
{
if ( mInterruptionChecker && mInterruptionChecker->isCanceled() )
return false;

if ( !mFeatureIdList.empty() && mRemainingFeatureIds.empty() )
return false;

bool success = mSource->sharedData()->getFeature( mFeatureIterator, f );
bool success = mSource->sharedData()->getFeature( mFeatureIterator, f, QgsRectangle(), mInterruptionChecker );
if ( !mFeatureIdList.empty() )
{
mRemainingFeatureIds.removeAll( mFeatureIterator );
@@ -178,3 +202,8 @@ bool QgsAfsFeatureIterator::close()
mClosed = true;
return true;
}

void QgsAfsFeatureIterator::setInterruptionChecker( QgsFeedback *interruptionChecker )
{
mInterruptionChecker = interruptionChecker;
}
@@ -45,6 +45,8 @@ class QgsAfsFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsAfs
bool rewind() override;
bool close() override;

void setInterruptionChecker( QgsFeedback *interruptionChecker ) override;

protected:
bool fetchFeature( QgsFeature &f ) override;

@@ -56,6 +58,9 @@ class QgsAfsFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsAfs

QgsCoordinateTransform mTransform;
QgsRectangle mFilterRect;

QgsFeedback *mInterruptionChecker = nullptr;
bool mDeferredFeaturesInFilterRectCheck = false;
};

#endif // QGSAFSFEATUREITERATOR_H
@@ -23,7 +23,7 @@ void QgsAfsSharedData::clearCache()
mCache.clear();
}

bool QgsAfsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, const QgsRectangle &filterRect )
bool QgsAfsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, const QgsRectangle &filterRect, QgsFeedback *feedback )
{
QMutexLocker locker( &mMutex );

@@ -70,7 +70,8 @@ bool QgsAfsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, const QgsRect
const QVariantMap queryData = QgsArcGisRestUtils::getObjects(
mDataSource.param( QStringLiteral( "url" ) ), objectIds, mDataSource.param( QStringLiteral( "crs" ) ), true,
fetchAttribNames, QgsWkbTypes::hasM( mGeometryType ), QgsWkbTypes::hasZ( mGeometryType ),
filterRect, errorTitle, errorMessage );
filterRect, errorTitle, errorMessage, feedback );

if ( queryData.isEmpty() )
{
// const_cast<QgsAfsProvider *>( this )->pushError( errorTitle + ": " + errorMessage );
@@ -141,15 +142,15 @@ bool QgsAfsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, const QgsRect
return false;
}

QgsFeatureIds QgsAfsSharedData::getFeatureIdsInExtent( const QgsRectangle &extent )
QgsFeatureIds QgsAfsSharedData::getFeatureIdsInExtent( const QgsRectangle &extent, QgsFeedback *feedback )
{
QString errorTitle;
QString errorText;


const QList<quint32> featuresInRect = QgsArcGisRestUtils::getObjectIdsByExtent( mDataSource.param( QStringLiteral( "url" ) ),
mObjectIdFieldName,
extent, errorTitle, errorText );
extent, errorTitle, errorText, feedback );

QgsFeatureIds ids;
for ( quint32 id : featuresInRect )
@@ -22,6 +22,8 @@
#include "qgsfeature.h"
#include "qgsdatasourceuri.h"

class QgsFeedback;

/**
* \brief This class holds data, shared between QgsAfsProvider and QgsAfsFeatureIterator
**/
@@ -36,8 +38,8 @@ class QgsAfsSharedData : public QObject
QgsCoordinateReferenceSystem crs() const { return mSourceCRS; }
void clearCache();

bool getFeature( QgsFeatureId id, QgsFeature &f, const QgsRectangle &filterRect = QgsRectangle() );
QgsFeatureIds getFeatureIdsInExtent( const QgsRectangle &extent );
bool getFeature( QgsFeatureId id, QgsFeature &f, const QgsRectangle &filterRect = QgsRectangle(), QgsFeedback *feedback = nullptr );
QgsFeatureIds getFeatureIdsInExtent( const QgsRectangle &extent, QgsFeedback *feedback );

private:
friend class QgsAfsProvider;
@@ -27,6 +27,7 @@
#include "geometry/qgsmulticurve.h"
#include "geometry/qgspolygon.h"
#include "geometry/qgspoint.h"
#include "qgsfeedback.h"

#include <QEventLoop>
#include <QNetworkRequest>
@@ -370,7 +371,7 @@ QVariantMap QgsArcGisRestUtils::getObjects( const QString &layerurl, const QList
bool fetchGeometry, const QStringList &fetchAttributes,
bool fetchM, bool fetchZ,
const QgsRectangle &filterRect,
QString &errorTitle, QString &errorText )
QString &errorTitle, QString &errorText, QgsFeedback *feedback )
{
QStringList ids;
foreach ( int id, objectIds )
@@ -404,10 +405,10 @@ QVariantMap QgsArcGisRestUtils::getObjects( const QString &layerurl, const QList
queryUrl.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
queryUrl.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
}
return queryServiceJSON( queryUrl, errorTitle, errorText );
return queryServiceJSON( queryUrl, errorTitle, errorText, feedback );
}

QList<quint32> QgsArcGisRestUtils::getObjectIdsByExtent( const QString &layerurl, const QString &objectIdField, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText )
QList<quint32> QgsArcGisRestUtils::getObjectIdsByExtent( const QString &layerurl, const QString &objectIdField, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText, QgsFeedback *feedback )
{
QUrl queryUrl( layerurl + "/query" );
queryUrl.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
@@ -418,7 +419,7 @@ QList<quint32> QgsArcGisRestUtils::getObjectIdsByExtent( const QString &layerurl
.arg( filterRect.xMaximum(), 0, 'f', -1 ).arg( filterRect.yMaximum(), 0, 'f', -1 ) );
queryUrl.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryEnvelope" ) );
queryUrl.addQueryItem( QStringLiteral( "spatialRel" ), QStringLiteral( "esriSpatialRelEnvelopeIntersects" ) );
const QVariantMap objectIdData = queryServiceJSON( queryUrl, errorTitle, errorText );
const QVariantMap objectIdData = queryServiceJSON( queryUrl, errorTitle, errorText, feedback );

if ( objectIdData.isEmpty() )
{
@@ -433,7 +434,7 @@ QList<quint32> QgsArcGisRestUtils::getObjectIdsByExtent( const QString &layerurl
return ids;
}

QByteArray QgsArcGisRestUtils::queryService( const QUrl &u, QString &errorTitle, QString &errorText )
QByteArray QgsArcGisRestUtils::queryService( const QUrl &u, QString &errorTitle, QString &errorText, QgsFeedback *feedback )
{
QEventLoop loop;
QUrl url = parseUrl( u );
@@ -447,11 +448,18 @@ QByteArray QgsArcGisRestUtils::queryService( const QUrl &u, QString &errorTitle,
{
reply = nam->get( request );
QObject::connect( reply, &QNetworkReply::finished, &loop, &QEventLoop::quit );
if ( feedback )
{
QObject::connect( feedback, &QgsFeedback::canceled, reply, &QNetworkReply::abort );
}

loop.exec( QEventLoop::ExcludeUserInputEvents );

reply->deleteLater();

if ( feedback && feedback->isCanceled() )
return QByteArray();

// Handle network errors
if ( reply->error() != QNetworkReply::NoError )
{
@@ -475,9 +483,9 @@ QByteArray QgsArcGisRestUtils::queryService( const QUrl &u, QString &errorTitle,
return result;
}

QVariantMap QgsArcGisRestUtils::queryServiceJSON( const QUrl &url, QString &errorTitle, QString &errorText )
QVariantMap QgsArcGisRestUtils::queryServiceJSON( const QUrl &url, QString &errorTitle, QString &errorText, QgsFeedback *feedback )
{
QByteArray reply = queryService( url, errorTitle, errorText );
QByteArray reply = queryService( url, errorTitle, errorText, feedback );
if ( !errorTitle.isEmpty() )
{
return QVariantMap();
@@ -26,6 +26,7 @@ class QgsFields;
class QgsRectangle;
class QgsAbstractGeometry;
class QgsCoordinateReferenceSystem;
class QgsFeedback;

class QgsArcGisRestUtils
{
@@ -40,10 +41,10 @@ class QgsArcGisRestUtils
static QVariantMap getObjectIds( const QString &layerurl, const QString &objectIdFieldName, QString &errorTitle, QString &errorText );
static QVariantMap getObjects( const QString &layerurl, const QList<quint32> &objectIds, const QString &crs,
bool fetchGeometry, const QStringList &fetchAttributes, bool fetchM, bool fetchZ,
const QgsRectangle &filterRect, QString &errorTitle, QString &errorText );
static QList<quint32> getObjectIdsByExtent( const QString &layerurl, const QString &objectIdField, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText );
static QByteArray queryService( const QUrl &url, QString &errorTitle, QString &errorText );
static QVariantMap queryServiceJSON( const QUrl &url, QString &errorTitle, QString &errorText );
const QgsRectangle &filterRect, QString &errorTitle, QString &errorText, QgsFeedback *feedback = nullptr );
static QList<quint32> getObjectIdsByExtent( const QString &layerurl, const QString &objectIdField, const QgsRectangle &filterRect, QString &errorTitle, QString &errorText, QgsFeedback *feedback = nullptr );
static QByteArray queryService( const QUrl &url, QString &errorTitle, QString &errorText, QgsFeedback *feedback = nullptr );
static QVariantMap queryServiceJSON( const QUrl &url, QString &errorTitle, QString &errorText, QgsFeedback *feedback = nullptr );

static QUrl parseUrl( const QUrl &url );
};

0 comments on commit 3aaf35a

Please sign in to comment.
You can’t perform that action at this time.