mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-04 00:30:59 -05:00
Merge pull request #31374 from troopa81/parallelize_snap_caching
[FEATURE] Parallelize snap index build
This commit is contained in:
commit
87b1aa9a5d
@ -31,7 +31,8 @@ Works with one layer.
|
||||
|
||||
explicit QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destinationCrs = QgsCoordinateReferenceSystem(),
|
||||
const QgsCoordinateTransformContext &transformContext = QgsCoordinateTransformContext(),
|
||||
const QgsRectangle *extent = 0 );
|
||||
const QgsRectangle *extent = 0,
|
||||
bool asynchronous = false );
|
||||
%Docstring
|
||||
Construct point locator for a ``layer``.
|
||||
|
||||
@ -40,6 +41,10 @@ do the searches on data reprojected to the given CRS. For accurate reprojection
|
||||
to set the correct ``transformContext`` if a ``destinationCrs`` is specified. This is usually taken
|
||||
from the current :py:func:`QgsProject.transformContext()`
|
||||
|
||||
if ``asynchronous`` is ``False``, point locator init() method will block until point locator index
|
||||
is completely built. if ``True``, index building will be done in another thread and init() method returns
|
||||
immediately. initFinished() signal will be emitted once the initialization is over.
|
||||
|
||||
If ``extent`` is not ``None``, the locator will index only a subset of the layer which falls within that extent.
|
||||
%End
|
||||
|
||||
@ -96,8 +101,13 @@ Configure render context - if not ``None``, it will use to index only visible f
|
||||
%Docstring
|
||||
Prepare the index for queries. Does nothing if the index already exists.
|
||||
If the number of features is greater than the value of maxFeaturesToIndex, creation of index is stopped
|
||||
to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used. Returns
|
||||
``False`` if the creation of index has been prematurely stopped due to the limit of features, otherwise ``True``
|
||||
to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used.
|
||||
|
||||
This method is either blocking or non blocking according to ``asynchronous`` parameter passed
|
||||
in the constructor.
|
||||
Returns false if the creation of index is blocking and has been prematurely stopped due to the limit of features, otherwise true
|
||||
|
||||
.. seealso:: :py:class:`QgsPointLocator`
|
||||
%End
|
||||
|
||||
bool hasIndex() const;
|
||||
@ -228,8 +238,27 @@ Returns how many geometries are cached in the index
|
||||
.. versionadded:: 2.14
|
||||
%End
|
||||
|
||||
bool isIndexing() const;
|
||||
%Docstring
|
||||
Returns ``True`` if the point locator is currently indexing the data.
|
||||
This method is useful if constructor parameter ``asynchronous`` is ``True``
|
||||
|
||||
.. seealso:: :py:class:`QgsPointLocator`
|
||||
%End
|
||||
|
||||
signals:
|
||||
|
||||
void initFinished( bool ok );
|
||||
%Docstring
|
||||
Emitted whenever index has been built and initialization is finished
|
||||
|
||||
:param ok: ``False`` if the creation of index has been prematurely stopped due to the limit of
|
||||
features, otherwise ``True``
|
||||
%End
|
||||
|
||||
protected:
|
||||
bool rebuildIndex( int maxFeaturesToIndex = -1 );
|
||||
|
||||
protected slots:
|
||||
void destroyIndex();
|
||||
};
|
||||
|
@ -35,9 +35,14 @@ which keeps the configuration in sync with map canvas (e.g. current view, active
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsSnappingUtils( QObject *parent /TransferThis/ = 0, bool enableSnappingForInvisibleFeature = true );
|
||||
QgsSnappingUtils( QObject *parent /TransferThis/ = 0, bool enableSnappingForInvisibleFeature = true,
|
||||
bool asynchronous = false );
|
||||
%Docstring
|
||||
Constructor for QgsSnappingUtils
|
||||
|
||||
:param parent: parent object
|
||||
:param enableSnappingForInvisibleFeature: ``True`` if we want to snap feature even if there are not visible
|
||||
:param asynchronous: indicated if point locator creation has to be made asynchronously (see :py:class:`QgsPointLocator`())
|
||||
%End
|
||||
~QgsSnappingUtils();
|
||||
|
||||
@ -172,13 +177,19 @@ Emitted when the snapping settings object changes.
|
||||
%End
|
||||
|
||||
protected:
|
||||
virtual void prepareIndexStarting( int count );
|
||||
|
||||
virtual void prepareIndexStarting( int count );
|
||||
%Docstring
|
||||
Called when starting to index - can be overridden and e.g. progress dialog can be provided
|
||||
This methods is now deprecated and never called
|
||||
|
||||
.. deprecated:: since QGIS 3.10
|
||||
%End
|
||||
virtual void prepareIndexProgress( int index );
|
||||
|
||||
virtual void prepareIndexProgress( int index );
|
||||
%Docstring
|
||||
Called when finished indexing a layer. When index == count the indexing is complete
|
||||
This methods is now deprecated and never called
|
||||
|
||||
.. deprecated:: since QGIS 3.10
|
||||
%End
|
||||
|
||||
void clearAllLocators();
|
||||
|
@ -23,13 +23,16 @@ Snapping utils instance that is connected to a canvas and updates the configurat
|
||||
#include "qgsmapcanvassnappingutils.h"
|
||||
%End
|
||||
public:
|
||||
QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent = 0 );
|
||||
|
||||
protected:
|
||||
virtual void prepareIndexStarting( int count );
|
||||
|
||||
virtual void prepareIndexProgress( int index );
|
||||
QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent = 0, bool asynchronous = false );
|
||||
%Docstring
|
||||
Construct map canvas snapping utils object
|
||||
|
||||
:param canvas: map canvas
|
||||
:param parent: parent object
|
||||
:param asynchronous: if ``True`` snapping cache index will be non blocking and done in another thread,
|
||||
if ``False`` it will block until indexing is done
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
Binary file not shown.
@ -937,7 +937,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
|
||||
endProfile();
|
||||
|
||||
startProfile( QStringLiteral( "Snapping utils" ) );
|
||||
mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this );
|
||||
mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this, true );
|
||||
mMapCanvas->setSnappingUtils( mSnappingUtils );
|
||||
connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, mSnappingUtils, &QgsSnappingUtils::setConfig );
|
||||
connect( QgsProject::instance(), &QgsProject::collectAttachedFiles, this, &QgisApp::generateProjectAttachedFiles );
|
||||
|
@ -317,6 +317,7 @@ SET(QGIS_CORE_SRCS
|
||||
qgspluginlayerregistry.cpp
|
||||
qgspointxy.cpp
|
||||
qgspointlocator.cpp
|
||||
qgspointlocatorinittask.cpp
|
||||
qgsproject.cpp
|
||||
qgsprojectbadlayerhandler.cpp
|
||||
qgsprojectfiletransform.cpp
|
||||
@ -708,6 +709,7 @@ SET(QGIS_CORE_MOC_HDRS
|
||||
qgspluginlayer.h
|
||||
qgspointxy.h
|
||||
qgspointlocator.h
|
||||
qgspointlocatorinittask.h
|
||||
qgsproject.h
|
||||
qgsproxyprogresstask.h
|
||||
qgsrelationmanager.h
|
||||
|
@ -22,11 +22,15 @@
|
||||
#include "qgis.h"
|
||||
#include "qgslogger.h"
|
||||
#include "qgsrenderer.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgsvectorlayerfeatureiterator.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgslinestring.h"
|
||||
#include "qgspointlocatorinittask.h"
|
||||
#include <spatialindex/SpatialIndex.h>
|
||||
|
||||
#include <QLinkedListIterator>
|
||||
#include <QtConcurrent>
|
||||
|
||||
using namespace SpatialIndex;
|
||||
|
||||
@ -522,8 +526,10 @@ class QgsPointLocator_DumpTree : public SpatialIndex::IQueryStrategy
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
QgsPointLocator::QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destCRS, const QgsCoordinateTransformContext &transformContext, const QgsRectangle *extent )
|
||||
QgsPointLocator::QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destCRS, const QgsCoordinateTransformContext &transformContext, const QgsRectangle *extent,
|
||||
bool asynchronous )
|
||||
: mLayer( layer )
|
||||
, mAsynchronous( asynchronous )
|
||||
{
|
||||
if ( destCRS.isValid() )
|
||||
{
|
||||
@ -554,6 +560,10 @@ QgsCoordinateReferenceSystem QgsPointLocator::destinationCrs() const
|
||||
|
||||
void QgsPointLocator::setExtent( const QgsRectangle *extent )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
// already indexing, return!
|
||||
return;
|
||||
|
||||
mExtent.reset( extent ? new QgsRectangle( *extent ) : nullptr );
|
||||
|
||||
destroyIndex();
|
||||
@ -561,6 +571,10 @@ void QgsPointLocator::setExtent( const QgsRectangle *extent )
|
||||
|
||||
void QgsPointLocator::setRenderContext( const QgsRenderContext *context )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
// already indexing, return!
|
||||
return;
|
||||
|
||||
disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
|
||||
|
||||
destroyIndex();
|
||||
@ -574,27 +588,93 @@ void QgsPointLocator::setRenderContext( const QgsRenderContext *context )
|
||||
|
||||
}
|
||||
|
||||
bool QgsPointLocator::init( int maxFeaturesToIndex )
|
||||
void QgsPointLocator::onInitTaskTerminated()
|
||||
{
|
||||
return hasIndex() ? true : rebuildIndex( maxFeaturesToIndex );
|
||||
mIsIndexing = false;
|
||||
mRenderer.reset();
|
||||
mSource.reset();
|
||||
}
|
||||
|
||||
void QgsPointLocator::onRebuildIndexFinished( bool ok )
|
||||
{
|
||||
onInitTaskTerminated();
|
||||
|
||||
// treat added and deleted feature while indexing
|
||||
for ( QgsFeatureId fid : mAddedFeatures )
|
||||
onFeatureAdded( fid );
|
||||
|
||||
for ( QgsFeatureId fid : mDeletedFeatures )
|
||||
onFeatureDeleted( fid );
|
||||
|
||||
emit initFinished( ok );
|
||||
}
|
||||
|
||||
bool QgsPointLocator::init( int maxFeaturesToIndex )
|
||||
{
|
||||
const QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::NullGeometry // nothing to index
|
||||
|| hasIndex()
|
||||
|| mIsIndexing ) // already indexing, return!
|
||||
return true;
|
||||
|
||||
mRenderer.reset( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
|
||||
mSource.reset( new QgsVectorLayerFeatureSource( mLayer ) );
|
||||
|
||||
if ( mContext )
|
||||
{
|
||||
mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
|
||||
}
|
||||
|
||||
mIsIndexing = true;
|
||||
|
||||
if ( mAsynchronous )
|
||||
{
|
||||
QgsPointLocatorInitTask *task = new QgsPointLocatorInitTask( this );
|
||||
connect( task, &QgsPointLocatorInitTask::rebuildIndexFinished, this, &QgsPointLocator::onRebuildIndexFinished );
|
||||
connect( task, &QgsPointLocatorInitTask::taskTerminated, this, &QgsPointLocator::onInitTaskTerminated );
|
||||
QgsApplication::taskManager()->addTask( task );
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool ok = rebuildIndex( maxFeaturesToIndex );
|
||||
mIsIndexing = false;
|
||||
emit initFinished( ok );
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsPointLocator::hasIndex() const
|
||||
{
|
||||
return mRTree || mIsEmptyLayer;
|
||||
return mIsIndexing || mRTree || mIsEmptyLayer;
|
||||
}
|
||||
|
||||
bool QgsPointLocator::prepare()
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
return false;
|
||||
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( mIsIndexing || !mRTree ) // asynchronous mode and currently indexing or still invalid?
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
|
||||
{
|
||||
QTime t;
|
||||
t.start();
|
||||
|
||||
QgsDebugMsg( QStringLiteral( "RebuildIndex start : %1" ).arg( mSource->id() ) );
|
||||
|
||||
destroyIndex();
|
||||
|
||||
QLinkedList<RTree::Data *> dataList;
|
||||
QgsFeature f;
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::NullGeometry )
|
||||
return true; // nothing to index
|
||||
|
||||
QgsFeatureRequest request;
|
||||
request.setNoAttributes();
|
||||
@ -619,22 +699,20 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
|
||||
}
|
||||
|
||||
bool filter = false;
|
||||
std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
|
||||
QgsRenderContext *ctx = nullptr;
|
||||
if ( mContext )
|
||||
{
|
||||
mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
|
||||
ctx = mContext.get();
|
||||
if ( renderer )
|
||||
if ( mRenderer )
|
||||
{
|
||||
// setup scale for scale dependent visibility (rule based)
|
||||
renderer->startRender( *ctx, mLayer->fields() );
|
||||
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
|
||||
request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
|
||||
mRenderer->startRender( *ctx, mSource->fields() );
|
||||
filter = mRenderer->capabilities() & QgsFeatureRenderer::Filter;
|
||||
request.setSubsetOfAttributes( mRenderer->usedAttributes( *ctx ), mSource->fields() );
|
||||
}
|
||||
}
|
||||
|
||||
QgsFeatureIterator fi = mLayer->getFeatures( request );
|
||||
QgsFeatureIterator fi = mSource->getFeatures( request );
|
||||
int indexedCount = 0;
|
||||
|
||||
while ( fi.nextFeature( f ) )
|
||||
@ -642,10 +720,10 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
|
||||
if ( !f.hasGeometry() )
|
||||
continue;
|
||||
|
||||
if ( filter && ctx && renderer )
|
||||
if ( filter && ctx && mRenderer )
|
||||
{
|
||||
ctx->expressionContext().setFeature( f );
|
||||
if ( !renderer->willRenderFeature( f, *ctx ) )
|
||||
if ( !mRenderer->willRenderFeature( f, *ctx ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -706,10 +784,13 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
|
||||
mRTree.reset( RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
|
||||
leafCapacity, dimension, variant, indexId ) );
|
||||
|
||||
if ( ctx && renderer )
|
||||
if ( ctx && mRenderer )
|
||||
{
|
||||
renderer->stopRender( *ctx );
|
||||
mRenderer->stopRender( *ctx );
|
||||
}
|
||||
|
||||
QgsDebugMsg( QStringLiteral( "RebuildIndex end : %1 ms (%2)" ).arg( t.elapsed() ).arg( mSource->id() ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -727,10 +808,17 @@ void QgsPointLocator::destroyIndex()
|
||||
|
||||
void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
{
|
||||
// will modify index once current indexing is finished
|
||||
mAddedFeatures << fid;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !mRTree )
|
||||
{
|
||||
if ( mIsEmptyLayer )
|
||||
rebuildIndex(); // first feature - let's built the index
|
||||
init(); // first feature - let's built the index
|
||||
return; // nothing to do if we are not initialized yet
|
||||
}
|
||||
|
||||
@ -796,6 +884,20 @@ void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
|
||||
|
||||
void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
{
|
||||
if ( mAddedFeatures.contains( fid ) )
|
||||
{
|
||||
mAddedFeatures.remove( fid );
|
||||
}
|
||||
else
|
||||
{
|
||||
// will modify index once current indexing is finished
|
||||
mDeletedFeatures << fid;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !mRTree )
|
||||
return; // nothing to do if we are not initialized yet
|
||||
|
||||
@ -828,12 +930,8 @@ void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point, double tolerance, MatchFilter *filter )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return Match();
|
||||
}
|
||||
if ( !prepare() )
|
||||
return Match();
|
||||
|
||||
Match m;
|
||||
QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
|
||||
@ -846,12 +944,8 @@ QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point,
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, double tolerance, MatchFilter *filter )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return Match();
|
||||
}
|
||||
if ( !prepare() )
|
||||
return Match();
|
||||
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::PointGeometry )
|
||||
@ -868,12 +962,8 @@ QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, do
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, double tolerance, MatchFilter *filter )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return Match();
|
||||
}
|
||||
if ( !prepare() )
|
||||
return Match();
|
||||
|
||||
MatchList mlist = pointInPolygon( point );
|
||||
if ( !mlist.isEmpty() && mlist.at( 0 ).isValid() )
|
||||
@ -902,12 +992,8 @@ QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, do
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return MatchList();
|
||||
}
|
||||
if ( !prepare() )
|
||||
return MatchList();
|
||||
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::PointGeometry )
|
||||
@ -928,12 +1014,8 @@ QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsPointXY &point
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return MatchList();
|
||||
}
|
||||
if ( !prepare() )
|
||||
return MatchList();
|
||||
|
||||
MatchList lst;
|
||||
QgsPointLocator_VisitorVerticesInRect visitor( this, lst, rect, filter );
|
||||
@ -948,15 +1030,10 @@ QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsPointXY &po
|
||||
return verticesInRect( rect, filter );
|
||||
}
|
||||
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::pointInPolygon( const QgsPointXY &point )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return MatchList();
|
||||
}
|
||||
if ( !prepare() )
|
||||
return MatchList();
|
||||
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
|
||||
|
@ -21,6 +21,7 @@ class QgsVectorLayer;
|
||||
class QgsFeatureRenderer;
|
||||
class QgsRenderContext;
|
||||
class QgsRectangle;
|
||||
class QgsVectorLayerFeatureSource;
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgspointxy.h"
|
||||
@ -65,11 +66,16 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
* to set the correct \a transformContext if a \a destinationCrs is specified. This is usually taken
|
||||
* from the current QgsProject::transformContext().
|
||||
*
|
||||
* if \a asynchronous is FALSE, point locator init() method will block until point locator index
|
||||
* is completely built. if TRUE, index building will be done in another thread and init() method returns
|
||||
* immediately. initFinished() signal will be emitted once the initialization is over.
|
||||
*
|
||||
* If \a extent is not NULLPTR, the locator will index only a subset of the layer which falls within that extent.
|
||||
*/
|
||||
explicit QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destinationCrs = QgsCoordinateReferenceSystem(),
|
||||
const QgsCoordinateTransformContext &transformContext = QgsCoordinateTransformContext(),
|
||||
const QgsRectangle *extent = nullptr );
|
||||
const QgsRectangle *extent = nullptr,
|
||||
bool asynchronous = false );
|
||||
|
||||
~QgsPointLocator() override;
|
||||
|
||||
@ -120,9 +126,14 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
/**
|
||||
* Prepare the index for queries. Does nothing if the index already exists.
|
||||
* If the number of features is greater than the value of maxFeaturesToIndex, creation of index is stopped
|
||||
* to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used. Returns
|
||||
* FALSE if the creation of index has been prematurely stopped due to the limit of features, otherwise TRUE
|
||||
*/
|
||||
* to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used.
|
||||
*
|
||||
* This method is either blocking or non blocking according to \a asynchronous parameter passed
|
||||
* in the constructor.
|
||||
* Returns false if the creation of index is blocking and has been prematurely stopped due to the limit of features, otherwise true
|
||||
*
|
||||
* \see QgsPointLocator()
|
||||
*/
|
||||
bool init( int maxFeaturesToIndex = -1 );
|
||||
|
||||
//! Indicate whether the data have been already indexed
|
||||
@ -282,17 +293,41 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
*/
|
||||
int cachedGeometryCount() const { return mGeoms.count(); }
|
||||
|
||||
/**
|
||||
* Returns TRUE if the point locator is currently indexing the data.
|
||||
* This method is useful if constructor parameter \a asynchronous is TRUE
|
||||
*
|
||||
* \see QgsPointLocator()
|
||||
*/
|
||||
bool isIndexing() const { return mIsIndexing; }
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Emitted whenever index has been built and initialization is finished
|
||||
* \param ok FALSE if the creation of index has been prematurely stopped due to the limit of
|
||||
* features, otherwise TRUE
|
||||
*/
|
||||
void initFinished( bool ok );
|
||||
|
||||
protected:
|
||||
bool rebuildIndex( int maxFeaturesToIndex = -1 );
|
||||
|
||||
protected slots:
|
||||
void destroyIndex();
|
||||
private slots:
|
||||
void onInitTaskTerminated();
|
||||
void onRebuildIndexFinished( bool ok );
|
||||
void onFeatureAdded( QgsFeatureId fid );
|
||||
void onFeatureDeleted( QgsFeatureId fid );
|
||||
void onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom );
|
||||
void onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value );
|
||||
|
||||
private:
|
||||
|
||||
//! prepare index if need and returns TRUE if the index is ready to be used
|
||||
bool prepare();
|
||||
|
||||
//! Storage manager
|
||||
std::unique_ptr< SpatialIndex::IStorageManager > mStorage;
|
||||
|
||||
@ -309,12 +344,21 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
std::unique_ptr< QgsRectangle > mExtent;
|
||||
|
||||
std::unique_ptr<QgsRenderContext> mContext;
|
||||
std::unique_ptr<QgsFeatureRenderer> mRenderer;
|
||||
std::unique_ptr<QgsVectorLayerFeatureSource> mSource;
|
||||
int mMaxFeaturesToIndex = -1;
|
||||
bool mAsynchronous = false;
|
||||
bool mIsIndexing = false;
|
||||
QgsFeatureIds mAddedFeatures;
|
||||
QgsFeatureIds mDeletedFeatures;
|
||||
|
||||
friend class QgsPointLocator_VisitorNearestVertex;
|
||||
friend class QgsPointLocator_VisitorNearestEdge;
|
||||
friend class QgsPointLocator_VisitorArea;
|
||||
friend class QgsPointLocator_VisitorEdgesInRect;
|
||||
friend class QgsPointLocator_VisitorVerticesInRect;
|
||||
friend class QgsPointLocatorInitTask;
|
||||
friend class TestQgsPointLocator;
|
||||
};
|
||||
|
||||
|
||||
|
34
src/core/qgspointlocatorinittask.cpp
Normal file
34
src/core/qgspointlocatorinittask.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
/***************************************************************************
|
||||
qgspointlocatorinittask.cpp
|
||||
--------------------------------------
|
||||
Date : September 2019
|
||||
Copyright : (C) 2019 by Julien Cabieces
|
||||
Email : julien dot cabieces at oslandia dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgspointlocatorinittask.h"
|
||||
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
/// @cond PRIVATE
|
||||
|
||||
QgsPointLocatorInitTask::QgsPointLocatorInitTask( QgsPointLocator *loc )
|
||||
: QgsTask( tr( "Indexing %1" ).arg( loc->layer()->id() ), QgsTask::Flags() )
|
||||
, mLoc( loc )
|
||||
{}
|
||||
|
||||
bool QgsPointLocatorInitTask::run()
|
||||
{
|
||||
const bool ok = mLoc->rebuildIndex();
|
||||
emit rebuildIndexFinished( ok );
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @endcond
|
56
src/core/qgspointlocatorinittask.h
Normal file
56
src/core/qgspointlocatorinittask.h
Normal file
@ -0,0 +1,56 @@
|
||||
/***************************************************************************
|
||||
qgspointlocatorinittask.h
|
||||
--------------------------------------
|
||||
Date : September 2019
|
||||
Copyright : (C) 2019 by Julien Cabieces
|
||||
Email : julien dot cabieces at oslandia dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSPOINTLOCATORINITTASK_H
|
||||
#define QGSPOINTLOCATORINITTASK_H
|
||||
|
||||
/// @cond PRIVATE
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the QGIS API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include "qgstaskmanager.h"
|
||||
#include "qgspointlocator.h"
|
||||
|
||||
class QgsPointLocatorInitTask : public QgsTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
QgsPointLocatorInitTask( QgsPointLocator *loc );
|
||||
|
||||
bool run();
|
||||
|
||||
signals:
|
||||
|
||||
void rebuildIndexFinished( bool ok );
|
||||
|
||||
private:
|
||||
|
||||
QgsPointLocator *mLoc = nullptr;
|
||||
};
|
||||
|
||||
/// @endcond
|
||||
|
||||
#endif // QGSPOINTLOCATORINITTASK_H
|
@ -21,10 +21,12 @@
|
||||
#include "qgslogger.h"
|
||||
#include "qgsrenderer.h"
|
||||
|
||||
QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
|
||||
QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature,
|
||||
bool asynchronous )
|
||||
: QObject( parent )
|
||||
, mSnappingConfig( QgsProject::instance() )
|
||||
, mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
|
||||
, mAsynchronous( asynchronous )
|
||||
{
|
||||
}
|
||||
|
||||
@ -41,7 +43,8 @@ QgsPointLocator *QgsSnappingUtils::locatorForLayer( QgsVectorLayer *vl )
|
||||
|
||||
if ( !mLocators.contains( vl ) )
|
||||
{
|
||||
QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext() );
|
||||
QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), nullptr, mAsynchronous );
|
||||
connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
|
||||
mLocators.insert( vl, vlpl );
|
||||
}
|
||||
return mLocators.value( vl );
|
||||
@ -74,7 +77,9 @@ QgsPointLocator *QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer *vl,
|
||||
|
||||
QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
|
||||
pointMap.x() + tolerance, pointMap.y() + tolerance );
|
||||
QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
|
||||
QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect, mAsynchronous );
|
||||
connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
|
||||
|
||||
mTemporaryLocators.insert( vl, vlpl );
|
||||
return mTemporaryLocators.value( vl );
|
||||
}
|
||||
@ -86,6 +91,9 @@ bool QgsSnappingUtils::isIndexPrepared( QgsVectorLayer *vl, const QgsRectangle &
|
||||
|
||||
QgsPointLocator *loc = locatorForLayer( vl );
|
||||
|
||||
if ( loc->isIndexing() )
|
||||
return true;
|
||||
|
||||
if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
|
||||
return true;
|
||||
|
||||
@ -338,12 +346,20 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
return QgsPointLocator::Match();
|
||||
}
|
||||
|
||||
void QgsSnappingUtils::onInitFinished( bool ok )
|
||||
{
|
||||
QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
|
||||
|
||||
// point locator init didn't work out - too many features!
|
||||
// let's make the allowed area smaller for the next time
|
||||
if ( !ok )
|
||||
{
|
||||
mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
return;
|
||||
mIsIndexing = true;
|
||||
|
||||
// check if we need to build any index
|
||||
QList<LayerAndAreaOfInterest> layersToIndex;
|
||||
const auto constLayers = layers;
|
||||
@ -362,8 +378,6 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
|
||||
// build indexes
|
||||
QTime t;
|
||||
t.start();
|
||||
int i = 0;
|
||||
prepareIndexStarting( layersToIndex.count() );
|
||||
const auto constLayersToIndex = layersToIndex;
|
||||
for ( const LayerAndAreaOfInterest &entry : constLayersToIndex )
|
||||
{
|
||||
@ -422,24 +436,14 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
|
||||
loc->setExtent( &rect );
|
||||
|
||||
// see if it's possible build index for this area
|
||||
if ( !loc->init( mHybridPerLayerFeatureLimit ) )
|
||||
{
|
||||
// hmm that didn't work out - too many features!
|
||||
// let's make the allowed area smaller for the next time
|
||||
mHybridMaxAreaPerLayer[vl->id()] /= 4;
|
||||
}
|
||||
loc->init( mHybridPerLayerFeatureLimit );
|
||||
}
|
||||
|
||||
}
|
||||
else // full index strategy
|
||||
loc->init();
|
||||
|
||||
QgsDebugMsg( QStringLiteral( "Index init: %1 ms (%2)" ).arg( tt.elapsed() ).arg( vl->id() ) );
|
||||
prepareIndexProgress( ++i );
|
||||
}
|
||||
QgsDebugMsg( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
|
||||
}
|
||||
mIsIndexing = false;
|
||||
}
|
||||
|
||||
QgsSnappingConfig QgsSnappingUtils::config() const
|
||||
|
@ -52,8 +52,14 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
||||
|
||||
public:
|
||||
|
||||
//! Constructor for QgsSnappingUtils
|
||||
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true );
|
||||
/**
|
||||
* Constructor for QgsSnappingUtils
|
||||
* \param parent parent object
|
||||
* \param enableSnappingForInvisibleFeature TRUE if we want to snap feature even if there are not visible
|
||||
* \param asynchronous indicated if point locator creation has to be made asynchronously (see QgsPointLocator())
|
||||
*/
|
||||
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true,
|
||||
bool asynchronous = false );
|
||||
~QgsSnappingUtils() override;
|
||||
|
||||
// main actions
|
||||
@ -190,14 +196,27 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
||||
void configChanged( const QgsSnappingConfig &snappingConfig );
|
||||
|
||||
protected:
|
||||
//! Called when starting to index - can be overridden and e.g. progress dialog can be provided
|
||||
virtual void prepareIndexStarting( int count ) { Q_UNUSED( count ) }
|
||||
//! Called when finished indexing a layer. When index == count the indexing is complete
|
||||
virtual void prepareIndexProgress( int index ) { Q_UNUSED( index ) }
|
||||
|
||||
/**
|
||||
* This methods is now deprecated and never called
|
||||
* \deprecated since QGIS 3.10
|
||||
*/
|
||||
Q_DECL_DEPRECATED virtual void prepareIndexStarting( int count ) { Q_UNUSED( count ); }
|
||||
|
||||
/**
|
||||
* This methods is now deprecated and never called
|
||||
* \deprecated since QGIS 3.10
|
||||
*/
|
||||
Q_DECL_DEPRECATED virtual void prepareIndexProgress( int index ) { Q_UNUSED( index ); }
|
||||
|
||||
//! Deletes all existing locators (e.g. when destination CRS has changed and we need to reindex)
|
||||
void clearAllLocators();
|
||||
|
||||
private slots:
|
||||
|
||||
//! called whenever a point locator has finished
|
||||
void onInitFinished( bool ok );
|
||||
|
||||
private:
|
||||
void onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings );
|
||||
//! Gets destination CRS from map settings, or an invalid CRS if projections are disabled
|
||||
@ -249,12 +268,11 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
||||
//! if using hybrid strategy, how many features of one layer may be indexed (to limit amount of consumed memory)
|
||||
int mHybridPerLayerFeatureLimit = 50000;
|
||||
|
||||
//! internal flag that an indexing process is going on. Prevents starting two processes in parallel.
|
||||
bool mIsIndexing = false;
|
||||
|
||||
//! Disable or not the snapping on all features. By default is always TRUE except for non visible features on map canvas.
|
||||
bool mEnableSnappingForInvisibleFeature = true;
|
||||
|
||||
//! true if we have to build point locator asynchronously
|
||||
bool mAsynchronous = false;
|
||||
};
|
||||
|
||||
|
||||
|
@ -21,8 +21,8 @@
|
||||
#include <QApplication>
|
||||
#include <QProgressDialog>
|
||||
|
||||
QgsMapCanvasSnappingUtils::QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent )
|
||||
: QgsSnappingUtils( parent, QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() )
|
||||
QgsMapCanvasSnappingUtils::QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent, bool asynchronous )
|
||||
: QgsSnappingUtils( parent, QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool(), asynchronous )
|
||||
, mCanvas( canvas )
|
||||
|
||||
{
|
||||
@ -58,24 +58,3 @@ void QgsMapCanvasSnappingUtils::canvasMapToolChanged()
|
||||
{
|
||||
setEnableSnappingForInvisibleFeature( QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() );
|
||||
}
|
||||
|
||||
void QgsMapCanvasSnappingUtils::prepareIndexStarting( int count )
|
||||
{
|
||||
QApplication::setOverrideCursor( Qt::WaitCursor );
|
||||
mProgress = new QProgressDialog( tr( "Indexing data…" ), QString(), 0, count, mCanvas->topLevelWidget() );
|
||||
mProgress->setWindowModality( Qt::WindowModal );
|
||||
}
|
||||
|
||||
void QgsMapCanvasSnappingUtils::prepareIndexProgress( int index )
|
||||
{
|
||||
if ( !mProgress )
|
||||
return;
|
||||
|
||||
mProgress->setValue( index );
|
||||
if ( index == mProgress->maximum() )
|
||||
{
|
||||
delete mProgress;
|
||||
mProgress = nullptr;
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,16 @@ class GUI_EXPORT QgsMapCanvasSnappingUtils : public QgsSnappingUtils
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent = nullptr );
|
||||
|
||||
protected:
|
||||
void prepareIndexStarting( int count ) override;
|
||||
void prepareIndexProgress( int index ) override;
|
||||
/**
|
||||
* Construct map canvas snapping utils object
|
||||
*
|
||||
* \param canvas map canvas
|
||||
* \param parent parent object
|
||||
* \param asynchronous if TRUE snapping cache index will be non blocking and done in another thread,
|
||||
* if FALSE it will block until indexing is done
|
||||
*/
|
||||
QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent = nullptr, bool asynchronous = false );
|
||||
|
||||
private slots:
|
||||
void canvasMapSettingsChanged();
|
||||
|
@ -162,7 +162,6 @@ void TestQgsMapToolReshape::initTestCase()
|
||||
cfg.setType( QgsSnappingConfig::VertexAndSegment );
|
||||
cfg.setEnabled( true );
|
||||
mCanvas->snappingUtils()->setConfig( cfg );
|
||||
|
||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLineZ << mLayerPointZ << mLayerPolygonZ );
|
||||
mCanvas->setCurrentLayer( mLayerLineZ );
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include "testqgsmaptoolutils.h"
|
||||
#include "qgsmaptoolreverseline.h"
|
||||
#include "qgsmapmouseevent.h"
|
||||
|
||||
#include "qgssnappingutils.h"
|
||||
|
||||
class TestQgsMapToolReverseLine : public QObject
|
||||
{
|
||||
@ -60,7 +60,6 @@ void TestQgsMapToolReverseLine::initTestCase()
|
||||
|
||||
mCanvas = new QgsMapCanvas();
|
||||
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) );
|
||||
|
||||
}
|
||||
|
||||
void TestQgsMapToolReverseLine::cleanupTestCase()
|
||||
|
@ -247,9 +247,8 @@ void TestQgsVertexTool::initTestCase()
|
||||
|
||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerPolygon << mLayerPoint << mLayerLineZ );
|
||||
|
||||
// TODO: set up snapping
|
||||
|
||||
mCanvas->setSnappingUtils( new QgsMapCanvasSnappingUtils( mCanvas, this ) );
|
||||
QgsMapCanvasSnappingUtils *snappingUtils = new QgsMapCanvasSnappingUtils( mCanvas, this );
|
||||
mCanvas->setSnappingUtils( snappingUtils );
|
||||
|
||||
// create vertex tool
|
||||
mVertexTool = new QgsVertexTool( mCanvas, mAdvancedDigitizingDockWidget );
|
||||
|
@ -346,6 +346,96 @@ class TestQgsPointLocator : public QObject
|
||||
|
||||
delete vlEmptyGeom;
|
||||
}
|
||||
|
||||
void testAsynchronousMode()
|
||||
{
|
||||
QgsPointLocator loc( mVL, QgsCoordinateReferenceSystem(), QgsCoordinateTransformContext(), nullptr, true );
|
||||
QgsPointXY pt( 2, 2 );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( &loc, &QgsPointLocator::initFinished, &loop, &QEventLoop::quit );
|
||||
|
||||
// locator is not ready yet
|
||||
QgsPointLocator::Match m = loc.nearestVertex( pt, 999 );
|
||||
QVERIFY( !m.isValid() );
|
||||
QVERIFY( loc.mIsIndexing );
|
||||
|
||||
// we block until initFinished is called from another thread
|
||||
loop.exec();
|
||||
|
||||
QVERIFY( !loc.mIsIndexing );
|
||||
|
||||
// now locator is ready
|
||||
m = loc.nearestVertex( pt, 999 );
|
||||
QVERIFY( m.isValid() );
|
||||
QVERIFY( m.hasVertex() );
|
||||
QCOMPARE( m.layer(), mVL );
|
||||
QCOMPARE( m.featureId(), ( QgsFeatureId )1 );
|
||||
QCOMPARE( m.point(), QgsPointXY( 1, 1 ) );
|
||||
QCOMPARE( m.distance(), std::sqrt( 2.0 ) );
|
||||
QCOMPARE( m.vertexIndex(), 2 );
|
||||
}
|
||||
|
||||
void testLayerUpdatesAsynchronous()
|
||||
{
|
||||
|
||||
QgsPointLocator loc( mVL, QgsCoordinateReferenceSystem(), QgsCoordinateTransformContext(), nullptr, true );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( &loc, &QgsPointLocator::initFinished, &loop, &QEventLoop::quit );
|
||||
|
||||
// trigger locator initialization
|
||||
QgsPointLocator::Match mAddV0 = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 );
|
||||
|
||||
// locator is not ready yet
|
||||
QVERIFY( !mAddV0.isValid() );
|
||||
QVERIFY( loc.mIsIndexing );
|
||||
|
||||
mVL->startEditing();
|
||||
|
||||
// add a new feature
|
||||
QgsFeature ff( 0 );
|
||||
QgsPolygonXY polygon;
|
||||
QgsPolylineXY polyline;
|
||||
polyline << QgsPointXY( 10, 11 ) << QgsPointXY( 11, 10 ) << QgsPointXY( 11, 11 ) << QgsPointXY( 10, 11 );
|
||||
polygon << polyline;
|
||||
QgsGeometry ffGeom = QgsGeometry::fromPolygonXY( polygon ) ;
|
||||
ff.setGeometry( ffGeom );
|
||||
QgsFeatureList flist;
|
||||
flist << ff;
|
||||
bool resA = mVL->addFeature( ff );
|
||||
QVERIFY( resA );
|
||||
|
||||
// indexing is still running, change geometry
|
||||
QgsGeometry *newGeom = new QgsGeometry( ff.geometry() );
|
||||
newGeom->moveVertex( 10, 10, 2 ); // change 11,11 to 10,10
|
||||
mVL->changeGeometry( ff.id(), *newGeom );
|
||||
delete newGeom;
|
||||
|
||||
// we block until initFinished is called from another thread
|
||||
loop.exec();
|
||||
|
||||
QVERIFY( !loc.mIsIndexing );
|
||||
|
||||
// verify it is changed in the point locator
|
||||
QgsPointLocator::Match mChV = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 );
|
||||
QVERIFY( mChV.isValid() );
|
||||
QVERIFY( mChV.point() != QgsPointXY( 11, 11 ) ); // that point does not exist anymore
|
||||
mChV = loc.nearestVertex( QgsPointXY( 9, 9 ), 999 );
|
||||
QVERIFY( mChV.isValid() );
|
||||
QVERIFY( mChV.point() == QgsPointXY( 10, 10 ) ); // updated point
|
||||
|
||||
// delete feature while no indexing is running
|
||||
bool resD = mVL->deleteFeature( ff.id() );
|
||||
QVERIFY( resD );
|
||||
|
||||
// verify it is deleted from the point locator
|
||||
QgsPointLocator::Match mDelV = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 );
|
||||
QVERIFY( mDelV.isValid() );
|
||||
QCOMPARE( mDelV.point(), QgsPointXY( 1, 1 ) );
|
||||
|
||||
mVL->rollBack();
|
||||
}
|
||||
};
|
||||
|
||||
QGSTEST_MAIN( TestQgsPointLocator )
|
||||
|
Loading…
x
Reference in New Issue
Block a user