Skip to content

Commit c6a91da

Browse files
committed
[FEATURE] Use native interpolate point method instead of GEOS method
Because: - Exactly follows curves and doesn't require segmentizing input geometry - Also interpolates z/m values if they are present in input geometry - Is faster
1 parent 8365335 commit c6a91da

17 files changed

+343
-19
lines changed

python/core/auto_generated/geometry/qgscircularstring.sip.in

+2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ Sets the circular string's points
143143

144144
virtual QgsCircularString *reversed() const /Factory/;
145145

146+
virtual QgsPoint *interpolatePoint( double distance ) const /Factory/;
147+
146148
virtual QgsCircularString *curveSubstring( double startDistance, double endDistance ) const /Factory/;
147149

148150
virtual bool addZValue( double zValue = 0 );

python/core/auto_generated/geometry/qgscompoundcurve.sip.in

+2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ Appends first point if not already closed.
144144

145145
virtual QgsCompoundCurve *reversed() const /Factory/;
146146

147+
virtual QgsPoint *interpolatePoint( double distance ) const /Factory/;
148+
147149
virtual QgsCompoundCurve *curveSubstring( double startDistance, double endDistance ) const /Factory/;
148150

149151

python/core/auto_generated/geometry/qgscurve.sip.in

+13
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ Returns the y-coordinate of the specified node in the line string.
180180
virtual QPolygonF asQPolygonF() const;
181181
%Docstring
182182
Returns a QPolygonF representing the points.
183+
%End
184+
185+
virtual QgsPoint *interpolatePoint( double distance ) const = 0 /Factory/;
186+
%Docstring
187+
Returns an interpolated point on the curve at the specified ``distance``.
188+
189+
If z or m values are present, the output z and m will be interpolated using
190+
the existing vertices' z or m values.
191+
192+
If distance is negative, or is greater than the length of the curve, a None
193+
will be returned.
194+
195+
.. versionadded:: 3.4
183196
%End
184197

185198
virtual QgsCurve *curveSubstring( double startDistance, double endDistance ) const = 0 /Factory/;

python/core/auto_generated/geometry/qgsgeometry.sip.in

+8-4
Original file line numberDiff line numberDiff line change
@@ -1154,12 +1154,16 @@ by calling `error()` on the returned geometry.
11541154

11551155
QgsGeometry interpolate( double distance ) const;
11561156
%Docstring
1157-
Returns interpolated point on line at distance.
1157+
Returns an interpolated point on the geometry at the specified ``distance``.
11581158

1159-
If the input is a NULL geometry, the output will also be a NULL geometry.
1159+
If the original geometry is a polygon type, the boundary of the polygon
1160+
will be used during interpolation. If the original geometry is a point
1161+
type, a null geometry will be returned.
11601162

1161-
If an error was encountered while creating the result, more information can be retrieved
1162-
by calling `error()` on the returned geometry.
1163+
If z or m values are present, the output z and m will be interpolated using
1164+
the existing vertices' z or m values.
1165+
1166+
If the input is a NULL geometry, the output will also be a NULL geometry.
11631167

11641168
.. seealso:: :py:func:`lineLocatePoint`
11651169

python/core/auto_generated/geometry/qgslinestring.sip.in

+2
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ of the curve.
283283

284284
virtual QgsLineString *reversed() const /Factory/;
285285

286+
virtual QgsPoint *interpolatePoint( double distance ) const /Factory/;
287+
286288
virtual QgsLineString *curveSubstring( double startDistance, double endDistance ) const /Factory/;
287289

288290

Binary file not shown.

src/analysis/processing/qgsalgorithminterpolatepoint.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,14 @@ QgsProcessing::SourceType QgsInterpolatePointAlgorithm::outputLayerType() const
7171
return QgsProcessing::TypeVectorPoint;
7272
}
7373

74-
QgsWkbTypes::Type QgsInterpolatePointAlgorithm::outputWkbType( QgsWkbTypes::Type ) const
75-
{
76-
return QgsWkbTypes::Point;
74+
QgsWkbTypes::Type QgsInterpolatePointAlgorithm::outputWkbType( QgsWkbTypes::Type inputType ) const
75+
{
76+
QgsWkbTypes::Type out = QgsWkbTypes::Point;
77+
if ( QgsWkbTypes::hasZ( inputType ) )
78+
out = QgsWkbTypes::addZ( out );
79+
if ( QgsWkbTypes::hasM( inputType ) )
80+
out = QgsWkbTypes::addM( out );
81+
return out;
7782
}
7883

7984
QgsInterpolatePointAlgorithm *QgsInterpolatePointAlgorithm::createInstance() const

src/core/geometry/qgscircularstring.cpp

+69
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,75 @@ QgsCircularString *QgsCircularString::reversed() const
11781178
return copy;
11791179
}
11801180

1181+
QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1182+
{
1183+
if ( distance < 0 )
1184+
return nullptr;
1185+
1186+
double distanceTraversed = 0;
1187+
const int totalPoints = numPoints();
1188+
if ( totalPoints == 0 )
1189+
return nullptr;
1190+
1191+
QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
1192+
if ( is3D() )
1193+
pointType = QgsWkbTypes::PointZ;
1194+
if ( isMeasure() )
1195+
pointType = QgsWkbTypes::addM( pointType );
1196+
1197+
const double *x = mX.constData();
1198+
const double *y = mY.constData();
1199+
const double *z = is3D() ? mZ.constData() : nullptr;
1200+
const double *m = isMeasure() ? mM.constData() : nullptr;
1201+
1202+
double prevX = *x++;
1203+
double prevY = *y++;
1204+
double prevZ = z ? *z++ : 0.0;
1205+
double prevM = m ? *m++ : 0.0;
1206+
1207+
if ( qgsDoubleNear( distance, 0.0 ) )
1208+
{
1209+
return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1210+
}
1211+
1212+
for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1213+
{
1214+
double x1 = prevX;
1215+
double y1 = prevY;
1216+
double z1 = prevZ;
1217+
double m1 = prevM;
1218+
1219+
double x2 = *x++;
1220+
double y2 = *y++;
1221+
double z2 = z ? *z++ : 0.0;
1222+
double m2 = m ? *m++ : 0.0;
1223+
1224+
double x3 = *x++;
1225+
double y3 = *y++;
1226+
double z3 = z ? *z++ : 0.0;
1227+
double m3 = m ? *m++ : 0.0;
1228+
1229+
const double segmentLength = QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1230+
if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1231+
{
1232+
// point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1233+
const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1234+
return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1235+
QgsPoint( pointType, x2, y2, z2, m2 ),
1236+
QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1237+
}
1238+
1239+
distanceTraversed += segmentLength;
1240+
1241+
prevX = x3;
1242+
prevY = y3;
1243+
prevZ = z3;
1244+
prevM = m3;
1245+
}
1246+
1247+
return nullptr;
1248+
}
1249+
11811250
QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
11821251
{
11831252
if ( startDistance < 0 && endDistance < 0 )

src/core/geometry/qgscircularstring.h

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
118118
double vertexAngle( QgsVertexId vertex ) const override;
119119
double segmentLength( QgsVertexId startVertex ) const override;
120120
QgsCircularString *reversed() const override SIP_FACTORY;
121+
QgsPoint *interpolatePoint( double distance ) const override SIP_FACTORY;
121122
QgsCircularString *curveSubstring( double startDistance, double endDistance ) const override SIP_FACTORY;
122123
bool addZValue( double zValue = 0 ) override;
123124
bool addMValue( double mValue = 0 ) override;

src/core/geometry/qgscompoundcurve.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,30 @@ QgsCompoundCurve *QgsCompoundCurve::reversed() const
864864
return clone;
865865
}
866866

867+
QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
868+
{
869+
if ( distance < 0 )
870+
return nullptr;
871+
872+
double distanceTraversed = 0;
873+
for ( const QgsCurve *curve : mCurves )
874+
{
875+
const double thisCurveLength = curve->length();
876+
if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
877+
{
878+
// point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
879+
const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
880+
881+
// point falls on this curve
882+
return curve->interpolatePoint( distanceToPoint );
883+
}
884+
885+
distanceTraversed += thisCurveLength;
886+
}
887+
888+
return nullptr;
889+
}
890+
867891
QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
868892
{
869893
if ( startDistance < 0 && endDistance < 0 )

src/core/geometry/qgscompoundcurve.h

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
115115
double vertexAngle( QgsVertexId vertex ) const override;
116116
double segmentLength( QgsVertexId startVertex ) const override;
117117
QgsCompoundCurve *reversed() const override SIP_FACTORY;
118+
QgsPoint *interpolatePoint( double distance ) const override SIP_FACTORY;
118119
QgsCompoundCurve *curveSubstring( double startDistance, double endDistance ) const override SIP_FACTORY;
119120

120121
bool addZValue( double zValue = 0 ) override;

src/core/geometry/qgscurve.h

+13
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,19 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry
166166
*/
167167
virtual QPolygonF asQPolygonF() const;
168168

169+
/**
170+
* Returns an interpolated point on the curve at the specified \a distance.
171+
*
172+
* If z or m values are present, the output z and m will be interpolated using
173+
* the existing vertices' z or m values.
174+
*
175+
* If distance is negative, or is greater than the length of the curve, a nullptr
176+
* will be returned.
177+
*
178+
* \since QGIS 3.4
179+
*/
180+
virtual QgsPoint *interpolatePoint( double distance ) const = 0 SIP_FACTORY;
181+
169182
/**
170183
* Returns a new curve representing a substring of this curve.
171184
*

src/core/geometry/qgsgeometry.cpp

+26-8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ email : morb at ozemail dot com dot au
4545
#include "qgspolygon.h"
4646
#include "qgslinestring.h"
4747
#include "qgscircle.h"
48+
#include "qgscurve.h"
4849

4950
struct QgsGeometryPrivate
5051
{
@@ -2005,25 +2006,42 @@ QgsGeometry QgsGeometry::subdivide( int maxNodes ) const
20052006
return QgsGeometry( std::move( result ) );
20062007
}
20072008

2008-
QgsGeometry QgsGeometry::interpolate( double distance ) const
2009+
QgsGeometry QgsGeometry::interpolate( const double distance ) const
20092010
{
20102011
if ( !d->geometry )
20112012
{
20122013
return QgsGeometry();
20132014
}
20142015

20152016
QgsGeometry line = *this;
2016-
if ( type() == QgsWkbTypes::PolygonGeometry )
2017+
if ( type() == QgsWkbTypes::PointGeometry )
2018+
return QgsGeometry();
2019+
else if ( type() == QgsWkbTypes::PolygonGeometry )
2020+
{
20172021
line = QgsGeometry( d->geometry->boundary() );
2022+
}
20182023

2019-
QgsGeos geos( line.constGet() );
2020-
mLastError.clear();
2021-
std::unique_ptr< QgsAbstractGeometry > result( geos.interpolate( distance, &mLastError ) );
2024+
const QgsCurve *curve = nullptr;
2025+
if ( line.isMultipart() )
2026+
{
2027+
// if multi part, just use first part
2028+
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( line.constGet() );
2029+
if ( collection && collection->numGeometries() > 0 )
2030+
{
2031+
curve = qgsgeometry_cast< const QgsCurve * >( collection->geometryN( 0 ) );
2032+
}
2033+
}
2034+
else
2035+
{
2036+
curve = qgsgeometry_cast< const QgsCurve * >( line.constGet() );
2037+
}
2038+
if ( !curve )
2039+
return QgsGeometry();
2040+
2041+
std::unique_ptr< QgsPoint > result( curve->interpolatePoint( distance ) );
20222042
if ( !result )
20232043
{
2024-
QgsGeometry geom;
2025-
geom.mLastError = mLastError;
2026-
return geom;
2044+
return QgsGeometry();
20272045
}
20282046
return QgsGeometry( std::move( result ) );
20292047
}

src/core/geometry/qgsgeometry.h

+8-4
Original file line numberDiff line numberDiff line change
@@ -1134,12 +1134,16 @@ class CORE_EXPORT QgsGeometry
11341134
QgsGeometry subdivide( int maxNodes = 256 ) const;
11351135

11361136
/**
1137-
* Returns interpolated point on line at distance.
1137+
* Returns an interpolated point on the geometry at the specified \a distance.
11381138
*
1139-
* If the input is a NULL geometry, the output will also be a NULL geometry.
1139+
* If the original geometry is a polygon type, the boundary of the polygon
1140+
* will be used during interpolation. If the original geometry is a point
1141+
* type, a null geometry will be returned.
11401142
*
1141-
* If an error was encountered while creating the result, more information can be retrieved
1142-
* by calling `error()` on the returned geometry.
1143+
* If z or m values are present, the output z and m will be interpolated using
1144+
* the existing vertices' z or m values.
1145+
*
1146+
* If the input is a NULL geometry, the output will also be a NULL geometry.
11431147
*
11441148
* \see lineLocatePoint()
11451149
* \since QGIS 2.0

src/core/geometry/qgslinestring.cpp

+62
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,68 @@ QgsLineString *QgsLineString::reversed() const
711711
return copy;
712712
}
713713

714+
QgsPoint *QgsLineString::interpolatePoint( const double distance ) const
715+
{
716+
if ( distance < 0 )
717+
return nullptr;
718+
719+
double distanceTraversed = 0;
720+
const int totalPoints = numPoints();
721+
if ( totalPoints == 0 )
722+
return nullptr;
723+
724+
const double *x = mX.constData();
725+
const double *y = mY.constData();
726+
const double *z = is3D() ? mZ.constData() : nullptr;
727+
const double *m = isMeasure() ? mM.constData() : nullptr;
728+
729+
QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
730+
if ( is3D() )
731+
pointType = QgsWkbTypes::PointZ;
732+
if ( isMeasure() )
733+
pointType = QgsWkbTypes::addM( pointType );
734+
735+
double prevX = *x++;
736+
double prevY = *y++;
737+
double prevZ = z ? *z++ : 0.0;
738+
double prevM = m ? *m++ : 0.0;
739+
740+
if ( qgsDoubleNear( distance, 0.0 ) )
741+
{
742+
return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
743+
}
744+
745+
for ( int i = 1; i < totalPoints; ++i )
746+
{
747+
double thisX = *x++;
748+
double thisY = *y++;
749+
double thisZ = z ? *z++ : 0.0;
750+
double thisM = m ? *m++ : 0.0;
751+
752+
const double segmentLength = std::sqrt( ( thisX - prevX ) * ( thisX - prevX ) + ( thisY - prevY ) * ( thisY - prevY ) );
753+
if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
754+
{
755+
// point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
756+
const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
757+
double pX, pY;
758+
double pZ = 0;
759+
double pM = 0;
760+
QgsGeometryUtils::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY,
761+
z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr,
762+
m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
763+
return new QgsPoint( pointType, pX, pY, pZ, pM );
764+
}
765+
766+
distanceTraversed += segmentLength;
767+
prevX = thisX;
768+
prevY = thisY;
769+
prevZ = thisZ;
770+
prevM = thisM;
771+
}
772+
773+
return nullptr;
774+
}
775+
714776
QgsLineString *QgsLineString::curveSubstring( double startDistance, double endDistance ) const
715777
{
716778
if ( startDistance < 0 && endDistance < 0 )

src/core/geometry/qgslinestring.h

+1
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve
308308
bool deleteVertex( QgsVertexId position ) override;
309309

310310
QgsLineString *reversed() const override SIP_FACTORY;
311+
QgsPoint *interpolatePoint( double distance ) const override SIP_FACTORY;
311312
QgsLineString *curveSubstring( double startDistance, double endDistance ) const override SIP_FACTORY;
312313

313314
double closestSegment( const QgsPoint &pt, QgsPoint &segmentPt SIP_OUT, QgsVertexId &vertexAfter SIP_OUT, int *leftOf SIP_OUT = nullptr, double epsilon = 4 * std::numeric_limits<double>::epsilon() ) const override;

0 commit comments

Comments
 (0)