Skip to content

Commit 1dca332

Browse files
authored
Merge pull request #4771 from m-kuhn/closest_point
Closest point
2 parents 81653d6 + fd8158b commit 1dca332

10 files changed

+100
-46
lines changed

python/core/geometry/qgsgeometryutils.sip

+5-3
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ class QgsGeometryUtils
3737
:rtype: QgsPoint
3838
%End
3939

40-
static double closestPointMeasure( const QgsAbstractGeometry &geom, const QgsPoint &pt );
40+
static QgsPoint closestPoint( const QgsAbstractGeometry &geometry, const QgsPoint &point );
4141
%Docstring
42-
Returns measure of nearest point on a geometry for a specified point or NaN if geometry does not have measures
43-
:rtype: float
42+
Returns the nearest point on a segment of a ``geometry``
43+
for the specified ``point``. The z and m values will be linearly interpolated between
44+
the two neighbouring vertices.
45+
:rtype: QgsPoint
4446
%End
4547

4648
static double distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id );

python/core/geometry/qgslinestring.sip

+4-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class QgsLineString: QgsCurve
2929
QgsLineString( const QVector<QgsPoint> &points );
3030
%Docstring
3131
Construct a linestring from a vector of points.
32+
Z and M type will be set based on the type of the first point
33+
in the vector.
3234
.. versionadded:: 3.0
3335
%End
3436

@@ -73,7 +75,7 @@ class QgsLineString: QgsCurve
7375
%Docstring
7476
Returns the z-coordinate of the specified node in the line string.
7577
\param index index of node, where the first node in the line is 0
76-
:return: z-coordinate of node, or 0.0 if index is out of bounds or the line
78+
:return: z-coordinate of node, or ``nan`` if index is out of bounds or the line
7779
does not have a z dimension
7880
.. seealso:: setZAt()
7981
:rtype: float
@@ -83,7 +85,7 @@ class QgsLineString: QgsCurve
8385
%Docstring
8486
Returns the m value of the specified node in the line string.
8587
\param index index of node, where the first node in the line is 0
86-
:return: m value of node, or 0.0 if index is out of bounds or the line
88+
:return: m value of node, or ``nan`` if index is out of bounds or the line
8789
does not have m values
8890
.. seealso:: setMAt()
8991
:rtype: float

src/core/geometry/qgscurve.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ bool QgsCurve::isClosed() const
3131
//don't consider M-coordinates when testing closedness
3232
QgsPoint start = startPoint();
3333
QgsPoint end = endPoint();
34-
return ( qgsDoubleNear( start.x(), end.x(), 1E-8 ) &&
35-
qgsDoubleNear( start.y(), end.y(), 1E-8 ) &&
36-
qgsDoubleNear( start.z(), end.z(), 1E-8 ) );
34+
35+
bool closed = qgsDoubleNear( start.x(), end.x(), 1E-8 ) &&
36+
qgsDoubleNear( start.y(), end.y(), 1E-8 );
37+
if ( is3D() && closed )
38+
closed &= qgsDoubleNear( start.z(), end.z(), 1E-8 ) || ( qIsNaN( start.z() ) && qIsNaN( end.z() ) );
39+
return closed;
3740
}
3841

3942
bool QgsCurve::isRing() const

src/core/geometry/qgsgeometryutils.cpp

+25-20
Original file line numberDiff line numberDiff line change
@@ -93,33 +93,38 @@ QgsPoint QgsGeometryUtils::closestVertex( const QgsAbstractGeometry &geom, const
9393
return minDistPoint;
9494
}
9595

96-
double QgsGeometryUtils::closestPointMeasure( const QgsAbstractGeometry &geom, const QgsPoint &pt )
96+
QgsPoint QgsGeometryUtils::closestPoint( const QgsAbstractGeometry &geometry, const QgsPoint &point )
9797
{
98-
if ( QgsWkbTypes::hasM( geom.wkbType() ) )
99-
{
100-
QgsPoint closestPoint;
101-
QgsVertexId vertexAfter;
102-
bool leftOf;
103-
geom.closestSegment( pt, closestPoint, vertexAfter, &leftOf, DEFAULT_SEGMENT_EPSILON );
104-
if ( vertexAfter.isValid() )
98+
QgsPoint closestPoint;
99+
QgsVertexId vertexAfter;
100+
bool leftOf;
101+
geometry.closestSegment( point, closestPoint, vertexAfter, &leftOf, DEFAULT_SEGMENT_EPSILON );
102+
if ( vertexAfter.isValid() )
103+
{
104+
QgsPoint pointAfter = geometry.vertexAt( vertexAfter );
105+
if ( vertexAfter.vertex > 0 )
105106
{
106-
QgsPoint pointAfter = geom.vertexAt( vertexAfter );
107-
if ( vertexAfter.vertex > 0 )
108-
{
109-
QgsVertexId vertexBefore = vertexAfter;
110-
vertexBefore.vertex--;
111-
QgsPoint pointBefore = geom.vertexAt( vertexBefore );
112-
double length = pointBefore.distance( pointAfter );
113-
double distance = pointBefore.distance( closestPoint );
114-
return pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distance / length;
115-
}
107+
QgsVertexId vertexBefore = vertexAfter;
108+
vertexBefore.vertex--;
109+
QgsPoint pointBefore = geometry.vertexAt( vertexBefore );
110+
double length = pointBefore.distance( pointAfter );
111+
double distance = pointBefore.distance( closestPoint );
112+
113+
if ( qgsDoubleNear( distance, 0.0 ) )
114+
closestPoint = pointBefore;
115+
else if ( qgsDoubleNear( distance, length ) )
116+
closestPoint = pointAfter;
116117
else
117118
{
118-
return pointAfter.m();
119+
if ( QgsWkbTypes::hasZ( geometry.wkbType() ) && length )
120+
closestPoint.addZValue( pointBefore.z() + ( pointAfter.z() - pointBefore.z() ) * distance / length );
121+
if ( QgsWkbTypes::hasM( geometry.wkbType() ) )
122+
closestPoint.addMValue( pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distance / length );
119123
}
120124
}
121125
}
122-
return std::numeric_limits<double>::quiet_NaN();
126+
127+
return closestPoint;
123128
}
124129

125130
double QgsGeometryUtils::distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id )

src/core/geometry/qgsgeometryutils.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ class CORE_EXPORT QgsGeometryUtils
4444
*/
4545
static QgsPoint closestVertex( const QgsAbstractGeometry &geom, const QgsPoint &pt, QgsVertexId &id SIP_OUT );
4646

47-
/** Returns measure of nearest point on a geometry for a specified point or NaN if geometry does not have measures
47+
/**
48+
* Returns the nearest point on a segment of a \a geometry
49+
* for the specified \a point. The z and m values will be linearly interpolated between
50+
* the two neighbouring vertices.
4851
*/
49-
static double closestPointMeasure( const QgsAbstractGeometry &geom, const QgsPoint &pt );
52+
static QgsPoint closestPoint( const QgsAbstractGeometry &geometry, const QgsPoint &point );
5053

5154
/** Returns the distance along a geometry from its first vertex to the specified vertex.
5255
* \param geom geometry

src/core/geometry/qgslinestring.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ QgsPoint QgsLineString::pointN( int i ) const
380380

381381
double x = mX.at( i );
382382
double y = mY.at( i );
383-
double z = 0;
384-
double m = 0;
383+
double z = std::numeric_limits<double>::quiet_NaN();
384+
double m = std::numeric_limits<double>::quiet_NaN();
385385

386386
bool hasZ = is3D();
387387
if ( hasZ )
@@ -441,15 +441,15 @@ double QgsLineString::zAt( int index ) const
441441
if ( index >= 0 && index < mZ.size() )
442442
return mZ.at( index );
443443
else
444-
return 0.0;
444+
return std::numeric_limits<double>::quiet_NaN();
445445
}
446446

447447
double QgsLineString::mAt( int index ) const
448448
{
449449
if ( index >= 0 && index < mM.size() )
450450
return mM.at( index );
451451
else
452-
return 0.0;
452+
return std::numeric_limits<double>::quiet_NaN();
453453
}
454454

455455
void QgsLineString::setXAt( int index, double x )

src/core/geometry/qgslinestring.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class CORE_EXPORT QgsLineString: public QgsCurve
4444

4545
/**
4646
* Construct a linestring from a vector of points.
47+
* Z and M type will be set based on the type of the first point
48+
* in the vector.
4749
* \since QGIS 3.0
4850
*/
4951
QgsLineString( const QVector<QgsPoint> &points );
@@ -81,15 +83,15 @@ class CORE_EXPORT QgsLineString: public QgsCurve
8183

8284
/** Returns the z-coordinate of the specified node in the line string.
8385
* \param index index of node, where the first node in the line is 0
84-
* \returns z-coordinate of node, or 0.0 if index is out of bounds or the line
86+
* \returns z-coordinate of node, or ``nan`` if index is out of bounds or the line
8587
* does not have a z dimension
8688
* \see setZAt()
8789
*/
8890
double zAt( int index ) const;
8991

9092
/** Returns the m value of the specified node in the line string.
9193
* \param index index of node, where the first node in the line is 0
92-
* \returns m value of node, or 0.0 if index is out of bounds or the line
94+
* \returns m value of node, or ``nan`` if index is out of bounds or the line
9395
* does not have m values
9496
* \see setMAt()
9597
*/

src/gui/qgsmaptoolidentify.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,8 @@ void QgsMapToolIdentify::closestPointAttributes( const QgsAbstractGeometry &geom
332332
// measure
333333
if ( QgsWkbTypes::hasM( geometry.wkbType() ) )
334334
{
335-
double measure = QgsGeometryUtils::closestPointMeasure( geometry, QgsPoint( layerPoint.x(), layerPoint.y() ) );
336-
QString str = QLocale::system().toString( measure, 'g', 10 );
335+
QgsPoint closestPoint = QgsGeometryUtils::closestPoint( geometry, QgsPoint( layerPoint.x(), layerPoint.y() ) );
336+
QString str = QLocale::system().toString( closestPoint.m(), 'g', 10 );
337337
derivedAttributes.insert( QStringLiteral( "Closest point M" ), str );
338338
}
339339
}

tests/src/core/testqgsgeometry.cpp

+8-8
Original file line numberDiff line numberDiff line change
@@ -1317,8 +1317,8 @@ void TestQgsGeometry::lineString()
13171317
QCOMPARE( l9.zAt( 0 ), 3.0 );
13181318
QCOMPARE( l9.zAt( 1 ), 13.0 );
13191319
QCOMPARE( l9.zAt( 2 ), 23.0 );
1320-
QCOMPARE( l9.zAt( -1 ), 0.0 ); //out of range
1321-
QCOMPARE( l9.zAt( 11 ), 0.0 ); //out of range
1320+
QVERIFY( qIsNaN( l9.zAt( -1 ) ) ); //out of range
1321+
QVERIFY( qIsNaN( l9.zAt( 11 ) ) ); //out of range
13221322

13231323
l9.setZAt( 0, 53.0 );
13241324
QCOMPARE( l9.zAt( 0 ), 53.0 );
@@ -1330,8 +1330,8 @@ void TestQgsGeometry::lineString()
13301330
QCOMPARE( l9.mAt( 0 ), 4.0 );
13311331
QCOMPARE( l9.mAt( 1 ), 14.0 );
13321332
QCOMPARE( l9.mAt( 2 ), 24.0 );
1333-
QCOMPARE( l9.mAt( -1 ), 0.0 ); //out of range
1334-
QCOMPARE( l9.mAt( 11 ), 0.0 ); //out of range
1333+
QVERIFY( qIsNaN( l9.mAt( -1 ) ) ); //out of range
1334+
QVERIFY( qIsNaN( l9.mAt( 11 ) ) ); //out of range
13351335

13361336
l9.setMAt( 0, 54.0 );
13371337
QCOMPARE( l9.mAt( 0 ), 54.0 );
@@ -1346,8 +1346,8 @@ void TestQgsGeometry::lineString()
13461346
<< QgsPoint( QgsWkbTypes::PointM, 21, 22, 0, 24 ) );
13471347

13481348
//basically we just don't want these to crash
1349-
QCOMPARE( l9.zAt( 0 ), 0.0 );
1350-
QCOMPARE( l9.zAt( 1 ), 0.0 );
1349+
QVERIFY( qIsNaN( l9.zAt( 0 ) ) );
1350+
QVERIFY( qIsNaN( l9.zAt( 1 ) ) );
13511351
l9.setZAt( 0, 53.0 );
13521352
l9.setZAt( 1, 63.0 );
13531353

@@ -1357,8 +1357,8 @@ void TestQgsGeometry::lineString()
13571357
<< QgsPoint( 21, 22 ) );
13581358

13591359
//basically we just don't want these to crash
1360-
QCOMPARE( l9.mAt( 0 ), 0.0 );
1361-
QCOMPARE( l9.mAt( 1 ), 0.0 );
1360+
QVERIFY( qIsNaN( l9.mAt( 0 ) ) );
1361+
QVERIFY( qIsNaN( l9.mAt( 1 ) ) );
13621362
l9.setMAt( 0, 53.0 );
13631363
l9.setMAt( 1, 63.0 );
13641364

tests/src/core/testqgsgeometryutils.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class TestQgsGeometryUtils: public QObject
5555
void testGradient();
5656
void testCoefficients();
5757
void testPerpendicularSegment();
58+
void testClosestPoint();
5859
};
5960

6061

@@ -633,5 +634,41 @@ void TestQgsGeometryUtils::testPerpendicularSegment()
633634
QCOMPARE( line.pointN( 1 ), line_r.pointN( 1 ) );
634635
}
635636

637+
void TestQgsGeometryUtils::testClosestPoint()
638+
{
639+
QgsLineString linestringZ( QVector<QgsPoint>()
640+
<< QgsPoint( 1, 1, 1 )
641+
<< QgsPoint( 1, 3, 2 ) );
642+
643+
QgsPoint pt1 = QgsGeometryUtils::closestPoint( linestringZ, QgsPoint( 1, 0 ) );
644+
QGSCOMPARENEAR( pt1.z(), 1, 0.0001 );
645+
QVERIFY( qIsNaN( pt1.m() ) );
646+
647+
QgsLineString linestringM( QVector<QgsPoint>()
648+
<< QgsPoint( 1, 1, std::numeric_limits<double>::quiet_NaN(), 1 )
649+
<< QgsPoint( 1, 3, std::numeric_limits<double>::quiet_NaN(), 2 ) );
650+
651+
QgsPoint pt2 = QgsGeometryUtils::closestPoint( linestringM, QgsPoint( 1, 4 ) );
652+
QVERIFY( qIsNaN( pt2.z() ) );
653+
QGSCOMPARENEAR( pt2.m(), 2, 0.0001 );
654+
655+
QgsLineString linestringZM( QVector<QgsPoint>()
656+
<< QgsPoint( 1, 1, 1, 1 )
657+
<< QgsPoint( 1, 3, 2, 2 ) );
658+
659+
QgsPoint pt3 = QgsGeometryUtils::closestPoint( linestringZM, QgsPoint( 2, 2 ) );
660+
QGSCOMPARENEAR( pt3.z(), 1.5, 0.0001 );
661+
QGSCOMPARENEAR( pt3.m(), 1.5, 0.0001 );
662+
663+
QgsLineString linestringDuplicatedPoint( QVector<QgsPoint>()
664+
<< QgsPoint( 1, 1, 1, 1 )
665+
<< QgsPoint( 1, 1, 1, 1 )
666+
<< QgsPoint( 1, 3, 2, 2 ) );
667+
668+
QgsPoint pt4 = QgsGeometryUtils::closestPoint( linestringDuplicatedPoint, QgsPoint( 1, 0 ) );
669+
QGSCOMPARENEAR( pt4.z(), 1, 0.0001 );
670+
QGSCOMPARENEAR( pt4.m(), 1, 0.0001 );
671+
}
672+
636673
QGSTEST_MAIN( TestQgsGeometryUtils )
637674
#include "testqgsgeometryutils.moc"

0 commit comments

Comments
 (0)