Skip to content

Commit

Permalink
Add method to QgsGeometry to extend the start/end of a linestring
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Oct 30, 2016
1 parent c494c47 commit ef34e39
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 0 deletions.
8 changes: 8 additions & 0 deletions python/core/geometry/qgsgeometry.sip
Expand Up @@ -514,6 +514,14 @@ class QgsGeometry
JoinStyle joinStyle = JoinStyleRound,
double mitreLimit = 2.0 ) const;

/**
* Extends a (multi)line geometry by extrapolating out the start or end of the line
* by a specified distance. Lines are extended using the bearing of the first or last
* segment in the line.
* @note added in QGIS 3.0
*/
QgsGeometry extendLine( double startDistance, double endDistance ) const;

/** Returns a simplified version of this geometry using a specified tolerance value */
QgsGeometry simplify( double tolerance ) const;

Expand Down
8 changes: 8 additions & 0 deletions python/core/geometry/qgslinestring.sip
Expand Up @@ -92,6 +92,14 @@ class QgsLineString: public QgsCurve
@return the converted geometry. Caller takes ownership*/
QgsAbstractGeometry* toCurveType() const /Factory/;

/**
* Extends the line geometry by extrapolating out the start or end of the line
* by a specified distance. Lines are extended using the bearing of the first or last
* segment in the line.
* @note added in QGIS 3.0
*/
void extend( double startDistance, double endDistance );

//reimplemented methods

virtual QString geometryType() const;
Expand Down
39 changes: 39 additions & 0 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -1452,6 +1452,45 @@ QgsGeometry QgsGeometry::singleSidedBuffer( double distance, int segments, Buffe
}
}

QgsGeometry QgsGeometry::extendLine( double startDistance, double endDistance ) const
{
if ( !d->geometry || type() != QgsWkbTypes::LineGeometry )
{
return QgsGeometry();
}

if ( QgsWkbTypes::isMultiType( d->geometry->wkbType() ) )
{
QList<QgsGeometry> parts = asGeometryCollection();
QList<QgsGeometry> results;
Q_FOREACH ( const QgsGeometry& part, parts )
{
QgsGeometry result = part.extendLine( startDistance, endDistance );
if ( result )
results << result;
}
if ( results.isEmpty() )
return QgsGeometry();

QgsGeometry first = results.takeAt( 0 );
Q_FOREACH ( const QgsGeometry& result, results )
{
first.addPart( & result );
}
return first;
}
else
{
QgsLineString* line = dynamic_cast< QgsLineString* >( d->geometry );
if ( !line )
return QgsGeometry();

QgsLineString* newLine = line->clone();
newLine->extend( startDistance, endDistance );
return QgsGeometry( newLine );
}
}

QgsGeometry QgsGeometry::simplify( double tolerance ) const
{
if ( !d->geometry )
Expand Down
8 changes: 8 additions & 0 deletions src/core/geometry/qgsgeometry.h
Expand Up @@ -558,6 +558,14 @@ class CORE_EXPORT QgsGeometry
JoinStyle joinStyle = JoinStyleRound,
double mitreLimit = 2.0 ) const;

/**
* Extends a (multi)line geometry by extrapolating out the start or end of the line
* by a specified distance. Lines are extended using the bearing of the first or last
* segment in the line.
* @note added in QGIS 3.0
*/
QgsGeometry extendLine( double startDistance, double endDistance ) const;

//! Returns a simplified version of this geometry using a specified tolerance value
QgsGeometry simplify( double tolerance ) const;

Expand Down
26 changes: 26 additions & 0 deletions src/core/geometry/qgslinestring.cpp
Expand Up @@ -583,6 +583,32 @@ QgsAbstractGeometry* QgsLineString::toCurveType() const
return compoundCurve;
}

void QgsLineString::extend( double startDistance, double endDistance )
{
if ( mX.size() < 2 || mY.size() < 2 )
return;

// start of line
if ( startDistance > 0 )
{
double currentLen = sqrt( qPow( mX.at( 0 ) - mX.at( 1 ), 2 ) +
qPow( mY.at( 0 ) - mY.at( 1 ), 2 ) );
double newLen = currentLen + startDistance;
mX[ 0 ] = mX.at( 1 ) + ( mX.at( 0 ) - mX.at( 1 ) ) / currentLen * newLen;
mY[ 0 ] = mY.at( 1 ) + ( mY.at( 0 ) - mY.at( 1 ) ) / currentLen * newLen;
}
// end of line
if ( endDistance > 0 )
{
int last = mX.size() - 1;
double currentLen = sqrt( qPow( mX.at( last ) - mX.at( last - 1 ), 2 ) +
qPow( mY.at( last ) - mY.at( last - 1 ), 2 ) );
double newLen = currentLen + endDistance;
mX[ last ] = mX.at( last - 1 ) + ( mX.at( last ) - mX.at( last - 1 ) ) / currentLen * newLen;
mY[ last ] = mY.at( last - 1 ) + ( mY.at( last ) - mY.at( last - 1 ) ) / currentLen * newLen;
}
}

/***************************************************************************
* This class is considered CRITICAL and any change MUST be accompanied with
* full unit tests.
Expand Down
8 changes: 8 additions & 0 deletions src/core/geometry/qgslinestring.h
Expand Up @@ -120,6 +120,14 @@ class CORE_EXPORT QgsLineString: public QgsCurve
@return the converted geometry. Caller takes ownership*/
QgsAbstractGeometry* toCurveType() const override;

/**
* Extends the line geometry by extrapolating out the start or end of the line
* by a specified distance. Lines are extended using the bearing of the first or last
* segment in the line.
* @note added in QGIS 3.0
*/
void extend( double startDistance, double endDistance );

//reimplemented methods

virtual QString geometryType() const override { return QStringLiteral( "LineString" ); }
Expand Down
10 changes: 10 additions & 0 deletions tests/src/core/testqgsgeometry.cpp
Expand Up @@ -2208,6 +2208,16 @@ void TestQgsGeometry::lineString()
QCOMPARE( static_cast< QgsPointV2*>( mpBoundary->geometryN( 1 ) )->x(), 1.0 );
QCOMPARE( static_cast< QgsPointV2*>( mpBoundary->geometryN( 1 ) )->y(), 1.0 );
QCOMPARE( static_cast< QgsPointV2*>( mpBoundary->geometryN( 1 ) )->z(), 20.0 );


//extend
QgsLineString extend1;
extend1.extend( 10, 10 ); //test no crash
extend1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0, 0 ) << QgsPointV2( 1, 0 ) << QgsPointV2( 1, 1 ) );
extend1.extend( 1, 2 );
QCOMPARE( extend1.pointN( 0 ), QgsPointV2( QgsWkbTypes::Point, -1, 0 ) );
QCOMPARE( extend1.pointN( 1 ), QgsPointV2( QgsWkbTypes::Point, 1, 0 ) );
QCOMPARE( extend1.pointN( 2 ), QgsPointV2( QgsWkbTypes::Point, 1, 3 ) );
}

void TestQgsGeometry::polygon()
Expand Down
26 changes: 26 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -3594,5 +3594,31 @@ def testAngleAtVertex(self):
self.assertAlmostEqual(polygon.angleAtVertex(3), math.radians(225.0), places=3)
self.assertAlmostEqual(polygon.angleAtVertex(4), math.radians(135.0), places=3)

def testExtendLine(self):
""" test QgsGeometry.extendLine """

empty = QgsGeometry()
self.assertFalse(empty.extendLine(1, 2))

# not a linestring
point = QgsGeometry.fromWkt('Point(1 2)')
self.assertFalse(point.extendLine(1, 2))

# linestring
linestring = QgsGeometry.fromWkt('LineString(0 0, 1 0, 1 1)')
extended = linestring.extendLine(1, 2)
exp = 'LineString(-1 0, 1 0, 1 3)'
result = extended.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

# multilinestring
multilinestring = QgsGeometry.fromWkt('MultiLineString((0 0, 1 0, 1 1),(11 11, 11 10, 10 10))')
extended = multilinestring.extendLine(1, 2)
exp = 'MultiLineString((-1 0, 1 0, 1 3),(11 12, 11 10, 8 10))'
result = extended.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

if __name__ == '__main__':
unittest.main()

0 comments on commit ef34e39

Please sign in to comment.