Skip to content

Commit e34d7fb

Browse files
committedNov 28, 2017
Fix left of test for linestrings
The test was returning invalid results for certain geometries
1 parent 8b454ea commit e34d7fb

File tree

4 files changed

+99
-6
lines changed

4 files changed

+99
-6
lines changed
 

‎python/core/geometry/qgsgeometryutils.sip

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ class QgsGeometryUtils
125125

126126
static double leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
127127
%Docstring
128-
Returns < 0 if point(x/y) is left of the line x1,y1 -> x2,y2
128+
Returns a value < 0 if the point (``x``, ``y``) is left of the line from (``x1``, ``y1``) -> ( ``x2``, ``y2``).
129+
A positive return value indicates the point is to the right of the line.
130+
131+
If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
132+
on the line, or exactly in line with the segment) and the result is undefined.
129133
:rtype: float
130134
%End
131135

‎src/core/geometry/qgsgeometryutils.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,13 @@ class CORE_EXPORT QgsGeometryUtils
147147
*/
148148
static QVector<SelfIntersection> getSelfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance ) SIP_SKIP;
149149

150-
//! Returns < 0 if point(x/y) is left of the line x1,y1 -> x2,y2
150+
/**
151+
* Returns a value < 0 if the point (\a x, \a y) is left of the line from (\a x1, \a y1) -> ( \a x2, \a y2).
152+
* A positive return value indicates the point is to the right of the line.
153+
*
154+
* If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
155+
* on the line, or exactly in line with the segment) and the result is undefined.
156+
*/
151157
static double leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
152158

153159
/**

‎src/core/geometry/qgslinestring.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -903,9 +903,16 @@ void QgsLineString::addVertex( const QgsPoint &pt )
903903
double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, bool *leftOf, double epsilon ) const
904904
{
905905
double sqrDist = std::numeric_limits<double>::max();
906+
double leftOfDist = std::numeric_limits<double>::max();
907+
bool prevLeftOf = false;
908+
double prevLeftOfX;
909+
double prevLeftOfY;
906910
double testDist = 0;
907911
double segmentPtX, segmentPtY;
908912

913+
if ( leftOf )
914+
*leftOf = false;
915+
909916
int size = mX.size();
910917
if ( size == 0 || size == 1 )
911918
{
@@ -924,14 +931,36 @@ double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt,
924931
sqrDist = testDist;
925932
segmentPt.setX( segmentPtX );
926933
segmentPt.setY( segmentPtY );
927-
if ( leftOf )
928-
{
929-
*leftOf = ( QgsGeometryUtils::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY ) < 0 );
930-
}
931934
vertexAfter.part = 0;
932935
vertexAfter.ring = 0;
933936
vertexAfter.vertex = i;
934937
}
938+
if ( leftOf && qgsDoubleNear( testDist, sqrDist ) )
939+
{
940+
double left = QgsGeometryUtils::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY );
941+
// if left equals 0, the test could not be performed (e.g. point in line with segment or on segment)
942+
// so don't set leftOf in this case, and hope that there's another segment that's the same distance
943+
// where we can perform the check
944+
if ( !qgsDoubleNear( left, 0 ) )
945+
{
946+
if ( qgsDoubleNear( testDist, leftOfDist ) && ( left < 0 ) != prevLeftOf )
947+
{
948+
// we have two possible segments each with equal distance to point, but they disagree
949+
// on whether or not the point is to the left of them.
950+
// so we test the segments themselves and flip the result.
951+
// see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline
952+
*leftOf = QgsGeometryUtils::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY ) > 0;
953+
}
954+
else
955+
{
956+
*leftOf = left < 0;
957+
}
958+
prevLeftOf = *leftOf;
959+
leftOfDist = testDist;
960+
prevLeftOfX = prevX;
961+
prevLeftOfY = prevY;
962+
}
963+
}
935964
}
936965
return sqrDist;
937966
}

‎tests/src/core/testqgsgeometry.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3880,6 +3880,60 @@ void TestQgsGeometry::lineString()
38803880
QCOMPARE( v, QgsVertexId( 0, 0, 2 ) );
38813881
QCOMPARE( leftOf, false );
38823882

3883+
l35.setPoints( QgsPointSequence() << QgsPoint( 5, 5 )
3884+
<< QgsPoint( 6, 4 )
3885+
<< QgsPoint( 4, 4 )
3886+
<< QgsPoint( 5, 5 ) );
3887+
QGSCOMPARENEAR( l35.closestSegment( QgsPoint( 2.35, 4 ), p, v, &leftOf ), 2.7225, 4 * DBL_EPSILON );
3888+
QCOMPARE( p, QgsPoint( 4, 4 ) );
3889+
QCOMPARE( v, QgsVertexId( 0, 0, 2 ) );
3890+
QCOMPARE( leftOf, true );
3891+
3892+
l35.setPoints( QgsPointSequence() << QgsPoint( 5, 5 )
3893+
<< QgsPoint( 4, 4 )
3894+
<< QgsPoint( 6, 4 )
3895+
<< QgsPoint( 5, 5 ) );
3896+
QGSCOMPARENEAR( l35.closestSegment( QgsPoint( 2.35, 4 ), p, v, &leftOf ), 2.7225, 4 * DBL_EPSILON );
3897+
QCOMPARE( p, QgsPoint( 4, 4 ) );
3898+
QCOMPARE( v, QgsVertexId( 0, 0, 1 ) );
3899+
QCOMPARE( leftOf, false );
3900+
3901+
l35.setPoints( QgsPointSequence() << QgsPoint( 5, 5 )
3902+
<< QgsPoint( 6, 4 )
3903+
<< QgsPoint( 4, 4 )
3904+
<< QgsPoint( 5, 5 ) );
3905+
QGSCOMPARENEAR( l35.closestSegment( QgsPoint( 3.5, 2 ), p, v, &leftOf ), 4.250000, 4 * DBL_EPSILON );
3906+
QCOMPARE( p, QgsPoint( 4, 4 ) );
3907+
QCOMPARE( v, QgsVertexId( 0, 0, 2 ) );
3908+
QCOMPARE( leftOf, true );
3909+
3910+
l35.setPoints( QgsPointSequence() << QgsPoint( 5, 5 )
3911+
<< QgsPoint( 4, 4 )
3912+
<< QgsPoint( 6, 4 )
3913+
<< QgsPoint( 5, 5 ) );
3914+
QGSCOMPARENEAR( l35.closestSegment( QgsPoint( 3.5, 2 ), p, v, &leftOf ), 4.250000, 4 * DBL_EPSILON );
3915+
QCOMPARE( p, QgsPoint( 4, 4 ) );
3916+
QCOMPARE( v, QgsVertexId( 0, 0, 1 ) );
3917+
QCOMPARE( leftOf, false );
3918+
3919+
l35.setPoints( QgsPointSequence() << QgsPoint( 1, 1 )
3920+
<< QgsPoint( 1, 4 )
3921+
<< QgsPoint( 2, 2 )
3922+
<< QgsPoint( 1, 1 ) );
3923+
QGSCOMPARENEAR( l35.closestSegment( QgsPoint( 1, 0 ), p, v, &leftOf ), 1, 4 * DBL_EPSILON );
3924+
QCOMPARE( p, QgsPoint( 1, 1 ) );
3925+
QCOMPARE( v, QgsVertexId( 0, 0, 1 ) );
3926+
QCOMPARE( leftOf, true );
3927+
3928+
l35.setPoints( QgsPointSequence() << QgsPoint( 1, 1 )
3929+
<< QgsPoint( 2, 2 )
3930+
<< QgsPoint( 1, 4 )
3931+
<< QgsPoint( 1, 1 ) );
3932+
QGSCOMPARENEAR( l35.closestSegment( QgsPoint( 1, 0 ), p, v, &leftOf ), 1, 4 * DBL_EPSILON );
3933+
QCOMPARE( p, QgsPoint( 1, 1 ) );
3934+
QCOMPARE( v, QgsVertexId( 0, 0, 1 ) );
3935+
QCOMPARE( leftOf, false );
3936+
38833937
//sumUpArea
38843938
QgsLineString l36;
38853939
double area = 1.0; //sumUpArea adds to area, so start with non-zero value

0 commit comments

Comments
 (0)
Please sign in to comment.