Skip to content

Commit

Permalink
Implement distance within for postgres iterator
Browse files Browse the repository at this point in the history
Note: this could be optimised by converting automatically to
a ST_DWithin(...) clause!
  • Loading branch information
nyalldawson committed Aug 5, 2021
1 parent a592eb3 commit 85f67fd
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 64 deletions.
6 changes: 3 additions & 3 deletions src/providers/postgres/qgspostgresconn.cpp
Expand Up @@ -1060,9 +1060,9 @@ QString QgsPostgresConn::postgisVersion() const
result = PQexec( QStringLiteral( "SELECT postgis_geos_version(), postgis_proj_version()" ) );
mGeosAvailable = result.PQntuples() == 1 && !result.PQgetisnull( 0, 0 );
mProjAvailable = result.PQntuples() == 1 && !result.PQgetisnull( 0, 1 );
QgsDebugMsg( QStringLiteral( "geos:%1 proj:%2" )
.arg( mGeosAvailable ? result.PQgetvalue( 0, 0 ) : "none" )
.arg( mProjAvailable ? result.PQgetvalue( 0, 1 ) : "none" ) );
QgsDebugMsgLevel( QStringLiteral( "geos:%1 proj:%2" )
.arg( mGeosAvailable ? result.PQgetvalue( 0, 0 ) : "none" )
.arg( mProjAvailable ? result.PQgetvalue( 0, 1 ) : "none" ), 2 );
}
else
{
Expand Down
157 changes: 96 additions & 61 deletions src/providers/postgres/qgspostgresfeatureiterator.cpp
Expand Up @@ -22,6 +22,7 @@
#include "qgsmessagelog.h"
#include "qgssettings.h"
#include "qgsexception.h"
#include "qgsgeometryengine.h"

#include <QElapsedTimer>
#include <QObject>
Expand Down Expand Up @@ -70,11 +71,32 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
return;
}

bool limitAtProvider = ( mRequest.limit() >= 0 );

// prepare spatial filter geometries for optimal speed
// TODO -- if mTransform is short circuited (i.e. requesting features in same CRS as database layer),
// then we should avoid the local distance check and instead add a ST_DWithin(...) clause to the
// SQL query
switch ( mRequest.spatialFilterType() )
{
case Qgis::SpatialFilterType::NoFilter:
case Qgis::SpatialFilterType::BoundingBox:
break;

case Qgis::SpatialFilterType::DistanceWithin:
if ( !mRequest.referenceGeometry().isEmpty() )
{
mDistanceWithinGeom = mRequest.referenceGeometry();
mDistanceWithinEngine.reset( QgsGeometry::createGeometryEngine( mDistanceWithinGeom.constGet() ) );
mDistanceWithinEngine->prepareGeometry();
limitAtProvider = false;
}
break;
}

mCursorName = mConn->uniqueCursorName();
QString whereClause;

bool limitAtProvider = ( mRequest.limit() >= 0 );

bool useFallbackWhereClause = false;
QString fallbackWhereClause;

Expand Down Expand Up @@ -246,79 +268,88 @@ bool QgsPostgresFeatureIterator::fetchFeature( QgsFeature &feature )
if ( mClosed )
return false;

if ( mFeatureQueue.empty() && !mLastFetch )
while ( true )
{
if ( mFeatureQueue.empty() && !mLastFetch )
{
#if 0 //disabled dynamic queue size
QElapsedTimer timer;
timer.start();
QElapsedTimer timer;
timer.start();
#endif

QString fetch = QStringLiteral( "FETCH FORWARD %1 FROM %2" ).arg( mFeatureQueueSize ).arg( mCursorName );
QgsDebugMsgLevel( QStringLiteral( "fetching %1 features." ).arg( mFeatureQueueSize ), 4 );

lock();
if ( mConn->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously
{
QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName, mConn->PQerrorMessage() ), QObject::tr( "PostGIS" ) );
}

QgsPostgresResult queryResult;
for ( ;; )
{
queryResult = mConn->PQgetResult();
if ( !queryResult.result() )
break;
QString fetch = QStringLiteral( "FETCH FORWARD %1 FROM %2" ).arg( mFeatureQueueSize ).arg( mCursorName );
QgsDebugMsgLevel( QStringLiteral( "fetching %1 features." ).arg( mFeatureQueueSize ), 4 );

if ( queryResult.PQresultStatus() != PGRES_TUPLES_OK )
lock();
if ( mConn->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously
{
QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName, mConn->PQerrorMessage() ), QObject::tr( "PostGIS" ) );
break;
}

int rows = queryResult.PQntuples();
if ( rows == 0 )
continue;
QgsPostgresResult queryResult;
for ( ;; )
{
queryResult = mConn->PQgetResult();
if ( !queryResult.result() )
break;

mLastFetch = rows < mFeatureQueueSize;
if ( queryResult.PQresultStatus() != PGRES_TUPLES_OK )
{
QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName, mConn->PQerrorMessage() ), QObject::tr( "PostGIS" ) );
break;
}

for ( int row = 0; row < rows; row++ )
{
mFeatureQueue.enqueue( QgsFeature() );
getFeature( queryResult, row, mFeatureQueue.back() );
} // for each row in queue
}
unlock();
int rows = queryResult.PQntuples();
if ( rows == 0 )
continue;

mLastFetch = rows < mFeatureQueueSize;

for ( int row = 0; row < rows; row++ )
{
mFeatureQueue.enqueue( QgsFeature() );
getFeature( queryResult, row, mFeatureQueue.back() );
} // for each row in queue
}
unlock();

#if 0 //disabled dynamic queue size
if ( timer.elapsed() > 500 && mFeatureQueueSize > 1 )
{
mFeatureQueueSize /= 2;
if ( timer.elapsed() > 500 && mFeatureQueueSize > 1 )
{
mFeatureQueueSize /= 2;
}
else if ( timer.elapsed() < 50 && mFeatureQueueSize < 10000 )
{
mFeatureQueueSize *= 2;
}
#endif
}
else if ( timer.elapsed() < 50 && mFeatureQueueSize < 10000 )

if ( mFeatureQueue.empty() )
{
mFeatureQueueSize *= 2;
mLastFetch = true;
break;
}
#endif
}

if ( mFeatureQueue.empty() )
{
QgsDebugMsg( QStringLiteral( "Finished after %1 features" ).arg( mFetched ) );
close();
feature = mFeatureQueue.dequeue();
mFetched++;

mSource->mShared->ensureFeaturesCountedAtLeast( mFetched );
geometryToDestinationCrs( feature, mTransform );
if ( mDistanceWithinEngine && mDistanceWithinEngine->distance( feature.geometry().constGet() ) > mRequest.distanceWithin() )
{
continue;
}

return false;
feature.setValid( true );
feature.setFields( mSource->mFields ); // allow name-based attribute lookups
return true;
}
QgsDebugMsgLevel( QStringLiteral( "Finished after %1 features" ).arg( mFetched ), 2 );
close();

feature = mFeatureQueue.dequeue();
mFetched++;

feature.setValid( true );
feature.setFields( mSource->mFields ); // allow name-based attribute lookups
geometryToDestinationCrs( feature, mTransform );
mSource->mShared->ensureFeaturesCountedAtLeast( mFetched );

return true;
return false;
}

bool QgsPostgresFeatureIterator::nextFeatureFilterExpression( QgsFeature &f )
Expand Down Expand Up @@ -552,7 +583,7 @@ QString QgsPostgresFeatureIterator::whereClauseRect()

if ( mSource->mRequestedGeomType != QgsWkbTypes::Unknown && mSource->mRequestedGeomType != mSource->mDetectedGeomType )
{
whereClause += QStringLiteral( " AND %1" ).arg( QgsPostgresConn::postgisTypeFilter( mSource->mGeometryColumn, ( QgsWkbTypes::Type )mSource->mRequestedGeomType, castToGeometry ) );
whereClause += QStringLiteral( " AND %1" ).arg( QgsPostgresConn::postgisTypeFilter( mSource->mGeometryColumn, mSource->mRequestedGeomType, castToGeometry ) );
}

QgsDebugMsgLevel( QStringLiteral( "whereClause = %1" ).arg( whereClause ), 4 );
Expand All @@ -563,7 +594,10 @@ QString QgsPostgresFeatureIterator::whereClauseRect()

bool QgsPostgresFeatureIterator::declareCursor( const QString &whereClause, long limit, bool closeOnFail, const QString &orderBy )
{
mFetchGeometry = ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) || mFilterRequiresGeometry ) && !mSource->mGeometryColumn.isNull();
mFetchGeometry = ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry )
|| mFilterRequiresGeometry
|| mRequest.spatialFilterType() == Qgis::SpatialFilterType::DistanceWithin )
&& !mSource->mGeometryColumn.isNull();
#if 0
// TODO: check that all field indexes exist
if ( !hasAllFields )
Expand Down Expand Up @@ -617,7 +651,7 @@ bool QgsPostgresFeatureIterator::declareCursor( const QString &whereClause, long
// For postgis >= 2.2 Use ST_RemoveRepeatedPoints instead
// Do it only if threshold is <= 1 pixel to avoid holes in adjacent polygons
// We should perhaps use it always for Linestrings, even if threshold > 1 ?
if ( mRequest.simplifyMethod().threshold() <= 1.0 )
if ( mRequest.simplifyMethod().threshold() <= 1.0f )
{
simplifyPostgisMethod = QStringLiteral( "st_removerepeatedpoints" );
postSimplification = true; // Ask to apply a post-filtering simplification
Expand All @@ -637,10 +671,10 @@ bool QgsPostgresFeatureIterator::declareCursor( const QString &whereClause, long
simplifyPostgisMethod = QStringLiteral( "st_simplifypreservetopology" );
}
}
QgsDebugMsg(
QString( "PostGIS Server side simplification : threshold %1 pixels - method %2" )
QgsDebugMsgLevel(
QStringLiteral( "PostGIS Server side simplification : threshold %1 pixels - method %2" )
.arg( mRequest.simplifyMethod().threshold() )
.arg( simplifyPostgisMethod )
.arg( simplifyPostgisMethod ), 3
);

geom = QStringLiteral( "%1(%2,%3)" )
Expand Down Expand Up @@ -750,7 +784,7 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
memcpy( &wkbType, featureGeom + 1, sizeof( wkbType ) );
QgsWkbTypes::Type newType = QgsPostgresConn::wkbTypeFromOgcWkbType( wkbType );

if ( ( unsigned int )newType != wkbType )
if ( static_cast< unsigned int >( newType ) != wkbType )
{
// overwrite type
unsigned int n = newType;
Expand Down Expand Up @@ -846,6 +880,7 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
case PktFidMap:
{
QVariantList primaryKeyVals;
primaryKeyVals.reserve( mSource->mPrimaryKeyAttrs.size() );

for ( int idx : std::as_const( mSource->mPrimaryKeyAttrs ) )
{
Expand Down Expand Up @@ -874,7 +909,7 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
break;

case PktUnknown:
Q_ASSERT( !"FAILURE: cannot get feature with unknown primary key" );
Q_ASSERT_X( false, "QgsPostgresFeatureIterator::getFeature", "FAILURE: cannot get feature with unknown primary key" );
return false;
}

Expand Down
3 changes: 3 additions & 0 deletions src/providers/postgres/qgspostgresfeatureiterator.h
Expand Up @@ -127,6 +127,9 @@ class QgsPostgresFeatureIterator final: public QgsAbstractFeatureIteratorFromSou

QgsCoordinateTransform mTransform;
QgsRectangle mFilterRect;
QgsGeometry mDistanceWithinGeom;
std::unique_ptr< QgsGeometryEngine > mDistanceWithinEngine;

};

#endif // QGSPOSTGRESFEATUREITERATOR_H

0 comments on commit 85f67fd

Please sign in to comment.