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() )
159 if ( modifiers & Qt::ShiftModifier && modifiers & Qt::ControlModifier )
161 else if ( modifiers & Qt::ShiftModifier )
163 else if ( modifiers & Qt::ControlModifier )
166 bool doContains = modifiers & Qt::AltModifier;
172 selectGeomTrans = selectGeometry;
180 if ( poly.size() == 1 && poly.at( 0 ).size() == 5 )
185 newpoly[0].resize( 41 );
188 ringOut[0] = ringIn.at( 0 );
191 for (
int j = 1; j < 5; j++ )
193 QgsVector v( ( ringIn.at( j ) - ringIn.at( j - 1 ) ) / 10.0 );
194 for (
int k = 0; k < 9; k++ )
196 ringOut[i] = ringOut[i - 1] + v;
199 ringOut[i++] = ringIn.at( j );
227 QApplication::setOverrideCursor( Qt::WaitCursor );
228 switch ( layer->
type() )
234 if ( selectedFeatures.isEmpty() )
236 if ( !( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier ) )
244 QApplication::restoreOverrideCursor();
249 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
253 if ( layerSelectedFeatures.contains( selectId ) )
273 messageBar->pushMessage( QObject::tr(
"Selection extends beyond layer's coordinate system" ), QString(),
Qgis::MessageLevel::Warning );
279 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
301 QApplication::restoreOverrideCursor();
310 QApplication::setOverrideCursor( Qt::WaitCursor );
315 switch ( layer->
type() )
321 vLayer->
selectByIds( selectedFeatures, selectBehavior );
334 messageBar->pushMessage( QObject::tr(
"Selection extends beyond layer's coordinate system" ), QString(),
Qgis::MessageLevel::Warning );
358 QApplication::restoreOverrideCursor();
366 return newSelectedFeatures;
369 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( targetLayer );
371 return newSelectedFeatures;
383 messageBar->pushMessage( QObject::tr(
"Selection extends beyond layer's coordinate system" ), QString(),
Qgis::MessageLevel::Warning );
385 return newSelectedFeatures;
390 QgsDebugMsgLevel(
"doContains: " + QString( doContains ? u
"T"_s : u
"F"_s ), 3 );
396 selectGeomTrans = selectGeomTrans.
buffer( 0, 1 );
397 if ( selectGeomTrans.
isEmpty() )
398 return newSelectedFeatures;
402 selectionGeometryEngine->setLogErrors(
false );
403 selectionGeometryEngine->prepareGeometry();
411 std::unique_ptr<QgsFeatureRenderer> r;
415 r->startRender( context, vlayer->
fields() );
419 if ( canvasFilter ==
"FALSE"_L1 )
420 return newSelectedFeatures;
430 if ( !canvasFilter.isEmpty() )
434 const QString filterExpression = r->filter( vlayer->
fields() );
435 if ( !filterExpression.isEmpty() )
446 bool foundSingleFeature =
false;
447 double closestFeatureDist = std::numeric_limits<double>::max();
452 if ( r && !r->willRenderFeature( f, context ) )
456 QString errorMessage;
462 const bool notContained = !selectionGeometryEngine->contains( g.
constGet(), &errorMessage ) && ( errorMessage.isEmpty() ||
465 if ( !errorMessage.isEmpty() )
479 const bool notIntersects = !selectionGeometryEngine->intersects( g.
constGet(), &errorMessage ) && ( errorMessage.isEmpty() ||
480 !selectionGeometryEngine->intersects( g.
makeValid().
constGet(), &errorMessage ) );
482 if ( !errorMessage.isEmpty() )
493 foundSingleFeature =
true;
494 double distance = g.
distance( selectGeomTrans );
495 if ( distance <= closestFeatureDist )
497 closestFeatureDist = distance;
498 closestFeatureId = f.
id();
503 newSelectedFeatures.insert( f.
id() );
506 if ( singleSelect && foundSingleFeature )
508 newSelectedFeatures.insert( closestFeatureId );
512 r->stopRender( context );
514 QgsDebugMsgLevel(
"Number of new selected features: " + QString::number( newSelectedFeatures.size() ), 2 );
516 return newSelectedFeatures;
525 , mVectorLayer( vectorLayer )
526 , mBehavior( behavior )
527 , mSelectGeometry( selectionGeometry )
529 connect( mVectorLayer, &QgsMapLayer::destroyed,
this, &QgsMapToolSelectMenuActions::onLayerDestroyed );
531 mFutureWatcher =
new QFutureWatcher<QgsFeatureIds>(
this );
532 connect( mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapToolSelectMenuActions::onSearchFinished );
539 mJobData->isCanceled =
true;
540 if ( mFutureWatcher )
541 mFutureWatcher->waitForFinished();
546 mActionChooseAll =
new QAction( textForChooseAll(),
this );
547 menu->addAction( mActionChooseAll );
548 connect( mActionChooseAll, &QAction::triggered,
this, &QgsMapToolSelectMenuActions::chooseAllCandidateFeature );
549 mMenuChooseOne =
new QMenu( textForChooseOneMenu() );
550 menu->addMenu( mMenuChooseOne );
551 mMenuChooseOne->setEnabled(
false );
553 startFeatureSearch();
556void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::startFeatureSearch()
559 if ( canvasFilter ==
"FALSE"_L1 )
562 mJobData = std::make_shared<DataForSearchingJob>();
563 mJobData->isCanceled =
false;
564 mJobData->source = std::make_unique<QgsVectorLayerFeatureSource>( mVectorLayer );
565 mJobData->selectGeometry = mSelectGeometry;
567 mJobData->filterString = canvasFilter;
568 mJobData->ct =
QgsCoordinateTransform( mCanvas->mapSettings().destinationCrs(), mVectorLayer->crs(), mJobData->context.transformContext() );
569 mJobData->featureRenderer.reset( mVectorLayer->renderer()->clone() );
571 mJobData->context.setExpressionContext( mCanvas->createExpressionContext() );
573 mJobData->selectBehavior = mBehavior;
575 mJobData->existingSelection = mVectorLayer->selectedFeatureIds();
576 QFuture<QgsFeatureIds> future = QtConcurrent::run( search, mJobData );
577 mFutureWatcher->setFuture( future );
580QgsFeatureIds QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::search( std::shared_ptr<DataForSearchingJob> data )
585 return newSelectedFeatures;
587 QgsGeometry selectGeomTrans = data->selectGeometry;
592 return newSelectedFeatures;
598 selectGeomTrans = selectGeomTrans.
buffer( 0, 1 );
602 selectionGeometryEngine->setLogErrors(
false );
603 selectionGeometryEngine->prepareGeometry();
605 std::unique_ptr<QgsFeatureRenderer> r;
606 if ( data->featureRenderer )
608 r.reset( data->featureRenderer->clone() );
609 r->startRender( data->context, data->source->fields() );
612 QgsFeatureRequest request;
616 if ( !data->filterString.isEmpty() )
622 const QString filterExpression = r->filter( data->source->fields() );
623 if ( !filterExpression.isEmpty() )
630 QgsFeatureIterator fit = data->source->getFeatures( request );
634 while ( fit.
nextFeature( f ) && !data->isCanceled )
636 data->context.expressionContext().setFeature( f );
638 if ( r && !r->willRenderFeature( f, data->context ) )
642 QString errorMessage;
647 const bool notIntersects = !selectionGeometryEngine->
intersects( g.
constGet(), &errorMessage ) && ( errorMessage.isEmpty() ||
648 !selectionGeometryEngine->intersects( g.
makeValid().
constGet(), &errorMessage ) );
650 if ( !errorMessage.isEmpty() )
659 newSelectedFeatures.insert( f.
id() );
663 r->stopRender( data->context );
664 return filterIds( newSelectedFeatures, data->existingSelection, data->selectBehavior );
667void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onSearchFinished()
669 if ( !mFutureWatcher || !mFutureWatcher->isFinished() )
672 mAllFeatureIds = mFutureWatcher->result();
673 mActionChooseAll->setText( textForChooseAll( mAllFeatureIds.size() ) );
674 if ( !mAllFeatureIds.isEmpty() )
675 connect( mActionChooseAll, &QAction::hovered,
this, &QgsMapToolSelectMenuActions::highlightAllFeatures );
677 mActionChooseAll->setEnabled(
false );
678 if ( mAllFeatureIds.count() > 1 )
679 populateChooseOneMenu( mAllFeatureIds );
683QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseAll( qint64 featureCount )
const
685 if ( featureCount == 0 || featureCount == 1 )
690 return tr(
"Select Feature" );
693 return tr(
"Add to Selection" );
696 return tr(
"Intersect with Selection" );
699 return tr(
"Remove from Selection" );
703 QString featureCountText;
704 if ( featureCount < 0 )
705 featureCountText = tr(
"Searching…" );
707 featureCountText = QLocale().toString( featureCount );
712 return tr(
"Select All (%1)" ).arg( featureCountText );
714 return tr(
"Add All to Selection (%1)" ).arg( featureCountText );
716 return tr(
"Intersect All with Selection (%1)" ).arg( featureCountText );
718 return tr(
"Remove All from Selection (%1)" ).arg( featureCountText );
724QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseOneMenu()
const
729 return tr(
"Select Feature" );
731 return tr(
"Add Feature to Selection" );
733 return tr(
"Intersect Feature with Selection" );
735 return tr(
"Remove Feature from Selection" );
742void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::populateChooseOneMenu(
const QgsFeatureIds &ids )
749 QgsFeatureIds::ConstIterator it = ids.constBegin();
750 while ( displayedFeatureIds.count() <= 20 && it != ids.constEnd() )
751 displayedFeatureIds.insert( *( it++ ) );
754 QgsExpression exp = mVectorLayer->displayExpression();
757 QgsFeatureRequest request = QgsFeatureRequest().
setFilterFids( displayedFeatureIds );
759 QgsFeatureIterator featureIt = mVectorLayer->getFeatures( request );
763 context.setFeature( feat );
765 QString featureTitle = exp.
evaluate( &context ).toString();
766 if ( featureTitle.isEmpty() )
769 QAction *featureAction =
new QAction( featureTitle,
this );
770 connect( featureAction, &QAction::triggered,
this, [
this,
id]() { chooseOneCandidateFeature(
id ); } );
771 connect( featureAction, &QAction::hovered,
this, [
this,
id]() { this->highlightOneFeature(
id ); } );
772 mMenuChooseOne->addAction( featureAction );
775 mMenuChooseOne->setEnabled( ids.count() != 0 );
778void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseOneCandidateFeature(
QgsFeatureId id )
785 mVectorLayer->selectByIds( ids, mBehavior );
788void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseAllCandidateFeature()
790 if ( !mFutureWatcher )
793 if ( !mFutureWatcher->isFinished() )
795 QApplication::setOverrideCursor( Qt::WaitCursor );
796 mFutureWatcher->waitForFinished();
797 QApplication::restoreOverrideCursor();
798 mAllFeatureIds = mFutureWatcher->result();
801 if ( !mAllFeatureIds.empty() )
802 mVectorLayer->selectByIds( mAllFeatureIds, mBehavior );
805void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightAllFeatures()
812 if ( !mAllFeatureIds.empty() )
815 for (
const QgsFeatureId &
id : std::as_const( mAllFeatureIds ) )
817 QgsFeature feat = mVectorLayer->getFeature(
id );
821 QgsHighlight *hl =
new QgsHighlight( mCanvas, geom, mVectorLayer );
823 mHighlight.append( hl );
832void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightOneFeature(
QgsFeatureId id )
839 QgsFeature feat = mVectorLayer->getFeature(
id );
843 QgsHighlight *hl =
new QgsHighlight( mCanvas, geom, mVectorLayer );
845 mHighlight.append( hl );
860 if ( existingSelection.contains( newSelected ) )
861 effectiveFeatureIds.remove( newSelected );
870 if ( !existingSelection.contains( newSelected ) )
871 effectiveFeatureIds.remove( newSelected );
877 return effectiveFeatureIds;
881void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onLayerDestroyed()
883 mVectorLayer =
nullptr;
884 mJobData->isCanceled =
true;
888void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::removeHighlight()
890 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)