Skip to content

Commit

Permalink
Handle linestrings
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso authored and nyalldawson committed Nov 29, 2021
1 parent 9d65691 commit 3283aa4
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 74 deletions.
10 changes: 5 additions & 5 deletions resources/function_help/json/overlay_intersects
Expand Up @@ -30,13 +30,13 @@
"default": "false"
},
{
"arg": "min_area",
"description": "an optional minimum area in current feature squared units for the intersection (if the intersection results in multiple polygons the intersection will be returned if at least one of the polygons has an area greater or equal to the value)",
"arg": "min_overlap",
"description": "for polygons an optional minimum area in current feature squared units for the intersection (if the intersection results in multiple polygons the intersection will be returned if at least one of the polygons has an area greater or equal to the value), for lines an optional minimum length in current feature units (if the intersection results in multiple lines the intersection will be returned if at least one of the lines has a length greater or equal to the value).",
"optional": true
},
{
"arg": "min_inscribed_circle_radius",
"description": "an optional minimum radius in current feature units for the maximum inscribed circle of the intersection (if the intersection results in multiple polygons the intersection will be returned if at least one of the polygons has a radius for the maximum inscribed circle greater or equal to the value).<br>Read more on the underlying GEOS predicate, as described in PostGIS <a href='https://postgis.net/docs/ST_MaximumInscribedCircle.html'>ST_MaximumInscribedCircle</a> function.<br><b>This argument requires GEOS >= 3.9",
"description": "for polygons only an optional minimum radius in current feature units for the maximum inscribed circle of the intersection (if the intersection results in multiple polygons the intersection will be returned if at least one of the polygons has a radius for the maximum inscribed circle greater or equal to the value).<br>Read more on the underlying GEOS predicate, as described in PostGIS <a href='https://postgis.net/docs/ST_MaximumInscribedCircle.html'>ST_MaximumInscribedCircle</a> function.",
"optional": true
}
],
Expand Down Expand Up @@ -66,12 +66,12 @@
"returns": "an array of geometries (in WKT), for up to two regions intersected by the current feature"
},
{
"expression": "overlay_intersects(layer:='regions', min_area:=0.54)",
"expression": "overlay_intersects(layer:='regions', min_overlap:=0.54)",
"returns": "true if the current feature spatially intersects a region and the intersection area (of at least one of the parts in case of multipolygons) is greater or equal to 0.54"
},
{
"expression": "overlay_intersects(layer:='regions', min_inscribed_circle_radius:=0.54)",
"returns": "true if the current feature spatially intersects a region and the intersection area maximum inscribed circle's radius (of at least one of the parts in case of multipolygons) is greater or equal to the 0.54"
"returns": "true if the current feature spatially intersects a region and the intersection area maximum inscribed circle's radius (of at least one of the parts in case of multipart) is greater or equal to the 0.54"
}
]
}
155 changes: 100 additions & 55 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -6612,18 +6612,18 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
ENSURE_NO_EVAL_ERROR
bool cacheEnabled = cacheValue.toBool();

// Sixth parameter (for intersects only) is the min area
// Sixth parameter (for intersects only) is the min overlap (area or length)
// Seventh parameter (for intersects only) is the min inscribed circle radius
double minArea { -1 };
double minOverlap { -1 };
double minInscribedCircleRadius { -1 };
if ( isIntersectsFunc )
{

node = QgsExpressionUtils::getNode( values.at( 5 ), parent ); //in expressions overlay functions throw the exception: Eval Error: Cannot convert '' to int
ENSURE_NO_EVAL_ERROR
const QVariant minAreaValue = node->eval( parent, context );
const QVariant minOverlapValue = node->eval( parent, context );
ENSURE_NO_EVAL_ERROR
minArea = QgsExpressionUtils::getDoubleValue( minAreaValue, parent );
minOverlap = QgsExpressionUtils::getDoubleValue( minOverlapValue, parent );
node = QgsExpressionUtils::getNode( values.at( 6 ), parent ); //in expressions overlay functions throw the exception: Eval Error: Cannot convert '' to int
ENSURE_NO_EVAL_ERROR
const QVariant minInscribedCircleRadiusValue = node->eval( parent, context );
Expand Down Expand Up @@ -6753,76 +6753,121 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
if ( ! relationFunction || ( geometry.*relationFunction )( feat2.geometry() ) ) // Calls the method provided as template argument for the function (e.g. QgsGeometry::intersects)
{

if ( isIntersectsFunc && ( minArea != -1 || minInscribedCircleRadius != -1 ) )
if ( isIntersectsFunc && ( minOverlap != -1 || minInscribedCircleRadius != -1 ) )
{
const QgsGeometry intersection { geometry.intersection( feat2.geometry() ) };

// Skip id not polygon
if ( intersection.type() != QgsWkbTypes::GeometryType::PolygonGeometry )
switch ( intersection.type() )
{
continue;
}

// Check min area for intersection (if set)
if ( intersection.isMultipart() )
{
bool testResult { false };
for ( auto it = intersection.const_parts_begin(); ! testResult && it != intersection.const_parts_end(); ++it )
case QgsWkbTypes::GeometryType::PolygonGeometry:
{
const QgsPolygon *geom = qgsgeometry_cast< const QgsPolygon * >( *it );
// qDebug() << "Area" << feat2.id() << geom->area();
if ( minArea != -1 )
// Check min overlap for intersection (if set)
if ( intersection.isMultipart() )
{
if ( geom->area() >= minArea )
bool testResult { false };
for ( auto it = intersection.const_parts_begin(); ! testResult && it != intersection.const_parts_end(); ++it )
{
testResult = true;
const QgsPolygon *geom = qgsgeometry_cast< const QgsPolygon * >( *it );
// qDebug() << "Area" << feat2.id() << geom->area();
if ( minOverlap != -1 )
{
if ( geom->area() >= minOverlap )
{
testResult = true;
}
else
{
continue;
}
}

// Check min inscribed circle radius for intersection (if set)
if ( minInscribedCircleRadius != -1 )
{
const QgsRectangle bbox = geom->boundingBox();
const double width = bbox.width();
const double height = bbox.height();
const double size = width > height ? width : height;
const double tolerance = size / 1000.0;
//qDebug() << "Inscribed circle radius" << feat2.id() << QgsGeos( geom ).maximumInscribedCircle( tolerance )->length();
testResult = QgsGeos( geom ).maximumInscribedCircle( tolerance )->length() >= minInscribedCircleRadius;
}
}
else

if ( ! testResult )
{
continue;
}
}

// Check min inscribed circle radius for intersection (if set)
if ( minInscribedCircleRadius != -1 )
}
else
{
const QgsRectangle bbox = geom->boundingBox();
const double width = bbox.width();
const double height = bbox.height();
const double size = width > height ? width : height;
const double tolerance = size / 1000.0;
//qDebug() << "Inscribed circle radius" << feat2.id() << QgsGeos( geom ).maximumInscribedCircle( tolerance )->length();
testResult = QgsGeos( geom ).maximumInscribedCircle( tolerance )->length() >= minInscribedCircleRadius;
if ( minOverlap != -1 && intersection.area() < minOverlap )
{
continue;
}

// Check min inscribed circle radius for intersection (if set)
if ( minInscribedCircleRadius != -1 )
{
const QgsAbstractGeometry *geom { intersection.constGet() };
const QgsRectangle bbox = geom->boundingBox();
const double width = bbox.width();
const double height = bbox.height();
const double size = width > height ? width : height;
const double tolerance = size / 1000.0;
// qDebug() << "Inscribed circle radius" << feat2.id() << QgsGeos( geom ).maximumInscribedCircle( tolerance )->length();
if ( QgsGeos( geom ).maximumInscribedCircle( tolerance )->length() < minInscribedCircleRadius )
{
continue;
}
}
}
break;
}

if ( ! testResult )
case QgsWkbTypes::GeometryType::LineGeometry:
{
continue;
}
// Check min overlap for intersection (if set)
if ( intersection.isMultipart() )
{
bool testResult { false };
for ( auto it = intersection.const_parts_begin(); ! testResult && it != intersection.const_parts_end(); ++it )
{
const QgsLineString *geom = qgsgeometry_cast< const QgsLineString * >( *it );
// qDebug() << "Length" << feat2.id() << geom->length();
if ( minOverlap != -1 )
{
if ( geom->length() >= minOverlap )
{
testResult = true;
}
else
{
continue;
}
}
}

}
else
{
if ( minArea != -1 && intersection.area() < minArea )
{
continue;
}
if ( ! testResult )
{
continue;
}

// Check min inscribed circle radius for intersection (if set)
if ( minInscribedCircleRadius != -1 )
{
const QgsAbstractGeometry *geom { intersection.constGet() };
const QgsRectangle bbox = geom->boundingBox();
const double width = bbox.width();
const double height = bbox.height();
const double size = width > height ? width : height;
const double tolerance = size / 1000.0;
// qDebug() << "Inscribed circle radius" << feat2.id() << QgsGeos( geom ).maximumInscribedCircle( tolerance )->length();
if ( QgsGeos( geom ).maximumInscribedCircle( tolerance )->length() < minInscribedCircleRadius )
}
else
{
continue;
if ( minOverlap != -1 && intersection.length() < minOverlap )
{
continue;
}
}
break;
}
case QgsWkbTypes::GeometryType::PointGeometry:
case QgsWkbTypes::GeometryType::NullGeometry:
case QgsWkbTypes::GeometryType::UnknownGeometry:
{
break;
}
}
}
Expand Down Expand Up @@ -7326,7 +7371,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< QgsExpressionFunction::Parameter( QStringLiteral( "filter" ), true, QVariant(), true )
<< QgsExpressionFunction::Parameter( QStringLiteral( "limit" ), true, QVariant( -1 ), true )
<< QgsExpressionFunction::Parameter( QStringLiteral( "cache" ), true, QVariant( false ), false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_area" ), true, QVariant( -1 ), false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_overlap" ), true, QVariant( -1 ), false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_inscribed_circle_radius" ), true, QVariant( -1 ), false ),
i.value(), QStringLiteral( "GeometryGroup" ), QString(), true, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES, true );

Expand Down
34 changes: 20 additions & 14 deletions tests/src/core/testqgsoverlayexpression.cpp
Expand Up @@ -158,40 +158,46 @@ void TestQgsOverlayExpression::testOverlay_data()
QTest::newRow( "disjoint no match [cached]" ) << "overlay_disjoint('rectangles',cache:=true)" << "LINESTRING(-155 15, -122 32, -84 4)" << false;

// Multi part intersection
QTest::newRow( "intersects min_area multi no match" ) << "overlay_intersects('polys', min_area:=1.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects min_area multi match" ) << "overlay_intersects('polys', min_area:=1.34)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << true;
QTest::newRow( "intersects min_overlap multi no match" ) << "overlay_intersects('polys', min_overlap:=1.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects min_overlap multi match" ) << "overlay_intersects('polys', min_overlap:=1.34)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << true;

#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
QTest::newRow( "intersects min_inscribed_circle_radius multi no match" ) << "overlay_intersects('polys', min_inscribed_circle_radius:=1.0)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects min_inscribed_circle_radius multi match" ) << "overlay_intersects('polys', min_inscribed_circle_radius:=0.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << true;
#endif

// Single part intersection
QTest::newRow( "intersects min_area no match" ) << "overlay_intersects('polys', min_area:=1.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects min_area match" ) << "overlay_intersects('polys', min_area:=1.34)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << true;
QTest::newRow( "intersects min_overlap no match" ) << "overlay_intersects('polys', min_overlap:=1.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects min_overlap match" ) << "overlay_intersects('polys', min_overlap:=1.34)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << true;

#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
QTest::newRow( "intersects min_inscribed_circle_radius no match" ) << "overlay_intersects('polys', min_inscribed_circle_radius:=1.0)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects min_inscribed_circle_radius match" ) << "overlay_intersects('polys', min_inscribed_circle_radius:=0.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << true;

// Test both checks combined: they must all pass
// Multi part intersection
QTest::newRow( "intersects multi combined no match 1" ) << "overlay_intersects('polys', min_area:=1.5, min_inscribed_circle_radius:=1.0)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects multi combined no match 2" ) << "overlay_intersects('polys', min_area:=1.5, min_inscribed_circle_radius:=0.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects multi combined no match 1" ) << "overlay_intersects('polys', min_overlap:=1.5, min_inscribed_circle_radius:=1.0)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects multi combined no match 2" ) << "overlay_intersects('polys', min_overlap:=1.5, min_inscribed_circle_radius:=0.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;

QTest::newRow( "intersects multi combined no match 3" ) << "overlay_intersects('polys', min_area:=1.34, min_inscribed_circle_radius:=1.0)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects multi combined match" ) << "overlay_intersects('polys', min_area:=1.34, min_inscribed_circle_radius:=0.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << true;
QTest::newRow( "intersects multi combined no match 3" ) << "overlay_intersects('polys', min_overlap:=1.34, min_inscribed_circle_radius:=1.0)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << false;
QTest::newRow( "intersects multi combined match" ) << "overlay_intersects('polys', min_overlap:=1.34, min_inscribed_circle_radius:=0.5)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << true;

// Single part intersection
QTest::newRow( "intersects combined no match 1" ) << "overlay_intersects('polys', min_area:=1.5, min_inscribed_circle_radius:=1.0)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects combined no match 2" ) << "overlay_intersects('polys', min_area:=1.34, min_inscribed_circle_radius:=1.0)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects combined no match 3" ) << "overlay_intersects('polys', min_area:=1.5, min_inscribed_circle_radius:=0.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects combined no match 1" ) << "overlay_intersects('polys', min_overlap:=1.5, min_inscribed_circle_radius:=1.0)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects combined no match 2" ) << "overlay_intersects('polys', min_overlap:=1.34, min_inscribed_circle_radius:=1.0)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;
QTest::newRow( "intersects combined no match 3" ) << "overlay_intersects('polys', min_overlap:=1.5, min_inscribed_circle_radius:=0.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << false;

QTest::newRow( "intersects combined match" ) << "overlay_intersects('polys', min_area:=1.34, min_inscribed_circle_radius:=0.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << true;
QTest::newRow( "intersects combined match" ) << "overlay_intersects('polys', min_overlap:=1.34, min_inscribed_circle_radius:=0.5)" << "POLYGON((-105 33.75, -102.76 33.75, -102.76 35.2, -105 35.2, -105 33.75))" << true;

// Check wrong args
QTest::newRow( "intersects wrong args" ) << "overlay_intersects('polys', min_area:=1.5, min_inscribed_circle_radius:=0.5)" << "LINESTRING(-105 33.75, -102.76 33.75)" << false;
#endif

// Check linestrings
QTest::newRow( "intersects linestring match" ) << "overlay_intersects('polys', min_overlap:=1.76)" << "LINESTRING(-105 33.75, -102.76 33.75)" << true;
QTest::newRow( "intersects linestring no match" ) << "overlay_intersects('polys', min_overlap:=2.0)" << "LINESTRING(-105 33.75, -102.76 33.75)" << false;

QTest::newRow( "intersects linestring multi match" ) << "overlay_intersects('polys', min_overlap:=1.76)" << "LINESTRING(-105 33.75, -102.76 33.75)" << true;
QTest::newRow( "intersects linestring multi no match" ) << "overlay_intersects('polys', min_overlap:=2.0)" << "LINESTRING(-102.76 33.74, -106.12 33.74)" << false;

}

void TestQgsOverlayExpression::testOverlayExpression()
Expand Down

0 comments on commit 3283aa4

Please sign in to comment.