QGIS API Documentation 4.2.0-Belém do Pará (ec9a7f91d0f)
Loading...
Searching...
No Matches
qgsmaptoolselectutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2qgsmaptoolselectutils.cpp - Utility methods to help with select map tools
3---------------------
4begin : May 2010
5copyright : (C) 2010 by Jeremy Palmer
6email : jpalmer at linz dot govt dot nz
7***************************************************************************
8* *
9* This program is free software; you can redistribute it and/or modify *
10* it under the terms of the GNU General Public License as published by *
11* the Free Software Foundation; either version 2 of the License, or *
12* (at your option) any later version. *
13* *
14***************************************************************************/
15
17
18#include <limits>
19#include <memory>
20
21#include "qgis.h"
22#include "qgsexception.h"
24#include "qgsfeature.h"
25#include "qgsfeatureiterator.h"
26#include "qgsgeometry.h"
27#include "qgsgeometryengine.h"
28#include "qgshighlight.h"
29#include "qgslogger.h"
30#include "qgsmapcanvas.h"
31#include "qgsmapcanvasutils.h"
32#include "qgsmessagebar.h"
33#include "qgsmessagelog.h"
34#include "qgsproject.h"
35#include "qgsrenderer.h"
36#include "qgsrubberband.h"
37#include "qgsselectioncontext.h"
38#include "qgsvectorlayer.h"
40#include "qgsvectortilelayer.h"
41
42#include <QAction>
43#include <QApplication>
44#include <QMenu>
45#include <QMouseEvent>
46#include <QString>
47#include <QtConcurrentRun>
48
49#include "moc_qgsmaptoolselectutils.cpp"
50
51using namespace Qt::StringLiterals;
52
54{
55 QgsMapLayer *layer = canvas->currentLayer();
56 if ( layer )
57 {
58 switch ( layer->type() )
59 {
62 // supported
63 break;
71 layer = nullptr; //not supported
72 break;
73 }
74 }
75
76 if ( !layer )
77 {
78 if ( QgsMessageBar *messageBar = canvas->messageBar() )
79 {
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 );
81 }
82 }
83 return layer;
84}
85
86void QgsMapToolSelectUtils::setRubberBand( QgsMapCanvas *canvas, QRect &selectRect, QgsRubberBand *rubberBand )
87{
88 const QgsMapToPixel *transform = canvas->getCoordinateTransform();
89 QgsPointXY ll = transform->toMapCoordinates( selectRect.left(), selectRect.bottom() );
90 QgsPointXY lr = transform->toMapCoordinates( selectRect.right(), selectRect.bottom() );
91 QgsPointXY ul = transform->toMapCoordinates( selectRect.left(), selectRect.top() );
92 QgsPointXY ur = transform->toMapCoordinates( selectRect.right(), selectRect.top() );
93
94 if ( rubberBand )
95 {
97 rubberBand->addPoint( ll, false );
98 rubberBand->addPoint( lr, false );
99 rubberBand->addPoint( ur, false );
100 rubberBand->addPoint( ul, true );
101 }
102}
103
105{
106 int boxSize = 0;
107 if ( !layer )
108 {
109 boxSize = 5;
110 }
111 else
112 {
113 switch ( layer->type() )
114 {
116 {
117 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
118 if ( vLayer->geometryType() != Qgis::GeometryType::Polygon )
119 {
120 //if point or line use an artificial bounding box of 10x10 pixels
121 //to aid the user to click on a feature accurately
122 boxSize = 5;
123 }
124 else
125 {
126 //otherwise just use the click point for polys
127 boxSize = 1;
128 }
129 break;
130 }
132 // mixed layer type, so aim for somewhere between the vector layer polygon/point sizes
133 boxSize = 2;
134 break;
135
143 break;
144 }
145 }
146
147 const QgsMapToPixel *transform = canvas->getCoordinateTransform();
148 QgsPointXY point = transform->transform( mapPoint );
149 QgsPointXY ll = transform->toMapCoordinates( static_cast<int>( point.x() - boxSize ), static_cast<int>( point.y() + boxSize ) );
150 QgsPointXY lr = transform->toMapCoordinates( static_cast<int>( point.x() + boxSize ), static_cast<int>( point.y() + boxSize ) );
151 QgsPointXY ur = transform->toMapCoordinates( static_cast<int>( point.x() + boxSize ), static_cast<int>( point.y() - boxSize ) );
152 QgsPointXY ul = transform->toMapCoordinates( static_cast<int>( point.x() - boxSize ), static_cast<int>( point.y() - boxSize ) );
153 return QgsGeometry::fromPolygonXY( QgsPolygonXY() << QgsPolylineXY { ll, lr, ur, ul, ll } ).boundingBox();
154}
155
156void QgsMapToolSelectUtils::selectMultipleFeatures( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qt::KeyboardModifiers modifiers )
157{
159 if ( modifiers & Qt::ShiftModifier && modifiers & Qt::ControlModifier )
161 else if ( modifiers & Qt::ShiftModifier )
163 else if ( modifiers & Qt::ControlModifier )
165
166 bool doContains = modifiers & Qt::AltModifier;
167 setSelectedFeatures( canvas, selectGeometry, behavior, doContains );
168}
169
170bool transformSelectGeometry( const QgsGeometry &selectGeometry, QgsGeometry &selectGeomTrans, const QgsCoordinateTransform &ct )
171{
172 selectGeomTrans = selectGeometry;
173 try
174 {
175 if ( !ct.isShortCircuited() && selectGeomTrans.type() == Qgis::GeometryType::Polygon )
176 {
177 // convert add more points to the edges of the rectangle
178 // improve transformation result
179 QgsPolygonXY poly( selectGeomTrans.asPolygon() );
180 if ( poly.size() == 1 && poly.at( 0 ).size() == 5 )
181 {
182 const QgsPolylineXY &ringIn = poly.at( 0 );
183
184 QgsPolygonXY newpoly( 1 );
185 newpoly[0].resize( 41 );
186 QgsPolylineXY &ringOut = newpoly[0];
187
188 ringOut[0] = ringIn.at( 0 );
189
190 int i = 1;
191 for ( int j = 1; j < 5; j++ )
192 {
193 QgsVector v( ( ringIn.at( j ) - ringIn.at( j - 1 ) ) / 10.0 );
194 for ( int k = 0; k < 9; k++ )
195 {
196 ringOut[i] = ringOut[i - 1] + v;
197 i++;
198 }
199 ringOut[i++] = ringIn.at( j );
200 }
201 selectGeomTrans = QgsGeometry::fromPolygonXY( newpoly );
202 }
203 }
204
205 selectGeomTrans.transform( ct );
206 return true;
207 }
208 catch ( QgsCsException &cse )
209 {
210 Q_UNUSED( cse )
211 // catch exception for 'invalid' point and leave existing selection unchanged
212 QgsDebugError( u"Caught CRS exception "_s );
213 return false;
214 }
215}
216
217void QgsMapToolSelectUtils::selectSingleFeature( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qt::KeyboardModifiers modifiers )
218{
220 if ( !layer )
221 return;
222
224 QgsSelectionContext context;
225 context.setScale( canvas->scale() );
226
227 QApplication::setOverrideCursor( Qt::WaitCursor );
228 switch ( layer->type() )
229 {
231 {
232 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
233 QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, false, true );
234 if ( selectedFeatures.isEmpty() )
235 {
236 if ( !( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier ) )
237 {
238 // if no modifiers then clicking outside features clears the selection
239 // but if there's a shift or ctrl modifier, then it's likely the user was trying
240 // to modify an existing selection by adding or subtracting features and just
241 // missed the feature
242 vlayer->removeSelection();
243 }
244 QApplication::restoreOverrideCursor();
245 return;
246 }
247
248 //either shift or control modifier switches to "toggle" selection mode
249 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
250 {
251 QgsFeatureId selectId = *selectedFeatures.constBegin();
252 QgsFeatureIds layerSelectedFeatures = vlayer->selectedFeatureIds();
253 if ( layerSelectedFeatures.contains( selectId ) )
255 else
257 }
258
259 vlayer->selectByIds( selectedFeatures, behavior );
260 break;
261 }
262
264 {
265 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
266
268 QgsGeometry selectGeomTrans;
269 if ( !transformSelectGeometry( selectGeometry, selectGeomTrans, ct ) )
270 {
271 if ( QgsMessageBar *messageBar = canvas->messageBar() )
272 {
273 messageBar->pushMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning );
274 }
275 break;
276 }
277
279 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
281
283 QgsExpressionContext expressionContext = canvas->createExpressionContext();
284 expressionContext << QgsExpressionContextUtils::layerScope( vtLayer );
285 renderContext.setExpressionContext( expressionContext );
286
287 vtLayer->selectByGeometry( selectGeomTrans, context, behavior, Qgis::SelectGeometryRelationship::Intersect, flags, &renderContext );
288 break;
289 }
290
298 break;
299 }
300
301 QApplication::restoreOverrideCursor();
302}
303
304void QgsMapToolSelectUtils::setSelectedFeatures( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qgis::SelectBehavior selectBehavior, bool doContains, bool singleSelect )
305{
307 if ( !layer )
308 return;
309
310 QApplication::setOverrideCursor( Qt::WaitCursor );
311
312 QgsSelectionContext context;
313 context.setScale( canvas->scale() );
314
315 switch ( layer->type() )
316 {
318 {
319 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
320 QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, doContains, singleSelect );
321 vLayer->selectByIds( selectedFeatures, selectBehavior );
322 break;
323 }
324
326 {
327 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
329 QgsGeometry selectGeomTrans;
330 if ( !transformSelectGeometry( selectGeometry, selectGeomTrans, ct ) )
331 {
332 if ( QgsMessageBar *messageBar = canvas->messageBar() )
333 {
334 messageBar->pushMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning );
335 }
336 break;
337 }
338
340 QgsExpressionContext expressionContext = canvas->createExpressionContext();
341 expressionContext << QgsExpressionContextUtils::layerScope( vtLayer );
342 renderContext.setExpressionContext( expressionContext );
343
344 vtLayer->selectByGeometry( selectGeomTrans, context, selectBehavior, doContains ? Qgis::SelectGeometryRelationship::Within : Qgis::SelectGeometryRelationship::Intersect, Qgis::SelectionFlags(), &renderContext );
345 break;
346 }
347
355 break;
356 }
357
358 QApplication::restoreOverrideCursor();
359}
360
361QgsFeatureIds QgsMapToolSelectUtils::getMatchingFeatures( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, bool doContains, bool singleSelect )
362{
363 QgsFeatureIds newSelectedFeatures;
364
365 if ( selectGeometry.type() != Qgis::GeometryType::Polygon )
366 return newSelectedFeatures;
367
369 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( targetLayer );
370 if ( !vlayer )
371 return newSelectedFeatures;
372
373 // toLayerCoordinates will throw an exception for any 'invalid' points in
374 // the rubber band.
375 // For example, if you project a world map onto a globe using EPSG 2163
376 // and then click somewhere off the globe, an exception will be thrown.
377 QgsGeometry selectGeomTrans;
379 if ( !transformSelectGeometry( selectGeometry, selectGeomTrans, ct ) )
380 {
381 if ( QgsMessageBar *messageBar = canvas->messageBar() )
382 {
383 messageBar->pushMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning );
384 }
385 return newSelectedFeatures;
386 }
387
388 QgsDebugMsgLevel( "Selection layer: " + vlayer->name(), 3 );
389 QgsDebugMsgLevel( "Selection polygon: " + selectGeomTrans.asWkt(), 3 );
390 QgsDebugMsgLevel( "doContains: " + QString( doContains ? u"T"_s : u"F"_s ), 3 );
391
392 // make sure the selection geometry is valid, or intersection tests won't work correctly...
393 if ( !selectGeomTrans.isGeosValid() )
394 {
395 // a zero width buffer is safer than calling make valid here!
396 selectGeomTrans = selectGeomTrans.buffer( 0, 1 );
397 if ( selectGeomTrans.isEmpty() )
398 return newSelectedFeatures;
399 }
400
401 std::unique_ptr<QgsGeometryEngine> selectionGeometryEngine( QgsGeometry::createGeometryEngine( selectGeomTrans.constGet() ) );
402 selectionGeometryEngine->setLogErrors( false );
403 selectionGeometryEngine->prepareGeometry();
404
406
407 QgsExpressionContext expressionContext = canvas->createExpressionContext();
408 expressionContext << QgsExpressionContextUtils::layerScope( vlayer );
409 context.setExpressionContext( expressionContext );
410
411 std::unique_ptr<QgsFeatureRenderer> r;
412 if ( vlayer->renderer() )
413 {
414 r.reset( vlayer->renderer()->clone() );
415 r->startRender( context, vlayer->fields() );
416 }
417
418 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( canvas, vlayer );
419 if ( canvasFilter == "FALSE"_L1 )
420 return newSelectedFeatures;
421
422 QgsFeatureRequest request;
423 request.setFilterRect( selectGeomTrans.boundingBox() );
425 if ( r )
426 request.setSubsetOfAttributes( r->usedAttributes( context ), vlayer->fields() );
427 else
428 request.setNoAttributes();
429
430 if ( !canvasFilter.isEmpty() )
431 request.setFilterExpression( canvasFilter );
432 if ( r )
433 {
434 const QString filterExpression = r->filter( vlayer->fields() );
435 if ( !filterExpression.isEmpty() )
436 {
437 request.combineFilterExpression( filterExpression );
438 }
439 }
440
441 request.setExpressionContext( context.expressionContext() );
442 QgsFeatureIterator fit = vlayer->getFeatures( request );
443
444 QgsFeature f;
445 QgsFeatureId closestFeatureId = 0;
446 bool foundSingleFeature = false;
447 double closestFeatureDist = std::numeric_limits<double>::max();
448 while ( fit.nextFeature( f ) )
449 {
450 context.expressionContext().setFeature( f );
451 // make sure to only use features that are visible
452 if ( r && !r->willRenderFeature( f, context ) )
453 continue;
454
455 QgsGeometry g = f.geometry();
456 QString errorMessage;
457 if ( doContains )
458 {
459 // if we get an error from the contains check then it indicates that the geometry is invalid and GEOS choked on it.
460 // in this case we consider the bounding box intersection check which has already been performed by the iterator as sufficient and
461 // allow the feature to be selected
462 const bool notContained = !selectionGeometryEngine->contains( g.constGet(), &errorMessage ) && ( errorMessage.isEmpty() || /* message will be non empty if geometry g is invalid */
463 !selectionGeometryEngine->contains( g.makeValid().constGet(), &errorMessage ) ); /* second chance for invalid geometries, repair and re-test */
464
465 if ( !errorMessage.isEmpty() )
466 {
467 // contains relation test still failed, even after trying to make valid!
468 QgsMessageLog::logMessage( QObject::tr( "Error determining selection: %1" ).arg( errorMessage ), QString(), Qgis::MessageLevel::Warning );
469 }
470
471 if ( notContained )
472 continue;
473 }
474 else
475 {
476 // if we get an error from the intersects check then it indicates that the geometry is invalid and GEOS choked on it.
477 // in this case we consider the bounding box intersection check which has already been performed by the iterator as sufficient and
478 // allow the feature to be selected
479 const bool notIntersects = !selectionGeometryEngine->intersects( g.constGet(), &errorMessage ) && ( errorMessage.isEmpty() || /* message will be non empty if geometry g is invalid */
480 !selectionGeometryEngine->intersects( g.makeValid().constGet(), &errorMessage ) ); /* second chance for invalid geometries, repair and re-test */
481
482 if ( !errorMessage.isEmpty() )
483 {
484 // intersects relation test still failed, even after trying to make valid!
485 QgsMessageLog::logMessage( QObject::tr( "Error determining selection: %1" ).arg( errorMessage ), QString(), Qgis::MessageLevel::Warning );
486 }
487
488 if ( notIntersects )
489 continue;
490 }
491 if ( singleSelect )
492 {
493 foundSingleFeature = true;
494 double distance = g.distance( selectGeomTrans );
495 if ( distance <= closestFeatureDist )
496 {
497 closestFeatureDist = distance;
498 closestFeatureId = f.id();
499 }
500 }
501 else
502 {
503 newSelectedFeatures.insert( f.id() );
504 }
505 }
506 if ( singleSelect && foundSingleFeature )
507 {
508 newSelectedFeatures.insert( closestFeatureId );
509 }
510
511 if ( r )
512 r->stopRender( context );
513
514 QgsDebugMsgLevel( "Number of new selected features: " + QString::number( newSelectedFeatures.size() ), 2 );
515
516 return newSelectedFeatures;
517}
518
519
521 QgsMapCanvas *canvas, QgsVectorLayer *vectorLayer, Qgis::SelectBehavior behavior, const QgsGeometry &selectionGeometry, QObject *parent
522)
523 : QObject( parent )
524 , mCanvas( canvas )
525 , mVectorLayer( vectorLayer )
526 , mBehavior( behavior )
527 , mSelectGeometry( selectionGeometry )
528{
529 connect( mVectorLayer, &QgsMapLayer::destroyed, this, &QgsMapToolSelectMenuActions::onLayerDestroyed );
530
531 mFutureWatcher = new QFutureWatcher<QgsFeatureIds>( this );
532 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapToolSelectMenuActions::onSearchFinished );
533}
534
536{
537 removeHighlight();
538 if ( mJobData )
539 mJobData->isCanceled = true;
540 if ( mFutureWatcher )
541 mFutureWatcher->waitForFinished();
542}
543
545{
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 );
552
553 startFeatureSearch();
554}
555
556void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::startFeatureSearch()
557{
558 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mCanvas, mVectorLayer );
559 if ( canvasFilter == "FALSE"_L1 )
560 return;
561
562 mJobData = std::make_shared<DataForSearchingJob>();
563 mJobData->isCanceled = false;
564 mJobData->source = std::make_unique<QgsVectorLayerFeatureSource>( mVectorLayer );
565 mJobData->selectGeometry = mSelectGeometry;
566 mJobData->context = QgsRenderContext::fromMapSettings( mCanvas->mapSettings() );
567 mJobData->filterString = canvasFilter;
568 mJobData->ct = QgsCoordinateTransform( mCanvas->mapSettings().destinationCrs(), mVectorLayer->crs(), mJobData->context.transformContext() );
569 mJobData->featureRenderer.reset( mVectorLayer->renderer()->clone() );
570
571 mJobData->context.setExpressionContext( mCanvas->createExpressionContext() );
572 mJobData->context.expressionContext() << QgsExpressionContextUtils::layerScope( mVectorLayer );
573 mJobData->selectBehavior = mBehavior;
574 if ( mBehavior != Qgis::SelectBehavior::SetSelection )
575 mJobData->existingSelection = mVectorLayer->selectedFeatureIds();
576 QFuture<QgsFeatureIds> future = QtConcurrent::run( search, mJobData );
577 mFutureWatcher->setFuture( future );
578}
579
580QgsFeatureIds QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::search( std::shared_ptr<DataForSearchingJob> data )
581{
582 QgsFeatureIds newSelectedFeatures;
583
584 if ( data->selectGeometry.type() != Qgis::GeometryType::Polygon )
585 return newSelectedFeatures;
586
587 QgsGeometry selectGeomTrans = data->selectGeometry;
588
589 if ( !transformSelectGeometry( data->selectGeometry, selectGeomTrans, data->ct ) )
590 {
591 QgsMessageLog::logMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning, true );
592 return newSelectedFeatures;
593 }
594 // make sure the selection geometry is valid, or intersection tests won't work correctly...
595 if ( !selectGeomTrans.isGeosValid() )
596 {
597 // a zero width buffer is safer than calling make valid here!
598 selectGeomTrans = selectGeomTrans.buffer( 0, 1 );
599 }
600
601 std::unique_ptr<QgsGeometryEngine> selectionGeometryEngine( QgsGeometry::createGeometryEngine( selectGeomTrans.constGet() ) );
602 selectionGeometryEngine->setLogErrors( false );
603 selectionGeometryEngine->prepareGeometry();
604
605 std::unique_ptr<QgsFeatureRenderer> r;
606 if ( data->featureRenderer )
607 {
608 r.reset( data->featureRenderer->clone() );
609 r->startRender( data->context, data->source->fields() );
610 }
611
612 QgsFeatureRequest request;
613 request.setFilterRect( selectGeomTrans.boundingBox() );
615
616 if ( !data->filterString.isEmpty() )
617 request.setFilterExpression( data->filterString );
618
619 if ( r )
620 {
621 request.setSubsetOfAttributes( r->usedAttributes( data->context ), data->source->fields() );
622 const QString filterExpression = r->filter( data->source->fields() );
623 if ( !filterExpression.isEmpty() )
624 {
625 request.combineFilterExpression( filterExpression );
626 }
627 }
628 request.setExpressionContext( data->context.expressionContext() );
629
630 QgsFeatureIterator fit = data->source->getFeatures( request );
631
632 QgsFeature f;
633
634 while ( fit.nextFeature( f ) && !data->isCanceled )
635 {
636 data->context.expressionContext().setFeature( f );
637 // make sure to only use features that are visible
638 if ( r && !r->willRenderFeature( f, data->context ) )
639 continue;
640
641 QgsGeometry g = f.geometry();
642 QString errorMessage;
643
644 // if we get an error from the intersects check then it indicates that the geometry is invalid and GEOS choked on it.
645 // in this case we consider the bounding box intersection check which has already been performed by the iterator as sufficient and
646 // allow the feature to be selected
647 const bool notIntersects = !selectionGeometryEngine->intersects( g.constGet(), &errorMessage ) && ( errorMessage.isEmpty() || /* message will be non empty if geometry g is invalid */
648 !selectionGeometryEngine->intersects( g.makeValid().constGet(), &errorMessage ) ); /* second chance for invalid geometries, repair and re-test */
649
650 if ( !errorMessage.isEmpty() )
651 {
652 // intersects relation test still failed, even after trying to make valid!
653 QgsMessageLog::logMessage( QObject::tr( "Error determining selection: %1" ).arg( errorMessage ), QString(), Qgis::MessageLevel::Warning );
654 }
655
656 if ( notIntersects )
657 continue;
658
659 newSelectedFeatures.insert( f.id() );
660 }
661
662 if ( r )
663 r->stopRender( data->context );
664 return filterIds( newSelectedFeatures, data->existingSelection, data->selectBehavior );
665}
666
667void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onSearchFinished()
668{
669 if ( !mFutureWatcher || !mFutureWatcher->isFinished() )
670 return;
671
672 mAllFeatureIds = mFutureWatcher->result();
673 mActionChooseAll->setText( textForChooseAll( mAllFeatureIds.size() ) );
674 if ( !mAllFeatureIds.isEmpty() )
675 connect( mActionChooseAll, &QAction::hovered, this, &QgsMapToolSelectMenuActions::highlightAllFeatures );
676 else
677 mActionChooseAll->setEnabled( false );
678 if ( mAllFeatureIds.count() > 1 )
679 populateChooseOneMenu( mAllFeatureIds );
680}
681
682
683QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseAll( qint64 featureCount ) const
684{
685 if ( featureCount == 0 || featureCount == 1 )
686 {
687 switch ( mBehavior )
688 {
690 return tr( "Select Feature" );
691
693 return tr( "Add to Selection" );
694
696 return tr( "Intersect with Selection" );
697
699 return tr( "Remove from Selection" );
700 }
701 }
702
703 QString featureCountText;
704 if ( featureCount < 0 )
705 featureCountText = tr( "Searching…" );
706 else
707 featureCountText = QLocale().toString( featureCount );
708
709 switch ( mBehavior )
710 {
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 );
719 }
720
721 return QString();
722}
723
724QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseOneMenu() const
725{
726 switch ( mBehavior )
727 {
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" );
736 }
737
738 return QString();
739}
740
741
742void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::populateChooseOneMenu( const QgsFeatureIds &ids )
743{
744 if ( !mVectorLayer )
745 return;
746
747 QgsFeatureIds displayedFeatureIds;
748
749 QgsFeatureIds::ConstIterator it = ids.constBegin();
750 while ( displayedFeatureIds.count() <= 20 && it != ids.constEnd() ) //for now hardcoded, but maybe define a settings for this
751 displayedFeatureIds.insert( *( it++ ) );
752
753 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mVectorLayer ) );
754 QgsExpression exp = mVectorLayer->displayExpression();
755 exp.prepare( &context );
756
757 QgsFeatureRequest request = QgsFeatureRequest().setFilterFids( displayedFeatureIds );
758 QgsFeature feat;
759 QgsFeatureIterator featureIt = mVectorLayer->getFeatures( request );
760 while ( featureIt.nextFeature( feat ) )
761 {
762 const QgsFeatureId id = feat.id();
763 context.setFeature( feat );
764
765 QString featureTitle = exp.evaluate( &context ).toString();
766 if ( featureTitle.isEmpty() )
767 featureTitle = tr( "Feature %1" ).arg( FID_TO_STRING( feat.id() ) );
768
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 );
773 }
774
775 mMenuChooseOne->setEnabled( ids.count() != 0 );
776}
777
778void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseOneCandidateFeature( QgsFeatureId id )
779{
780 if ( !mVectorLayer )
781 return;
782
783 QgsFeatureIds ids;
784 ids << id;
785 mVectorLayer->selectByIds( ids, mBehavior );
786}
787
788void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseAllCandidateFeature()
789{
790 if ( !mFutureWatcher )
791 return;
792
793 if ( !mFutureWatcher->isFinished() )
794 {
795 QApplication::setOverrideCursor( Qt::WaitCursor );
796 mFutureWatcher->waitForFinished();
797 QApplication::restoreOverrideCursor();
798 mAllFeatureIds = mFutureWatcher->result();
799 }
800
801 if ( !mAllFeatureIds.empty() )
802 mVectorLayer->selectByIds( mAllFeatureIds, mBehavior );
803}
804
805void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightAllFeatures()
806{
807 removeHighlight();
808
809 if ( !mVectorLayer )
810 return;
811
812 if ( !mAllFeatureIds.empty() )
813 {
814 int count = 0;
815 for ( const QgsFeatureId &id : std::as_const( mAllFeatureIds ) )
816 {
817 QgsFeature feat = mVectorLayer->getFeature( id );
818 QgsGeometry geom = feat.geometry();
819 if ( !geom.isEmpty() )
820 {
821 QgsHighlight *hl = new QgsHighlight( mCanvas, geom, mVectorLayer );
822 hl->applyDefaultStyle();
823 mHighlight.append( hl );
824 count++;
825 }
826 if ( count > 1000 ) //for now hardcoded, but maybe define a settings for this
827 return;
828 }
829 }
830}
831
832void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightOneFeature( QgsFeatureId id )
833{
834 removeHighlight();
835
836 if ( !mVectorLayer )
837 return;
838
839 QgsFeature feat = mVectorLayer->getFeature( id );
840 QgsGeometry geom = feat.geometry();
841 if ( !geom.isEmpty() )
842 {
843 QgsHighlight *hl = new QgsHighlight( mCanvas, geom, mVectorLayer );
844 hl->applyDefaultStyle();
845 mHighlight.append( hl );
846 }
847}
848
849QgsFeatureIds QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::filterIds( const QgsFeatureIds &ids, const QgsFeatureIds &existingSelection, Qgis::SelectBehavior behavior )
850{
851 QgsFeatureIds effectiveFeatureIds = ids;
852 switch ( behavior )
853 {
855 break;
857 {
858 for ( QgsFeatureId newSelected : ids )
859 {
860 if ( existingSelection.contains( newSelected ) )
861 effectiveFeatureIds.remove( newSelected );
862 }
863 }
864 break;
867 {
868 for ( QgsFeatureId newSelected : ids )
869 {
870 if ( !existingSelection.contains( newSelected ) )
871 effectiveFeatureIds.remove( newSelected );
872 }
873 }
874 break;
875 }
876
877 return effectiveFeatureIds;
878}
879
880
881void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onLayerDestroyed()
882{
883 mVectorLayer = nullptr;
884 mJobData->isCanceled = true;
885 removeHighlight();
886}
887
888void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::removeHighlight()
889{
890 qDeleteAll( mHighlight );
891 mHighlight.clear();
892}
QFlags< SelectionFlag > SelectionFlags
Flags which control feature selection behavior.
Definition qgis.h:1928
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2332
@ Warning
Warning message.
Definition qgis.h:162
@ Info
Information message.
Definition qgis.h:161
@ Polygon
Polygons.
Definition qgis.h:382
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
@ ToggleSelection
Enables a "toggle" selection mode, where previously selected matching features will be deselected and...
Definition qgis.h:1920
@ SingleFeatureSelection
Select only a single feature, picking the "best" match for the selection geometry.
Definition qgis.h:1919
@ Within
Select where features are within the reference geometry.
Definition qgis.h:1908
@ Intersect
Select where features intersect the reference geometry.
Definition qgis.h:1907
SelectBehavior
Specifies how a selection should be applied.
Definition qgis.h:1892
@ SetSelection
Set selection, removing any existing selection.
Definition qgis.h:1893
@ AddToSelection
Add selection to current selection.
Definition qgis.h:1894
@ IntersectSelection
Modify current selection to include only select features which match.
Definition qgis.h:1895
@ RemoveFromSelection
Remove from current selection.
Definition qgis.h:1896
Handles coordinate transforms between two coordinate systems.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
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...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
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.
Qgis::GeometryType type
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.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Qgis::LayerType type
Definition qgsmaplayer.h:93
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.
void populateMenu(QMenu *menu)
Populates the menu with "All Feature" action and a empty menu that could contain later the "One Featu...
QgsMapToolSelectMenuActions(QgsMapCanvas *canvas, QgsVectorLayer *vectorLayer, Qgis::SelectBehavior behavior, const QgsGeometry &selectionGeometry, QObject *parent=nullptr)
Constructor.
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).
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
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.
Definition qgsvector.h:34
void selectSingleFeature(QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qt::KeyboardModifiers modifiers)
Selects a single feature from within currently selected layer.
QgsFeatureIds getMatchingFeatures(QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, bool doContains, bool singleSelect)
Calculates a list of features matching a selection geometry and flags.
void selectMultipleFeatures(QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qt::KeyboardModifiers modifiers)
Selects multiple matching features from within currently selected layer.
QgsMapLayer * getCurrentTargetLayer(QgsMapCanvas *canvas)
Get the current selected canvas map layer.
QgsRectangle GUI_EXPORT expandSelectRectangle(QgsPointXY mapPoint, QgsMapCanvas *canvas, QgsMapLayer *layer)
Expands a point to a rectangle with minimum size for selection based on the layer.
void GUI_EXPORT setRubberBand(QgsMapCanvas *canvas, QRect &selectRect, QgsRubberBand *rubberBand)
Sets a QgsRubberband to rectangle in map units using a rectangle defined in device coords.
void setSelectedFeatures(QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qgis::SelectBehavior selectBehavior=Qgis::SelectBehavior::SetSelection, bool doContains=true, bool singleSelect=false)
Selects the features within currently selected layer.
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.
Definition qgsgeometry.h:92
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:63
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
bool transformSelectGeometry(const QgsGeometry &selectGeometry, QgsGeometry &selectGeomTrans, const QgsCoordinateTransform &ct)