Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgspointlocator.h
3 : : --------------------------------------
4 : : Date : November 2014
5 : : Copyright : (C) 2014 by Martin Dobias
6 : : Email : wonder dot sk at gmail dot com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #ifndef QGSPOINTLOCATOR_H
17 : : #define QGSPOINTLOCATOR_H
18 : :
19 : : class QgsPointXY;
20 : : class QgsFeatureRenderer;
21 : : class QgsRenderContext;
22 : : class QgsRectangle;
23 : : class QgsVectorLayerFeatureSource;
24 : :
25 : : #include "qgis_core.h"
26 : : #include "qgspointxy.h"
27 : : #include "qgscoordinatereferencesystem.h"
28 : : #include "qgscoordinatetransform.h"
29 : : #include "qgsfeatureid.h"
30 : : #include "qgsgeometry.h"
31 : : #include "qgsgeometryutils.h"
32 : : #include "qgsvectorlayer.h"
33 : : #include "qgslinestring.h"
34 : : #include "qgspointlocatorinittask.h"
35 : : #include <memory>
36 : :
37 : : #include <QPointer>
38 : :
39 : : /**
40 : : * \ingroup core
41 : : * \brief Helper class used when traversing the index looking for vertices - builds a list of matches.
42 : : * \note not available in Python bindings
43 : : */
44 : : class QgsPointLocator_VisitorNearestVertex;
45 : :
46 : : /**
47 : : * \ingroup core
48 : : * \brief Helper class used when traversing the index looking for centroid - builds a list of matches.
49 : : * \note not available in Python bindings
50 : : * \since QGIS 3.12
51 : : */
52 : : class QgsPointLocator_VisitorNearestCentroid;
53 : :
54 : : /**
55 : : * \ingroup core
56 : : * \brief Helper class used when traversing the index looking for middle segment - builds a list of matches.
57 : : * \note not available in Python bindings
58 : : * \since QGIS 3.12
59 : : */
60 : : class QgsPointLocator_VisitorNearestMiddleOfSegment;
61 : :
62 : : /**
63 : : * \ingroup core
64 : : * \brief Helper class used when traversing the index looking for edges - builds a list of matches.
65 : : * \note not available in Python bindings
66 : : */
67 : : class QgsPointLocator_VisitorNearestEdge;
68 : :
69 : : /**
70 : : * \ingroup core
71 : : * \brief Helper class used when traversing the index with areas - builds a list of matches.
72 : : * \note not available in Python bindings
73 : : */
74 : : class QgsPointLocator_VisitorArea;
75 : :
76 : : /**
77 : : * \ingroup core
78 : : * \brief Helper class used when traversing the index looking for edges - builds a list of matches.
79 : : * \note not available in Python bindings
80 : : */
81 : : class QgsPointLocator_VisitorEdgesInRect;
82 : :
83 : : namespace SpatialIndex SIP_SKIP
84 : : {
85 : : class IStorageManager;
86 : : class ISpatialIndex;
87 : : }
88 : :
89 : : /**
90 : : * \ingroup core
91 : : * \brief The class defines interface for querying point location:
92 : : *
93 : : * - query nearest vertices / edges to a point
94 : : * - query vertices / edges in rectangle
95 : : * - query areas covering a point
96 : : *
97 : : * Works with one layer.
98 : : *
99 : : * \since QGIS 2.8
100 : : */
101 : : class CORE_EXPORT QgsPointLocator : public QObject
102 : : {
103 : : Q_OBJECT
104 : : public:
105 : :
106 : : /**
107 : : * Construct point locator for a \a layer.
108 : : *
109 : : * If a valid QgsCoordinateReferenceSystem is passed for \a destinationCrs then the locator will
110 : : * do the searches on data reprojected to the given CRS. For accurate reprojection it is important
111 : : * to set the correct \a transformContext if a \a destinationCrs is specified. This is usually taken
112 : : * from the current QgsProject::transformContext().
113 : : *
114 : : * If \a extent is not NULLPTR, the locator will index only a subset of the layer which falls within that extent.
115 : : */
116 : : explicit QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateReferenceSystem &destinationCrs = QgsCoordinateReferenceSystem(),
117 : : const QgsCoordinateTransformContext &transformContext = QgsCoordinateTransformContext(),
118 : : const QgsRectangle *extent = nullptr );
119 : :
120 : : ~QgsPointLocator() override;
121 : :
122 : : /**
123 : : * Gets associated layer
124 : : * \since QGIS 2.14
125 : : */
126 : 0 : QgsVectorLayer *layer() const { return mLayer; }
127 : :
128 : : /**
129 : : * Gets destination CRS - may be an invalid QgsCoordinateReferenceSystem if not doing OTF reprojection
130 : : * \since QGIS 2.14
131 : : */
132 : : QgsCoordinateReferenceSystem destinationCrs() const;
133 : :
134 : : /**
135 : : * Gets extent of the area point locator covers - if NULLPTR then it caches the whole layer
136 : : * \since QGIS 2.14
137 : : */
138 : 0 : const QgsRectangle *extent() const { return mExtent.get(); }
139 : :
140 : : /**
141 : : * Configure extent - if not NULLPTR, it will index only that area
142 : : * \since QGIS 2.14
143 : : */
144 : : void setExtent( const QgsRectangle *extent );
145 : :
146 : : /**
147 : : * Configure render context - if not NULLPTR, it will use to index only visible feature
148 : : * \since QGIS 3.2
149 : : */
150 : : void setRenderContext( const QgsRenderContext *context );
151 : :
152 : : /**
153 : : * The type of a snap result or the filter type for a snap request.
154 : : */
155 : : enum Type
156 : : {
157 : : Invalid = 0, //!< Invalid
158 : : Vertex = 1 << 0, //!< Snapped to a vertex. Can be a vertex of the geometry or an intersection.
159 : : Edge = 1 << 1, //!< Snapped to an edge
160 : : Area = 1 << 2, //!< Snapped to an area
161 : : Centroid = 1 << 3, //!< Snapped to a centroid
162 : : MiddleOfSegment = 1 << 4, //!< Snapped to the middle of a segment
163 : : LineEndpoint = 1 << 5, //!< Start or end points of lines only (since QGIS 3.20)
164 : : All = Vertex | Edge | Area | Centroid | MiddleOfSegment //!< Combination of all types. Note LineEndpoint is not included as endpoints made redundant by the presence of the Vertex flag.
165 : : };
166 : :
167 : : Q_DECLARE_FLAGS( Types, Type )
168 : :
169 : : /**
170 : : * Prepare the index for queries. Does nothing if the index already exists.
171 : : * If the number of features is greater than the value of maxFeaturesToIndex, creation of index is stopped
172 : : * to make sure we do not run out of memory. If maxFeaturesToIndex is -1, no limits are used.
173 : : *
174 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
175 : : * in the constructor. if TRUE, index building will be done in another thread and init() method returns
176 : : * immediately. initFinished() signal will be emitted once the initialization is over.
177 : : *
178 : : * Returns FALSE if the creation of index is blocking and has been prematurely stopped due to the limit of features, otherwise TRUE
179 : : *
180 : : * \see QgsPointLocator()
181 : : */
182 : : bool init( int maxFeaturesToIndex = -1, bool relaxed = false );
183 : :
184 : : //! Indicate whether the data have been already indexed
185 : : bool hasIndex() const;
186 : :
187 : 0 : struct Match
188 : : {
189 : : //! construct invalid match
190 : 0 : Match() = default;
191 : :
192 : 0 : Match( QgsPointLocator::Type t, QgsVectorLayer *vl, QgsFeatureId fid, double dist, const QgsPointXY &pt, int vertexIndex = 0, QgsPointXY *edgePoints = nullptr )
193 : 0 : : mType( t )
194 : 0 : , mDist( dist )
195 : 0 : , mPoint( pt )
196 : 0 : , mLayer( vl )
197 : 0 : , mFid( fid )
198 : 0 : , mVertexIndex( vertexIndex )
199 : : {
200 : 0 : if ( edgePoints )
201 : : {
202 : 0 : mEdgePoints[0] = edgePoints[0];
203 : 0 : mEdgePoints[1] = edgePoints[1];
204 : 0 : }
205 : 0 : }
206 : :
207 : 0 : QgsPointLocator::Type type() const { return mType; }
208 : :
209 : 0 : bool isValid() const { return mType != Invalid; }
210 : : //! Returns TRUE if the Match is a vertex
211 : : bool hasVertex() const { return mType == Vertex; }
212 : : //! Returns TRUE if the Match is an edge
213 : 0 : bool hasEdge() const { return mType == Edge; }
214 : : //! Returns TRUE if the Match is a centroid
215 : : bool hasCentroid() const { return mType == Centroid; }
216 : : //! Returns TRUE if the Match is an area
217 : : bool hasArea() const { return mType == Area; }
218 : : //! Returns TRUE if the Match is the middle of a segment
219 : : bool hasMiddleSegment() const { return mType == MiddleOfSegment; }
220 : :
221 : : /**
222 : : * Returns TRUE if the Match is a line endpoint (start or end vertex).
223 : : *
224 : : * \since QGIS 3.20
225 : : */
226 : : bool hasLineEndpoint() const { return mType == LineEndpoint; }
227 : :
228 : : /**
229 : : * for vertex / edge match
230 : : * units depending on what class returns it (geom.cache: layer units, map canvas snapper: dest crs units)
231 : : */
232 : 0 : double distance() const { return mDist; }
233 : :
234 : : /**
235 : : * for vertex / edge match
236 : : * coords depending on what class returns it (geom.cache: layer coords, map canvas snapper: dest coords)
237 : : */
238 : 0 : QgsPointXY point() const { return mPoint; }
239 : :
240 : : //! for vertex / edge match (first vertex of the edge)
241 : 0 : int vertexIndex() const { return mVertexIndex; }
242 : :
243 : : /**
244 : : * The vector layer where the snap occurred.
245 : : * Will be NULLPTR if the snap happened on an intersection.
246 : : */
247 : 0 : QgsVectorLayer *layer() const { return mLayer; }
248 : :
249 : : /**
250 : : * The id of the feature to which the snapped geometry belongs.
251 : : */
252 : 0 : QgsFeatureId featureId() const { return mFid; }
253 : :
254 : : //! Only for a valid edge match - obtain endpoints of the edge
255 : 0 : void edgePoints( QgsPointXY &pt1 SIP_OUT, QgsPointXY &pt2 SIP_OUT ) const
256 : : {
257 : 0 : pt1 = mEdgePoints[0];
258 : 0 : pt2 = mEdgePoints[1];
259 : 0 : }
260 : :
261 : : /**
262 : : * Convenient method to return a point on an edge with linear
263 : : * interpolation of the Z value.
264 : : * \since 3.10
265 : : */
266 : : QgsPoint interpolatedPoint() const
267 : : {
268 : : QgsPoint point;
269 : : const QgsGeometry geom = mLayer->getGeometry( mFid );
270 : : if ( !( geom.isNull() || geom.isEmpty() ) )
271 : : {
272 : : QgsLineString line( geom.vertexAt( mVertexIndex ), geom.vertexAt( mVertexIndex + 1 ) );
273 : :
274 : : point = QgsGeometryUtils::closestPoint( line, QgsPoint( mPoint ) );
275 : : }
276 : : return point;
277 : : }
278 : :
279 : : bool operator==( const QgsPointLocator::Match &other ) const
280 : : {
281 : : return mType == other.mType &&
282 : : mDist == other.mDist &&
283 : : mPoint == other.mPoint &&
284 : : mLayer == other.mLayer &&
285 : : mFid == other.mFid &&
286 : : mVertexIndex == other.mVertexIndex &&
287 : : mEdgePoints == other.mEdgePoints &&
288 : : mCentroid == other.mCentroid &&
289 : : mMiddleOfSegment == other.mMiddleOfSegment;
290 : : }
291 : :
292 : : protected:
293 : 0 : Type mType = Invalid;
294 : 0 : double mDist = 0;
295 : : QgsPointXY mPoint;
296 : 0 : QgsVectorLayer *mLayer = nullptr;
297 : 0 : QgsFeatureId mFid = 0;
298 : 0 : int mVertexIndex = 0; // e.g. vertex index
299 : : QgsPointXY mEdgePoints[2];
300 : : QgsPointXY mCentroid;
301 : : QgsPointXY mMiddleOfSegment;
302 : : };
303 : :
304 : : #ifndef SIP_RUN
305 : : typedef class QList<QgsPointLocator::Match> MatchList;
306 : : #else
307 : : typedef QList<QgsPointLocator::Match> MatchList;
308 : : #endif
309 : :
310 : : /**
311 : : * Interface that allows rejection of some matches in intersection queries
312 : : * (e.g. a match can only belong to a particular feature / match must not be a particular point).
313 : : * Implement the interface and pass its instance to QgsPointLocator or QgsSnappingUtils methods.
314 : : */
315 : : struct MatchFilter
316 : : {
317 : : virtual ~MatchFilter() = default;
318 : : virtual bool acceptMatch( const QgsPointLocator::Match &match ) = 0;
319 : : };
320 : :
321 : : // intersection queries
322 : :
323 : : /**
324 : : * Find nearest vertex to the specified point - up to distance specified by tolerance
325 : : * Optional filter may discard unwanted matches.
326 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
327 : : */
328 : : Match nearestVertex( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
329 : :
330 : : /**
331 : : * Find nearest centroid to the specified point - up to distance specified by tolerance
332 : : * Optional filter may discard unwanted matches.
333 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
334 : : * \since 3.12
335 : : */
336 : : Match nearestCentroid( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
337 : :
338 : : /**
339 : : * Find nearest middle of segment to the specified point - up to distance specified by tolerance
340 : : * Optional filter may discard unwanted matches.
341 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
342 : : * \since 3.12
343 : : */
344 : : Match nearestMiddleOfSegment( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
345 : :
346 : : /**
347 : : * Find nearest line endpoint (start or end vertex) to the specified point - up to distance specified by tolerance
348 : : * Optional filter may discard unwanted matches.
349 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
350 : : * \since 3.20
351 : : */
352 : : Match nearestLineEndpoints( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
353 : :
354 : : /**
355 : : * Find nearest edge to the specified point - up to distance specified by tolerance
356 : : * Optional filter may discard unwanted matches.
357 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
358 : : */
359 : : Match nearestEdge( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
360 : :
361 : : /**
362 : : * Find nearest area to the specified point - up to distance specified by tolerance
363 : : * Optional filter may discard unwanted matches.
364 : : * This will first perform a pointInPolygon and return first result.
365 : : * If no match is found and tolerance is not 0, it will return nearestEdge.
366 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
367 : : * \since QGIS 3.0
368 : : */
369 : : Match nearestArea( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
370 : :
371 : : /**
372 : : * Find edges within a specified rectangle
373 : : * Optional filter may discard unwanted matches.
374 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
375 : : */
376 : : MatchList edgesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
377 : :
378 : : /**
379 : : * Override of edgesInRect that construct rectangle from a center point and tolerance
380 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
381 : : */
382 : : MatchList edgesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
383 : :
384 : : /**
385 : : * Find vertices within a specified rectangle
386 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
387 : : * Optional filter may discard unwanted matches.
388 : : * \since QGIS 3.6
389 : : */
390 : : MatchList verticesInRect( const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
391 : :
392 : : /**
393 : : * Override of verticesInRect that construct rectangle from a center point and tolerance
394 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
395 : : * \since QGIS 3.6
396 : : */
397 : : MatchList verticesInRect( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
398 : :
399 : : // point-in-polygon query
400 : :
401 : : // TODO: function to return just the first match?
402 : :
403 : : /**
404 : : * find out if the \a point is in any polygons
405 : : * This method is either blocking or non blocking according to \a relaxed parameter passed
406 : : */
407 : : //!
408 : : MatchList pointInPolygon( const QgsPointXY &point, bool relaxed = false );
409 : :
410 : : /**
411 : : * Returns how many geometries are cached in the index
412 : : * \since QGIS 2.14
413 : : */
414 : 0 : int cachedGeometryCount() const { return mGeoms.count(); }
415 : :
416 : : /**
417 : : * Returns TRUE if the point locator is currently indexing the data.
418 : : * This method is useful if constructor parameter \a relaxed is TRUE
419 : : *
420 : : * \see QgsPointLocator()
421 : : */
422 : 0 : bool isIndexing() const { return mIsIndexing; }
423 : :
424 : : /**
425 : : * If the point locator has been initialized relaxedly and is currently indexing,
426 : : * this methods waits for the indexing to be finished
427 : : */
428 : : void waitForIndexingFinished();
429 : :
430 : : signals:
431 : :
432 : : /**
433 : : * Emitted whenever index has been built and initialization is finished
434 : : * \param ok FALSE if the creation of index has been prematurely stopped due to the limit of
435 : : * features, otherwise TRUE
436 : : */
437 : : void initFinished( bool ok );
438 : :
439 : : protected:
440 : : bool rebuildIndex( int maxFeaturesToIndex = -1 );
441 : :
442 : : protected slots:
443 : : void destroyIndex();
444 : : private slots:
445 : : void onInitTaskFinished();
446 : : void onFeatureAdded( QgsFeatureId fid );
447 : : void onFeatureDeleted( QgsFeatureId fid );
448 : : void onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom );
449 : : void onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value );
450 : :
451 : : private:
452 : :
453 : : /**
454 : : * prepare index if need and returns TRUE if the index is ready to be used
455 : : * \param relaxed TRUE if index build has to be non blocking
456 : : */
457 : : bool prepare( bool relaxed );
458 : :
459 : : //! Storage manager
460 : : std::unique_ptr< SpatialIndex::IStorageManager > mStorage;
461 : :
462 : : QHash<QgsFeatureId, QgsGeometry *> mGeoms;
463 : : std::unique_ptr< SpatialIndex::ISpatialIndex > mRTree;
464 : :
465 : : //! flag whether the layer is currently empty (i.e. mRTree is NULLPTR but it is not necessary to rebuild it)
466 : : bool mIsEmptyLayer = false;
467 : :
468 : :
469 : : //! R-tree containing spatial index
470 : : QgsCoordinateTransform mTransform;
471 : : QgsVectorLayer *mLayer = nullptr;
472 : : std::unique_ptr< QgsRectangle > mExtent;
473 : :
474 : : std::unique_ptr<QgsRenderContext> mContext;
475 : : std::unique_ptr<QgsFeatureRenderer> mRenderer;
476 : : std::unique_ptr<QgsVectorLayerFeatureSource> mSource;
477 : : int mMaxFeaturesToIndex = -1;
478 : : bool mIsIndexing = false;
479 : : bool mIsDestroying = false;
480 : : QgsFeatureIds mAddedFeatures;
481 : : QgsFeatureIds mDeletedFeatures;
482 : : QPointer<QgsPointLocatorInitTask> mInitTask;
483 : :
484 : : friend class QgsPointLocator_VisitorNearestVertex;
485 : : friend class QgsPointLocator_VisitorNearestCentroid;
486 : : friend class QgsPointLocator_VisitorNearestMiddleOfSegment;
487 : : friend class QgsPointLocator_VisitorNearestEdge;
488 : : friend class QgsPointLocator_VisitorArea;
489 : : friend class QgsPointLocator_VisitorEdgesInRect;
490 : : friend class QgsPointLocator_VisitorVerticesInRect;
491 : : friend class QgsPointLocatorInitTask;
492 : : friend class TestQgsPointLocator;
493 : : friend class QgsPointLocator_VisitorCentroidsInRect;
494 : : friend class QgsPointLocator_VisitorMiddlesInRect;
495 : : friend class QgsPointLocator_VisitorNearestLineEndpoint;
496 : : };
497 : :
498 : :
499 : : #endif // QGSPOINTLOCATOR_H
|