From 4359d66d5f530a4b9a5bea1e18049b8f75c9a70a Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 18 Nov 2024 13:19:36 +0100 Subject: [PATCH] overlay_intersects: flatten collections and filter by type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partial fix for #59408 - try to merge multilinestrings to get the max length - consider the tested geomery type when intersection is a collection Funded by: Body & Soul ♬ --- .../function_help/json/overlay_intersects | 2 +- src/core/expression/qgsexpressionfunction.cpp | 66 ++++++++++++++++++- tests/src/core/testqgsoverlayexpression.cpp | 60 +++++++++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/resources/function_help/json/overlay_intersects b/resources/function_help/json/overlay_intersects index 5353aab455be..b19f202aacf5 100644 --- a/resources/function_help/json/overlay_intersects +++ b/resources/function_help/json/overlay_intersects @@ -41,7 +41,7 @@ }, { "arg": "return_details", - "description": "Set this to true to return a list of maps containing (key names in quotes) the feature 'id', the expression 'result' and the 'overlap' value. The 'radius' of the maximum inscribed circle is also returned when the target layer is a polygon. Only valid when used with the expression parameter", + "description": "Set this to true to return a list of maps containing (key names in quotes) the feature 'id', the expression 'result' and the 'overlap' value (of the largest element in case of multipart). The 'radius' of the maximum inscribed circle is also returned when the target layer is a polygon. Only valid when used with the expression parameter", "optional": true }, { diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 6b17cde4475c..6d5aee88eff8 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -7951,7 +7951,8 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress bool testResult { false }; // For return measures: QVector overlapValues; - for ( auto it = intersection.const_parts_begin(); ! testResult && it != intersection.const_parts_end(); ++it ) + const QgsGeometry merged { intersection.mergeLines() }; + for ( auto it = merged.const_parts_begin(); ! testResult && it != merged.const_parts_end(); ++it ) { const QgsCurve *geom = qgsgeometry_cast< const QgsCurve * >( *it ); // Check min overlap for intersection (if set) @@ -8059,7 +8060,68 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress if ( isIntersectsFunc && ( requireMeasures || overlapOrRadiusFilter ) ) { - const QgsGeometry intersection { geometry.intersection( feat2.geometry() ) }; + + QgsGeometry intersection { geometry.intersection( feat2.geometry() ) }; + + // Pre-process collections: if the tested geometry is a polygon we take the polygons from the collection + if ( intersection.wkbType() == Qgis::WkbType::GeometryCollection ) + { + const QVector geometries { intersection.asGeometryCollection() }; + intersection = QgsGeometry(); + QgsMultiPolygonXY poly; + QgsMultiPolylineXY line; + QgsMultiPointXY point; + for ( const auto &geom : std::as_const( geometries ) ) + { + switch ( geom.type() ) + { + case Qgis::GeometryType::Polygon: + { + poly.append( geom.asPolygon() ); + break; + } + case Qgis::GeometryType::Line: + { + line.append( geom.asPolyline() ); + break; + } + case Qgis::GeometryType::Point: + { + point.append( geom.asPoint() ); + break; + } + case Qgis::GeometryType::Unknown: + case Qgis::GeometryType::Null: + { + break; + } + } + } + + switch ( geometry.type() ) + { + case Qgis::GeometryType::Polygon: + { + intersection = QgsGeometry::fromMultiPolygonXY( poly ); + break; + } + case Qgis::GeometryType::Line: + { + intersection = QgsGeometry::fromMultiPolylineXY( line ); + break; + } + case Qgis::GeometryType::Point: + { + intersection = QgsGeometry::fromMultiPointXY( point ); + break; + } + case Qgis::GeometryType::Unknown: + case Qgis::GeometryType::Null: + { + break; + } + } + } // Depending on the intersection geometry type and on the geometry type of // the tested geometry we can run different tests and collect different measures diff --git a/tests/src/core/testqgsoverlayexpression.cpp b/tests/src/core/testqgsoverlayexpression.cpp index 95e38be77f7a..47aa736aa6aa 100644 --- a/tests/src/core/testqgsoverlayexpression.cpp +++ b/tests/src/core/testqgsoverlayexpression.cpp @@ -65,6 +65,8 @@ class TestQgsOverlayExpression: public QObject void testOverlayMeasure(); void testOverlayMeasure_data(); + void testOverlayIntersectsDetails(); + }; @@ -405,6 +407,64 @@ void TestQgsOverlayExpression::testOverlayMeasure_data() QTest::newRow( "intersects line expression no match" ) << "overlay_intersects('polygons2', expression:=fid, return_details:=true, min_inscribed_circle_radius:=3, sort_by_intersection_size:='desc')" << "Polygon ((2604689.01899999985471368 1231313.05799999996088445, 2604695.41300000017508864 1231337.88999999989755452, 2604704.85499999998137355 1231335.10299999988637865, 2604713.89399999985471368 1231333.42900000000372529, 2604719.80599999986588955 1231332.34700000006705523, 2604713.325999999884516 1231305.375, 2604697.20899999979883432 1231310.25600000005215406, 2604689.01899999985471368 1231313.05799999996088445))" << ( QVariantList() << expectedPoly ); } +void TestQgsOverlayExpression::testOverlayIntersectsDetails() +{ + // Create polygon memory layer 1 + QgsVectorLayer *poly1 = new QgsVectorLayer( QStringLiteral( "Polygon?crs=epsg:4326" ), QStringLiteral( "poly_details" ), QStringLiteral( "memory" ) ); + QgsFeature f1; + f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon ((0 0, 0 6, 6 6, 6 0, 0 0))" ) ) ); + poly1->dataProvider()->addFeature( f1 ); + QgsProject::instance()->addMapLayer( poly1 ); + + // Create linestring memory layer + QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326" ), QStringLiteral( "line_details" ), QStringLiteral( "memory" ) ); + QgsFeature f2; + f2.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 0, 1 1, 2 1, 3 1, 4 0)" ) ) ); + line->dataProvider()->addFeature( f2 ); + QgsProject::instance()->addMapLayer( line ); + + QgsExpressionContext context; + QgsFeature feat; + feat.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon ((6 0, 6 2, 4 2, 4 4, 6 4, 6 6, 12 6, 12 0, 6 0))" ) ) ); + context.setFeature( feat ); + context.appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) ); + + QgsExpression exp( QStringLiteral( "overlay_intersects('poly_details', return_details:=true, expression:=$id)" ) ); + QVERIFY2( exp.prepare( &context ), exp.parserErrorString().toUtf8().constData() ); + QVariantList result = exp.evaluate( &context ).toList(); + QCOMPARE( result.size(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "id" ) ).toLongLong(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "overlap" ) ).toLongLong(), 4 ); + + feat.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon ((7 0, 7 1, 5 1, 5 2, 7 2, 7 3, 4 3, 4 5, 7 5, 7 6, 12 6, 12 0, 7 0))" ) ) ); + context.setFeature( feat ); + result = exp.evaluate( &context ).toList(); + QCOMPARE( result.size(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "id" ) ).toLongLong(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "overlap" ) ).toLongLong(), 4 ); + + feat.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon ((6 0, 6 1, 5 1, 5 2, 6 2, 6 3, 4 3, 4 5, 6 5, 6 6, 12 6, 12 0, 6 0))" ) ) ); + context.setFeature( feat ); + result = exp.evaluate( &context ).toList(); + QCOMPARE( result.size(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "id" ) ).toLongLong(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "overlap" ) ).toLongLong(), 4 ); + + // Test linestring + QgsExpression exp2( QStringLiteral( "overlay_intersects('line_details', return_details:=true, expression:=$id)" ) ); + feat.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (0 2, 1 1, 2 1, 3 1, 4 2)" ) ) ); + context.setFeature( feat ); + QVERIFY2( exp2.prepare( &context ), exp2.parserErrorString().toUtf8().constData() ); + result = exp2.evaluate( &context ).toList(); + QCOMPARE( result.size(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "id" ) ).toLongLong(), 1 ); + QCOMPARE( result.at( 0 ).toMap().value( QStringLiteral( "overlap" ) ).toLongLong(), 2 ); + + QgsProject::instance()->removeMapLayer( poly1->id() ); + QgsProject::instance()->removeMapLayer( line->id() ); + +} + void TestQgsOverlayExpression::testOverlayExpression() { QFETCH( QString, expression );