From 3fb18a2260cbc432e892a4dacbae0092a775ec9d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 30 Jan 2020 16:21:53 +1000 Subject: [PATCH] [processing] Fix Points Along Geometry handling of multipart geometries Fixes #34022 --- .../testdata/expected/points_along_polys.gml | 24 +++++++---- src/core/geometry/qgsgeometry.cpp | 18 ++++++-- src/core/geometry/qgsgeometryutils.cpp | 2 +- tests/src/core/testqgsgeometryutils.cpp | 42 +++++++++++++++++++ tests/src/python/test_qgsgeometry.py | 39 ++++++++++++++++- 5 files changed, 111 insertions(+), 14 deletions(-) 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 """