Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Port Select by Location to c++
  • Loading branch information
nyalldawson committed Sep 8, 2017
1 parent a9f4540 commit 1aa76ac
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 1 deletion.
179 changes: 178 additions & 1 deletion src/core/processing/qgsnativealgorithms.cpp
Expand Up @@ -76,6 +76,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() );
addAlgorithm( new QgsConvexHullAlgorithm() );
addAlgorithm( new QgsPromoteToMultipartAlgorithm() );
addAlgorithm( new QgsSelectByLocationAlgorithm() );
}

void QgsCentroidAlgorithm::initAlgorithm( const QVariantMap & )
Expand Down Expand Up @@ -1337,8 +1338,184 @@ QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
return new QgsCollectAlgorithm();
}

///@endcond


void QgsSelectByLocationAlgorithm::initAlgorithm( const QVariantMap & )
{
QStringList predicates = QStringList() << QObject::tr( "intersects" )
<< QObject::tr( "contains" )
<< QObject::tr( "is disjoint" )
<< QObject::tr( "equals" )
<< QObject::tr( "touches" )
<< QObject::tr( "overlaps" )
<< QObject::tr( "within" )
<< QObject::tr( "crosses" );

QStringList methods = QStringList() << QObject::tr( "creating new selection" )
<< QObject::tr( "adding to current selection" )
<< QObject::tr( "select within current selection" )
<< QObject::tr( "removing from current selection" );

addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );


addParameter( new QgsProcessingParameterEnum( QStringLiteral( "PREDICATE" ),
QObject::tr( "Where the features are (geometric predicate)" ),
predicates, true, QVariant::fromValue( QList< int >() << 0 ) ) );

addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
QObject::tr( "By comparing to the features from" ),
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );

addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
QObject::tr( "Modify current selection by" ),
methods, false, 0 ) );
}

QString QgsSelectByLocationAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm creates a selection in a vector layer. The criteria for selecting "
"features is based on the spatial relationship between each feature and the features in an additional layer." );
}

QgsSelectByLocationAlgorithm *QgsSelectByLocationAlgorithm::createInstance() const
{
return new QgsSelectByLocationAlgorithm();
}

QVariantMap QgsSelectByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
QgsVectorLayer::SelectBehavior method = static_cast< QgsVectorLayer::SelectBehavior >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
QgsFeatureSource *intersectSource = parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context );
const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );

// build a list of 'reversed' predicates, because in this function
// we actually test the reverse of what the user wants (allowing us
// to prepare geometries and optimise the algorithm)
QList< Predicate > predicates;
for ( int i : selectedPredicates )
{
predicates << reversePredicate( static_cast< Predicate >( i ) );
}

QgsFeatureIds disjointSet;
if ( predicates.contains( Disjoint ) )
disjointSet = selectLayer->allFeatureIds();

QgsFeatureIds selectedSet;
QgsFeatureRequest request = QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ).setDestinationCrs( selectLayer->crs() );
QgsFeatureIterator fIt = intersectSource->getFeatures( request );
double step = intersectSource->featureCount() > 0 ? 100.0 / intersectSource->featureCount() : 1;
int current = 0;
QgsFeature f;
while ( fIt.nextFeature( f ) )
{
if ( feedback->isCanceled() )
break;

if ( !f.hasGeometry() )
continue;

std::unique_ptr< QgsGeometryEngine > engine( QgsGeometry::createGeometryEngine( f.geometry().geometry() ) );
engine->prepareGeometry();
QgsRectangle bbox = f.geometry().boundingBox();

request = QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() );
QgsFeatureIterator testFeatureIt = selectLayer->getFeatures( request );
QgsFeature testFeature;
while ( testFeatureIt.nextFeature( testFeature ) )
{
if ( feedback->isCanceled() )
break;

if ( selectedSet.contains( testFeature.id() ) )
{
// already added this one, no need for further tests
continue;
}

for ( Predicate predicate : qgsAsConst( predicates ) )
{
bool isMatch = false;
switch ( predicate )
{
case Intersects:
isMatch = engine->intersects( testFeature.geometry().geometry() );
break;
case Contains:
isMatch = engine->contains( testFeature.geometry().geometry() );
break;
case Disjoint:
if ( engine->intersects( testFeature.geometry().geometry() ) )
{
disjointSet.remove( testFeature.id() );
}
break;
case IsEqual:
isMatch = engine->isEqual( testFeature.geometry().geometry() );
break;
case Touches:
isMatch = engine->touches( testFeature.geometry().geometry() );
break;
case Overlaps:
isMatch = engine->overlaps( testFeature.geometry().geometry() );
break;
case Within:
isMatch = engine->within( testFeature.geometry().geometry() );
break;
case Crosses:
isMatch = engine->crosses( testFeature.geometry().geometry() );
break;
}
if ( isMatch )
selectedSet.insert( testFeature.id() );
}

}

feedback->setProgress( int( current * step ) );
}

if ( predicates.contains( Disjoint ) )
{
selectedSet = selectedSet.unite( disjointSet );
}

selectLayer->selectByIds( selectedSet, method );
QVariantMap results;
results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
return results;
}

QgsSelectByLocationAlgorithm::Predicate QgsSelectByLocationAlgorithm::reversePredicate( QgsSelectByLocationAlgorithm::Predicate predicate ) const
{
switch ( predicate )
{
case Intersects:
return Intersects;
case Contains:
return Within;
case Disjoint:
return Disjoint;
case IsEqual:
return IsEqual;
case Touches:
return Touches;
case Overlaps:
return Overlaps;
case Within:
return Contains;
case Crosses:
return Crosses;
}
// no warnings
return Intersects;
}



///@endcond


41 changes: 41 additions & 0 deletions src/core/processing/qgsnativealgorithms.h
Expand Up @@ -482,6 +482,47 @@ class QgsConvexHullAlgorithm : public QgsProcessingFeatureBasedAlgorithm

};


/**
* Native select by location algorithm
*/
class QgsSelectByLocationAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsSelectByLocationAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "selectbylocation" ); }
QString displayName() const override { return QObject::tr( "Select by location" ); }
virtual QStringList tags() const override { return QObject::tr( "select,intersects,intersecting,disjoint,touching,within,contains,overlaps,relation" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector selection" ); }
QString shortHelpString() const override;
QgsSelectByLocationAlgorithm *createInstance() const override SIP_FACTORY;

protected:

virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

private:

enum Predicate
{
Intersects,
Contains,
Disjoint,
IsEqual,
Touches,
Overlaps,
Within,
Crosses,
};

Predicate reversePredicate( Predicate predicate ) const;

};

///@endcond PRIVATE

#endif // QGSNATIVEALGORITHMS_H
Expand Down

0 comments on commit 1aa76ac

Please sign in to comment.