43#include <QApplication>
47#include <QtConcurrentRun>
49#include "moc_qgsmaptoolselectutils.cpp"
51using namespace Qt::StringLiterals;
58 switch ( layer->
type() )
80 messageBar->pushMessage( QObject::tr(
"No active vector layer" ), QObject::tr(
"To select features, choose a vector layer in the layers panel" ),
Qgis::MessageLevel::Info );
113 switch ( layer->
type() )
157 if ( modifiers & Qt::ShiftModifier && modifiers & Qt::ControlModifier )
159 else if ( modifiers & Qt::ShiftModifier )
161 else if ( modifiers & Qt::ControlModifier )
164 bool doContains = modifiers & Qt::AltModifier;
170 selectGeomTrans = selectGeometry;
178 if ( poly.size() == 1 && poly.at( 0 ).size() == 5 )
183 newpoly[0].resize( 41 );
186 ringOut[0] = ringIn.at( 0 );
189 for (
int j = 1; j < 5; j++ )
191 QgsVector v( ( ringIn.at( j ) - ringIn.at( j - 1 ) ) / 10.0 );
192 for (
int k = 0; k < 9; k++ )
194 ringOut[i] = ringOut[i - 1] + v;
197 ringOut[i++] = ringIn.at( j );
225 QApplication::setOverrideCursor( Qt::WaitCursor );
226 switch ( layer->
type() )
232 if ( selectedFeatures.isEmpty() )
234 if ( !( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier ) )
242 QApplication::restoreOverrideCursor();
247 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
251 if ( layerSelectedFeatures.contains( selectId ) )
271 messageBar->pushMessage( QObject::tr(
"Selection extends beyond layer's coordinate system" ), QString(),
Qgis::MessageLevel::Warning );
277 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
299 QApplication::restoreOverrideCursor();
308 QApplication::setOverrideCursor( Qt::WaitCursor );
313 switch ( layer->
type() )
319 vLayer->
selectByIds( selectedFeatures, selectBehavior );
332 messageBar->pushMessage( QObject::tr(
"Selection extends beyond layer's coordinate system" ), QString(),
Qgis::MessageLevel::Warning );
356 QApplication::restoreOverrideCursor();
364 return newSelectedFeatures;
367 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( targetLayer );
369 return newSelectedFeatures;
381 messageBar->pushMessage( QObject::tr(
"Selection extends beyond layer's coordinate system" ), QString(),
Qgis::MessageLevel::Warning );
383 return newSelectedFeatures;
388 QgsDebugMsgLevel(
"doContains: " + QString( doContains ? u
"T"_s : u
"F"_s ), 3 );
394 selectGeomTrans = selectGeomTrans.
buffer( 0, 1 );
395 if ( selectGeomTrans.
isEmpty() )
396 return newSelectedFeatures;
400 selectionGeometryEngine->setLogErrors(
false );
401 selectionGeometryEngine->prepareGeometry();
409 std::unique_ptr<QgsFeatureRenderer> r;
413 r->startRender( context, vlayer->
fields() );
417 if ( canvasFilter ==
"FALSE"_L1 )
418 return newSelectedFeatures;
428 if ( !canvasFilter.isEmpty() )
432 const QString filterExpression = r->filter( vlayer->
fields() );
433 if ( !filterExpression.isEmpty() )
444 bool foundSingleFeature =
false;
445 double closestFeatureDist = std::numeric_limits<double>::max();
450 if ( r && !r->willRenderFeature( f, context ) )
454 QString errorMessage;
460 const bool notContained = !selectionGeometryEngine->contains( g.
constGet(), &errorMessage ) && ( errorMessage.isEmpty() ||
463 if ( !errorMessage.isEmpty() )
477 const bool notIntersects = !selectionGeometryEngine->intersects( g.
constGet(), &errorMessage ) && ( errorMessage.isEmpty() ||
478 !selectionGeometryEngine->intersects( g.
makeValid().
constGet(), &errorMessage ) );
480 if ( !errorMessage.isEmpty() )
491 foundSingleFeature =
true;
492 double distance = g.
distance( selectGeomTrans );
493 if ( distance <= closestFeatureDist )
495 closestFeatureDist = distance;
496 closestFeatureId = f.
id();
501 newSelectedFeatures.insert( f.
id() );
504 if ( singleSelect && foundSingleFeature )
506 newSelectedFeatures.insert( closestFeatureId );
510 r->stopRender( context );
512 QgsDebugMsgLevel(
"Number of new selected features: " + QString::number( newSelectedFeatures.size() ), 2 );
514 return newSelectedFeatures;
523 , mVectorLayer( vectorLayer )
524 , mBehavior( behavior )
525 , mSelectGeometry( selectionGeometry )
527 connect( mVectorLayer, &QgsMapLayer::destroyed,
this, &QgsMapToolSelectMenuActions::onLayerDestroyed );
529 mFutureWatcher =
new QFutureWatcher<QgsFeatureIds>(
this );
530 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapToolSelectMenuActions::onSearchFinished );
537 mJobData->isCanceled =
true;
538 if ( mFutureWatcher )
539 mFutureWatcher->waitForFinished();
544 mActionChooseAll =
new QAction( textForChooseAll(),
this );
545 menu->addAction( mActionChooseAll );
546 connect( mActionChooseAll, &QAction::triggered,
this, &QgsMapToolSelectMenuActions::chooseAllCandidateFeature );
547 mMenuChooseOne =
new QMenu( textForChooseOneMenu() );
548 menu->addMenu( mMenuChooseOne );
549 mMenuChooseOne->setEnabled(
false );
551 startFeatureSearch();
554void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::startFeatureSearch()
557 if ( canvasFilter ==
"FALSE"_L1 )
560 mJobData = std::make_shared<DataForSearchingJob>();
561 mJobData->isCanceled =
false;
562 mJobData->source = std::make_unique<QgsVectorLayerFeatureSource>( mVectorLayer );
563 mJobData->selectGeometry = mSelectGeometry;
565 mJobData->filterString = canvasFilter;
566 mJobData->ct =
QgsCoordinateTransform( mCanvas->mapSettings().destinationCrs(), mVectorLayer->crs(), mJobData->context.transformContext() );
567 mJobData->featureRenderer.reset( mVectorLayer->renderer()->clone() );
569 mJobData->context.setExpressionContext( mCanvas->createExpressionContext() );
571 mJobData->selectBehavior = mBehavior;
573 mJobData->existingSelection = mVectorLayer->selectedFeatureIds();
574 QFuture<QgsFeatureIds> future = QtConcurrent::run( search, mJobData );
575 mFutureWatcher->setFuture( future );
578QgsFeatureIds QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::search( std::shared_ptr<DataForSearchingJob> data )
583 return newSelectedFeatures;
585 QgsGeometry selectGeomTrans = data->selectGeometry;
590 return newSelectedFeatures;
596 selectGeomTrans = selectGeomTrans.
buffer( 0, 1 );
600 selectionGeometryEngine->setLogErrors(
false );
601 selectionGeometryEngine->prepareGeometry();
603 std::unique_ptr<QgsFeatureRenderer> r;
604 if ( data->featureRenderer )
606 r.reset( data->featureRenderer->clone() );
607 r->startRender( data->context, data->source->fields() );
610 QgsFeatureRequest request;
614 if ( !data->filterString.isEmpty() )
620 const QString filterExpression = r->filter( data->source->fields() );
621 if ( !filterExpression.isEmpty() )
628 QgsFeatureIterator fit = data->source->getFeatures( request );
632 while ( fit.
nextFeature( f ) && !data->isCanceled )
634 data->context.expressionContext().setFeature( f );
636 if ( r && !r->willRenderFeature( f, data->context ) )
640 QString errorMessage;
645 const bool notIntersects = !selectionGeometryEngine->
intersects( g.
constGet(), &errorMessage ) && ( errorMessage.isEmpty() ||
646 !selectionGeometryEngine->intersects( g.
makeValid().
constGet(), &errorMessage ) );
648 if ( !errorMessage.isEmpty() )
657 newSelectedFeatures.insert( f.
id() );
661 r->stopRender( data->context );
662 return filterIds( newSelectedFeatures, data->existingSelection, data->selectBehavior );
665void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onSearchFinished()
667 if ( !mFutureWatcher || !mFutureWatcher->isFinished() )
670 mAllFeatureIds = mFutureWatcher->result();
671 mActionChooseAll->setText( textForChooseAll( mAllFeatureIds.size() ) );
672 if ( !mAllFeatureIds.isEmpty() )
673 connect( mActionChooseAll, &QAction::hovered,
this, &QgsMapToolSelectMenuActions::highlightAllFeatures );
675 mActionChooseAll->setEnabled(
false );
676 if ( mAllFeatureIds.count() > 1 )
677 populateChooseOneMenu( mAllFeatureIds );
681QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseAll( qint64 featureCount )
const
683 if ( featureCount == 0 || featureCount == 1 )
688 return tr(
"Select Feature" );
691 return tr(
"Add to Selection" );
694 return tr(
"Intersect with Selection" );
697 return tr(
"Remove from Selection" );
701 QString featureCountText;
702 if ( featureCount < 0 )
703 featureCountText = tr(
"Searching…" );
705 featureCountText = QLocale().toString( featureCount );
710 return tr(
"Select All (%1)" ).arg( featureCountText );
712 return tr(
"Add All to Selection (%1)" ).arg( featureCountText );
714 return tr(
"Intersect All with Selection (%1)" ).arg( featureCountText );
716 return tr(
"Remove All from Selection (%1)" ).arg( featureCountText );
722QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseOneMenu()
const
727 return tr(
"Select Feature" );
729 return tr(
"Add Feature to Selection" );
731 return tr(
"Intersect Feature with Selection" );
733 return tr(
"Remove Feature from Selection" );
740void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::populateChooseOneMenu(
const QgsFeatureIds &ids )
747 QgsFeatureIds::ConstIterator it = ids.constBegin();
748 while ( displayedFeatureIds.count() <= 20 && it != ids.constEnd() )
749 displayedFeatureIds.insert( *( it++ ) );
752 QgsExpression exp = mVectorLayer->displayExpression();
755 QgsFeatureRequest request = QgsFeatureRequest().
setFilterFids( displayedFeatureIds );
757 QgsFeatureIterator featureIt = mVectorLayer->getFeatures( request );
761 context.setFeature( feat );
763 QString featureTitle = exp.
evaluate( &context ).toString();
764 if ( featureTitle.isEmpty() )
767 QAction *featureAction =
new QAction( featureTitle,
this );
768 connect( featureAction, &QAction::triggered,
this, [
this,
id]() { chooseOneCandidateFeature(
id ); } );
769 connect( featureAction, &QAction::hovered,
this, [
this,
id]() { this->highlightOneFeature(
id ); } );
770 mMenuChooseOne->addAction( featureAction );
773 mMenuChooseOne->setEnabled( ids.count() != 0 );
776void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseOneCandidateFeature(
QgsFeatureId id )
783 mVectorLayer->selectByIds( ids, mBehavior );
786void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseAllCandidateFeature()
788 if ( !mFutureWatcher )
791 if ( !mFutureWatcher->isFinished() )
793 QApplication::setOverrideCursor( Qt::WaitCursor );
794 mFutureWatcher->waitForFinished();
795 QApplication::restoreOverrideCursor();
796 mAllFeatureIds = mFutureWatcher->result();
799 if ( !mAllFeatureIds.empty() )
800 mVectorLayer->selectByIds( mAllFeatureIds, mBehavior );
803void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightAllFeatures()
810 if ( !mAllFeatureIds.empty() )
813 for (
const QgsFeatureId &
id : std::as_const( mAllFeatureIds ) )
815 QgsFeature feat = mVectorLayer->getFeature(
id );
819 QgsHighlight *hl =
new QgsHighlight( mCanvas, geom, mVectorLayer );
821 mHighlight.append( hl );
830void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightOneFeature(
QgsFeatureId id )
837 QgsFeature feat = mVectorLayer->getFeature(
id );
841 QgsHighlight *hl =
new QgsHighlight( mCanvas, geom, mVectorLayer );
843 mHighlight.append( hl );
858 if ( existingSelection.contains( newSelected ) )
859 effectiveFeatureIds.remove( newSelected );
868 if ( !existingSelection.contains( newSelected ) )
869 effectiveFeatureIds.remove( newSelected );
875 return effectiveFeatureIds;
879void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onLayerDestroyed()
881 mVectorLayer =
nullptr;
882 mJobData->isCanceled =
true;
886void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::removeHighlight()
888 qDeleteAll( mHighlight );
QFlags< SelectionFlag > SelectionFlags
Flags which control feature selection behavior.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ Warning
Warning message.
@ Info
Information message.
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ ToggleSelection
Enables a "toggle" selection mode, where previously selected matching features will be deselected and...
@ SingleFeatureSelection
Select only a single feature, picking the "best" match for the selection geometry.
@ Within
Select where features are within the reference geometry.
@ Intersect
Select where features intersect the reference geometry.
SelectBehavior
Specifies how a selection should be applied.
@ SetSelection
Set selection, removing any existing selection.
@ AddToSelection
Add selection to current selection.
@ IntersectSelection
Modify current selection to include only select features which match.
@ RemoveFromSelection
Remove from current selection.
Custom exception class for Coordinate Reference System related exceptions.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QgsPolygonXY asPolygon() const
Returns the contents of the geometry as a polygon.
double distance(const QgsGeometry &geom) const
Returns the minimum distance between this geometry and another geometry.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
QgsGeometry makeValid(Qgis::MakeValidMethod method=Qgis::MakeValidMethod::Linework, bool keepCollapsed=false, QgsFeedback *feedback=nullptr) const
Attempts to make an invalid geometry valid without losing vertices.
static QgsGeometry fromPolygonXY(const QgsPolygonXY &polygon)
Creates a new geometry from a QgsPolygonXY.
QgsGeometry buffer(double distance, int segments, QgsFeedback *feedback=nullptr) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Q_INVOKABLE QString asWkt(int precision=17) const
Exports the geometry to WKT.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
Q_INVOKABLE bool intersects(const QgsRectangle &rectangle) const
Returns true if this geometry exactly intersects with a rectangle.
void applyDefaultStyle()
Applies the default style from the user settings to the highlight.
static QString filterForLayer(QgsMapCanvas *canvas, QgsVectorLayer *layer)
Constructs a filter to use for selecting features from the given layer, in order to apply filters whi...
Map canvas is a class for displaying all GIS data types on a canvas.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QgsMessageBar * messageBar()
Returns the message bar associated with the map canvas.
double scale() const
Returns the last reported scale of the canvas.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
Base class for all map layer types.
QgsCoordinateReferenceSystem crs
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
Perform transforms between map coordinates and device coordinates.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
A bar for displaying non-blocking messages to the user.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
static QgsProject * instance()
Returns the QgsProject singleton instance.
A rectangle specified with double values.
Contains information about the context of a rendering operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Responsible for drawing transient features (e.g.
void reset(Qgis::GeometryType geometryType=Qgis::GeometryType::Line)
Clears all the geometries in this rubberband.
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Encapsulates the context of a layer selection operation.
void setScale(double scale)
Sets the map scale at which the selection should occur.
Represents a vector layer which manages a vector based dataset.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE void selectByIds(const QgsFeatureIds &ids, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection, bool validateIds=false)
Selects matching features using a list of feature IDs.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
Q_INVOKABLE void removeSelection()
Clear selection.
Implements a map layer that is dedicated to rendering of vector tiles.
void selectByGeometry(const QgsGeometry &geometry, const QgsSelectionContext &context, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection, Qgis::SelectGeometryRelationship relationship=Qgis::SelectGeometryRelationship::Intersect, Qgis::SelectionFlags flags=Qgis::SelectionFlags(), QgsRenderContext *renderContext=nullptr)
Selects features found within the search geometry (in layer's coordinates).
Represent a 2-dimensional vector.
QSet< QgsFeatureId > QgsFeatureIds
#define FID_TO_STRING(fid)
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
QVector< QgsPolylineXY > QgsPolygonXY
Polygon: first item of the list is outer ring, inner rings (if any) start from second item.
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)