Skip to content

Commit

Permalink
Add support for match filtering, fix unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wonder-sk committed Jan 20, 2015
1 parent 24adc02 commit 30684c7
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 49 deletions.
36 changes: 22 additions & 14 deletions src/core/qgspointlocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,12 @@ static SpatialIndex::Region rect2region( const QgsRectangle& rect )
return SpatialIndex::Region( pLow, pHigh, 2 );
}

#if 0
static QgsRectangle region2rect( const SpatialIndex::Region& region )
{
return QgsRectangle( region.m_pLow[0], region.m_pLow[1], region.m_pHigh[0], region.m_pHigh[1] );
}
#endif

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


#if 0
static LineSegment edge2lineSegment( const RTree::Data& dd )
{
double pStart[2], pEnd[2];
edgeGetEndpoints( dd, pStart[0], pStart[1], pEnd[0], pEnd[1] );
return LineSegment( pStart, pEnd, 2 );
}
#endif

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

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

void visitNode( const INode& n ) { Q_UNUSED( n ); }
void visitData( std::vector<const IData*>& v ) { Q_UNUSED( v ); }
Expand All @@ -327,7 +330,7 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
QgsPoint edgePoints[2];
if ( mNNQuery )
{
// neirest neighbor query
// nearest neighbor query
if ( mVertexTree )
{
pt = QgsPoint( dd.m_region.m_pLow[0], dd.m_region.m_pLow[1] );
Expand Down Expand Up @@ -372,7 +375,11 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
}
QgsPointLocator::Type t = mVertexTree ? QgsPointLocator::Vertex : QgsPointLocator::Edge;
int vertexIndex = *reinterpret_cast<int*>( dd.m_pData );
mList << QgsPointLocator::Match( t, mLayer, d.getIdentifier(), dist, pt, vertexIndex, t == QgsPointLocator::Edge ? edgePoints : 0 );
QgsPointLocator::Match m( t, mLayer, d.getIdentifier(), dist, pt, vertexIndex, t == QgsPointLocator::Edge ? edgePoints : 0 );
// in range queries the filter may reject some matches
if ( mFilter && !mFilter->acceptMatch( m ) )
return;
mList << m;
}

private:
Expand All @@ -383,6 +390,7 @@ class QgsPointLocator_VisitorVertexEdge : public IVisitor
QgsRectangle mOrigRect; // only for range queries
QgsPointLocator::MatchList& mList;
const QgsPoint* mDistToPoint; // optionally for range queries
QgsPointLocator::MatchFilter* mFilter; // optionally for range queries
};


Expand Down Expand Up @@ -704,21 +712,21 @@ QgsPointLocator::MatchList QgsPointLocator::nearestEdges( const QgsPoint& point,
return lst;
}

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

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

QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint )
QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint, MatchFilter* filter )
{
if ( !mRTreeVertex )
{
Expand All @@ -741,7 +749,7 @@ QgsPointLocator::MatchList QgsPointLocator::verticesInRect( const QgsRectangle&
}

MatchList lst;
QgsPointLocator_VisitorVertexEdge visitor( mLayer, true, rect, lst, distToPoint );
QgsPointLocator_VisitorVertexEdge visitor( mLayer, true, rect, lst, distToPoint, filter );
mRTreeVertex->intersectsWithQuery( rect2region( rect ), visitor );

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


QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint )
QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint, MatchFilter* filter )
{
if ( !mRTreeEdge )
{
Expand All @@ -762,7 +770,7 @@ QgsPointLocator::MatchList QgsPointLocator::edgesInRect( const QgsRectangle& rec
}

MatchList lst;
QgsPointLocator_VisitorVertexEdge visitor( mLayer, false, rect, lst, distToPoint );
QgsPointLocator_VisitorVertexEdge visitor( mLayer, false, rect, lst, distToPoint, filter );
mRTreeEdge->intersectsWithQuery( rect2region( rect ), visitor );

// if there is no distToPoint, all distances are zero, so no need to sort
Expand Down
38 changes: 25 additions & 13 deletions src/core/qgspointlocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ class QgsPointLocator : public QObject

typedef struct QList<Match> MatchList;

//! Interface that allows rejection of some matches in intersection queries
//! (e.g. a match can only belong to a particular feature / match must not be a particular point).
//! Implement the interface and pass its instance to QgsPointLocator or QgsSnappingUtils methods.
struct MatchFilter
{
virtual bool acceptMatch( const Match& match ) = 0;
};

// 1-NN queries

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

// intersection queries

//! find nearest vertices to the specified point - sorted by distance
//! will return matches up to distance given by tolerance
MatchList verticesInTolerance( const QgsPoint& point, double tolerance );
//! find nearest edges to the specified point - sorted by distance
//! will return matches up to distance given by tolerance
MatchList edgesInTolerance( const QgsPoint& point, double tolerance );

//! find vertices within given rectangle
//! if distToPoint is given, the matches will be sorted by distance to that point
MatchList verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0 );
//! find edges within given rectangle
//! if distToPoint is given, the matches will be sorted by distance to that point
MatchList edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0 );
//! Find nearest vertices to the specified point - sorted by distance.
//! Will return matches up to distance given by tolerance.
//! Optional filter may discard unwanted matches.
MatchList verticesInTolerance( const QgsPoint& point, double tolerance, MatchFilter* filter = 0 );
//! Find nearest edges to the specified point - sorted by distance.
//! Will return matches up to distance given by tolerance.
//! Optional filter may discard unwanted matches.
MatchList edgesInTolerance( const QgsPoint& point, double tolerance, MatchFilter* filter = 0 );

//! Find vertices within given rectangle.
//! If distToPoint is given, the matches will be sorted by distance to that point.
//! Optional filter may discard unwanted matches.
MatchList verticesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0, MatchFilter* filter = 0 );
//! Find edges within given rectangle.
//! If distToPoint is given, the matches will be sorted by distance to that point.
//! Optional filter may discard unwanted matches.
MatchList edgesInRect( const QgsRectangle& rect, const QgsPoint* distToPoint = 0, MatchFilter* filter = 0 );

// point-in-polygon query

Expand Down
49 changes: 38 additions & 11 deletions src/core/qgssnappingutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,44 @@ static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPoint& p
}


QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QPoint& point )
static void _updateBestMatch( QgsPointLocator::Match& bestMatch, const QgsPoint& pointMap, QgsPointLocator* loc, int type, double tolerance, QgsPointLocator::MatchFilter* filter )
{
return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ) );
// when filter is used we can't use just the closest match (NN queries do not support filters)
// TODO: could be optimized with a new call nearestVertexInTolerance() / nearestEdgeInTolerance()
// so that we do not waste time gathering matches that are not needed

if ( type & QgsPointLocator::Vertex )
{
if ( filter )
{
QgsPointLocator::MatchList lst = loc->verticesInTolerance( pointMap, tolerance, filter );
if ( !lst.isEmpty() )
bestMatch.replaceIfBetter( lst.first(), tolerance );
}
else
bestMatch.replaceIfBetter( loc->nearestVertex( pointMap ), tolerance );
}
if ( type & QgsPointLocator::Edge )
{
if ( filter )
{
QgsPointLocator::MatchList lst = loc->edgesInTolerance( pointMap, tolerance, filter );
if ( !lst.isEmpty() )
bestMatch.replaceIfBetter( lst.first(), tolerance );
}
else
bestMatch.replaceIfBetter( loc->nearestEdge( pointMap ), tolerance );
}
}

QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap )


QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QPoint& point, QgsPointLocator::MatchFilter* filter )
{
return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter );
}

QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap, QgsPointLocator::MatchFilter* filter )
{
Q_ASSERT( mMapSettings.hasValidSettings() );

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

QgsPointLocator::Match bestMatch;
if ( type & QgsPointLocator::Vertex )
bestMatch.replaceIfBetter( loc->nearestVertex( pointMap ), tolerance );
if ( type & QgsPointLocator::Edge )
bestMatch.replaceIfBetter( loc->nearestEdge( pointMap ), tolerance );
_updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter );

if ( mSnapOnIntersection )
{
Expand All @@ -183,10 +212,8 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint& pointMap )
if ( QgsPointLocator* loc = locatorForLayer( layerConfig.layer ) )
{
loc->init( layerConfig.type );
if ( layerConfig.type & QgsPointLocator::Vertex )
bestMatch.replaceIfBetter( loc->nearestVertex( pointMap ), tolerance );
if ( layerConfig.type & QgsPointLocator::Edge )
bestMatch.replaceIfBetter( loc->nearestEdge( pointMap ), tolerance );

_updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter );

if ( mSnapOnIntersection )
{
Expand Down
6 changes: 3 additions & 3 deletions src/core/qgssnappingutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ class QgsSnappingUtils : public QObject
/** get a point locator for the given layer. If such locator does not exist, it will be created */
QgsPointLocator* locatorForLayer( QgsVectorLayer* vl );

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


Expand Down
22 changes: 22 additions & 0 deletions tests/src/core/testqgspointlocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
#include "qgspointlocator.h"


struct FilterExcludePoint : public QgsPointLocator::MatchFilter
{
FilterExcludePoint( const QgsPoint& p ) : mPoint( p ) {}

bool acceptMatch( const QgsPointLocator::Match& match ) { return match.point() != mPoint; }

QgsPoint mPoint;
};


class TestQgsPointLocator : public QObject
{
Q_OBJECT
Expand Down Expand Up @@ -126,6 +136,12 @@ class TestQgsPointLocator : public QObject

QgsPointLocator::MatchList lst2 = loc.verticesInTolerance( QgsPoint( 1, 0 ), 1 );
QCOMPARE( lst2.count(), 2 );

// test match filtering
FilterExcludePoint myFilter( QgsPoint( 1, 0 ) );
QgsPointLocator::MatchList lst3 = loc.verticesInTolerance( QgsPoint( 1, 0 ), 1, &myFilter );
QCOMPARE( lst3.count(), 1 );
QCOMPARE( lst3[0].point(), QgsPoint( 1, 1 ) );
}

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

QgsPointLocator::MatchList lst2 = loc.edgesInTolerance( QgsPoint( 0, 0 ), 0.9 );
QCOMPARE( lst2.count(), 1 );

// test match filtering
FilterExcludePoint myFilter( QgsPoint( 0.5, 0.5 ) );
QgsPointLocator::MatchList lst3 = loc.edgesInTolerance( QgsPoint( 0, 0 ), 2, &myFilter );
QCOMPARE( lst3.count(), 2 );
QVERIFY( lst3[0].point() == QgsPoint( 0, 1 ) || lst3[0].point() == QgsPoint( 1, 0 ) );
}


Expand Down
29 changes: 21 additions & 8 deletions tests/src/core/testqgssnappingutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@
#include "qgssnappingutils.h"


struct FilterExcludePoint : public QgsPointLocator::MatchFilter
{
FilterExcludePoint( const QgsPoint& p ) : mPoint( p ) {}

bool acceptMatch( const QgsPointLocator::Match& match ) { return match.point() != mPoint; }

QgsPoint mPoint;
};


class TestQgsSnappingUtils : public QObject
{
Q_OBJECT
Expand Down Expand Up @@ -72,22 +82,19 @@ class TestQgsSnappingUtils : public QObject
mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
QVERIFY( mapSettings.hasValidSettings() );

QSettings settings;
QgsSnappingUtils u;
u.setMapSettings( mapSettings );
u.setCurrentLayer( mVL );

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

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

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

QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) );
QVERIFY( m.isValid() );
Expand All @@ -97,6 +104,11 @@ class TestQgsSnappingUtils : public QObject
QgsPointLocator::Match m2 = u.snapToMap( QPoint( 0, 100 ) );
QVERIFY( !m2.isValid() );
QVERIFY( !m2.hasVertex() );

// test with filtering
FilterExcludePoint myFilter( QgsPoint( 1, 0 ) );
QgsPointLocator::Match m3 = u.snapToMap( QPoint( 100, 100 ), &myFilter );
QVERIFY( !m3.isValid() );
}

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

QSettings settings;
settings.setValue( "/qgis/digitizing/default_snap_mode", "off" );

QgsSnappingUtils u;
u.setMapSettings( mapSettings );
u.setSnapToMapMode( QgsSnappingUtils::SnapPerLayerConfig );
Expand All @@ -121,6 +130,10 @@ class TestQgsSnappingUtils : public QObject
QVERIFY( m.hasVertex() );
QCOMPARE( m.point(), QgsPoint( 1, 0 ) );

// test with filtering
FilterExcludePoint myFilter( QgsPoint( 1, 0 ) );
QgsPointLocator::Match m2 = u.snapToMap( QPoint( 100, 100 ), &myFilter );
QVERIFY( !m2.isValid() );
}
};

Expand Down

0 comments on commit 30684c7

Please sign in to comment.