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 c920fe14cdec..ddb92e63c0d4 100644 --- a/tests/src/core/testqgsoverlayexpression.cpp +++ b/tests/src/core/testqgsoverlayexpression.cpp @@ -63,6 +63,8 @@ class TestQgsOverlayExpression : public QObject void testOverlayMeasure(); void testOverlayMeasure_data(); + + void testOverlayIntersectsDetails(); }; @@ -393,6 +395,63 @@ 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 );