Skip to content
Permalink
Browse files
overlay_intesects: return measure and sort by measure
  • Loading branch information
elpaso committed Dec 21, 2021
1 parent 8dc8331 commit c842b2d01e047585d28942c878038a9d9813b19e
Showing with 187 additions and 25 deletions.
  1. +124 −24 src/core/expression/qgsexpressionfunction.cpp
  2. +63 −1 tests/src/core/testqgsoverlayexpression.cpp
@@ -6649,8 +6649,14 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress

// Sixth parameter (for intersects only) is the min overlap (area or length)
// Seventh parameter (for intersects only) is the min inscribed circle radius
// Eight parameter (for intersects only) is the return_measures
// Nineth parameter (for intersects only) is the sort_by_measure flag
double minOverlap { -1 };
double minInscribedCircleRadius { -1 };
bool returnMeasures = false;
bool sortByMeasure = false;
bool requireMeasures = false;
bool overlapOrRadiusFilter = false;
if ( isIntersectsFunc )
{

@@ -6671,6 +6677,14 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
return QVariant();
}
#endif
node = QgsExpressionUtils::getNode( values.at( 7 ), parent );
// Return measures is only effective when an expression is set
returnMeasures = !testOnly && node->eval( parent, context ).toBool();
node = QgsExpressionUtils::getNode( values.at( 8 ), parent );
// Sort by measures is only effective when an expression is set
sortByMeasure = !testOnly && node->eval( parent, context ).toBool();
requireMeasures = sortByMeasure || returnMeasures;
overlapOrRadiusFilter = minInscribedCircleRadius != -1 || minOverlap != -1;
}


@@ -6788,53 +6802,77 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
QVariantList results;

QListIterator<QgsFeature> i( features );
while ( i.hasNext() && ( limit == -1 || foundCount < limit ) )
while ( i.hasNext() && ( sortByMeasure || limit == -1 || foundCount < limit ) )
{
QgsFeature feat2 = i.next();

if ( ! relationFunction || ( geometry.*relationFunction )( feat2.geometry() ) ) // Calls the method provided as template argument for the function (e.g. QgsGeometry::intersects)
{

if ( isIntersectsFunc && ( minOverlap != -1 || minInscribedCircleRadius != -1 ) )
double overlapValue = -1;
double radiusValue = -1;

if ( isIntersectsFunc && ( requireMeasures || overlapOrRadiusFilter ) )
{
const QgsGeometry intersection { geometry.intersection( feat2.geometry() ) };

// overlap and inscribed circle tests must be checked both (if the valuea are != -1)
// overlap and inscribed circle tests must be checked both (if the values are != -1)
switch ( intersection.type() )
{
case QgsWkbTypes::GeometryType::PolygonGeometry:
{

bool testResult { false };
for ( auto it = intersection.const_parts_begin(); ! testResult && it != intersection.const_parts_end(); ++it )
// For return measures:
QVector<double> overlapValues;
QVector<double> radiusValues;
for ( auto it = intersection.const_parts_begin(); ( ! testResult || requireMeasures ) && it != intersection.const_parts_end(); ++it )
{
const QgsCurvePolygon *geom = qgsgeometry_cast< const QgsCurvePolygon * >( *it );
// Check min overlap for intersection (if set)
if ( minOverlap != -1 )
if ( minOverlap != -1 || requireMeasures )
{
if ( geom->area() >= minOverlap )
overlapValue = geom->area();
overlapValues.append( geom->area() );
if ( minOverlap != - 1 )
{
testResult = true;
}
else
{
continue;
if ( overlapValue >= minOverlap )
{
testResult = true;
}
else
{
continue;
}
}
}

// Check min inscribed circle radius for intersection (if set)
if ( minInscribedCircleRadius != -1 )
if ( minInscribedCircleRadius != -1 || requireMeasures )
{
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 / 100.0;
testResult = QgsGeos( geom ).maximumInscribedCircle( tolerance )->length() >= minInscribedCircleRadius;
radiusValue = QgsGeos( geom ).maximumInscribedCircle( tolerance )->length();
testResult = radiusValue >= minInscribedCircleRadius;
radiusValues.append( radiusValues );
}
}

if ( ! testResult )
// Get the max values
if ( !radiusValues.isEmpty() )
{
radiusValue = *std::max_element( radiusValues.cbegin(), radiusValues.cend() );
}

if ( ! overlapValues.isEmpty() )
{
overlapValue = *std::max_element( overlapValues.cbegin(), overlapValues.cend() );
}

if ( ! testResult && overlapOrRadiusFilter )
{
continue;
}
@@ -6844,27 +6882,40 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
case QgsWkbTypes::GeometryType::LineGeometry:
{
bool testResult { false };
// For return measures:
QVector<double> overlapValues;
for ( auto it = intersection.const_parts_begin(); ! testResult && it != intersection.const_parts_end(); ++it )
{
const QgsCurve *geom = qgsgeometry_cast< const QgsCurve * >( *it );
// Check min overlap for intersection (if set)
if ( minOverlap != -1 )
if ( minOverlap != -1 || requireMeasures )
{
if ( geom->length() >= minOverlap )
overlapValue = geom->length();
overlapValues.append( overlapValue );
if ( minOverlap != -1 )
{
testResult = true;
}
else
{
continue;
if ( overlapValue >= minOverlap )
{
testResult = true;
}
else
{
continue;
}
}
}
}

if ( ! testResult )
if ( ! overlapValues.isEmpty() )
{
overlapValue = *std::max_element( overlapValues.cbegin(), overlapValues.cend() );
}

if ( ! testResult && overlapOrRadiusFilter )
{
continue;
}

break;
}
case QgsWkbTypes::GeometryType::PointGeometry:
@@ -6887,7 +6938,26 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
{
// We want a list of attributes / geometries / other expression values, evaluate now
subContext.setFeature( feat2 );
results.append( subExpression.evaluate( &subContext ) );
const QVariant expResult { subExpression.evaluate( &subContext ) };

if ( requireMeasures )
{
QVariantMap resultRecord;
resultRecord.insert( QStringLiteral( "id" ), feat2.id() );
resultRecord.insert( QStringLiteral( "result" ), expResult );
// Overlap is always added because return measures was set
resultRecord.insert( QStringLiteral( "overlap" ), overlapValue );
// radius is only added when is different than -1 (because for linestrings is not set)
if ( radiusValue != -1 )
{
resultRecord.insert( QStringLiteral( "radius" ), radiusValue );
}
results.append( resultRecord );
}
else
{
results.append( expResult );
}
}
else
{
@@ -6905,7 +6975,35 @@ static QVariant executeGeomOverlay( const QVariantList &values, const QgsExpress
}

if ( !invert )
{
if ( requireMeasures )
{
if ( sortByMeasure )
{
std::sort( results.begin(), results.end(), [ ]( const QVariant & recordA, const QVariant & recordB ) -> bool
{
return recordA.toMap().value( QStringLiteral( "overlap" ) ).toDouble() > recordB.toMap().value( QStringLiteral( "overlap" ) ).toDouble();
} );
}
// Resize
if ( limit > 0 && results.size() > limit )
{
results.erase( results.begin() + limit );
}

if ( ! returnMeasures )
{
QVariantList expResults;
for ( auto it = results.constBegin(); it != results.constEnd(); ++it )
{
expResults.append( it->toMap().value( QStringLiteral( "result" ) ) );
}
return expResults;
}
}

return results;
}

// for disjoint condition returns the results for cached layers not intersected feats
QVariantList disjoint_results;
@@ -7376,7 +7474,9 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< QgsExpressionFunction::Parameter( QStringLiteral( "limit" ), true, QVariant( -1 ), true )
<< QgsExpressionFunction::Parameter( QStringLiteral( "cache" ), true, QVariant( false ), false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_overlap" ), true, QVariant( -1 ), false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_inscribed_circle_radius" ), true, QVariant( -1 ), false ),
<< QgsExpressionFunction::Parameter( QStringLiteral( "min_inscribed_circle_radius" ), true, QVariant( -1 ), false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "return_measures" ), true, false, false )
<< QgsExpressionFunction::Parameter( QStringLiteral( "sort_by_measure" ), true, false, false ),
i.value(), QStringLiteral( "GeometryGroup" ), QString(), true, QSet<QString>() << QgsFeatureRequest::ALL_ATTRIBUTES, true );

// The current feature is accessed for the geometry, so this should not be cached
@@ -57,12 +57,16 @@ class TestQgsOverlayExpression: public QObject
void initTestCase();

void cleanupTestCase();
void testOverlaySelf();

void testOverlay();
void testOverlay_data();

private slots:

void testOverlayMeasure();
void testOverlayMeasure_data();

void testOverlaySelf();
};


@@ -109,6 +113,7 @@ void TestQgsOverlayExpression::testOverlay()
const QVariant result = exp.evaluate( &context );

QCOMPARE( result.toBool(), expectedResult );

}

void TestQgsOverlayExpression::testOverlay_data()
@@ -200,6 +205,63 @@ void TestQgsOverlayExpression::testOverlay_data()

}


void TestQgsOverlayExpression::testOverlayMeasure()
{
QFETCH( QString, expression );
QFETCH( QString, geometry );
QFETCH( QVariantList, expectedResult );

QgsExpressionContext context;
context.appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );

QgsFeature feat;
feat.setGeometry( QgsGeometry::fromWkt( geometry ) );
context.setFeature( feat );

QgsExpression exp( expression );
QVERIFY2( exp.prepare( &context ), exp.parserErrorString().toUtf8().constData() );
const QVariant result = exp.evaluate( &context );

QCOMPARE( result, expectedResult );

}

void TestQgsOverlayExpression::testOverlayMeasure_data()
{

QTest::addColumn<QString>( "expression" );
QTest::addColumn<QString>( "geometry" );
QTest::addColumn<QVariantList>( "expectedResult" );

// Check return measures
QVariantMap expected3;
expected3.insert( QStringLiteral( "id" ), 3LL );
expected3.insert( QStringLiteral( "result" ), 3LL );
expected3.insert( QStringLiteral( "overlap" ), 1.4033836999701634 );
expected3 .insert( QStringLiteral( "radius" ), 0.5344336346973622 );
QVariantMap expected1;
expected1.insert( QStringLiteral( "id" ), 1LL );
expected1.insert( QStringLiteral( "result" ), 1LL );
expected1.insert( QStringLiteral( "overlap" ), 1.2281139270096446 );
expected1.insert( QStringLiteral( "radius" ), 0.46454276882989376 );

QTest::newRow( "intersects min_overlap multi match return measure" ) << "overlay_intersects('polys', expression:=$id, min_overlap:=1.34, return_measures:=true)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << expected3 ) ;

QTest::newRow( "intersects multi match return measure" ) << "overlay_intersects('polys', expression:=$id, return_measures:=true)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << expected1 << expected3 ) ;

QTest::newRow( "intersects multi match return sorted measure" ) << "overlay_intersects('polys', expression:=$id, sort_by_measure:=true, return_measures:=true)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << expected3 << expected1 ) ;

QTest::newRow( "intersects multi match return sorted" ) << "overlay_intersects('polys', expression:=$id, sort_by_measure:=true)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << 3LL << 1LL ) ;

QTest::newRow( "intersects multi match return unsorted" ) << "overlay_intersects('polys', expression:=$id)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << 1LL << 3LL ) ;

QTest::newRow( "intersects multi match return unsorted limit " ) << "overlay_intersects('polys', limit:=1, expression:=$id)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << 1LL ) ;

QTest::newRow( "intersects multi match return sorted limit " ) << "overlay_intersects('polys', sort_by_measure:=true, limit:=1, expression:=$id)" << "POLYGON((-107.37 33.75, -102.76 33.75, -102.76 36.97, -107.37 36.97, -107.37 33.75))" << ( QVariantList() << 3LL ) ;

}

void TestQgsOverlayExpression::testOverlayExpression()
{
QFETCH( QString, expression );

0 comments on commit c842b2d

Please sign in to comment.