mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-18 00:03:05 -04:00
Merge pull request #31648 from troopa81/fix_snaptocurrentlayer
Parallelize snap caching
This commit is contained in:
commit
e86c2afb4e
@ -92,12 +92,19 @@ Configure render context - if not ``None``, it will use to index only visible f
|
||||
typedef QFlags<QgsPointLocator::Type> Types;
|
||||
|
||||
|
||||
bool init( int maxFeaturesToIndex = -1 );
|
||||
bool init( int maxFeaturesToIndex = -1, bool relaxed = false );
|
||||
%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 ``relaxed`` parameter passed
|
||||
in the constructor. 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.
|
||||
|
||||
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;
|
||||
@ -176,58 +183,64 @@ interpolation of the Z value.
|
||||
};
|
||||
|
||||
|
||||
Match nearestVertex( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
Match nearestVertex( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Find nearest vertex to the specified point - up to distance specified by tolerance
|
||||
Optional filter may discard unwanted matches.
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
%End
|
||||
|
||||
Match nearestEdge( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
Match nearestEdge( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Find nearest edge to the specified point - up to distance specified by tolerance
|
||||
Optional filter may discard unwanted matches.
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
%End
|
||||
|
||||
Match nearestArea( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
Match nearestArea( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Find nearest area to the specified point - up to distance specified by tolerance
|
||||
Optional filter may discard unwanted matches.
|
||||
This will first perform a pointInPolygon and return first result.
|
||||
If no match is found and tolerance is not 0, it will return nearestEdge.
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
MatchList edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
MatchList edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Find edges within a specified recangle
|
||||
Optional filter may discard unwanted matches.
|
||||
%End
|
||||
MatchList edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
%Docstring
|
||||
Override of edgesInRect that construct rectangle from a center point and tolerance
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
%End
|
||||
|
||||
MatchList verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
MatchList edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Override of edgesInRect that construct rectangle from a center point and tolerance
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
%End
|
||||
|
||||
MatchList verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Find vertices within a specified recangle
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
Optional filter may discard unwanted matches.
|
||||
|
||||
.. versionadded:: 3.6
|
||||
%End
|
||||
|
||||
MatchList verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
MatchList verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Override of verticesInRect that construct rectangle from a center point and tolerance
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
|
||||
.. versionadded:: 3.6
|
||||
%End
|
||||
|
||||
|
||||
MatchList pointInPolygon( const QgsPointXY &point );
|
||||
%Docstring
|
||||
find out if the point is in any polygons
|
||||
%End
|
||||
|
||||
MatchList pointInPolygon( const QgsPointXY &point, bool relaxed = false );
|
||||
|
||||
int cachedGeometryCount() const;
|
||||
%Docstring
|
||||
@ -236,8 +249,33 @@ 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 ``relaxed`` is ``True``
|
||||
|
||||
.. seealso:: :py:class:`QgsPointLocator`
|
||||
%End
|
||||
|
||||
void waitForIndexingFinished();
|
||||
%Docstring
|
||||
If the point locator has been initialized relaxedly and is currently indexing,
|
||||
this methods waits for the indexing to be finished
|
||||
%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();
|
||||
};
|
||||
|
@ -38,6 +38,9 @@ which keeps the configuration in sync with map canvas (e.g. current view, active
|
||||
QgsSnappingUtils( QObject *parent /TransferThis/ = 0, bool enableSnappingForInvisibleFeature = true );
|
||||
%Docstring
|
||||
Constructor for QgsSnappingUtils
|
||||
|
||||
:param parent: parent object
|
||||
:param enableSnappingForInvisibleFeature: ``True`` if we want to snap feature even if there are not visible
|
||||
%End
|
||||
~QgsSnappingUtils();
|
||||
|
||||
@ -45,14 +48,27 @@ Constructor for QgsSnappingUtils
|
||||
QgsPointLocator *locatorForLayer( QgsVectorLayer *vl );
|
||||
%Docstring
|
||||
Gets a point locator for the given layer. If such locator does not exist, it will be created
|
||||
|
||||
:param vl: the vector layer
|
||||
%End
|
||||
|
||||
QgsPointLocator::Match snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
QgsPointLocator::Match snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Snap to map according to the current configuration. Optional filter allows discarding unwanted matches.
|
||||
%End
|
||||
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
Snap to map according to the current configuration.
|
||||
|
||||
:param point: point in canvas coordinates
|
||||
:param filter: allows discarding unwanted matches.
|
||||
:param relaxed: ``True`` if this method is non blocking and the matching result can be invalid while indexing
|
||||
%End
|
||||
|
||||
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Snap to map according to the current configuration.
|
||||
|
||||
:param pointMap: point in map coordinates
|
||||
:param filter: allows discarding unwanted matches.
|
||||
:param relaxed: ``True`` if this method is non blocking and the matching result can be invalid while indexing
|
||||
%End
|
||||
|
||||
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = 0 );
|
||||
%Docstring
|
||||
@ -172,13 +188,14 @@ Emitted when the snapping settings object changes.
|
||||
%End
|
||||
|
||||
protected:
|
||||
|
||||
virtual void prepareIndexStarting( int count );
|
||||
%Docstring
|
||||
Called when starting to index - can be overridden and e.g. progress dialog can be provided
|
||||
Called when starting to index with snapToMap - can be overridden and e.g. progress dialog can be provided
|
||||
%End
|
||||
virtual void prepareIndexProgress( int index );
|
||||
%Docstring
|
||||
Called when finished indexing a layer. When index == count the indexing is complete
|
||||
Called when finished indexing a layer with snapToMap. When index == count the indexing is complete
|
||||
%End
|
||||
|
||||
void clearAllLocators();
|
||||
|
@ -23,7 +23,15 @@ Snapping utils instance that is connected to a canvas and updates the configurat
|
||||
#include "qgsmapcanvassnappingutils.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent = 0 );
|
||||
%Docstring
|
||||
Construct map canvas snapping utils object
|
||||
|
||||
:param canvas: map canvas
|
||||
:param parent: parent object
|
||||
if ``False`` it will block until indexing is done
|
||||
%End
|
||||
|
||||
protected:
|
||||
virtual void prepareIndexStarting( int count );
|
||||
|
@ -86,7 +86,7 @@ void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
|
||||
{
|
||||
case StepLimit:
|
||||
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter, true );
|
||||
if ( match.isValid() )
|
||||
{
|
||||
mIs3DLayer = QgsWkbTypes::hasZ( match.layer()->wkbType() );
|
||||
@ -128,7 +128,7 @@ void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
|
||||
}
|
||||
|
||||
filter.setLayer( mVlayer );
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter, true );
|
||||
|
||||
if ( match.isValid() )
|
||||
{
|
||||
@ -234,7 +234,7 @@ void QgsMapToolTrimExtendFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
|
||||
switch ( mStep )
|
||||
{
|
||||
case StepLimit:
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter, true );
|
||||
if ( mRubberBandLimit && mRubberBandLimit->isVisible() )
|
||||
{
|
||||
if ( getPoints( match, pLimit1, pLimit2 ) )
|
||||
@ -248,7 +248,7 @@ void QgsMapToolTrimExtendFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
|
||||
if ( mIsModified )
|
||||
{
|
||||
filter.setLayer( mVlayer );
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
|
||||
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter, true );
|
||||
|
||||
if ( match.layer() )
|
||||
{
|
||||
@ -308,4 +308,3 @@ void QgsMapToolTrimExtendFeature::deactivate()
|
||||
mVlayer = nullptr;
|
||||
mLimitLayer = nullptr;
|
||||
}
|
||||
|
||||
|
@ -760,7 +760,7 @@ QgsPointLocator::Match QgsVertexTool::snapToEditableLayer( QgsMapMouseEvent *e )
|
||||
|
||||
snapUtils->setConfig( config );
|
||||
SelectedMatchFilter filter( tol, mLockedFeature.get() );
|
||||
m = snapUtils->snapToMap( mapPoint, &filter );
|
||||
m = snapUtils->snapToMap( mapPoint, &filter, true );
|
||||
|
||||
// we give priority to snap matches that are from selected features
|
||||
if ( filter.hasSelectedMatch() )
|
||||
@ -787,7 +787,7 @@ QgsPointLocator::Match QgsVertexTool::snapToEditableLayer( QgsMapMouseEvent *e )
|
||||
|
||||
snapUtils->setConfig( config );
|
||||
SelectedMatchFilter filter( tol, mLockedFeature.get() );
|
||||
m = snapUtils->snapToMap( mapPoint, &filter );
|
||||
m = snapUtils->snapToMap( mapPoint, &filter, true );
|
||||
|
||||
// we give priority to snap matches that are from selected features
|
||||
if ( filter.hasSelectedMatch() )
|
||||
@ -802,7 +802,7 @@ QgsPointLocator::Match QgsVertexTool::snapToEditableLayer( QgsMapMouseEvent *e )
|
||||
if ( mLastSnap )
|
||||
{
|
||||
OneFeatureFilter filterLast( mLastSnap->layer(), mLastSnap->featureId() );
|
||||
QgsPointLocator::Match lastMatch = snapUtils->snapToMap( mapPoint, &filterLast );
|
||||
QgsPointLocator::Match lastMatch = snapUtils->snapToMap( mapPoint, &filterLast, true );
|
||||
// but skip the the previously used feature if it would only snap to segment, while now we have snap to vertex
|
||||
// so that if there is a point on a line, it gets priority (as is usual with combined vertex+segment snapping)
|
||||
bool matchHasVertexLastHasEdge = m.hasVertex() && lastMatch.hasEdge();
|
||||
@ -834,7 +834,7 @@ QgsPointLocator::Match QgsVertexTool::snapToPolygonInterior( QgsMapMouseEvent *e
|
||||
{
|
||||
if ( currentVlayer->isEditable() && currentVlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
|
||||
{
|
||||
QgsPointLocator::MatchList matchList = snapUtils->locatorForLayer( currentVlayer )->pointInPolygon( mapPoint );
|
||||
QgsPointLocator::MatchList matchList = snapUtils->locatorForLayer( currentVlayer )->pointInPolygon( mapPoint, true );
|
||||
if ( !matchList.isEmpty() )
|
||||
{
|
||||
m = matchList.first();
|
||||
@ -854,7 +854,7 @@ QgsPointLocator::Match QgsVertexTool::snapToPolygonInterior( QgsMapMouseEvent *e
|
||||
|
||||
if ( vlayer->isEditable() && vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
|
||||
{
|
||||
QgsPointLocator::MatchList matchList = snapUtils->locatorForLayer( vlayer )->pointInPolygon( mapPoint );
|
||||
QgsPointLocator::MatchList matchList = snapUtils->locatorForLayer( vlayer )->pointInPolygon( mapPoint, true );
|
||||
if ( !matchList.isEmpty() )
|
||||
{
|
||||
m = matchList.first();
|
||||
@ -886,7 +886,7 @@ QList<QgsPointLocator::Match> QgsVertexTool::findEditableLayerMatches( const Qgs
|
||||
|
||||
if ( layer->geometryType() == QgsWkbTypes::PolygonGeometry )
|
||||
{
|
||||
matchList << locator->pointInPolygon( mapPoint );
|
||||
matchList << locator->pointInPolygon( mapPoint, true );
|
||||
}
|
||||
|
||||
double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
|
||||
@ -1773,7 +1773,7 @@ QList<QgsPointLocator::Match> QgsVertexTool::layerVerticesSnappedToPoint( QgsVec
|
||||
{
|
||||
MatchCollectingFilter myfilter( this );
|
||||
QgsPointLocator *loc = canvas()->snappingUtils()->locatorForLayer( layer );
|
||||
loc->nearestVertex( mapPoint, 0, &myfilter );
|
||||
loc->nearestVertex( mapPoint, 0, &myfilter, true );
|
||||
return myfilter.matches;
|
||||
}
|
||||
|
||||
|
@ -317,6 +317,7 @@ SET(QGIS_CORE_SRCS
|
||||
qgspluginlayerregistry.cpp
|
||||
qgspointxy.cpp
|
||||
qgspointlocator.cpp
|
||||
qgspointlocatorinittask.cpp
|
||||
qgsproject.cpp
|
||||
qgsprojectbadlayerhandler.cpp
|
||||
qgsprojectfiletransform.cpp
|
||||
@ -713,6 +714,7 @@ SET(QGIS_CORE_MOC_HDRS
|
||||
qgspluginlayer.h
|
||||
qgspointxy.h
|
||||
qgspointlocator.h
|
||||
qgspointlocatorinittask.h
|
||||
qgsproject.h
|
||||
qgsprojectviewsettings.h
|
||||
qgsproxyprogresstask.h
|
||||
|
@ -41,13 +41,13 @@ QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &o
|
||||
res.softLockCommonAngle = -1;
|
||||
|
||||
// try to snap to anything
|
||||
QgsPointLocator::Match snapMatch = ctx.snappingUtils->snapToMap( originalMapPoint );
|
||||
QgsPointLocator::Match snapMatch = ctx.snappingUtils->snapToMap( originalMapPoint, nullptr, true );
|
||||
QgsPointXY point = snapMatch.isValid() ? snapMatch.point() : originalMapPoint;
|
||||
|
||||
// try to snap explicitly to a segment - useful for some constraints
|
||||
QgsPointXY edgePt0, edgePt1;
|
||||
EdgesOnlyFilter edgesOnlyFilter;
|
||||
QgsPointLocator::Match edgeMatch = ctx.snappingUtils->snapToMap( originalMapPoint, &edgesOnlyFilter );
|
||||
QgsPointLocator::Match edgeMatch = ctx.snappingUtils->snapToMap( originalMapPoint, &edgesOnlyFilter, true );
|
||||
if ( edgeMatch.hasEdge() )
|
||||
edgeMatch.edgePoints( edgePt0, edgePt1 );
|
||||
|
||||
|
@ -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;
|
||||
|
||||
@ -544,6 +548,10 @@ QgsPointLocator::QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateRefe
|
||||
|
||||
QgsPointLocator::~QgsPointLocator()
|
||||
{
|
||||
// don't delete a locator if there is an indexing task running on it
|
||||
if ( mIsIndexing )
|
||||
waitForIndexingFinished();
|
||||
|
||||
destroyIndex();
|
||||
}
|
||||
|
||||
@ -554,6 +562,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 +573,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 +590,107 @@ void QgsPointLocator::setRenderContext( const QgsRenderContext *context )
|
||||
|
||||
}
|
||||
|
||||
bool QgsPointLocator::init( int maxFeaturesToIndex )
|
||||
void QgsPointLocator::onInitTaskFinished()
|
||||
{
|
||||
return hasIndex() ? true : rebuildIndex( maxFeaturesToIndex );
|
||||
// Check that we don't call this method twice, when calling waitForFinished
|
||||
// for instance (because of taskCompleted signal)
|
||||
if ( !mIsIndexing )
|
||||
return;
|
||||
|
||||
mIsIndexing = false;
|
||||
mRenderer.reset();
|
||||
mSource.reset();
|
||||
|
||||
// treat added and deleted feature while indexing
|
||||
for ( QgsFeatureId fid : mAddedFeatures )
|
||||
onFeatureAdded( fid );
|
||||
mAddedFeatures.clear();
|
||||
|
||||
for ( QgsFeatureId fid : mDeletedFeatures )
|
||||
onFeatureDeleted( fid );
|
||||
mDeletedFeatures.clear();
|
||||
|
||||
emit initFinished( mInitTask->isBuildOK() );
|
||||
}
|
||||
|
||||
bool QgsPointLocator::init( int maxFeaturesToIndex, bool relaxed )
|
||||
{
|
||||
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 ( relaxed )
|
||||
{
|
||||
mInitTask = new QgsPointLocatorInitTask( this );
|
||||
connect( mInitTask, &QgsPointLocatorInitTask::taskTerminated, this, &QgsPointLocator::onInitTaskFinished );
|
||||
connect( mInitTask, &QgsPointLocatorInitTask::taskCompleted, this, &QgsPointLocator::onInitTaskFinished );
|
||||
QgsApplication::taskManager()->addTask( mInitTask );
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool ok = rebuildIndex( maxFeaturesToIndex );
|
||||
mIsIndexing = false;
|
||||
emit initFinished( ok );
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
void QgsPointLocator::waitForIndexingFinished()
|
||||
{
|
||||
mInitTask->waitForFinished();
|
||||
|
||||
onInitTaskFinished();
|
||||
}
|
||||
|
||||
bool QgsPointLocator::hasIndex() const
|
||||
{
|
||||
return mRTree || mIsEmptyLayer;
|
||||
return mIsIndexing || mRTree || mIsEmptyLayer;
|
||||
}
|
||||
|
||||
bool QgsPointLocator::prepare( bool relaxed )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
{
|
||||
if ( relaxed )
|
||||
return false;
|
||||
else
|
||||
waitForIndexingFinished();
|
||||
}
|
||||
|
||||
if ( !mRTree )
|
||||
{
|
||||
init( -1, relaxed );
|
||||
if ( ( relaxed && mIsIndexing ) || !mRTree ) // relaxed 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 +715,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 +736,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 +800,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 +824,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 +900,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
|
||||
|
||||
@ -826,14 +944,10 @@ void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const
|
||||
}
|
||||
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point, double tolerance, MatchFilter *filter )
|
||||
QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return Match();
|
||||
}
|
||||
if ( !prepare( relaxed ) )
|
||||
return Match();
|
||||
|
||||
Match m;
|
||||
QgsPointLocator_VisitorNearestVertex visitor( this, m, point, filter );
|
||||
@ -844,14 +958,10 @@ QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point,
|
||||
return m;
|
||||
}
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, double tolerance, MatchFilter *filter )
|
||||
QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return Match();
|
||||
}
|
||||
if ( !prepare( relaxed ) )
|
||||
return Match();
|
||||
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::PointGeometry )
|
||||
@ -866,14 +976,10 @@ QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, do
|
||||
return m;
|
||||
}
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, double tolerance, MatchFilter *filter )
|
||||
QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return Match();
|
||||
}
|
||||
if ( !prepare( relaxed ) )
|
||||
return Match();
|
||||
|
||||
MatchList mlist = pointInPolygon( point );
|
||||
if ( !mlist.isEmpty() && mlist.at( 0 ).isValid() )
|
||||
@ -900,14 +1006,10 @@ QgsPointLocator::Match QgsPointLocator::nearestArea( const QgsPointXY &point, do
|
||||
}
|
||||
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter )
|
||||
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return MatchList();
|
||||
}
|
||||
if ( !prepare( relaxed ) )
|
||||
return MatchList();
|
||||
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::PointGeometry )
|
||||
@ -920,20 +1022,16 @@ QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle &rec
|
||||
return lst;
|
||||
}
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter )
|
||||
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
|
||||
return edgesInRect( rect, filter );
|
||||
return edgesInRect( rect, filter, relaxed );
|
||||
}
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter )
|
||||
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return MatchList();
|
||||
}
|
||||
if ( !prepare( relaxed ) )
|
||||
return MatchList();
|
||||
|
||||
MatchList lst;
|
||||
QgsPointLocator_VisitorVerticesInRect visitor( this, lst, rect, filter );
|
||||
@ -942,21 +1040,16 @@ QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle &
|
||||
return lst;
|
||||
}
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter )
|
||||
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
|
||||
return verticesInRect( rect, filter );
|
||||
return verticesInRect( rect, filter, relaxed );
|
||||
}
|
||||
|
||||
|
||||
QgsPointLocator::MatchList QgsPointLocator::pointInPolygon( const QgsPointXY &point )
|
||||
QgsPointLocator::MatchList QgsPointLocator::pointInPolygon( const QgsPointXY &point, bool relaxed )
|
||||
{
|
||||
if ( !mRTree )
|
||||
{
|
||||
init();
|
||||
if ( !mRTree ) // still invalid?
|
||||
return MatchList();
|
||||
}
|
||||
if ( !prepare( relaxed ) )
|
||||
return MatchList();
|
||||
|
||||
QgsWkbTypes::GeometryType geomType = mLayer->geometryType();
|
||||
if ( geomType == QgsWkbTypes::PointGeometry || geomType == QgsWkbTypes::LineGeometry )
|
||||
|
@ -20,6 +20,7 @@ class QgsPointXY;
|
||||
class QgsFeatureRenderer;
|
||||
class QgsRenderContext;
|
||||
class QgsRectangle;
|
||||
class QgsVectorLayerFeatureSource;
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgspointxy.h"
|
||||
@ -30,6 +31,7 @@ class QgsRectangle;
|
||||
#include "qgsgeometryutils.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgslinestring.h"
|
||||
#include "qgspointlocatorinittask.h"
|
||||
#include <memory>
|
||||
|
||||
class QgsPointLocator_VisitorNearestVertex;
|
||||
@ -122,10 +124,17 @@ 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
|
||||
*/
|
||||
bool init( int maxFeaturesToIndex = -1 );
|
||||
* 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 relaxed parameter passed
|
||||
* in the constructor. 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.
|
||||
*
|
||||
* 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, bool relaxed = false );
|
||||
|
||||
//! Indicate whether the data have been already indexed
|
||||
bool hasIndex() const;
|
||||
@ -251,50 +260,65 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
/**
|
||||
* Find nearest vertex to the specified point - up to distance specified by tolerance
|
||||
* Optional filter may discard unwanted matches.
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
*/
|
||||
Match nearestVertex( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
Match nearestVertex( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Find nearest edge to the specified point - up to distance specified by tolerance
|
||||
* Optional filter may discard unwanted matches.
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
*/
|
||||
Match nearestEdge( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
Match nearestEdge( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Find nearest area to the specified point - up to distance specified by tolerance
|
||||
* Optional filter may discard unwanted matches.
|
||||
* This will first perform a pointInPolygon and return first result.
|
||||
* If no match is found and tolerance is not 0, it will return nearestEdge.
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
Match nearestArea( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
Match nearestArea( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Find edges within a specified recangle
|
||||
* Optional filter may discard unwanted matches.
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
*/
|
||||
MatchList edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
//! Override of edgesInRect that construct rectangle from a center point and tolerance
|
||||
MatchList edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
MatchList edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Override of edgesInRect that construct rectangle from a center point and tolerance
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
*/
|
||||
MatchList edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Find vertices within a specified recangle
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
* Optional filter may discard unwanted matches.
|
||||
* \since QGIS 3.6
|
||||
*/
|
||||
MatchList verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
MatchList verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Override of verticesInRect that construct rectangle from a center point and tolerance
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
* \since QGIS 3.6
|
||||
*/
|
||||
MatchList verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
MatchList verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
// point-in-polygon query
|
||||
|
||||
// TODO: function to return just the first match?
|
||||
//! find out if the point is in any polygons
|
||||
MatchList pointInPolygon( const QgsPointXY &point );
|
||||
|
||||
/**
|
||||
* find out if the \a point is in any polygons
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
*/
|
||||
//!
|
||||
MatchList pointInPolygon( const QgsPointXY &point, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Returns how many geometries are cached in the index
|
||||
@ -302,17 +326,49 @@ 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 relaxed is TRUE
|
||||
*
|
||||
* \see QgsPointLocator()
|
||||
*/
|
||||
bool isIndexing() const { return mIsIndexing; }
|
||||
|
||||
/**
|
||||
* If the point locator has been initialized relaxedly and is currently indexing,
|
||||
* this methods waits for the indexing to be finished
|
||||
*/
|
||||
void waitForIndexingFinished();
|
||||
|
||||
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 onInitTaskFinished();
|
||||
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
|
||||
* \param relaxed TRUE if index build has to be non blocking
|
||||
*/
|
||||
bool prepare( bool relaxed );
|
||||
|
||||
//! Storage manager
|
||||
std::unique_ptr< SpatialIndex::IStorageManager > mStorage;
|
||||
|
||||
@ -329,12 +385,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 mIsIndexing = false;
|
||||
QgsFeatureIds mAddedFeatures;
|
||||
QgsFeatureIds mDeletedFeatures;
|
||||
QPointer<QgsPointLocatorInitTask> mInitTask;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
38
src/core/qgspointlocatorinittask.cpp
Normal file
38
src/core/qgspointlocatorinittask.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/***************************************************************************
|
||||
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 "qgspointlocator.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
/// @cond PRIVATE
|
||||
|
||||
QgsPointLocatorInitTask::QgsPointLocatorInitTask( QgsPointLocator *loc )
|
||||
: QgsTask( tr( "Indexing %1" ).arg( loc->layer()->id() ), QgsTask::Flags() )
|
||||
, mLoc( loc )
|
||||
{}
|
||||
|
||||
bool QgsPointLocatorInitTask::isBuildOK() const
|
||||
{
|
||||
return mBuildOK;
|
||||
}
|
||||
|
||||
bool QgsPointLocatorInitTask::run()
|
||||
{
|
||||
mBuildOK = mLoc->rebuildIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @endcond
|
59
src/core/qgspointlocatorinittask.h
Normal file
59
src/core/qgspointlocatorinittask.h
Normal file
@ -0,0 +1,59 @@
|
||||
/***************************************************************************
|
||||
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"
|
||||
|
||||
class QgsPointLocator;
|
||||
|
||||
class QgsPointLocatorInitTask : public QgsTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
QgsPointLocatorInitTask( QgsPointLocator *loc );
|
||||
|
||||
/**
|
||||
* Returns TRUE when the task has finished and the index build was ok
|
||||
*/
|
||||
bool isBuildOK() const;
|
||||
|
||||
bool run();
|
||||
|
||||
private:
|
||||
|
||||
QgsPointLocator *mLoc = nullptr;
|
||||
bool mBuildOK = false;
|
||||
};
|
||||
|
||||
/// @endcond
|
||||
|
||||
#endif // QGSPOINTLOCATORINITTASK_H
|
@ -41,7 +41,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 );
|
||||
connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
|
||||
mLocators.insert( vl, vlpl );
|
||||
}
|
||||
return mLocators.value( vl );
|
||||
@ -59,10 +60,16 @@ void QgsSnappingUtils::clearAllLocators()
|
||||
|
||||
QgsPointLocator *QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
|
||||
{
|
||||
if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
|
||||
return nullptr;
|
||||
|
||||
QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
|
||||
pointMap.x() + tolerance, pointMap.y() + tolerance );
|
||||
if ( isIndexPrepared( vl, aoi ) )
|
||||
return locatorForLayer( vl );
|
||||
|
||||
QgsPointLocator *loc = locatorForLayer( vl );
|
||||
|
||||
if ( loc->isIndexing() || isIndexPrepared( loc, aoi ) )
|
||||
return loc;
|
||||
else
|
||||
return temporaryLocatorForLayer( vl, pointMap, tolerance );
|
||||
}
|
||||
@ -74,18 +81,16 @@ 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 );
|
||||
connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
|
||||
|
||||
mTemporaryLocators.insert( vl, vlpl );
|
||||
return mTemporaryLocators.value( vl );
|
||||
}
|
||||
|
||||
bool QgsSnappingUtils::isIndexPrepared( QgsVectorLayer *vl, const QgsRectangle &areaOfInterest )
|
||||
bool QgsSnappingUtils::isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest )
|
||||
{
|
||||
if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
|
||||
return false;
|
||||
|
||||
QgsPointLocator *loc = locatorForLayer( vl );
|
||||
|
||||
if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
|
||||
return true;
|
||||
|
||||
@ -183,22 +188,22 @@ static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointL
|
||||
bestMatch = candidateMatch; // the other match is better!
|
||||
}
|
||||
|
||||
static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter )
|
||||
static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( type & QgsPointLocator::Vertex )
|
||||
{
|
||||
_replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter ), tolerance );
|
||||
_replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
|
||||
}
|
||||
if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
|
||||
{
|
||||
_replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter ), tolerance );
|
||||
_replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
|
||||
}
|
||||
if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
|
||||
{
|
||||
// if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
|
||||
if ( type & QgsPointLocator::Edge )
|
||||
tolerance = 0;
|
||||
_replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter ), tolerance );
|
||||
_replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,10 +224,9 @@ static QgsPointLocator::Types _snappingTypeToPointLocatorType( QgsSnappingConfig
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QgsPointLocator::Match QgsSnappingUtils::snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter )
|
||||
QgsPointLocator::Match QgsSnappingUtils::snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter );
|
||||
return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
|
||||
}
|
||||
|
||||
inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
|
||||
@ -231,7 +235,7 @@ inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
|
||||
point.x() + tolerance, point.y() + tolerance );
|
||||
}
|
||||
|
||||
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter )
|
||||
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
|
||||
{
|
||||
@ -247,7 +251,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
|
||||
QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.type() );
|
||||
|
||||
prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ) );
|
||||
prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
|
||||
|
||||
// use ad-hoc locator
|
||||
QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
|
||||
@ -255,11 +259,14 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
return QgsPointLocator::Match();
|
||||
|
||||
QgsPointLocator::Match bestMatch;
|
||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
|
||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
|
||||
|
||||
if ( mSnappingConfig.intersectionSnapping() )
|
||||
{
|
||||
QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
|
||||
if ( !locEdges )
|
||||
return QgsPointLocator::Match();
|
||||
|
||||
QgsPointLocator::MatchList edges = locEdges->edgesInRect( pointMap, tolerance );
|
||||
_replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
|
||||
}
|
||||
@ -274,7 +281,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
|
||||
layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
|
||||
}
|
||||
prepareIndex( layers );
|
||||
prepareIndex( layers, relaxed );
|
||||
|
||||
QgsPointLocator::Match bestMatch;
|
||||
QgsPointLocator::MatchList edges; // for snap on intersection
|
||||
@ -285,7 +292,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
|
||||
if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
|
||||
{
|
||||
_updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter );
|
||||
_updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
|
||||
|
||||
if ( mSnappingConfig.intersectionSnapping() )
|
||||
{
|
||||
@ -312,7 +319,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
for ( QgsMapLayer *layer : constLayers )
|
||||
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
|
||||
layers << qMakePair( vl, aoi );
|
||||
prepareIndex( layers );
|
||||
prepareIndex( layers, relaxed );
|
||||
|
||||
QgsPointLocator::MatchList edges; // for snap on intersection
|
||||
QgsPointLocator::Match bestMatch;
|
||||
@ -322,7 +329,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
QgsVectorLayer *vl = entry.first;
|
||||
if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
|
||||
{
|
||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
|
||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
|
||||
|
||||
if ( mSnappingConfig.intersectionSnapping() )
|
||||
edges << loc->edgesInRect( pointMap, tolerance );
|
||||
@ -338,12 +345,20 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
|
||||
return QgsPointLocator::Match();
|
||||
}
|
||||
|
||||
void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers )
|
||||
void QgsSnappingUtils::onInitFinished( bool ok )
|
||||
{
|
||||
if ( mIsIndexing )
|
||||
return;
|
||||
mIsIndexing = true;
|
||||
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, bool relaxed )
|
||||
{
|
||||
// check if we need to build any index
|
||||
QList<LayerAndAreaOfInterest> layersToIndex;
|
||||
const auto constLayers = layers;
|
||||
@ -354,25 +369,34 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
|
||||
if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
|
||||
continue;
|
||||
|
||||
if ( !isIndexPrepared( vl, entry.second ) )
|
||||
QgsPointLocator *loc = locatorForLayer( vl );
|
||||
|
||||
if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
|
||||
layersToIndex << entry;
|
||||
}
|
||||
if ( !layersToIndex.isEmpty() )
|
||||
{
|
||||
// build indexes
|
||||
QTime t;
|
||||
t.start();
|
||||
int i = 0;
|
||||
prepareIndexStarting( layersToIndex.count() );
|
||||
const auto constLayersToIndex = layersToIndex;
|
||||
for ( const LayerAndAreaOfInterest &entry : constLayersToIndex )
|
||||
|
||||
if ( !relaxed )
|
||||
{
|
||||
t.start();
|
||||
prepareIndexStarting( layersToIndex.count() );
|
||||
}
|
||||
|
||||
for ( const LayerAndAreaOfInterest &entry : layersToIndex )
|
||||
{
|
||||
QgsVectorLayer *vl = entry.first;
|
||||
QTime tt;
|
||||
tt.start();
|
||||
|
||||
QgsPointLocator *loc = locatorForLayer( vl );
|
||||
|
||||
if ( loc->isIndexing() && !relaxed )
|
||||
{
|
||||
loc->waitForIndexingFinished();
|
||||
}
|
||||
|
||||
|
||||
if ( !mEnableSnappingForInvisibleFeature )
|
||||
{
|
||||
QgsRenderContext ctx = QgsRenderContext::fromMapSettings( mMapSettings );
|
||||
@ -383,7 +407,7 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
|
||||
{
|
||||
QgsRectangle rect( mMapSettings.visibleExtent() );
|
||||
loc->setExtent( &rect );
|
||||
loc->init();
|
||||
loc->init( -1, relaxed );
|
||||
}
|
||||
else if ( mStrategy == IndexHybrid )
|
||||
{
|
||||
@ -410,7 +434,7 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
|
||||
if ( indexReasonableArea == -1 )
|
||||
{
|
||||
// we can safely index the whole layer
|
||||
loc->init();
|
||||
loc->init( -1, relaxed );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -422,24 +446,20 @@ 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, relaxed );
|
||||
}
|
||||
|
||||
}
|
||||
else // full index strategy
|
||||
loc->init();
|
||||
loc->init( relaxed );
|
||||
|
||||
QgsDebugMsg( QStringLiteral( "Index init: %1 ms (%2)" ).arg( tt.elapsed() ).arg( vl->id() ) );
|
||||
prepareIndexProgress( ++i );
|
||||
if ( !relaxed )
|
||||
prepareIndexProgress( ++i );
|
||||
}
|
||||
QgsDebugMsg( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
|
||||
|
||||
if ( !relaxed )
|
||||
QgsDebugMsg( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
|
||||
}
|
||||
mIsIndexing = false;
|
||||
}
|
||||
|
||||
QgsSnappingConfig QgsSnappingUtils::config() const
|
||||
@ -484,7 +504,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( QPoint point, QgsPo
|
||||
return QgsPointLocator::Match();
|
||||
|
||||
QgsPointLocator::Match bestMatch;
|
||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
|
||||
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
|
@ -52,20 +52,37 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
||||
|
||||
public:
|
||||
|
||||
//! Constructor for QgsSnappingUtils
|
||||
/**
|
||||
* Constructor for QgsSnappingUtils
|
||||
* \param parent parent object
|
||||
* \param enableSnappingForInvisibleFeature TRUE if we want to snap feature even if there are not visible
|
||||
*/
|
||||
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true );
|
||||
~QgsSnappingUtils() override;
|
||||
|
||||
// main actions
|
||||
|
||||
//! Gets a point locator for the given layer. If such locator does not exist, it will be created
|
||||
/**
|
||||
* Gets a point locator for the given layer. If such locator does not exist, it will be created
|
||||
* \param vl the vector layer
|
||||
*/
|
||||
QgsPointLocator *locatorForLayer( QgsVectorLayer *vl );
|
||||
|
||||
//! Snap to map according to the current configuration. Optional filter allows discarding unwanted matches.
|
||||
QgsPointLocator::Match snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
/**
|
||||
* Snap to map according to the current configuration.
|
||||
* \param point point in canvas coordinates
|
||||
* \param filter allows discarding unwanted matches.
|
||||
* \param relaxed TRUE if this method is non blocking and the matching result can be invalid while indexing
|
||||
*/
|
||||
QgsPointLocator::Match snapToMap( QPoint point, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
//! Snap to current layer
|
||||
/**
|
||||
* Snap to map according to the current configuration.
|
||||
* \param pointMap point in map coordinates
|
||||
* \param filter allows discarding unwanted matches.
|
||||
* \param relaxed TRUE if this method is non blocking and the matching result can be invalid while indexing
|
||||
*/
|
||||
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
//! Snap to current layer
|
||||
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = nullptr );
|
||||
@ -190,14 +207,20 @@ 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 ) }
|
||||
|
||||
//! Called when starting to index with snapToMap - 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 with snapToMap. When index == count the indexing is complete
|
||||
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
|
||||
@ -210,10 +233,10 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
|
||||
|
||||
typedef QPair< QgsVectorLayer *, QgsRectangle > LayerAndAreaOfInterest;
|
||||
|
||||
//! find out whether the strategy would index such layer or just use a temporary locator
|
||||
bool isIndexPrepared( QgsVectorLayer *vl, const QgsRectangle &areaOfInterest );
|
||||
//! Returns TRUE if \a loc index is ready to be used in the area of interest \a areaOfInterest
|
||||
bool isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest );
|
||||
//! initialize index for layers where it makes sense (according to the indexing strategy)
|
||||
void prepareIndex( const QList<LayerAndAreaOfInterest> &layers );
|
||||
void prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed );
|
||||
|
||||
private:
|
||||
// environment
|
||||
@ -249,12 +272,8 @@ 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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -670,7 +670,7 @@ bool QgsAdvancedDigitizingDockWidget::applyConstraints( QgsMapMouseEvent *e )
|
||||
// set the point coordinates in the map event
|
||||
e->setMapPoint( point );
|
||||
|
||||
mSnapMatch = context.snappingUtils->snapToMap( point );
|
||||
mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
|
||||
|
||||
if ( mSnapMatch.isValid() )
|
||||
{
|
||||
@ -782,7 +782,7 @@ QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const
|
||||
localConfig.setType( QgsSnappingConfig::Segment );
|
||||
snappingUtils->setConfig( localConfig );
|
||||
|
||||
match = snappingUtils->snapToMap( originalMapPoint );
|
||||
match = snappingUtils->snapToMap( originalMapPoint, nullptr, true );
|
||||
|
||||
snappingUtils->setConfig( canvasConfig );
|
||||
|
||||
|
@ -34,6 +34,14 @@ class GUI_EXPORT QgsMapCanvasSnappingUtils : public QgsSnappingUtils
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
/**
|
||||
* Construct map canvas snapping utils object
|
||||
*
|
||||
* \param canvas map canvas
|
||||
* \param parent parent object
|
||||
* if FALSE it will block until indexing is done
|
||||
*/
|
||||
QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent = nullptr );
|
||||
|
||||
protected:
|
||||
|
@ -49,7 +49,7 @@ QgsPointXY QgsMapMouseEvent::snapPoint()
|
||||
mHasCachedSnapResult = true;
|
||||
|
||||
QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
|
||||
mSnapMatch = snappingUtils->snapToMap( mMapPoint );
|
||||
mSnapMatch = snappingUtils->snapToMap( mMapPoint, nullptr, true );
|
||||
|
||||
if ( mSnapMatch.isValid() )
|
||||
{
|
||||
|
@ -119,6 +119,9 @@ void TestQgsMapToolAddFeaturePoint::initTestCase()
|
||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerPointZ << mLayerPointZSnap );
|
||||
mCanvas->setCurrentLayer( mLayerPointZ );
|
||||
|
||||
mCanvas->snappingUtils()->locatorForLayer( mLayerPointZ )->init();
|
||||
mCanvas->snappingUtils()->locatorForLayer( mLayerPointZSnap )->init();
|
||||
|
||||
// create the tool
|
||||
mCaptureTool = new QgsMapToolAddFeature( mCanvas, /*mAdvancedDigitizingDockWidget, */ QgsMapToolCapture::CapturePoint );
|
||||
mCanvas->setMapTool( mCaptureTool );
|
||||
|
@ -162,10 +162,14 @@ void TestQgsMapToolReshape::initTestCase()
|
||||
cfg.setType( QgsSnappingConfig::VertexAndSegment );
|
||||
cfg.setEnabled( true );
|
||||
mCanvas->snappingUtils()->setConfig( cfg );
|
||||
|
||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLineZ << mLayerPointZ << mLayerPolygonZ );
|
||||
mCanvas->setCurrentLayer( mLayerLineZ );
|
||||
|
||||
mCanvas->snappingUtils()->locatorForLayer( mLayerLineZ )->init();
|
||||
mCanvas->snappingUtils()->locatorForLayer( mLayerPointZ )->init();
|
||||
mCanvas->snappingUtils()->locatorForLayer( mLayerPolygonZ )->init();
|
||||
mCanvas->snappingUtils()->locatorForLayer( mLayerTopo )->init();
|
||||
|
||||
// create the tool
|
||||
mCaptureTool = new QgsMapToolReshape( mCanvas );
|
||||
mCanvas->setMapTool( mCaptureTool );
|
||||
|
@ -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()
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgsmapcanvas.h"
|
||||
#include "qgsmapmouseevent.h"
|
||||
#include "qgsmapcanvassnappingutils.h"
|
||||
#include "qgisapp.h"
|
||||
|
||||
#include "qgsmaptooltrimextendfeature.h"
|
||||
|
||||
@ -173,6 +174,12 @@ class TestQgsMapToolTrimExtendFeature : public QObject
|
||||
snappingConfig.setMode( QgsSnappingConfig::AllLayers );
|
||||
mSnappingUtils->setConfig( snappingConfig );
|
||||
|
||||
mSnappingUtils->locatorForLayer( vlPolygon.get() )->init();
|
||||
mSnappingUtils->locatorForLayer( vlMultiLine.get() )->init();
|
||||
mSnappingUtils->locatorForLayer( vlLineZ.get() )->init();
|
||||
mSnappingUtils->locatorForLayer( vlTopoEdit.get() )->init();
|
||||
mSnappingUtils->locatorForLayer( vlTopoLimit.get() )->init();
|
||||
|
||||
mCanvas->setSnappingUtils( mSnappingUtils );
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "qgsgeometry.h"
|
||||
#include "qgsmapcanvas.h"
|
||||
#include "qgsmapmouseevent.h"
|
||||
|
||||
#include "qgssnappingutils.h"
|
||||
|
||||
/**
|
||||
* \ingroup UnitTests
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "qgslinestring.h"
|
||||
#include "qgssnappingconfig.h"
|
||||
#include "qgssettings.h"
|
||||
#include "testqgsmaptoolutils.h"
|
||||
|
||||
bool operator==( const QgsGeometry &g1, const QgsGeometry &g2 )
|
||||
{
|
||||
@ -138,6 +139,7 @@ class TestQgsVertexTool : public QObject
|
||||
|
||||
private:
|
||||
QgsMapCanvas *mCanvas = nullptr;
|
||||
QgisApp *mQgisApp = nullptr;
|
||||
QgsAdvancedDigitizingDockWidget *mAdvancedDigitizingDockWidget = nullptr;
|
||||
QgsVertexTool *mVertexTool = nullptr;
|
||||
QgsVectorLayer *mLayerLine = nullptr;
|
||||
@ -161,6 +163,7 @@ void TestQgsVertexTool::initTestCase()
|
||||
// init QGIS's paths - true means that all path will be inited from prefix
|
||||
QgsApplication::init();
|
||||
QgsApplication::initQgis();
|
||||
mQgisApp = new QgisApp();
|
||||
|
||||
// Set up the QSettings environment
|
||||
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
|
||||
@ -247,9 +250,13 @@ void TestQgsVertexTool::initTestCase()
|
||||
|
||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerPolygon << mLayerPoint << mLayerLineZ );
|
||||
|
||||
// TODO: set up snapping
|
||||
QgsMapCanvasSnappingUtils *snappingUtils = new QgsMapCanvasSnappingUtils( mCanvas, this );
|
||||
mCanvas->setSnappingUtils( snappingUtils );
|
||||
|
||||
mCanvas->setSnappingUtils( new QgsMapCanvasSnappingUtils( mCanvas, this ) );
|
||||
snappingUtils->locatorForLayer( mLayerLine )->init();
|
||||
snappingUtils->locatorForLayer( mLayerPolygon )->init();
|
||||
snappingUtils->locatorForLayer( mLayerPoint )->init();
|
||||
snappingUtils->locatorForLayer( mLayerLineZ )->init();
|
||||
|
||||
// create vertex tool
|
||||
mVertexTool = new QgsVertexTool( mCanvas, mAdvancedDigitizingDockWidget );
|
||||
@ -868,6 +875,8 @@ void TestQgsVertexTool::testActiveLayerPriority()
|
||||
|
||||
// make one layer active and check its vertex is used
|
||||
|
||||
mCanvas->snappingUtils()->locatorForLayer( layerLine2 )->init();
|
||||
|
||||
mCanvas->setCurrentLayer( mLayerLine );
|
||||
|
||||
mouseClick( 1, 1, Qt::LeftButton );
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "qgssnappingutils.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup UnitTests
|
||||
* This is a unit test for the QgsCadUtils class.
|
||||
@ -98,6 +99,8 @@ void TestQgsCadUtils::initTestCase()
|
||||
mSnappingUtils = new QgsSnappingUtils;
|
||||
mSnappingUtils->setConfig( snapConfig );
|
||||
mSnappingUtils->setMapSettings( mMapSettings );
|
||||
|
||||
mSnappingUtils->locatorForLayer( mLayerPolygon )->init();
|
||||
}
|
||||
|
||||
//runs after all tests
|
||||
|
@ -346,6 +346,134 @@ class TestQgsPointLocator : public QObject
|
||||
|
||||
delete vlEmptyGeom;
|
||||
}
|
||||
|
||||
void testAsynchronousMode()
|
||||
{
|
||||
QgsPointLocator loc( mVL, QgsCoordinateReferenceSystem(), QgsCoordinateTransformContext(), nullptr );
|
||||
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, nullptr, true );
|
||||
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 );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( &loc, &QgsPointLocator::initFinished, &loop, &QEventLoop::quit );
|
||||
|
||||
// trigger locator initialization
|
||||
QgsPointLocator::Match mAddV0 = loc.nearestVertex( QgsPointXY( 12, 12 ), 999, nullptr, true );
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
void testWaitForIndexingFinished()
|
||||
{
|
||||
QgsPointLocator loc( mVL, QgsCoordinateReferenceSystem(), QgsCoordinateTransformContext(), nullptr );
|
||||
QgsPointXY pt( 2, 2 );
|
||||
|
||||
// locator is not ready yet
|
||||
QgsPointLocator::Match m = loc.nearestVertex( pt, 999, nullptr, true );
|
||||
QVERIFY( !m.isValid() );
|
||||
QVERIFY( loc.mIsIndexing );
|
||||
|
||||
// non relaxed call, this will block until the first indexing is finished
|
||||
// so the match point has to be valid
|
||||
m = loc.nearestVertex( pt, 999, nullptr );
|
||||
QVERIFY( m.isValid() );
|
||||
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 testDeleteLocator()
|
||||
{
|
||||
QgsPointLocator *loc = new QgsPointLocator( mVL, QgsCoordinateReferenceSystem(), QgsCoordinateTransformContext(), nullptr );
|
||||
QgsPointXY pt( 2, 2 );
|
||||
|
||||
// delete locator while we are indexing (could happen when closing project for instance)
|
||||
loc->nearestVertex( pt, 999, nullptr, true );
|
||||
delete loc;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QGSTEST_MAIN( TestQgsPointLocator )
|
||||
|
@ -401,10 +401,26 @@ class TestQgsSnappingUtils : public QObject
|
||||
QCOMPARE( m2.point(), QgsPointXY( 5.0, 2.5 ) );
|
||||
|
||||
}
|
||||
|
||||
void testSnapOnCurrentLayer()
|
||||
{
|
||||
QgsMapSettings mapSettings;
|
||||
mapSettings.setOutputSize( QSize( 100, 100 ) );
|
||||
mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
|
||||
QVERIFY( mapSettings.hasValidSettings() );
|
||||
|
||||
QgsSnappingUtils u( nullptr, true );
|
||||
u.setMapSettings( mapSettings );
|
||||
u.setCurrentLayer( mVL );
|
||||
|
||||
QgsPointLocator::Match m = u.snapToCurrentLayer( QPoint( 100, 100 ), QgsPointLocator::Vertex );
|
||||
QVERIFY( m.isValid() );
|
||||
QVERIFY( m.hasVertex() );
|
||||
QCOMPARE( m.point(), QgsPointXY( 1, 0 ) );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QGSTEST_MAIN( TestQgsSnappingUtils )
|
||||
|
||||
#include "testqgssnappingutils.moc"
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user