Skip to content

Commit

Permalink
Merge pull request #4771 from m-kuhn/closest_point
Browse files Browse the repository at this point in the history
Closest point
  • Loading branch information
m-kuhn committed Jun 26, 2017
2 parents 81653d6 + fd8158b commit 1dca332
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 46 deletions.
8 changes: 5 additions & 3 deletions python/core/geometry/qgsgeometryutils.sip
Expand Up @@ -37,10 +37,12 @@ class QgsGeometryUtils
:rtype: QgsPoint :rtype: QgsPoint
%End %End


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


static double distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id ); static double distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id );
Expand Down
6 changes: 4 additions & 2 deletions python/core/geometry/qgslinestring.sip
Expand Up @@ -29,6 +29,8 @@ class QgsLineString: QgsCurve
QgsLineString( const QVector<QgsPoint> &points ); QgsLineString( const QVector<QgsPoint> &points );
%Docstring %Docstring
Construct a linestring from a vector of points. Construct a linestring from a vector of points.
Z and M type will be set based on the type of the first point
in the vector.
.. versionadded:: 3.0 .. versionadded:: 3.0
%End %End


Expand Down Expand Up @@ -73,7 +75,7 @@ class QgsLineString: QgsCurve
%Docstring %Docstring
Returns the z-coordinate of the specified node in the line string. Returns the z-coordinate of the specified node in the line string.
\param index index of node, where the first node in the line is 0 \param index index of node, where the first node in the line is 0
:return: z-coordinate of node, or 0.0 if index is out of bounds or the line :return: z-coordinate of node, or ``nan`` if index is out of bounds or the line
does not have a z dimension does not have a z dimension
.. seealso:: setZAt() .. seealso:: setZAt()
:rtype: float :rtype: float
Expand All @@ -83,7 +85,7 @@ class QgsLineString: QgsCurve
%Docstring %Docstring
Returns the m value of the specified node in the line string. Returns the m value of the specified node in the line string.
\param index index of node, where the first node in the line is 0 \param index index of node, where the first node in the line is 0
:return: m value of node, or 0.0 if index is out of bounds or the line :return: m value of node, or ``nan`` if index is out of bounds or the line
does not have m values does not have m values
.. seealso:: setMAt() .. seealso:: setMAt()
:rtype: float :rtype: float
Expand Down
9 changes: 6 additions & 3 deletions src/core/geometry/qgscurve.cpp
Expand Up @@ -31,9 +31,12 @@ bool QgsCurve::isClosed() const
//don't consider M-coordinates when testing closedness //don't consider M-coordinates when testing closedness
QgsPoint start = startPoint(); QgsPoint start = startPoint();
QgsPoint end = endPoint(); QgsPoint end = endPoint();
return ( qgsDoubleNear( start.x(), end.x(), 1E-8 ) &&
qgsDoubleNear( start.y(), end.y(), 1E-8 ) && bool closed = qgsDoubleNear( start.x(), end.x(), 1E-8 ) &&
qgsDoubleNear( start.z(), end.z(), 1E-8 ) ); qgsDoubleNear( start.y(), end.y(), 1E-8 );
if ( is3D() && closed )
closed &= qgsDoubleNear( start.z(), end.z(), 1E-8 ) || ( qIsNaN( start.z() ) && qIsNaN( end.z() ) );
return closed;
} }


bool QgsCurve::isRing() const bool QgsCurve::isRing() const
Expand Down
45 changes: 25 additions & 20 deletions src/core/geometry/qgsgeometryutils.cpp
Expand Up @@ -93,33 +93,38 @@ QgsPoint QgsGeometryUtils::closestVertex( const QgsAbstractGeometry &geom, const
return minDistPoint; return minDistPoint;
} }


double QgsGeometryUtils::closestPointMeasure( const QgsAbstractGeometry &geom, const QgsPoint &pt ) QgsPoint QgsGeometryUtils::closestPoint( const QgsAbstractGeometry &geometry, const QgsPoint &point )
{ {
if ( QgsWkbTypes::hasM( geom.wkbType() ) ) QgsPoint closestPoint;
{ QgsVertexId vertexAfter;
QgsPoint closestPoint; bool leftOf;
QgsVertexId vertexAfter; geometry.closestSegment( point, closestPoint, vertexAfter, &leftOf, DEFAULT_SEGMENT_EPSILON );
bool leftOf; if ( vertexAfter.isValid() )
geom.closestSegment( pt, closestPoint, vertexAfter, &leftOf, DEFAULT_SEGMENT_EPSILON ); {
if ( vertexAfter.isValid() ) QgsPoint pointAfter = geometry.vertexAt( vertexAfter );
if ( vertexAfter.vertex > 0 )
{ {
QgsPoint pointAfter = geom.vertexAt( vertexAfter ); QgsVertexId vertexBefore = vertexAfter;
if ( vertexAfter.vertex > 0 ) vertexBefore.vertex--;
{ QgsPoint pointBefore = geometry.vertexAt( vertexBefore );
QgsVertexId vertexBefore = vertexAfter; double length = pointBefore.distance( pointAfter );
vertexBefore.vertex--; double distance = pointBefore.distance( closestPoint );
QgsPoint pointBefore = geom.vertexAt( vertexBefore );
double length = pointBefore.distance( pointAfter ); if ( qgsDoubleNear( distance, 0.0 ) )
double distance = pointBefore.distance( closestPoint ); closestPoint = pointBefore;
return pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distance / length; else if ( qgsDoubleNear( distance, length ) )
} closestPoint = pointAfter;
else else
{ {
return pointAfter.m(); if ( QgsWkbTypes::hasZ( geometry.wkbType() ) && length )
closestPoint.addZValue( pointBefore.z() + ( pointAfter.z() - pointBefore.z() ) * distance / length );
if ( QgsWkbTypes::hasM( geometry.wkbType() ) )
closestPoint.addMValue( pointBefore.m() + ( pointAfter.m() - pointBefore.m() ) * distance / length );
} }
} }
} }
return std::numeric_limits<double>::quiet_NaN();
return closestPoint;
} }


double QgsGeometryUtils::distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id ) double QgsGeometryUtils::distanceToVertex( const QgsAbstractGeometry &geom, QgsVertexId id )
Expand Down
7 changes: 5 additions & 2 deletions src/core/geometry/qgsgeometryutils.h
Expand Up @@ -44,9 +44,12 @@ class CORE_EXPORT QgsGeometryUtils
*/ */
static QgsPoint closestVertex( const QgsAbstractGeometry &geom, const QgsPoint &pt, QgsVertexId &id SIP_OUT ); static QgsPoint closestVertex( const QgsAbstractGeometry &geom, const QgsPoint &pt, QgsVertexId &id SIP_OUT );


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


/** Returns the distance along a geometry from its first vertex to the specified vertex. /** Returns the distance along a geometry from its first vertex to the specified vertex.
* \param geom geometry * \param geom geometry
Expand Down
8 changes: 4 additions & 4 deletions src/core/geometry/qgslinestring.cpp
Expand Up @@ -380,8 +380,8 @@ QgsPoint QgsLineString::pointN( int i ) const


double x = mX.at( i ); double x = mX.at( i );
double y = mY.at( i ); double y = mY.at( i );
double z = 0; double z = std::numeric_limits<double>::quiet_NaN();
double m = 0; double m = std::numeric_limits<double>::quiet_NaN();


bool hasZ = is3D(); bool hasZ = is3D();
if ( hasZ ) if ( hasZ )
Expand Down Expand Up @@ -441,15 +441,15 @@ double QgsLineString::zAt( int index ) const
if ( index >= 0 && index < mZ.size() ) if ( index >= 0 && index < mZ.size() )
return mZ.at( index ); return mZ.at( index );
else else
return 0.0; return std::numeric_limits<double>::quiet_NaN();
} }


double QgsLineString::mAt( int index ) const double QgsLineString::mAt( int index ) const
{ {
if ( index >= 0 && index < mM.size() ) if ( index >= 0 && index < mM.size() )
return mM.at( index ); return mM.at( index );
else else
return 0.0; return std::numeric_limits<double>::quiet_NaN();
} }


void QgsLineString::setXAt( int index, double x ) void QgsLineString::setXAt( int index, double x )
Expand Down
6 changes: 4 additions & 2 deletions src/core/geometry/qgslinestring.h
Expand Up @@ -44,6 +44,8 @@ class CORE_EXPORT QgsLineString: public QgsCurve


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


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


/** Returns the m value of the specified node in the line string. /** Returns the m value of the specified node in the line string.
* \param index index of node, where the first node in the line is 0 * \param index index of node, where the first node in the line is 0
* \returns m value of node, or 0.0 if index is out of bounds or the line * \returns m value of node, or ``nan`` if index is out of bounds or the line
* does not have m values * does not have m values
* \see setMAt() * \see setMAt()
*/ */
Expand Down
4 changes: 2 additions & 2 deletions src/gui/qgsmaptoolidentify.cpp
Expand Up @@ -332,8 +332,8 @@ void QgsMapToolIdentify::closestPointAttributes( const QgsAbstractGeometry &geom
// measure // measure
if ( QgsWkbTypes::hasM( geometry.wkbType() ) ) if ( QgsWkbTypes::hasM( geometry.wkbType() ) )
{ {
double measure = QgsGeometryUtils::closestPointMeasure( geometry, QgsPoint( layerPoint.x(), layerPoint.y() ) ); QgsPoint closestPoint = QgsGeometryUtils::closestPoint( geometry, QgsPoint( layerPoint.x(), layerPoint.y() ) );
QString str = QLocale::system().toString( measure, 'g', 10 ); QString str = QLocale::system().toString( closestPoint.m(), 'g', 10 );
derivedAttributes.insert( QStringLiteral( "Closest point M" ), str ); derivedAttributes.insert( QStringLiteral( "Closest point M" ), str );
} }
} }
Expand Down
16 changes: 8 additions & 8 deletions tests/src/core/testqgsgeometry.cpp
Expand Up @@ -1317,8 +1317,8 @@ void TestQgsGeometry::lineString()
QCOMPARE( l9.zAt( 0 ), 3.0 ); QCOMPARE( l9.zAt( 0 ), 3.0 );
QCOMPARE( l9.zAt( 1 ), 13.0 ); QCOMPARE( l9.zAt( 1 ), 13.0 );
QCOMPARE( l9.zAt( 2 ), 23.0 ); QCOMPARE( l9.zAt( 2 ), 23.0 );
QCOMPARE( l9.zAt( -1 ), 0.0 ); //out of range QVERIFY( qIsNaN( l9.zAt( -1 ) ) ); //out of range
QCOMPARE( l9.zAt( 11 ), 0.0 ); //out of range QVERIFY( qIsNaN( l9.zAt( 11 ) ) ); //out of range


l9.setZAt( 0, 53.0 ); l9.setZAt( 0, 53.0 );
QCOMPARE( l9.zAt( 0 ), 53.0 ); QCOMPARE( l9.zAt( 0 ), 53.0 );
Expand All @@ -1330,8 +1330,8 @@ void TestQgsGeometry::lineString()
QCOMPARE( l9.mAt( 0 ), 4.0 ); QCOMPARE( l9.mAt( 0 ), 4.0 );
QCOMPARE( l9.mAt( 1 ), 14.0 ); QCOMPARE( l9.mAt( 1 ), 14.0 );
QCOMPARE( l9.mAt( 2 ), 24.0 ); QCOMPARE( l9.mAt( 2 ), 24.0 );
QCOMPARE( l9.mAt( -1 ), 0.0 ); //out of range QVERIFY( qIsNaN( l9.mAt( -1 ) ) ); //out of range
QCOMPARE( l9.mAt( 11 ), 0.0 ); //out of range QVERIFY( qIsNaN( l9.mAt( 11 ) ) ); //out of range


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


//basically we just don't want these to crash //basically we just don't want these to crash
QCOMPARE( l9.zAt( 0 ), 0.0 ); QVERIFY( qIsNaN( l9.zAt( 0 ) ) );
QCOMPARE( l9.zAt( 1 ), 0.0 ); QVERIFY( qIsNaN( l9.zAt( 1 ) ) );
l9.setZAt( 0, 53.0 ); l9.setZAt( 0, 53.0 );
l9.setZAt( 1, 63.0 ); l9.setZAt( 1, 63.0 );


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


//basically we just don't want these to crash //basically we just don't want these to crash
QCOMPARE( l9.mAt( 0 ), 0.0 ); QVERIFY( qIsNaN( l9.mAt( 0 ) ) );
QCOMPARE( l9.mAt( 1 ), 0.0 ); QVERIFY( qIsNaN( l9.mAt( 1 ) ) );
l9.setMAt( 0, 53.0 ); l9.setMAt( 0, 53.0 );
l9.setMAt( 1, 63.0 ); l9.setMAt( 1, 63.0 );


Expand Down
37 changes: 37 additions & 0 deletions tests/src/core/testqgsgeometryutils.cpp
Expand Up @@ -55,6 +55,7 @@ class TestQgsGeometryUtils: public QObject
void testGradient(); void testGradient();
void testCoefficients(); void testCoefficients();
void testPerpendicularSegment(); void testPerpendicularSegment();
void testClosestPoint();
}; };




Expand Down Expand Up @@ -633,5 +634,41 @@ void TestQgsGeometryUtils::testPerpendicularSegment()
QCOMPARE( line.pointN( 1 ), line_r.pointN( 1 ) ); QCOMPARE( line.pointN( 1 ), line_r.pointN( 1 ) );
} }


void TestQgsGeometryUtils::testClosestPoint()
{
QgsLineString linestringZ( QVector<QgsPoint>()
<< QgsPoint( 1, 1, 1 )
<< QgsPoint( 1, 3, 2 ) );

QgsPoint pt1 = QgsGeometryUtils::closestPoint( linestringZ, QgsPoint( 1, 0 ) );
QGSCOMPARENEAR( pt1.z(), 1, 0.0001 );
QVERIFY( qIsNaN( pt1.m() ) );

QgsLineString linestringM( QVector<QgsPoint>()
<< QgsPoint( 1, 1, std::numeric_limits<double>::quiet_NaN(), 1 )
<< QgsPoint( 1, 3, std::numeric_limits<double>::quiet_NaN(), 2 ) );

QgsPoint pt2 = QgsGeometryUtils::closestPoint( linestringM, QgsPoint( 1, 4 ) );
QVERIFY( qIsNaN( pt2.z() ) );
QGSCOMPARENEAR( pt2.m(), 2, 0.0001 );

QgsLineString linestringZM( QVector<QgsPoint>()
<< QgsPoint( 1, 1, 1, 1 )
<< QgsPoint( 1, 3, 2, 2 ) );

QgsPoint pt3 = QgsGeometryUtils::closestPoint( linestringZM, QgsPoint( 2, 2 ) );
QGSCOMPARENEAR( pt3.z(), 1.5, 0.0001 );
QGSCOMPARENEAR( pt3.m(), 1.5, 0.0001 );

QgsLineString linestringDuplicatedPoint( QVector<QgsPoint>()
<< QgsPoint( 1, 1, 1, 1 )
<< QgsPoint( 1, 1, 1, 1 )
<< QgsPoint( 1, 3, 2, 2 ) );

QgsPoint pt4 = QgsGeometryUtils::closestPoint( linestringDuplicatedPoint, QgsPoint( 1, 0 ) );
QGSCOMPARENEAR( pt4.z(), 1, 0.0001 );
QGSCOMPARENEAR( pt4.m(), 1, 0.0001 );
}

QGSTEST_MAIN( TestQgsGeometryUtils ) QGSTEST_MAIN( TestQgsGeometryUtils )
#include "testqgsgeometryutils.moc" #include "testqgsgeometryutils.moc"

0 comments on commit 1dca332

Please sign in to comment.