Skip to content

Commit 513bcb6

Browse files
committedAug 15, 2018
[FEATURE] New geometry API call to return a curve substring
Returns a new curve representing a substring of a curve, from a start distance and end distance. If z or m values are present, the output z and m will be interpolated using the existing vertices' z or m values. Handles curved geometries without loss.
1 parent 5b0bdbd commit 513bcb6

File tree

12 files changed

+405
-0
lines changed

12 files changed

+405
-0
lines changed
 

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

Lines changed: 2 additions & 0 deletions
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 QgsCircularString *curveSubstring( double startDistance, double endDistance ) const /Factory/;
147+
146148
virtual bool addZValue( double zValue = 0 );
147149

148150
virtual bool addMValue( double mValue = 0 );

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

Lines changed: 2 additions & 0 deletions
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 QgsCompoundCurve *curveSubstring( double startDistance, double endDistance ) const /Factory/;
148+
147149

148150
virtual bool addZValue( double zValue = 0 );
149151

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,20 @@ 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 QgsCurve *curveSubstring( double startDistance, double endDistance ) const = 0 /Factory/;
186+
%Docstring
187+
Returns a new curve representing a substring of this curve.
188+
189+
The ``startDistance`` and ``endDistance`` arguments specify the length along the curve
190+
which the substring should start and end at. If the ``endDistance`` is greater than the
191+
total length of the curve then any "extra" length will be ignored.
192+
193+
If z or m values are present, the output z and m will be interpolated using
194+
the existing vertices' z or m values.
195+
196+
.. versionadded:: 3.4
183197
%End
184198

185199
double straightDistance2d() const;

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

Lines changed: 2 additions & 0 deletions
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 QgsLineString *curveSubstring( double startDistance, double endDistance ) const /Factory/;
287+
286288

287289
virtual double closestSegment( const QgsPoint &pt, QgsPoint &segmentPt /Out/, QgsVertexId &vertexAfter /Out/, int *leftOf /Out/ = 0, double epsilon = 4 * DBL_EPSILON ) const;
288290

‎src/core/geometry/qgscircularstring.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,133 @@ QgsCircularString *QgsCircularString::reversed() const
11781178
return copy;
11791179
}
11801180

1181+
QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1182+
{
1183+
if ( startDistance < 0 && endDistance < 0 )
1184+
return createEmptyWithSameType();
1185+
1186+
endDistance = std::max( startDistance, endDistance );
1187+
1188+
double distanceTraversed = 0;
1189+
const int totalPoints = numPoints();
1190+
if ( totalPoints == 0 )
1191+
return clone();
1192+
1193+
QVector< QgsPoint > substringPoints;
1194+
1195+
QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
1196+
if ( is3D() )
1197+
pointType = QgsWkbTypes::PointZ;
1198+
if ( isMeasure() )
1199+
pointType = QgsWkbTypes::addM( pointType );
1200+
1201+
const double *x = mX.constData();
1202+
const double *y = mY.constData();
1203+
const double *z = is3D() ? mZ.constData() : nullptr;
1204+
const double *m = isMeasure() ? mM.constData() : nullptr;
1205+
1206+
double prevX = *x++;
1207+
double prevY = *y++;
1208+
double prevZ = z ? *z++ : 0.0;
1209+
double prevM = m ? *m++ : 0.0;
1210+
bool foundStart = false;
1211+
1212+
if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
1213+
{
1214+
substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1215+
foundStart = true;
1216+
}
1217+
1218+
substringPoints.reserve( totalPoints );
1219+
1220+
for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1221+
{
1222+
double x1 = prevX;
1223+
double y1 = prevY;
1224+
double z1 = prevZ;
1225+
double m1 = prevM;
1226+
1227+
double x2 = *x++;
1228+
double y2 = *y++;
1229+
double z2 = z ? *z++ : 0.0;
1230+
double m2 = m ? *m++ : 0.0;
1231+
1232+
double x3 = *x++;
1233+
double y3 = *y++;
1234+
double z3 = z ? *z++ : 0.0;
1235+
double m3 = m ? *m++ : 0.0;
1236+
1237+
bool addedSegmentEnd = false;
1238+
const double segmentLength = QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1239+
if ( distanceTraversed < startDistance && distanceTraversed + segmentLength > startDistance )
1240+
{
1241+
// start point falls on this segment
1242+
const double distanceToStart = startDistance - distanceTraversed;
1243+
const QgsPoint startPoint = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1244+
QgsPoint( pointType, x2, y2, z2, m2 ),
1245+
QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
1246+
1247+
// does end point also fall on this segment?
1248+
const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
1249+
if ( endPointOnSegment )
1250+
{
1251+
const double distanceToEnd = endDistance - distanceTraversed;
1252+
const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
1253+
substringPoints << startPoint
1254+
<< QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1255+
QgsPoint( pointType, x2, y2, z2, m2 ),
1256+
QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1257+
<< QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1258+
QgsPoint( pointType, x2, y2, z2, m2 ),
1259+
QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1260+
addedSegmentEnd = true;
1261+
}
1262+
else
1263+
{
1264+
const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
1265+
substringPoints << startPoint
1266+
<< QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1267+
QgsPoint( pointType, x2, y2, z2, m2 ),
1268+
QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1269+
<< QgsPoint( pointType, x3, y3, z3, m3 );
1270+
addedSegmentEnd = true;
1271+
}
1272+
foundStart = true;
1273+
}
1274+
if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1275+
{
1276+
// end point falls on this segment
1277+
const double distanceToEnd = endDistance - distanceTraversed;
1278+
// add mid point, at half way along this arc, then add the interpolated end point
1279+
substringPoints << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1280+
QgsPoint( pointType, x2, y2, z2, m2 ),
1281+
QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
1282+
1283+
<< QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1284+
QgsPoint( pointType, x2, y2, z2, m2 ),
1285+
QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1286+
}
1287+
else if ( !addedSegmentEnd && foundStart )
1288+
{
1289+
substringPoints << QgsPoint( pointType, x2, y2, z2, m2 )
1290+
<< QgsPoint( pointType, x3, y3, z3, m3 );
1291+
}
1292+
1293+
distanceTraversed += segmentLength;
1294+
if ( distanceTraversed > endDistance )
1295+
break;
1296+
1297+
prevX = x3;
1298+
prevY = y3;
1299+
prevZ = z3;
1300+
prevM = m3;
1301+
}
1302+
1303+
std::unique_ptr< QgsCircularString > result = qgis::make_unique< QgsCircularString >();
1304+
result->setPoints( substringPoints );
1305+
return result.release();
1306+
}
1307+
11811308
bool QgsCircularString::addZValue( double zValue )
11821309
{
11831310
if ( QgsWkbTypes::hasZ( mWkbType ) )

‎src/core/geometry/qgscircularstring.h

Lines changed: 1 addition & 0 deletions
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+
QgsCircularString *curveSubstring( double startDistance, double endDistance ) const override SIP_FACTORY;
121122
bool addZValue( double zValue = 0 ) override;
122123
bool addMValue( double mValue = 0 ) override;
123124
bool dropZValue() override;

‎src/core/geometry/qgscompoundcurve.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,37 @@ QgsCompoundCurve *QgsCompoundCurve::reversed() const
864864
return clone;
865865
}
866866

867+
QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
868+
{
869+
if ( startDistance < 0 && endDistance < 0 )
870+
return createEmptyWithSameType();
871+
872+
endDistance = std::max( startDistance, endDistance );
873+
std::unique_ptr< QgsCompoundCurve > substring = qgis::make_unique< QgsCompoundCurve >();
874+
875+
double distanceTraversed = 0;
876+
for ( const QgsCurve *curve : mCurves )
877+
{
878+
const double thisCurveLength = curve->length();
879+
if ( distanceTraversed + thisCurveLength < startDistance )
880+
{
881+
// keep going - haven't found start yet, so no need to include this curve at all
882+
}
883+
else
884+
{
885+
std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
886+
if ( part )
887+
substring->addCurve( part.release() );
888+
}
889+
890+
distanceTraversed += thisCurveLength;
891+
if ( distanceTraversed > endDistance )
892+
break;
893+
}
894+
895+
return substring.release();
896+
}
897+
867898
bool QgsCompoundCurve::addZValue( double zValue )
868899
{
869900
if ( QgsWkbTypes::hasZ( mWkbType ) )

‎src/core/geometry/qgscompoundcurve.h

Lines changed: 1 addition & 0 deletions
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+
QgsCompoundCurve *curveSubstring( double startDistance, double endDistance ) const override SIP_FACTORY;
118119

119120
bool addZValue( double zValue = 0 ) override;
120121
bool addMValue( double mValue = 0 ) override;

‎src/core/geometry/qgscurve.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry
166166
*/
167167
virtual QPolygonF asQPolygonF() const;
168168

169+
/**
170+
* Returns a new curve representing a substring of this curve.
171+
*
172+
* The \a startDistance and \a endDistance arguments specify the length along the curve
173+
* which the substring should start and end at. If the \a endDistance is greater than the
174+
* total length of the curve then any "extra" length will be ignored.
175+
*
176+
* If z or m values are present, the output z and m will be interpolated using
177+
* the existing vertices' z or m values.
178+
*
179+
* \since QGIS 3.4
180+
*/
181+
virtual QgsCurve *curveSubstring( double startDistance, double endDistance ) const = 0 SIP_FACTORY;
182+
169183
/**
170184
* Returns the straight distance of the curve, i.e. the direct/euclidean distance
171185
* between the first and last vertex of the curve. (Also known as

‎src/core/geometry/qgslinestring.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,96 @@ QgsLineString *QgsLineString::reversed() const
711711
return copy;
712712
}
713713

714+
QgsLineString *QgsLineString::curveSubstring( double startDistance, double endDistance ) const
715+
{
716+
if ( startDistance < 0 && endDistance < 0 )
717+
return createEmptyWithSameType();
718+
719+
endDistance = std::max( startDistance, endDistance );
720+
721+
double distanceTraversed = 0;
722+
const int totalPoints = numPoints();
723+
if ( totalPoints == 0 )
724+
return clone();
725+
726+
QVector< QgsPoint > substringPoints;
727+
728+
QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
729+
if ( is3D() )
730+
pointType = QgsWkbTypes::PointZ;
731+
if ( isMeasure() )
732+
pointType = QgsWkbTypes::addM( pointType );
733+
734+
const double *x = mX.constData();
735+
const double *y = mY.constData();
736+
const double *z = is3D() ? mZ.constData() : nullptr;
737+
const double *m = isMeasure() ? mM.constData() : nullptr;
738+
739+
double prevX = *x++;
740+
double prevY = *y++;
741+
double prevZ = z ? *z++ : 0.0;
742+
double prevM = m ? *m++ : 0.0;
743+
bool foundStart = false;
744+
745+
if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
746+
{
747+
substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
748+
foundStart = true;
749+
}
750+
751+
substringPoints.reserve( totalPoints );
752+
753+
for ( int i = 1; i < totalPoints; ++i )
754+
{
755+
double thisX = *x++;
756+
double thisY = *y++;
757+
double thisZ = z ? *z++ : 0.0;
758+
double thisM = m ? *m++ : 0.0;
759+
760+
const double segmentLength = std::sqrt( ( thisX - prevX ) * ( thisX - prevX ) + ( thisY - prevY ) * ( thisY - prevY ) );
761+
if ( distanceTraversed < startDistance && distanceTraversed + segmentLength > startDistance )
762+
{
763+
// start point falls on this segment
764+
const double distanceToStart = startDistance - distanceTraversed;
765+
double startX, startY;
766+
double startZ = 0;
767+
double startM = 0;
768+
QgsGeometryUtils::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToStart, startX, startY,
769+
z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &startZ : nullptr,
770+
m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &startM : nullptr );
771+
substringPoints << QgsPoint( pointType, startX, startY, startZ, startM );
772+
foundStart = true;
773+
}
774+
if ( foundStart && ( distanceTraversed + segmentLength > endDistance ) )
775+
{
776+
// end point falls on this segment
777+
const double distanceToEnd = endDistance - distanceTraversed;
778+
double endX, endY;
779+
double endZ = 0;
780+
double endM = 0;
781+
QgsGeometryUtils::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToEnd, endX, endY,
782+
z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &endZ : nullptr,
783+
m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &endM : nullptr );
784+
substringPoints << QgsPoint( pointType, endX, endY, endZ, endM );
785+
}
786+
else if ( foundStart )
787+
{
788+
substringPoints << QgsPoint( pointType, thisX, thisY, thisZ, thisM );
789+
}
790+
791+
distanceTraversed += segmentLength;
792+
if ( distanceTraversed > endDistance )
793+
break;
794+
795+
prevX = thisX;
796+
prevY = thisY;
797+
prevZ = thisZ;
798+
prevM = thisM;
799+
}
800+
801+
return new QgsLineString( substringPoints );
802+
}
803+
714804
/***************************************************************************
715805
* This class is considered CRITICAL and any change MUST be accompanied with
716806
* full unit tests.

‎src/core/geometry/qgslinestring.h

Lines changed: 1 addition & 0 deletions
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+
QgsLineString *curveSubstring( double startDistance, double endDistance ) const override SIP_FACTORY;
311312

312313
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;
313314
bool pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const override;

0 commit comments

Comments
 (0)
Please sign in to comment.