QGIS API Documentation 4.1.0-Master (4aad578bf8d)
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 ur = transform->toMapCoordinates( static_cast<int>( point.x() + boxSize ), static_cast<int>( point.y() - boxSize ) );
151 return QgsRectangle( ll, ur );
152}
153
154void QgsMapToolSelectUtils::selectMultipleFeatures( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qt::KeyboardModifiers modifiers )
155{
157 if ( modifiers & Qt::ShiftModifier && modifiers & Qt::ControlModifier )
159 else if ( modifiers & Qt::ShiftModifier )
161 else if ( modifiers & Qt::ControlModifier )
163
164 bool doContains = modifiers & Qt::AltModifier;
165 setSelectedFeatures( canvas, selectGeometry, behavior, doContains );
166}
167
168bool transformSelectGeometry( const QgsGeometry &selectGeometry, QgsGeometry &selectGeomTrans, const QgsCoordinateTransform &ct )
169{
170 selectGeomTrans = selectGeometry;
171 try
172 {
173 if ( !ct.isShortCircuited() && selectGeomTrans.type() == Qgis::GeometryType::Polygon )
174 {
175 // convert add more points to the edges of the rectangle
176 // improve transformation result
177 QgsPolygonXY poly( selectGeomTrans.asPolygon() );
178 if ( poly.size() == 1 && poly.at( 0 ).size() == 5 )
179 {
180 const QgsPolylineXY &ringIn = poly.at( 0 );
181
182 QgsPolygonXY newpoly( 1 );
183 newpoly[0].resize( 41 );
184 QgsPolylineXY &ringOut = newpoly[0];
185
186 ringOut[0] = ringIn.at( 0 );
187
188 int i = 1;
189 for ( int j = 1; j < 5; j++ )
190 {
191 QgsVector v( ( ringIn.at( j ) - ringIn.at( j - 1 ) ) / 10.0 );
192 for ( int k = 0; k < 9; k++ )
193 {
194 ringOut[i] = ringOut[i - 1] + v;
195 i++;
196 }
197 ringOut[i++] = ringIn.at( j );
198 }
199 selectGeomTrans = QgsGeometry::fromPolygonXY( newpoly );
200 }
201 }
202
203 selectGeomTrans.transform( ct );
204 return true;
205 }
206 catch ( QgsCsException &cse )
207 {
208 Q_UNUSED( cse )
209 // catch exception for 'invalid' point and leave existing selection unchanged
210 QgsDebugError( u"Caught CRS exception "_s );
211 return false;
212 }
213}
214
215void QgsMapToolSelectUtils::selectSingleFeature( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qt::KeyboardModifiers modifiers )
216{
218 if ( !layer )
219 return;
220
222 QgsSelectionContext context;
223 context.setScale( canvas->scale() );
224
225 QApplication::setOverrideCursor( Qt::WaitCursor );
226 switch ( layer->type() )
227 {
229 {
230 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
231 QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, false, true );
232 if ( selectedFeatures.isEmpty() )
233 {
234 if ( !( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier ) )
235 {
236 // if no modifiers then clicking outside features clears the selection
237 // but if there's a shift or ctrl modifier, then it's likely the user was trying
238 // to modify an existing selection by adding or subtracting features and just
239 // missed the feature
240 vlayer->removeSelection();
241 }
242 QApplication::restoreOverrideCursor();
243 return;
244 }
245
246 //either shift or control modifier switches to "toggle" selection mode
247 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
248 {
249 QgsFeatureId selectId = *selectedFeatures.constBegin();
250 QgsFeatureIds layerSelectedFeatures = vlayer->selectedFeatureIds();
251 if ( layerSelectedFeatures.contains( selectId ) )
253 else
255 }
256
257 vlayer->selectByIds( selectedFeatures, behavior );
258 break;
259 }
260
262 {
263 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
264
266 QgsGeometry selectGeomTrans;
267 if ( !transformSelectGeometry( selectGeometry, selectGeomTrans, ct ) )
268 {
269 if ( QgsMessageBar *messageBar = canvas->messageBar() )
270 {
271 messageBar->pushMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning );
272 }
273 break;
274 }
275
277 if ( modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier )
279
281 QgsExpressionContext expressionContext = canvas->createExpressionContext();
282 expressionContext << QgsExpressionContextUtils::layerScope( vtLayer );
283 renderContext.setExpressionContext( expressionContext );
284
285 vtLayer->selectByGeometry( selectGeomTrans, context, behavior, Qgis::SelectGeometryRelationship::Intersect, flags, &renderContext );
286 break;
287 }
288
296 break;
297 }
298
299 QApplication::restoreOverrideCursor();
300}
301
302void QgsMapToolSelectUtils::setSelectedFeatures( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, Qgis::SelectBehavior selectBehavior, bool doContains, bool singleSelect )
303{
305 if ( !layer )
306 return;
307
308 QApplication::setOverrideCursor( Qt::WaitCursor );
309
310 QgsSelectionContext context;
311 context.setScale( canvas->scale() );
312
313 switch ( layer->type() )
314 {
316 {
317 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
318 QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, doContains, singleSelect );
319 vLayer->selectByIds( selectedFeatures, selectBehavior );
320 break;
321 }
322
324 {
325 QgsVectorTileLayer *vtLayer = qobject_cast<QgsVectorTileLayer *>( layer );
327 QgsGeometry selectGeomTrans;
328 if ( !transformSelectGeometry( selectGeometry, selectGeomTrans, ct ) )
329 {
330 if ( QgsMessageBar *messageBar = canvas->messageBar() )
331 {
332 messageBar->pushMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning );
333 }
334 break;
335 }
336
338 QgsExpressionContext expressionContext = canvas->createExpressionContext();
339 expressionContext << QgsExpressionContextUtils::layerScope( vtLayer );
340 renderContext.setExpressionContext( expressionContext );
341
342 vtLayer->selectByGeometry( selectGeomTrans, context, selectBehavior, doContains ? Qgis::SelectGeometryRelationship::Within : Qgis::SelectGeometryRelationship::Intersect, Qgis::SelectionFlags(), &renderContext );
343 break;
344 }
345
353 break;
354 }
355
356 QApplication::restoreOverrideCursor();
357}
358
359QgsFeatureIds QgsMapToolSelectUtils::getMatchingFeatures( QgsMapCanvas *canvas, const QgsGeometry &selectGeometry, bool doContains, bool singleSelect )
360{
361 QgsFeatureIds newSelectedFeatures;
362
363 if ( selectGeometry.type() != Qgis::GeometryType::Polygon )
364 return newSelectedFeatures;
365
367 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( targetLayer );
368 if ( !vlayer )
369 return newSelectedFeatures;
370
371 // toLayerCoordinates will throw an exception for any 'invalid' points in
372 // the rubber band.
373 // For example, if you project a world map onto a globe using EPSG 2163
374 // and then click somewhere off the globe, an exception will be thrown.
375 QgsGeometry selectGeomTrans;
377 if ( !transformSelectGeometry( selectGeometry, selectGeomTrans, ct ) )
378 {
379 if ( QgsMessageBar *messageBar = canvas->messageBar() )
380 {
381 messageBar->pushMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning );
382 }
383 return newSelectedFeatures;
384 }
385
386 QgsDebugMsgLevel( "Selection layer: " + vlayer->name(), 3 );
387 QgsDebugMsgLevel( "Selection polygon: " + selectGeomTrans.asWkt(), 3 );
388 QgsDebugMsgLevel( "doContains: " + QString( doContains ? u"T"_s : u"F"_s ), 3 );
389
390 // make sure the selection geometry is valid, or intersection tests won't work correctly...
391 if ( !selectGeomTrans.isGeosValid() )
392 {
393 // a zero width buffer is safer than calling make valid here!
394 selectGeomTrans = selectGeomTrans.buffer( 0, 1 );
395 if ( selectGeomTrans.isEmpty() )
396 return newSelectedFeatures;
397 }
398
399 std::unique_ptr<QgsGeometryEngine> selectionGeometryEngine( QgsGeometry::createGeometryEngine( selectGeomTrans.constGet() ) );
400 selectionGeometryEngine->setLogErrors( false );
401 selectionGeometryEngine->prepareGeometry();
402
404
405 QgsExpressionContext expressionContext = canvas->createExpressionContext();
406 expressionContext << QgsExpressionContextUtils::layerScope( vlayer );
407 context.setExpressionContext( expressionContext );
408
409 std::unique_ptr<QgsFeatureRenderer> r;
410 if ( vlayer->renderer() )
411 {
412 r.reset( vlayer->renderer()->clone() );
413 r->startRender( context, vlayer->fields() );
414 }
415
416 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( canvas, vlayer );
417 if ( canvasFilter == "FALSE"_L1 )
418 return newSelectedFeatures;
419
420 QgsFeatureRequest request;
421 request.setFilterRect( selectGeomTrans.boundingBox() );
423 if ( r )
424 request.setSubsetOfAttributes( r->usedAttributes( context ), vlayer->fields() );
425 else
426 request.setNoAttributes();
427
428 if ( !canvasFilter.isEmpty() )
429 request.setFilterExpression( canvasFilter );
430 if ( r )
431 {
432 const QString filterExpression = r->filter( vlayer->fields() );
433 if ( !filterExpression.isEmpty() )
434 {
435 request.combineFilterExpression( filterExpression );
436 }
437 }
438
439 request.setExpressionContext( context.expressionContext() );
440 QgsFeatureIterator fit = vlayer->getFeatures( request );
441
442 QgsFeature f;
443 QgsFeatureId closestFeatureId = 0;
444 bool foundSingleFeature = false;
445 double closestFeatureDist = std::numeric_limits<double>::max();
446 while ( fit.nextFeature( f ) )
447 {
448 context.expressionContext().setFeature( f );
449 // make sure to only use features that are visible
450 if ( r && !r->willRenderFeature( f, context ) )
451 continue;
452
453 QgsGeometry g = f.geometry();
454 QString errorMessage;
455 if ( doContains )
456 {
457 // if we get an error from the contains check then it indicates that the geometry is invalid and GEOS choked on it.
458 // in this case we consider the bounding box intersection check which has already been performed by the iterator as sufficient and
459 // allow the feature to be selected
460 const bool notContained = !selectionGeometryEngine->contains( g.constGet(), &errorMessage ) && ( errorMessage.isEmpty() || /* message will be non empty if geometry g is invalid */
461 !selectionGeometryEngine->contains( g.makeValid().constGet(), &errorMessage ) ); /* second chance for invalid geometries, repair and re-test */
462
463 if ( !errorMessage.isEmpty() )
464 {
465 // contains relation test still failed, even after trying to make valid!
466 QgsMessageLog::logMessage( QObject::tr( "Error determining selection: %1" ).arg( errorMessage ), QString(), Qgis::MessageLevel::Warning );
467 }
468
469 if ( notContained )
470 continue;
471 }
472 else
473 {
474 // if we get an error from the intersects check then it indicates that the geometry is invalid and GEOS choked on it.
475 // in this case we consider the bounding box intersection check which has already been performed by the iterator as sufficient and
476 // allow the feature to be selected
477 const bool notIntersects = !selectionGeometryEngine->intersects( g.constGet(), &errorMessage ) && ( errorMessage.isEmpty() || /* message will be non empty if geometry g is invalid */
478 !selectionGeometryEngine->intersects( g.makeValid().constGet(), &errorMessage ) ); /* second chance for invalid geometries, repair and re-test */
479
480 if ( !errorMessage.isEmpty() )
481 {
482 // intersects relation test still failed, even after trying to make valid!
483 QgsMessageLog::logMessage( QObject::tr( "Error determining selection: %1" ).arg( errorMessage ), QString(), Qgis::MessageLevel::Warning );
484 }
485
486 if ( notIntersects )
487 continue;
488 }
489 if ( singleSelect )
490 {
491 foundSingleFeature = true;
492 double distance = g.distance( selectGeomTrans );
493 if ( distance <= closestFeatureDist )
494 {
495 closestFeatureDist = distance;
496 closestFeatureId = f.id();
497 }
498 }
499 else
500 {
501 newSelectedFeatures.insert( f.id() );
502 }
503 }
504 if ( singleSelect && foundSingleFeature )
505 {
506 newSelectedFeatures.insert( closestFeatureId );
507 }
508
509 if ( r )
510 r->stopRender( context );
511
512 QgsDebugMsgLevel( "Number of new selected features: " + QString::number( newSelectedFeatures.size() ), 2 );
513
514 return newSelectedFeatures;
515}
516
517
519 QgsMapCanvas *canvas, QgsVectorLayer *vectorLayer, Qgis::SelectBehavior behavior, const QgsGeometry &selectionGeometry, QObject *parent
520)
521 : QObject( parent )
522 , mCanvas( canvas )
523 , mVectorLayer( vectorLayer )
524 , mBehavior( behavior )
525 , mSelectGeometry( selectionGeometry )
526{
527 connect( mVectorLayer, &QgsMapLayer::destroyed, this, &QgsMapToolSelectMenuActions::onLayerDestroyed );
528
529 mFutureWatcher = new QFutureWatcher<QgsFeatureIds>( this );
530 connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapToolSelectMenuActions::onSearchFinished );
531}
532
534{
535 removeHighlight();
536 if ( mJobData )
537 mJobData->isCanceled = true;
538 if ( mFutureWatcher )
539 mFutureWatcher->waitForFinished();
540}
541
543{
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 );
550
551 startFeatureSearch();
552}
553
554void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::startFeatureSearch()
555{
556 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mCanvas, mVectorLayer );
557 if ( canvasFilter == "FALSE"_L1 )
558 return;
559
560 mJobData = std::make_shared<DataForSearchingJob>();
561 mJobData->isCanceled = false;
562 mJobData->source = std::make_unique<QgsVectorLayerFeatureSource>( mVectorLayer );
563 mJobData->selectGeometry = mSelectGeometry;
564 mJobData->context = QgsRenderContext::fromMapSettings( mCanvas->mapSettings() );
565 mJobData->filterString = canvasFilter;
566 mJobData->ct = QgsCoordinateTransform( mCanvas->mapSettings().destinationCrs(), mVectorLayer->crs(), mJobData->context.transformContext() );
567 mJobData->featureRenderer.reset( mVectorLayer->renderer()->clone() );
568
569 mJobData->context.setExpressionContext( mCanvas->createExpressionContext() );
570 mJobData->context.expressionContext() << QgsExpressionContextUtils::layerScope( mVectorLayer );
571 mJobData->selectBehavior = mBehavior;
572 if ( mBehavior != Qgis::SelectBehavior::SetSelection )
573 mJobData->existingSelection = mVectorLayer->selectedFeatureIds();
574 QFuture<QgsFeatureIds> future = QtConcurrent::run( search, mJobData );
575 mFutureWatcher->setFuture( future );
576}
577
578QgsFeatureIds QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::search( std::shared_ptr<DataForSearchingJob> data )
579{
580 QgsFeatureIds newSelectedFeatures;
581
582 if ( data->selectGeometry.type() != Qgis::GeometryType::Polygon )
583 return newSelectedFeatures;
584
585 QgsGeometry selectGeomTrans = data->selectGeometry;
586
587 if ( !transformSelectGeometry( data->selectGeometry, selectGeomTrans, data->ct ) )
588 {
589 QgsMessageLog::logMessage( QObject::tr( "Selection extends beyond layer's coordinate system" ), QString(), Qgis::MessageLevel::Warning, true );
590 return newSelectedFeatures;
591 }
592 // make sure the selection geometry is valid, or intersection tests won't work correctly...
593 if ( !selectGeomTrans.isGeosValid() )
594 {
595 // a zero width buffer is safer than calling make valid here!
596 selectGeomTrans = selectGeomTrans.buffer( 0, 1 );
597 }
598
599 std::unique_ptr<QgsGeometryEngine> selectionGeometryEngine( QgsGeometry::createGeometryEngine( selectGeomTrans.constGet() ) );
600 selectionGeometryEngine->setLogErrors( false );
601 selectionGeometryEngine->prepareGeometry();
602
603 std::unique_ptr<QgsFeatureRenderer> r;
604 if ( data->featureRenderer )
605 {
606 r.reset( data->featureRenderer->clone() );
607 r->startRender( data->context, data->source->fields() );
608 }
609
610 QgsFeatureRequest request;
611 request.setFilterRect( selectGeomTrans.boundingBox() );
613
614 if ( !data->filterString.isEmpty() )
615 request.setFilterExpression( data->filterString );
616
617 if ( r )
618 {
619 request.setSubsetOfAttributes( r->usedAttributes( data->context ), data->source->fields() );
620 const QString filterExpression = r->filter( data->source->fields() );
621 if ( !filterExpression.isEmpty() )
622 {
623 request.combineFilterExpression( filterExpression );
624 }
625 }
626 request.setExpressionContext( data->context.expressionContext() );
627
628 QgsFeatureIterator fit = data->source->getFeatures( request );
629
630 QgsFeature f;
631
632 while ( fit.nextFeature( f ) && !data->isCanceled )
633 {
634 data->context.expressionContext().setFeature( f );
635 // make sure to only use features that are visible
636 if ( r && !r->willRenderFeature( f, data->context ) )
637 continue;
638
639 QgsGeometry g = f.geometry();
640 QString errorMessage;
641
642 // if we get an error from the intersects check then it indicates that the geometry is invalid and GEOS choked on it.
643 // in this case we consider the bounding box intersection check which has already been performed by the iterator as sufficient and
644 // allow the feature to be selected
645 const bool notIntersects = !selectionGeometryEngine->intersects( g.constGet(), &errorMessage ) && ( errorMessage.isEmpty() || /* message will be non empty if geometry g is invalid */
646 !selectionGeometryEngine->intersects( g.makeValid().constGet(), &errorMessage ) ); /* second chance for invalid geometries, repair and re-test */
647
648 if ( !errorMessage.isEmpty() )
649 {
650 // intersects relation test still failed, even after trying to make valid!
651 QgsMessageLog::logMessage( QObject::tr( "Error determining selection: %1" ).arg( errorMessage ), QString(), Qgis::MessageLevel::Warning );
652 }
653
654 if ( notIntersects )
655 continue;
656
657 newSelectedFeatures.insert( f.id() );
658 }
659
660 if ( r )
661 r->stopRender( data->context );
662 return filterIds( newSelectedFeatures, data->existingSelection, data->selectBehavior );
663}
664
665void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onSearchFinished()
666{
667 if ( !mFutureWatcher || !mFutureWatcher->isFinished() )
668 return;
669
670 mAllFeatureIds = mFutureWatcher->result();
671 mActionChooseAll->setText( textForChooseAll( mAllFeatureIds.size() ) );
672 if ( !mAllFeatureIds.isEmpty() )
673 connect( mActionChooseAll, &QAction::hovered, this, &QgsMapToolSelectMenuActions::highlightAllFeatures );
674 else
675 mActionChooseAll->setEnabled( false );
676 if ( mAllFeatureIds.count() > 1 )
677 populateChooseOneMenu( mAllFeatureIds );
678}
679
680
681QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseAll( qint64 featureCount ) const
682{
683 if ( featureCount == 0 || featureCount == 1 )
684 {
685 switch ( mBehavior )
686 {
688 return tr( "Select Feature" );
689
691 return tr( "Add to Selection" );
692
694 return tr( "Intersect with Selection" );
695
697 return tr( "Remove from Selection" );
698 }
699 }
700
701 QString featureCountText;
702 if ( featureCount < 0 )
703 featureCountText = tr( "Searching…" );
704 else
705 featureCountText = QLocale().toString( featureCount );
706
707 switch ( mBehavior )
708 {
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 );
717 }
718
719 return QString();
720}
721
722QString QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::textForChooseOneMenu() const
723{
724 switch ( mBehavior )
725 {
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" );
734 }
735
736 return QString();
737}
738
739
740void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::populateChooseOneMenu( const QgsFeatureIds &ids )
741{
742 if ( !mVectorLayer )
743 return;
744
745 QgsFeatureIds displayedFeatureIds;
746
747 QgsFeatureIds::ConstIterator it = ids.constBegin();
748 while ( displayedFeatureIds.count() <= 20 && it != ids.constEnd() ) //for now hardcoded, but maybe define a settings for this
749 displayedFeatureIds.insert( *( it++ ) );
750
751 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mVectorLayer ) );
752 QgsExpression exp = mVectorLayer->displayExpression();
753 exp.prepare( &context );
754
755 QgsFeatureRequest request = QgsFeatureRequest().setFilterFids( displayedFeatureIds );
756 QgsFeature feat;
757 QgsFeatureIterator featureIt = mVectorLayer->getFeatures( request );
758 while ( featureIt.nextFeature( feat ) )
759 {
760 const QgsFeatureId id = feat.id();
761 context.setFeature( feat );
762
763 QString featureTitle = exp.evaluate( &context ).toString();
764 if ( featureTitle.isEmpty() )
765 featureTitle = tr( "Feature %1" ).arg( FID_TO_STRING( feat.id() ) );
766
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 );
771 }
772
773 mMenuChooseOne->setEnabled( ids.count() != 0 );
774}
775
776void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseOneCandidateFeature( QgsFeatureId id )
777{
778 if ( !mVectorLayer )
779 return;
780
781 QgsFeatureIds ids;
782 ids << id;
783 mVectorLayer->selectByIds( ids, mBehavior );
784}
785
786void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::chooseAllCandidateFeature()
787{
788 if ( !mFutureWatcher )
789 return;
790
791 if ( !mFutureWatcher->isFinished() )
792 {
793 QApplication::setOverrideCursor( Qt::WaitCursor );
794 mFutureWatcher->waitForFinished();
795 QApplication::restoreOverrideCursor();
796 mAllFeatureIds = mFutureWatcher->result();
797 }
798
799 if ( !mAllFeatureIds.empty() )
800 mVectorLayer->selectByIds( mAllFeatureIds, mBehavior );
801}
802
803void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightAllFeatures()
804{
805 removeHighlight();
806
807 if ( !mVectorLayer )
808 return;
809
810 if ( !mAllFeatureIds.empty() )
811 {
812 int count = 0;
813 for ( const QgsFeatureId &id : std::as_const( mAllFeatureIds ) )
814 {
815 QgsFeature feat = mVectorLayer->getFeature( id );
816 QgsGeometry geom = feat.geometry();
817 if ( !geom.isEmpty() )
818 {
819 QgsHighlight *hl = new QgsHighlight( mCanvas, geom, mVectorLayer );
820 hl->applyDefaultStyle();
821 mHighlight.append( hl );
822 count++;
823 }
824 if ( count > 1000 ) //for now hardcoded, but maybe define a settings for this
825 return;
826 }
827 }
828}
829
830void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::highlightOneFeature( QgsFeatureId id )
831{
832 removeHighlight();
833
834 if ( !mVectorLayer )
835 return;
836
837 QgsFeature feat = mVectorLayer->getFeature( id );
838 QgsGeometry geom = feat.geometry();
839 if ( !geom.isEmpty() )
840 {
841 QgsHighlight *hl = new QgsHighlight( mCanvas, geom, mVectorLayer );
842 hl->applyDefaultStyle();
843 mHighlight.append( hl );
844 }
845}
846
847QgsFeatureIds QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::filterIds( const QgsFeatureIds &ids, const QgsFeatureIds &existingSelection, Qgis::SelectBehavior behavior )
848{
849 QgsFeatureIds effectiveFeatureIds = ids;
850 switch ( behavior )
851 {
853 break;
855 {
856 for ( QgsFeatureId newSelected : ids )
857 {
858 if ( existingSelection.contains( newSelected ) )
859 effectiveFeatureIds.remove( newSelected );
860 }
861 }
862 break;
865 {
866 for ( QgsFeatureId newSelected : ids )
867 {
868 if ( !existingSelection.contains( newSelected ) )
869 effectiveFeatureIds.remove( newSelected );
870 }
871 }
872 break;
873 }
874
875 return effectiveFeatureIds;
876}
877
878
879void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::onLayerDestroyed()
880{
881 mVectorLayer = nullptr;
882 mJobData->isCanceled = true;
883 removeHighlight();
884}
885
886void QgsMapToolSelectUtils::QgsMapToolSelectMenuActions::removeHighlight()
887{
888 qDeleteAll( mHighlight );
889 mHighlight.clear();
890}
QFlags< SelectionFlag > SelectionFlags
Flags which control feature selection behavior.
Definition qgis.h:1927
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2331
@ 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:1919
@ SingleFeatureSelection
Select only a single feature, picking the "best" match for the selection geometry.
Definition qgis.h:1918
@ Within
Select where features are within the reference geometry.
Definition qgis.h:1907
@ Intersect
Select where features intersect the reference geometry.
Definition qgis.h:1906
SelectBehavior
Specifies how a selection should be applied.
Definition qgis.h:1891
@ SetSelection
Set selection, removing any existing selection.
Definition qgis.h:1892
@ AddToSelection
Add selection to current selection.
Definition qgis.h:1893
@ IntersectSelection
Modify current selection to include only select features which match.
Definition qgis.h:1894
@ RemoveFromSelection
Remove from current selection.
Definition qgis.h:1895
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:68
QgsGeometry geometry
Definition qgsfeature.h:71
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:63
#define QgsDebugError(str)
Definition qgslogger.h:59
bool transformSelectGeometry(const QgsGeometry &selectGeometry, QgsGeometry &selectGeomTrans, const QgsCoordinateTransform &ct)