Skip to content

Commit 30684c7

Browse files
committedJan 20, 2015
Add support for match filtering, fix unit tests
1 parent 24adc02 commit 30684c7

File tree

6 files changed

+131
-49
lines changed

6 files changed

+131
-49
lines changed
 

‎src/core/qgspointlocator.cpp

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,12 @@ static SpatialIndex::Region rect2region( const QgsRectangle& rect )
110110
return SpatialIndex::Region( pLow, pHigh, 2 );
111111
}
112112

113+
#if 0
113114
static QgsRectangle region2rect( const SpatialIndex::Region& region )
114115
{
115116
return QgsRectangle( region.m_pLow[0], region.m_pLow[1], region.m_pHigh[0], region.m_pHigh[1] );
116117
}
118+
#endif
117119

118120
static void addPolylineToEdgeData( QLinkedList<RTree::Data*>& edgeDataList, const QgsPolyline& pl, id_type id, int& vertexIndex )
119121
{
@@ -255,13 +257,14 @@ static void edgeGetEndpoints( const RTree::Data& dd, double&x1, double&y1, doubl
255257
}
256258
}
257259

258-
260+
#if 0
259261
static LineSegment edge2lineSegment( const RTree::Data& dd )
260262
{
261263
double pStart[2], pEnd[2];
262264
edgeGetEndpoints( dd, pStart[0], pStart[1], pEnd[0], pEnd[1] );
263265
return LineSegment( pStart, pEnd, 2 );
264266
}
267+
#endif
265268

266269
// Ahh.... another magic number. Taken from QgsVectorLayer::snapToGeometry() call to closestSegmentWithContext().
267270
// The default epsilon used for sqrDistToSegment (1e-8) is too high when working with lat/lon coordinates
@@ -310,11 +313,11 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
310313
public:
311314
//! constructor for NN queries
312315
QgsPointLocator_VisitorVertexEdge( QgsVectorLayer* vl, bool vertexTree, const QgsPoint& origPt, QgsPointLocator::MatchList& list )
313-
: mLayer( vl ), mVertexTree( vertexTree ), mNNQuery( true ), mOrigPt( origPt ), mList( list ) {}
316+
: mLayer( vl ), mVertexTree( vertexTree ), mNNQuery( true ), mOrigPt( origPt ), mList( list ), mDistToPoint( 0 ), mFilter( 0 ) {}
314317

315318
//! constructor for range queries
316-
QgsPointLocator_VisitorVertexEdge( QgsVectorLayer* vl, bool vertexTree, const QgsRectangle& origRect, QgsPointLocator::MatchList& list, const QgsPoint* distToPoint = 0 )
317-
: mLayer( vl ), mVertexTree( vertexTree ), mNNQuery( false ), mOrigRect( origRect ), mList( list ), mDistToPoint( distToPoint ) {}
319+
QgsPointLocator_VisitorVertexEdge( QgsVectorLayer* vl, bool vertexTree, const QgsRectangle& origRect, QgsPointLocator::MatchList& list, const QgsPoint* distToPoint = 0, QgsPointLocator::MatchFilter* filter = 0 )
320+
: mLayer( vl ), mVertexTree( vertexTree ), mNNQuery( false ), mOrigRect( origRect ), mList( list ), mDistToPoint( distToPoint ), mFilter( filter ) {}
318321

319322
void visitNode( const INode& n ) { Q_UNUSED( n ); }
320323
void visitData( std::vector<const IData*>& v ) { Q_UNUSED( v ); }
@@ -327,7 +330,7 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
327330
QgsPoint edgePoints[2];
328331
if ( mNNQuery )
329332
{
330-
// neirest neighbor query
333+
// nearest neighbor query
331334
if ( mVertexTree )
332335
{
333336
pt = QgsPoint( dd.m_region.m_pLow[0], dd.m_region.m_pLow[1] );
@@ -372,7 +375,11 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
372375
}
373376
QgsPointLocator::Type t = mVertexTree ? QgsPointLocator::Vertex : QgsPointLocator::Edge;
374377
int vertexIndex = *reinterpret_cast<int*>( dd.m_pData );
375-
mList << QgsPointLocator::Match( t, mLayer, d.getIdentifier(), dist, pt, vertexIndex, t == QgsPointLocator::Edge ? edgePoints : 0 );
378+
QgsPointLocator::Match m( t, mLayer, d.getIdentifier(), dist, pt, vertexIndex, t == QgsPointLocator::Edge ? edgePoints : 0 );
379+
// in range queries the filter may reject some matches
380+
if ( mFilter && !mFilter->acceptMatch( m ) )
381+
return;
382+
mList << m;
376383
}
377384

378385
private:
@@ -383,6 +390,7 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
383390
QgsRectangle mOrigRect; // only for range queries
384391
QgsPointLocator::MatchList& mList;
385392
const QgsPoint* mDistToPoint; // optionally for range queries
393+
QgsPointLocator::MatchFilter* mFilter; // optionally for range queries
386394
};
387395

388396

@@ -704,21 +712,21 @@ QgsPointLocator::MatchList QgsPointLocator::nearestEdges( const QgsPoint& point,
704712
return lst;
705713
}
706714

707-
QgsPointLocator::MatchList QgsPointLocator::verticesInTolerance( const QgsPoint& point, double tolerance )
715+
QgsPointLocator::MatchList QgsPointLocator::verticesInTolerance( const QgsPoint& point, double tolerance, MatchFilter* filter )
708716
{
709717
QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
710-
MatchList lst = verticesInRect( rect, &point );
718+
MatchList lst = verticesInRect( rect, &point, filter );
711719
// make sure that only matches strictly within the tolerance are returned
712720
// (the intersection with rect may yield matches outside of tolerance)
713721
while ( !lst.isEmpty() && lst.last().distance() > tolerance )
714722
lst.removeLast();
715723
return lst;
716724
}
717725

718-
QgsPointLocator::MatchList QgsPointLocator::edgesInTolerance( const QgsPoint& point, double tolerance )
726+
QgsPointLocator::MatchList QgsPointLocator::edgesInTolerance( const QgsPoint& point, double tolerance, MatchFilter* filter )
719727
{
720728
QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
721-
MatchList lst = edgesInRect( rect, &point );
729+
MatchList lst = edgesInRect( rect, &point, filter );
722730
// make sure that only matches strictly within the tolerance are returned
723731
// (the intersection with rect may yield matches outside of tolerance)
724732
while ( !lst.isEmpty() && lst.last().distance() > tolerance )
@@ -731,7 +739,7 @@ static bool matchDistanceLessThan( const QgsPointLocator::Match& m1, const QgsPo
731739
return m1.distance() < m2.distance();
732740
}
733741

734-
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint )
742+
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint, MatchFilter* filter )
735743
{
736744
if ( !mRTreeVertex )
737745
{
@@ -741,7 +749,7 @@ QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle&
741749
}
742750

743751
MatchList lst;
744-
QgsPointLocator_VisitorVertexEdge visitor( mLayer, true, rect, lst, distToPoint );
752+
QgsPointLocator_VisitorVertexEdge visitor( mLayer, true, rect, lst, distToPoint, filter );
745753
mRTreeVertex->intersectsWithQuery( rect2region( rect ), visitor );
746754

747755
// if there is no distToPoint, all distances are zero, so no need to sort
@@ -752,7 +760,7 @@ QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle&
752760
}
753761

754762

755-
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint )
763+
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint, MatchFilter* filter )
756764
{
757765
if ( !mRTreeEdge )
758766
{
@@ -762,7 +770,7 @@ QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle& rec
762770
}
763771

764772
MatchList lst;
765-
QgsPointLocator_VisitorVertexEdge visitor( mLayer, false, rect, lst, distToPoint );
773+
QgsPointLocator_VisitorVertexEdge visitor( mLayer, false, rect, lst, distToPoint, filter );
766774
mRTreeEdge->intersectsWithQuery( rect2region( rect ), visitor );
767775

768776
// if there is no distToPoint, all distances are zero, so no need to sort

‎src/core/qgspointlocator.h

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ class QgsPointLocator : public QObject
133133

134134
typedef struct QList<Match> MatchList;
135135

136+
//! Interface that allows rejection of some matches in intersection queries
137+
//! (e.g. a match can only belong to a particular feature / match must not be a particular point).
138+
//! Implement the interface and pass its instance to QgsPointLocator or QgsSnappingUtils methods.
139+
struct MatchFilter
140+
{
141+
virtual bool acceptMatch( const Match& match ) = 0;
142+
};
143+
136144
// 1-NN queries
137145

138146
//! find nearest vertex to the specified point
@@ -159,19 +167,23 @@ class QgsPointLocator : public QObject
159167

160168
// intersection queries
161169

162-
//! find nearest vertices to the specified point - sorted by distance
163-
//! will return matches up to distance given by tolerance
164-
MatchList verticesInTolerance( const QgsPoint& point, double tolerance );
165-
//! find nearest edges to the specified point - sorted by distance
166-
//! will return matches up to distance given by tolerance
167-
MatchList edgesInTolerance( const QgsPoint& point, double tolerance );
168-
169-
//! find vertices within given rectangle
170-
//! if distToPoint is given, the matches will be sorted by distance to that point
171-
MatchList verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0 );
172-
//! find edges within given rectangle
173-
//! if distToPoint is given, the matches will be sorted by distance to that point
174-
MatchList edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0 );
170+
//! Find nearest vertices to the specified point - sorted by distance.
171+
//! Will return matches up to distance given by tolerance.
172+
//! Optional filter may discard unwanted matches.
173+
MatchList verticesInTolerance( const QgsPoint& point, double tolerance, MatchFilter* filter = 0 );
174+
//! Find nearest edges to the specified point - sorted by distance.
175+
//! Will return matches up to distance given by tolerance.
176+
//! Optional filter may discard unwanted matches.
177+
MatchList edgesInTolerance( const QgsPoint& point, double tolerance, MatchFilter* filter = 0 );
178+
179+
//! Find vertices within given rectangle.
180+
//! If distToPoint is given, the matches will be sorted by distance to that point.
181+
//! Optional filter may discard unwanted matches.
182+
MatchList verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0, MatchFilter* filter = 0 );
183+
//! Find edges within given rectangle.
184+
//! If distToPoint is given, the matches will be sorted by distance to that point.
185+
//! Optional filter may discard unwanted matches.
186+
MatchList edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0, MatchFilter* filter = 0 );
175187

176188
// point-in-polygon query
177189

‎src/core/qgssnappingutils.cpp

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,44 @@ static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPoint& p
133133
}
134134

135135

136-
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QPoint& point )
136+
static void _updateBestMatch( QgsPointLocator::Match& bestMatch, const QgsPoint& pointMap, QgsPointLocator* loc, int type, double tolerance, QgsPointLocator::MatchFilter* filter )
137137
{
138-
return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ) );
138+
// when filter is used we can't use just the closest match (NN queries do not support filters)
139+
// TODO: could be optimized with a new call nearestVertexInTolerance() / nearestEdgeInTolerance()
140+
// so that we do not waste time gathering matches that are not needed
141+
142+
if ( type & QgsPointLocator::Vertex )
143+
{
144+
if ( filter )
145+
{
146+
QgsPointLocator::MatchList lst = loc->verticesInTolerance( pointMap, tolerance, filter );
147+
if ( !lst.isEmpty() )
148+
bestMatch.replaceIfBetter( lst.first(), tolerance );
149+
}
150+
else
151+
bestMatch.replaceIfBetter( loc->nearestVertex( pointMap ), tolerance );
152+
}
153+
if ( type & QgsPointLocator::Edge )
154+
{
155+
if ( filter )
156+
{
157+
QgsPointLocator::MatchList lst = loc->edgesInTolerance( pointMap, tolerance, filter );
158+
if ( !lst.isEmpty() )
159+
bestMatch.replaceIfBetter( lst.first(), tolerance );
160+
}
161+
else
162+
bestMatch.replaceIfBetter( loc->nearestEdge( pointMap ), tolerance );
163+
}
139164
}
140165

141-
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap )
166+
167+
168+
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QPoint& point, QgsPointLocator::MatchFilter* filter )
169+
{
170+
return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter );
171+
}
172+
173+
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter )
142174
{
143175
Q_ASSERT( mMapSettings.hasValidSettings() );
144176

@@ -158,10 +190,7 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap )
158190
return QgsPointLocator::Match();
159191

160192
QgsPointLocator::Match bestMatch;
161-
if ( type & QgsPointLocator::Vertex )
162-
bestMatch.replaceIfBetter( loc->nearestVertex( pointMap ), tolerance );
163-
if ( type & QgsPointLocator::Edge )
164-
bestMatch.replaceIfBetter( loc->nearestEdge( pointMap ), tolerance );
193+
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );
165194

166195
if ( mSnapOnIntersection )
167196
{
@@ -183,10 +212,8 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap )
183212
if ( QgsPointLocator* loc = locatorForLayer( layerConfig.layer ) )
184213
{
185214
loc->init( layerConfig.type );
186-
if ( layerConfig.type & QgsPointLocator::Vertex )
187-
bestMatch.replaceIfBetter( loc->nearestVertex( pointMap ), tolerance );
188-
if ( layerConfig.type & QgsPointLocator::Edge )
189-
bestMatch.replaceIfBetter( loc->nearestEdge( pointMap ), tolerance );
215+
216+
_updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter );
190217

191218
if ( mSnapOnIntersection )
192219
{

‎src/core/qgssnappingutils.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ class QgsSnappingUtils : public QObject
3939
/** get a point locator for the given layer. If such locator does not exist, it will be created */
4040
QgsPointLocator* locatorForLayer( QgsVectorLayer* vl );
4141

42-
/** snap to map according to the current configuration (mode) */
43-
QgsPointLocator::Match snapToMap( const QPoint& point );
44-
QgsPointLocator::Match snapToMap( const QgsPoint& pointMap );
42+
/** snap to map according to the current configuration (mode). Optional filter allows to discard unwanted matches. */
43+
QgsPointLocator::Match snapToMap( const QPoint& point, QgsPointLocator::MatchFilter* filter = 0 );
44+
QgsPointLocator::Match snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter = 0 );
4545
// TODO: multi-variant
4646

4747

‎tests/src/core/testqgspointlocator.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@
2525
#include "qgspointlocator.h"
2626

2727

28+
struct FilterExcludePoint : public QgsPointLocator::MatchFilter
29+
{
30+
FilterExcludePoint( const QgsPoint& p ) : mPoint( p ) {}
31+
32+
bool acceptMatch( const QgsPointLocator::Match& match ) { return match.point() != mPoint; }
33+
34+
QgsPoint mPoint;
35+
};
36+
37+
2838
class TestQgsPointLocator : public QObject
2939
{
3040
Q_OBJECT
@@ -126,6 +136,12 @@ class TestQgsPointLocator : public QObject
126136

127137
QgsPointLocator::MatchList lst2 = loc.verticesInTolerance( QgsPoint( 1, 0 ), 1 );
128138
QCOMPARE( lst2.count(), 2 );
139+
140+
// test match filtering
141+
FilterExcludePoint myFilter( QgsPoint( 1, 0 ) );
142+
QgsPointLocator::MatchList lst3 = loc.verticesInTolerance( QgsPoint( 1, 0 ), 1, &myFilter );
143+
QCOMPARE( lst3.count(), 1 );
144+
QCOMPARE( lst3[0].point(), QgsPoint( 1, 1 ) );
129145
}
130146

131147
void testEdgesInTolerance()
@@ -142,6 +158,12 @@ class TestQgsPointLocator : public QObject
142158

143159
QgsPointLocator::MatchList lst2 = loc.edgesInTolerance( QgsPoint( 0, 0 ), 0.9 );
144160
QCOMPARE( lst2.count(), 1 );
161+
162+
// test match filtering
163+
FilterExcludePoint myFilter( QgsPoint( 0.5, 0.5 ) );
164+
QgsPointLocator::MatchList lst3 = loc.edgesInTolerance( QgsPoint( 0, 0 ), 2, &myFilter );
165+
QCOMPARE( lst3.count(), 2 );
166+
QVERIFY( lst3[0].point() == QgsPoint( 0, 1 ) || lst3[0].point() == QgsPoint( 1, 0 ) );
145167
}
146168

147169

‎tests/src/core/testqgssnappingutils.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@
2525
#include "qgssnappingutils.h"
2626

2727

28+
struct FilterExcludePoint : public QgsPointLocator::MatchFilter
29+
{
30+
FilterExcludePoint( const QgsPoint& p ) : mPoint( p ) {}
31+
32+
bool acceptMatch( const QgsPointLocator::Match& match ) { return match.point() != mPoint; }
33+
34+
QgsPoint mPoint;
35+
};
36+
37+
2838
class TestQgsSnappingUtils : public QObject
2939
{
3040
Q_OBJECT
@@ -72,22 +82,19 @@ class TestQgsSnappingUtils : public QObject
7282
mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
7383
QVERIFY( mapSettings.hasValidSettings() );
7484

75-
QSettings settings;
7685
QgsSnappingUtils u;
7786
u.setMapSettings( mapSettings );
7887
u.setCurrentLayer( mVL );
7988

8089
// first try with no snapping enabled
81-
settings.setValue( "/qgis/digitizing/default_snap_mode", "off" );
90+
u.setDefaultSettings( 0, 10, QgsTolerance::Pixels );
8291

8392
QgsPointLocator::Match m0 = u.snapToMap( QPoint( 100, 100 ) );
8493
QVERIFY( !m0.isValid() );
8594
QVERIFY( !m0.hasVertex() );
8695

8796
// now enable snapping
88-
settings.setValue( "/qgis/digitizing/default_snap_mode", "to vertex and segment" );
89-
settings.setValue( "/qgis/digitizing/default_snapping_tolerance", 10 );
90-
settings.setValue( "/qgis/digitizing/default_snapping_tolerance_unit", 1 );
97+
u.setDefaultSettings( QgsPointLocator::Vertex | QgsPointLocator::Edge, 10, QgsTolerance::Pixels );
9198

9299
QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) );
93100
QVERIFY( m.isValid() );
@@ -97,6 +104,11 @@ class TestQgsSnappingUtils : public QObject
97104
QgsPointLocator::Match m2 = u.snapToMap( QPoint( 0, 100 ) );
98105
QVERIFY( !m2.isValid() );
99106
QVERIFY( !m2.hasVertex() );
107+
108+
// test with filtering
109+
FilterExcludePoint myFilter( QgsPoint( 1, 0 ) );
110+
QgsPointLocator::Match m3 = u.snapToMap( QPoint( 100, 100 ), &myFilter );
111+
QVERIFY( !m3.isValid() );
100112
}
101113

102114
void testSnapModePerLayer()
@@ -106,9 +118,6 @@ class TestQgsSnappingUtils : public QObject
106118
mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
107119
QVERIFY( mapSettings.hasValidSettings() );
108120

109-
QSettings settings;
110-
settings.setValue( "/qgis/digitizing/default_snap_mode", "off" );
111-
112121
QgsSnappingUtils u;
113122
u.setMapSettings( mapSettings );
114123
u.setSnapToMapMode( QgsSnappingUtils::SnapPerLayerConfig );
@@ -121,6 +130,10 @@ class TestQgsSnappingUtils : public QObject
121130
QVERIFY( m.hasVertex() );
122131
QCOMPARE( m.point(), QgsPoint( 1, 0 ) );
123132

133+
// test with filtering
134+
FilterExcludePoint myFilter( QgsPoint( 1, 0 ) );
135+
QgsPointLocator::Match m2 = u.snapToMap( QPoint( 100, 100 ), &myFilter );
136+
QVERIFY( !m2.isValid() );
124137
}
125138
};
126139

0 commit comments

Comments
 (0)
Please sign in to comment.