diff --git a/python/plugins/processing/tests/testdata/expected/points_along_polys.gml b/python/plugins/processing/tests/testdata/expected/points_along_polys.gml
index d1665e6470b1..53ccbab6cb36 100644
--- a/python/plugins/processing/tests/testdata/expected/points_along_polys.gml
+++ b/python/plugins/processing/tests/testdata/expected/points_along_polys.gml
@@ -453,74 +453,82 @@
+ 7,-1
ASDF
0
17
- 135
+ 180
+ 7,-2
ASDF
0
18
- 180
+ 135
+ 8,-2
ASDF
0
19
- 180
+ 90
+ 9,-2
ASDF
0
20
- 90
+ 45
+ 9,-1
ASDF
0
21
- 90
+ 0
+ 9,0
ASDF
0
22
- 0
+ 315
+ 8,0
ASDF
0
23
- 0
+ 270
+ 7,0
ASDF
0
24
- 270
+ 225
diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp
index 12c2a67a66bc..0f7ddd983934 100644
--- a/src/core/geometry/qgsgeometry.cpp
+++ b/src/core/geometry/qgsgeometry.cpp
@@ -2125,7 +2125,7 @@ QgsGeometry QgsGeometry::subdivide( int maxNodes ) const
return QgsGeometry( std::move( result ) );
}
-QgsGeometry QgsGeometry::interpolate( const double distance ) const
+QgsGeometry QgsGeometry::interpolate( double distance ) const
{
if ( !d->geometry )
{
@@ -2143,11 +2143,21 @@ QgsGeometry QgsGeometry::interpolate( const double distance ) const
const QgsCurve *curve = nullptr;
if ( line.isMultipart() )
{
- // if multi part, just use first part
+ // if multi part, iterate through parts to find target part
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( line.constGet() );
- if ( collection && collection->numGeometries() > 0 )
+ for ( int part = 0; part < collection->numGeometries(); ++part )
{
- curve = qgsgeometry_cast< const QgsCurve * >( collection->geometryN( 0 ) );
+ const QgsCurve *candidate = qgsgeometry_cast< const QgsCurve * >( collection->geometryN( part ) );
+ if ( !candidate )
+ continue;
+ const double candidateLength = candidate->length();
+ if ( candidateLength >= distance )
+ {
+ curve = candidate;
+ break;
+ }
+
+ distance -= candidateLength;
}
}
else
diff --git a/src/core/geometry/qgsgeometryutils.cpp b/src/core/geometry/qgsgeometryutils.cpp
index 27560617d417..b2812e0e0bee 100644
--- a/src/core/geometry/qgsgeometryutils.cpp
+++ b/src/core/geometry/qgsgeometryutils.cpp
@@ -166,7 +166,7 @@ bool QgsGeometryUtils::verticesAtDistance( const QgsAbstractGeometry &geometry,
bool first = true;
while ( currentDist < distance && geometry.nextVertex( nextVertex, point ) )
{
- if ( !first )
+ if ( !first && nextVertex.part == previousVertex.part && nextVertex.ring == previousVertex.ring )
{
currentDist += std::sqrt( QgsGeometryUtils::sqrDistance2D( previousPoint, point ) );
}
diff --git a/tests/src/core/testqgsgeometryutils.cpp b/tests/src/core/testqgsgeometryutils.cpp
index 9a426802f428..b5be8a60051b 100644
--- a/tests/src/core/testqgsgeometryutils.cpp
+++ b/tests/src/core/testqgsgeometryutils.cpp
@@ -511,6 +511,48 @@ void TestQgsGeometryUtils::testVerticesAtDistance()
QCOMPARE( previous, QgsVertexId( 0, 0, 4 ) );
QCOMPARE( next, QgsVertexId( 0, 0, 4 ) );
+ // with ring
+ QgsLineString *ring1 = new QgsLineString();
+ ring1->setPoints( QVector() << QgsPoint( 1.1, 1.1 ) << QgsPoint( 1.1, 1.2 ) << QgsPoint( 1.2, 1.2 ) << QgsPoint( 1.2, 1.1 ) << QgsPoint( 1.1, 1.1 ) );
+ polygon1.addInteriorRing( ring1 );
+ QVERIFY( QgsGeometryUtils::verticesAtDistance( polygon1, 4, previous, next ) );
+ QCOMPARE( previous, QgsVertexId( 0, 0, 4 ) );
+ QCOMPARE( next, QgsVertexId( 0, 0, 4 ) );
+ QVERIFY( QgsGeometryUtils::verticesAtDistance( polygon1, 4.01, previous, next ) );
+ QCOMPARE( previous, QgsVertexId( 0, 1, 0 ) );
+ QCOMPARE( next, QgsVertexId( 0, 1, 1 ) );
+ QVERIFY( QgsGeometryUtils::verticesAtDistance( polygon1, 4.11, previous, next ) );
+ QCOMPARE( previous, QgsVertexId( 0, 1, 1 ) );
+ QCOMPARE( next, QgsVertexId( 0, 1, 2 ) );
+
+ // multipolygon
+ outerRing1 = new QgsLineString();
+ outerRing1->setPoints( QVector() << QgsPoint( 1, 1 ) << QgsPoint( 1, 2 ) << QgsPoint( 2, 2 ) << QgsPoint( 2, 1 ) << QgsPoint( 1, 1 ) );
+ QgsPolygon *polygon2 = new QgsPolygon();
+ polygon2->setExteriorRing( outerRing1 );
+
+ QgsLineString *outerRing2 = new QgsLineString();
+ outerRing2->setPoints( QVector() << QgsPoint( 10, 10 ) << QgsPoint( 10, 20 ) << QgsPoint( 20, 20 ) << QgsPoint( 20, 10 ) << QgsPoint( 10, 10 ) );
+ QgsPolygon *polygon3 = new QgsPolygon();
+ polygon3->setExteriorRing( outerRing2 );
+
+ QgsLineString *innerRing2 = new QgsLineString();
+ innerRing2->setPoints( QVector() << QgsPoint( 14, 14 ) << QgsPoint( 14, 16 ) << QgsPoint( 16, 16 ) << QgsPoint( 16, 14 ) << QgsPoint( 14, 14 ) );
+ polygon3->setInteriorRings( QVector() << innerRing2 );
+
+ QgsMultiPolygon mpg;
+ mpg.addGeometry( polygon2 );
+ mpg.addGeometry( polygon3 );
+ QVERIFY( QgsGeometryUtils::verticesAtDistance( mpg, 0.1, previous, next ) );
+ QCOMPARE( previous, QgsVertexId( 0, 0, 0 ) );
+ QCOMPARE( next, QgsVertexId( 0, 0, 1 ) );
+ QVERIFY( QgsGeometryUtils::verticesAtDistance( mpg, 5, previous, next ) );
+ QCOMPARE( previous, QgsVertexId( 1, 0, 0 ) );
+ QCOMPARE( next, QgsVertexId( 1, 0, 1 ) );
+ QVERIFY( QgsGeometryUtils::verticesAtDistance( mpg, 45, previous, next ) );
+ QCOMPARE( previous, QgsVertexId( 1, 1, 0 ) );
+ QCOMPARE( next, QgsVertexId( 1, 1, 1 ) );
+
//test with point
QgsPoint point( 1, 2 );
QVERIFY( !QgsGeometryUtils::verticesAtDistance( point, .5, previous, next ) );
diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py
index 8fe3981b52df..b0d1b135d247 100644
--- a/tests/src/python/test_qgsgeometry.py
+++ b/tests/src/python/test_qgsgeometry.py
@@ -4317,13 +4317,50 @@ def testInterpolate(self):
exp = 'Point(5 0)'
result = linestring.interpolate(5).asWkt()
self.assertTrue(compareWkt(result, exp, 0.00001), "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ self.assertTrue(linestring.interpolate(25).isNull())
+
+ # multilinestring
+ linestring = QgsGeometry.fromWkt('MultiLineString((0 0, 10 0, 10 10),(20 0, 30 0, 30 10))')
+ exp = 'Point(5 0)'
+ result = linestring.interpolate(5).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.00001),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ exp = 'Point(10 5)'
+ result = linestring.interpolate(15).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.00001),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ exp = 'Point(10 10)'
+ result = linestring.interpolate(20).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.00001),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ exp = 'Point(25 0)'
+ result = linestring.interpolate(25).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.00001),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ exp = 'Point(30 0)'
+ result = linestring.interpolate(30).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.00001),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ exp = 'Point(30 5)'
+ result = linestring.interpolate(35).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.00001),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ self.assertTrue(linestring.interpolate(50).isNull())
# polygon
polygon = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 20 20, 10 20, 0 0))') # NOQA
exp = 'Point(10 5)'
- result = linestring.interpolate(15).asWkt()
+ result = polygon.interpolate(15).asWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
+ self.assertTrue(polygon.interpolate(68).isNull())
+
+ # polygon with ring
+ polygon = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 20 20, 10 20, 0 0),(5 5, 6 5, 6 6, 5 6, 5 5))') # NOQA
+ exp = 'Point (6 5.5)'
+ result = polygon.interpolate(68).asWkt()
+ self.assertTrue(compareWkt(result, exp, 0.1),
+ "Interpolate: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
def testAngleAtVertex(self):
""" test QgsGeometry.angleAtVertex """