QGIS API Documentation 3.27.0-Master (75dc696944)
qgsmaptoolcapture.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaptoolcapture.cpp - map tool for capturing points, lines, polygons
3 ---------------------
4 begin : January 2006
5 copyright : (C) 2006 by Martin Dobias
6 email : wonder.sk at gmail dot com
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
16#include "qgsmaptoolcapture.h"
17#include "qgsexception.h"
18#include "qgsfeatureiterator.h"
20#include "qgslayertreeview.h"
21#include "qgslinestring.h"
22#include "qgslogger.h"
23#include "qgsmapcanvas.h"
24#include "qgsmapcanvastracer.h"
25#include "qgsmapmouseevent.h"
26#include "qgspolygon.h"
27#include "qgsrubberband.h"
28#include "qgssnapindicator.h"
29#include "qgsvectorlayer.h"
30#include "qgsvertexmarker.h"
32#include "qgsapplication.h"
34#include "qgsproject.h"
38#include "qgsgui.h"
39
40#include <QAction>
41#include <QCursor>
42#include <QPixmap>
43#include <QStatusBar>
44
45
47 : QgsMapToolAdvancedDigitizing( canvas, cadDockWidget )
48 , mCaptureMode( mode )
49 , mCaptureModeFromLayer( mode == CaptureNone )
50{
51 mTempRubberBand.setParentOwner( canvas );
52
53 mSnapIndicator.reset( new QgsSnapIndicator( canvas ) );
54
55 setCursor( QgsApplication::getThemeCursor( QgsApplication::Cursor::CapturePoint ) );
56
58 this, &QgsMapToolCapture::currentLayerChanged );
59
61 layerOptions.skipCrsValidation = true;
62 layerOptions.loadDefaultStyle = false;
63 mExtraSnapLayer = new QgsVectorLayer( QStringLiteral( "LineString?crs=" ), QStringLiteral( "extra snap" ), QStringLiteral( "memory" ), layerOptions );
64 mExtraSnapLayer->startEditing();
65 QgsFeature f;
66 mExtraSnapLayer->addFeature( f );
67 mExtraSnapFeatureId = f.id();
68
70 this, &QgsMapToolCapture::updateExtraSnapLayer );
71
72 currentLayerChanged( canvas->currentLayer() );
73}
74
76{
77 // during tear down we have to clean up mExtraSnapLayer first, before
78 // we call stop capturing. Otherwise stopCapturing tries to access members
79 // from the mapcanvas, which is likely already being destroyed and triggering
80 // the deletion of this object...
81 if ( mCanvas )
82 {
83 mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
84 }
85 mExtraSnapLayer->deleteLater();
86 mExtraSnapLayer = nullptr;
87
89
90 if ( mValidator )
91 {
92 mValidator->deleteLater();
93 mValidator = nullptr;
94 }
95}
96
97QgsMapToolCapture::Capabilities QgsMapToolCapture::capabilities() const
98{
100}
101
103{
104 switch ( technique )
105 {
107 return true;
111 return false;
112 }
114}
115
117{
118 if ( mTempRubberBand )
119 mTempRubberBand->show();
120
121 mCanvas->snappingUtils()->addExtraSnapLayer( mExtraSnapLayer );
123
124 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
125 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
126}
127
129{
130 if ( mTempRubberBand )
131 mTempRubberBand->hide();
132
133 mSnapIndicator->setMatch( QgsPointLocator::Match() );
134
135 mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
136
137 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
138 mCurrentShapeMapTool->deactivate();
139
141}
142
143void QgsMapToolCapture::currentLayerChanged( QgsMapLayer *layer )
144{
145 if ( !mCaptureModeFromLayer )
146 return;
147
148 mCaptureMode = CaptureNone;
149
150 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
151 if ( !vlayer )
152 {
153 return;
154 }
155
156 switch ( vlayer->geometryType() )
157 {
159 mCaptureMode = CapturePoint;
160 break;
162 mCaptureMode = CaptureLine;
163 break;
165 mCaptureMode = CapturePolygon;
166 break;
167 default:
168 mCaptureMode = CaptureNone;
169 break;
170 }
171
172 if ( mTempRubberBand )
173 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
174
175 resetRubberBand();
177}
178
179
180bool QgsMapToolCapture::tracingEnabled()
181{
183 return tracer && ( !tracer->actionEnableTracing() || tracer->actionEnableTracing()->isChecked() )
184 && ( !tracer->actionEnableSnapping() || tracer->actionEnableSnapping()->isChecked() );
185}
186
187
188QgsPointXY QgsMapToolCapture::tracingStartPoint()
189{
190 // if we have starting point from previous trace, then preferably use that one
191 // (useful when tracing with offset)
192 if ( mTracingStartPoint != QgsPointXY() )
193 return mTracingStartPoint;
194
195 return mCaptureLastPoint;
196}
197
198
199bool QgsMapToolCapture::tracingMouseMove( QgsMapMouseEvent *e )
200{
201 if ( !e->isSnapped() )
202 return false;
203
204 QgsPointXY pt0 = tracingStartPoint();
205 if ( pt0 == QgsPointXY() )
206 return false;
207
209 if ( !tracer )
210 return false; // this should not happen!
211
213 QVector<QgsPointXY> points = tracer->findShortestPath( pt0, e->mapPoint(), &err );
214 if ( points.isEmpty() )
215 {
216 tracer->reportError( err, false );
217 return false;
218 }
219
220 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, QgsWkbTypes::LineString, mCaptureFirstPoint );
221 mTempRubberBand->addPoint( mCaptureLastPoint );
222
223 // if there is offset, we need to fix the rubber bands to make sure they are aligned correctly.
224 // There are two cases we need to sort out:
225 // 1. the last point of mRubberBand may need to be moved off the traced curve to respect the offset
226 // 2. first point of mTempRubberBand may be needed to be moved to the beginning of the offset trace
227 const QgsPoint lastPoint = mCaptureLastPoint;
228 QgsPointXY lastPointXY( lastPoint );
229 if ( lastPointXY == pt0 && points[0] != lastPointXY )
230 {
231 if ( mRubberBand->numberOfVertices() != 0 )
232 {
233 // if rubber band had just one point, for some strange reason it contains the point twice
234 // we only want to move the last point if there are multiple points already
235 if ( mRubberBand->numberOfVertices() > 2 || ( mRubberBand->numberOfVertices() == 2 && *mRubberBand->getPoint( 0, 0 ) != *mRubberBand->getPoint( 0, 1 ) ) )
236 mRubberBand->movePoint( points[0] );
237 }
238
239 mTempRubberBand->movePoint( 0, QgsPoint( points[0] ) );
240 }
241
242 mTempRubberBand->movePoint( QgsPoint( points[0] ) );
243
244 // update temporary rubberband
245 for ( int i = 1; i < points.count(); ++i ) //points added in the rubber band are 2D but will not be added to the capture curve
246 mTempRubberBand->addPoint( QgsPoint( points.at( i ) ), i == points.count() - 1 );
247
248
249 mTempRubberBand->addPoint( QgsPoint( points[points.size() - 1] ) );
250
251 tracer->reportError( QgsTracer::ErrNone, false ); // clear messagebar if there was any error
252 return true;
253}
254
255
256bool QgsMapToolCapture::tracingAddVertex( const QgsPointXY &point )
257{
259 if ( !tracer )
260 return false; // this should not happen!
261
262 if ( mTempRubberBand->pointsCount() == 0 )
263 {
264 if ( !tracer->init() )
265 {
267 return false;
268 }
269
270 // only accept first point if it is snapped to the graph (to vertex or edge)
271 const bool res = tracer->isPointSnapped( point );
272 if ( res )
273 {
274 mTracingStartPoint = point;
275 }
276 return false;
277 }
278
279 QgsPointXY pt0 = tracingStartPoint();
280 if ( pt0 == QgsPointXY() )
281 return false;
282
284 QVector<QgsPointXY> points = tracer->findShortestPath( pt0, point, &err );
285 if ( points.isEmpty() )
286 return false; // ignore the vertex - can't find path to the end point!
287
288 // transform points
289 QgsPointSequence layerPoints;
290 QgsPoint lp; // in layer coords
291 for ( int i = 0; i < points.count(); ++i )
292 {
293 if ( nextPoint( QgsPoint( points[i] ), lp ) != 0 )
294 return false;
295 layerPoints << lp;
296 }
297
298 // Move the last point of the captured curve to the first point on the trace string (necessary if there is offset)
299 const QgsVertexId lastVertexId( 0, 0, mCaptureCurve.numPoints() - 1 );
300 mCaptureCurve.moveVertex( lastVertexId, layerPoints.first() );
301 mSnappingMatches.removeLast();
302 mSnappingMatches.append( QgsPointLocator::Match() );
303
304 int pointBefore = mCaptureCurve.numPoints();
305 addCurve( new QgsLineString( layerPoints ) );
306
307 resetRubberBand();
308
309 // Curves de-approximation
311 {
312 // If the tool and the layer support curves
313 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
314 if ( vlayer && capabilities().testFlag( QgsMapToolCapture::Capability::SupportsCurves ) && vlayer->dataProvider()->capabilities().testFlag( QgsVectorDataProvider::Capability::CircularGeometries ) )
315 {
316 const QgsGeometry linear = QgsGeometry( mCaptureCurve.segmentize() );
317 const QgsGeometry curved = linear.convertToCurves(
320 );
322 {
323 mCaptureCurve.clear();
324 mCaptureCurve.addCurve( qgsgeometry_cast< const QgsCurve * >( curved.constGet() )->clone() );
325 }
326 else
327 {
328 mCaptureCurve = *qgsgeometry_cast<QgsCompoundCurve *>( curved.constGet() );
329 }
330 }
331 }
332
333 // sync the snapping matches list
334 const int pointAfter = mCaptureCurve.numPoints();
335 for ( ; pointBefore < pointAfter; ++pointBefore )
336 mSnappingMatches.append( QgsPointLocator::Match() );
337
338 tracer->reportError( QgsTracer::ErrNone, true ); // clear messagebar if there was any error
339
340 // adjust last captured point
341 const QgsPoint lastPt = mCaptureCurve.endPoint();
342 mCaptureLastPoint = toMapCoordinates( layer(), lastPt );
343
344 return true;
345}
346
347QgsMapToolCaptureRubberBand *QgsMapToolCapture::createCurveRubberBand() const
348{
349 QgsMapToolCaptureRubberBand *rb = new QgsMapToolCaptureRubberBand( mCanvas );
350 rb->setStrokeWidth( digitizingStrokeWidth() );
351 QColor color = digitizingStrokeColor();
352
354 color.setAlphaF( color.alphaF() * alphaScale );
355 rb->setLineStyle( Qt::DotLine );
356 rb->setStrokeColor( color );
357
358 const QColor fillColor = digitizingFillColor();
359 rb->setFillColor( fillColor );
360 rb->show();
361 return rb;
362}
363
364void QgsMapToolCapture::resetRubberBand()
365{
366 if ( !mRubberBand )
367 return;
368 QgsLineString *lineString = mCaptureCurve.curveToLine();
369
371 mRubberBand->addGeometry( QgsGeometry( lineString ), layer() );
372}
373
375{
376 return mRubberBand.release();
377}
378
380{
381 if ( enable )
383 else
385}
386
388{
389 if ( enable )
391 else
393}
394
396{
397 if ( mCurrentCaptureTechnique == technique )
398 return;
399
400 mStartNewCurve = true;
401
402 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
403 {
404 mCurrentShapeMapTool->deactivate();
405 clean();
406 }
407
408 switch ( technique )
409 {
411 mLineDigitizingType = QgsWkbTypes::LineString;
412 break;
414 mLineDigitizingType = QgsWkbTypes::CircularString;
415 break;
417 mLineDigitizingType = QgsWkbTypes::LineString;
419 break;
421 mLineDigitizingType = QgsWkbTypes::LineString;
422 break;
423
424 }
425
426 if ( mTempRubberBand )
427 mTempRubberBand->setStringType( mLineDigitizingType );
428
429 mCurrentCaptureTechnique = technique;
430
431 if ( technique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool && isActive() )
432 {
433 clean();
434 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
435 }
436}
437
439{
440 if ( mCurrentShapeMapTool )
441 {
442 if ( shapeMapToolMetadata && mCurrentShapeMapTool->id() == shapeMapToolMetadata->id() )
443 return;
444 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
445 mCurrentShapeMapTool->deactivate();
446 mCurrentShapeMapTool->deleteLater();
447 }
448
449 mCurrentShapeMapTool.reset( shapeMapToolMetadata ? shapeMapToolMetadata->factory( this ) : nullptr );
450
451 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && isActive() )
452 {
453 clean();
454 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
455 }
456}
457
459{
461
462 const QgsPointXY point = e->mapPoint();
463
464 mSnapIndicator->setMatch( e->mapPointMatch() );
465
466 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
467 {
468 if ( !mCurrentShapeMapTool )
469 {
470 emit messageEmitted( tr( "Cannot capture a shape without a shape tool defined" ), Qgis::MessageLevel::Warning );
471 }
472 else
473 {
474 if ( !mTempRubberBand )
475 {
476 mTempRubberBand.reset( createCurveRubberBand() );
477 mTempRubberBand->setStringType( mLineDigitizingType );
478 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
479 }
480
481 mCurrentShapeMapTool->cadCanvasMoveEvent( e, mCaptureMode );
482 return;
483 }
484 }
485 else
486 {
487 const QgsPoint mapPoint = QgsPoint( point );
488
489 if ( mCaptureMode != CapturePoint && mTempRubberBand && mCapturing )
490 {
491 bool hasTrace = false;
492
493 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Streaming )
494 {
495 if ( !mCaptureCurve.isEmpty() )
496 {
497 const QgsPoint prevPoint = mCaptureCurve.curveAt( mCaptureCurve.nCurves() - 1 )->endPoint();
498 if ( QgsPointXY( toCanvasCoordinates( toMapCoordinates( layer(), prevPoint ) ) ).distance( toCanvasCoordinates( point ) ) < mStreamingToleranceInPixels )
499 return;
500 }
501
502 mAllowAddingStreamingPoints = true;
504 mAllowAddingStreamingPoints = false;
505 }
506 else if ( tracingEnabled() && mCaptureCurve.numPoints() != 0 )
507 {
508 // Store the intermediate point for circular string to retrieve after tracing mouse move if
509 // the digitizing type is circular and the temp rubber band is effectivly circular and if this point is existing
510 // Store an empty point if the digitizing type is linear ot the point is not existing (curve not complete)
511 if ( mLineDigitizingType == QgsWkbTypes::CircularString &&
512 mTempRubberBand->stringType() == QgsWkbTypes::CircularString &&
513 mTempRubberBand->curveIsComplete() )
514 mCircularItermediatePoint = mTempRubberBand->pointFromEnd( 1 );
515 else if ( mLineDigitizingType == QgsWkbTypes::LineString ||
516 !mTempRubberBand->curveIsComplete() )
517 mCircularItermediatePoint = QgsPoint();
518
519 hasTrace = tracingMouseMove( e );
520
521 if ( !hasTrace )
522 {
523 // Restore the temp rubber band
524 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mLineDigitizingType, mCaptureFirstPoint );
525 mTempRubberBand->addPoint( mCaptureLastPoint );
526 if ( !mCircularItermediatePoint.isEmpty() )
527 {
528 mTempRubberBand->movePoint( mCircularItermediatePoint );
529 mTempRubberBand->addPoint( mCircularItermediatePoint );
530 }
531 }
532 }
533
534 if ( mCurrentCaptureTechnique != Qgis::CaptureTechnique::Streaming && !hasTrace )
535 {
536 if ( mCaptureCurve.numPoints() > 0 )
537 {
538 const QgsPoint mapPt = mCaptureLastPoint;
539
540 if ( mTempRubberBand )
541 {
542 mTempRubberBand->movePoint( mapPoint );
543 mTempRubberBand->movePoint( 0, mapPt );
544 }
545
546 // fix existing rubber band after tracing - the last point may have been moved if using offset
547 if ( mRubberBand->numberOfVertices() )
548 mRubberBand->movePoint( mapPt );
549 }
550 else if ( mTempRubberBand )
551 mTempRubberBand->movePoint( mapPoint );
552 }
553 }
554 }
555} // mouseMoveEvent
556
557
558int QgsMapToolCapture::nextPoint( const QgsPoint &mapPoint, QgsPoint &layerPoint )
559{
560 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
561 {
562 try
563 {
564 const QgsPointXY mapP( mapPoint.x(), mapPoint.y() ); //#spellok
565 layerPoint = QgsPoint( toLayerCoordinates( vlayer, mapP ) ); //transform snapped point back to layer crs //#spellok
566 if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !layerPoint.is3D() )
568 if ( QgsWkbTypes::hasM( vlayer->wkbType() ) && !layerPoint.isMeasure() )
570 }
571 catch ( QgsCsException & )
572 {
573 QgsDebugMsg( QStringLiteral( "transformation to layer coordinate failed" ) );
574 return 2;
575 }
576 }
577 else
578 {
579 layerPoint = QgsPoint( toLayerCoordinates( layer(), mapPoint ) );
580 }
581
582 return 0;
583}
584
585int QgsMapToolCapture::nextPoint( QPoint p, QgsPoint &layerPoint, QgsPoint &mapPoint )
586{
588 return nextPoint( mapPoint, layerPoint );
589}
590
592{
593 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
594 QgsVectorLayer *sourceLayer = match.layer();
596 {
598 return 0;
599 }
600 else if ( !vlayer )
601 {
602 return 1;
603 }
604 else
605 {
606 if ( match.isValid() && ( match.hasVertex() || match.hasLineEndpoint() || ( QgsProject::instance()->topologicalEditing() && ( match.hasEdge() || match.hasMiddleSegment() ) ) ) && sourceLayer &&
607 ( sourceLayer->crs() == vlayer->crs() ) )
608 {
609 QgsFeature f;
610 QgsFeatureRequest request;
611 request.setFilterFid( match.featureId() );
612 const bool fetched = match.layer()->getFeatures( request ).nextFeature( f );
613 if ( fetched )
614 {
615 QgsVertexId vId;
616 if ( !f.geometry().vertexIdFromVertexNr( match.vertexIndex(), vId ) )
617 return 2;
618
619 const QgsGeometry geom( f.geometry() );
620 if ( QgsProject::instance()->topologicalEditing() && ( match.hasEdge() || match.hasMiddleSegment() ) )
621 {
622 QgsVertexId vId2;
623 if ( !f.geometry().vertexIdFromVertexNr( match.vertexIndex() + 1, vId2 ) )
624 return 2;
625 const QgsLineString line( geom.constGet()->vertexAt( vId ), geom.constGet()->vertexAt( vId2 ) );
626
627 layerPoint = QgsGeometryUtils::closestPoint( line, QgsPoint( match.point() ) );
628 }
629 else
630 {
631 layerPoint = geom.constGet()->vertexAt( vId );
632 if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !layerPoint.is3D() )
633 layerPoint.addZValue( defaultZValue() );
634 if ( QgsWkbTypes::hasM( vlayer->wkbType() ) && !layerPoint.isMeasure() )
635 layerPoint.addMValue( defaultMValue() );
636 }
637
638 // ZM support depends on the target layer
639 if ( !QgsWkbTypes::hasZ( vlayer->wkbType() ) )
640 {
641 layerPoint.dropZValue();
642 }
643
644 if ( !QgsWkbTypes::hasM( vlayer->wkbType() ) )
645 {
646 layerPoint.dropMValue();
647 }
648
649 return 0;
650 }
651 else
652 {
653 return 2;
654 }
655 }
656 else
657 {
658 return 1;
659 }
660 }
661}
662
664{
665 return addVertex( point, QgsPointLocator::Match() );
666}
667
669{
670 if ( mode() == CaptureNone )
671 {
672 QgsDebugMsg( QStringLiteral( "invalid capture mode" ) );
673 return 2;
674 }
675
676 if ( mCapturing && mCurrentCaptureTechnique == Qgis::CaptureTechnique::Streaming && !mAllowAddingStreamingPoints )
677 return 0;
678
679 QgsPoint layerPoint;
680 if ( layer() )
681 {
682 int res = fetchLayerPoint( match, layerPoint );
683 if ( res != 0 )
684 {
685 res = nextPoint( QgsPoint( point ), layerPoint );
686 if ( res != 0 )
687 {
688 return res;
689 }
690 }
691 }
692 else
693 {
694 layerPoint = QgsPoint( point );
695 }
696 const QgsPoint mapPoint = toMapCoordinates( layer(), layerPoint );
697
698 if ( mCaptureMode == CapturePoint )
699 {
700 mCaptureCurve.addVertex( layerPoint );
701 mSnappingMatches.append( match );
702 }
703 else
704 {
705 if ( mCaptureFirstPoint.isEmpty() )
706 {
707 mCaptureFirstPoint = mapPoint;
708 }
709
710 if ( !mRubberBand )
712
713 if ( !mTempRubberBand )
714 {
715 mTempRubberBand.reset( createCurveRubberBand() );
716 mTempRubberBand->setStringType( mLineDigitizingType );
717 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mLineDigitizingType, mapPoint );
718 }
719
720 bool traceCreated = false;
721 if ( tracingEnabled() )
722 {
723 traceCreated = tracingAddVertex( mapPoint );
724 }
725
726 // keep new tracing start point if we created a trace. This is useful when tracing with
727 // offset so that the user stays "snapped"
728 mTracingStartPoint = traceCreated ? point : QgsPointXY();
729
730 if ( !traceCreated )
731 {
732 // ordinary digitizing
733 mTempRubberBand->movePoint( mapPoint ); //move the last point of the temp rubberband before operating with it
734 if ( mTempRubberBand->curveIsComplete() ) //2 points for line and 3 points for circular
735 {
736 if ( QgsCurve *curve = mTempRubberBand->curve() )
737 {
738 addCurve( curve );
739 // add curve append only invalid match to mSnappingMatches,
740 // so we need to remove them and add the one from here if it is valid
741 if ( match.isValid() && mSnappingMatches.count() > 0 && !mSnappingMatches.last().isValid() )
742 {
743 mSnappingMatches.removeLast();
744 if ( mTempRubberBand->stringType() == QgsWkbTypes::CircularString )
745 {
746 // for circular string two points are added and match for intermediate point is stored
747 mSnappingMatches.removeLast();
748 mSnappingMatches.append( mCircularIntermediateMatch );
749 }
750 mSnappingMatches.append( match );
751 }
752 }
753 mCaptureLastPoint = mapPoint;
754 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mLineDigitizingType, mCaptureFirstPoint );
755 }
756 else if ( mTempRubberBand->pointsCount() == 0 )
757 {
758 mCaptureLastPoint = mapPoint;
759 mCaptureCurve.addVertex( layerPoint );
760 mSnappingMatches.append( match );
761 }
762 else
763 {
764 if ( mTempRubberBand->stringType() == QgsWkbTypes::CircularString )
765 {
766 mCircularIntermediateMatch = match;
767 }
768 }
769
770 mTempRubberBand->addPoint( mapPoint );
771 }
772 else
773 {
774 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mLineDigitizingType, mCaptureFirstPoint );
775 mTempRubberBand->addPoint( mCaptureLastPoint );
776 }
777 }
778
779 updateExtraSnapLayer();
780 validateGeometry();
781
782 return 0;
783}
784
786{
787 if ( !c )
788 {
789 return 1;
790 }
791
792 if ( !mRubberBand )
793 {
795 }
796
797 if ( mTempRubberBand )
798 {
799 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mLineDigitizingType, mCaptureFirstPoint );
800 const QgsPoint endPt = c->endPoint();
801 mTempRubberBand->addPoint( endPt ); //add last point of c
802 }
803
804 //transform back to layer CRS in case map CRS and layer CRS are different
805 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( layer() );
806 if ( ct.isValid() )
807 {
808 c->transform( ct, Qgis::TransformDirection::Reverse );
809 }
810 const int countBefore = mCaptureCurve.vertexCount();
811 //if there is only one point, this the first digitized point that are in the this first curve added --> remove the point
812 if ( mCaptureCurve.numPoints() == 1 )
813 mCaptureCurve.removeCurve( 0 );
814
815 // we set the extendPrevious option to true to avoid creating compound curves with many 2 vertex linestrings -- instead we prefer
816 // to extend linestring curves so that they continue the previous linestring wherever possible...
817 mCaptureCurve.addCurve( c, !mStartNewCurve );
818 mStartNewCurve = false;
819
820 const int countAfter = mCaptureCurve.vertexCount();
821 const int addedPoint = countAfter - countBefore;
822
823 updateExtraSnapLayer();
824
825 for ( int i = 0; i < addedPoint; ++i )
826 mSnappingMatches.append( QgsPointLocator::Match() );
827
828 resetRubberBand();
829
830 return 0;
831}
832
834{
835 mCaptureCurve.clear();
836 updateExtraSnapLayer();
837}
838
839QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
840{
841 return mSnappingMatches;
842}
843
844void QgsMapToolCapture::undo( bool isAutoRepeat )
845{
846 mTracingStartPoint = QgsPointXY();
847
848 if ( mTempRubberBand )
849 {
850 if ( size() <= 1 && mTempRubberBand->pointsCount() != 0 )
851 return;
852
853 if ( isAutoRepeat && mIgnoreSubsequentAutoRepeatUndo )
854 return;
855 mIgnoreSubsequentAutoRepeatUndo = false;
856
857 const QgsPoint lastPoint = mTempRubberBand->lastPoint();
858
859 if ( mTempRubberBand->stringType() == QgsWkbTypes::CircularString && mTempRubberBand->pointsCount() > 2 )
860 {
861 mTempRubberBand->removeLastPoint();
862 mTempRubberBand->movePoint( lastPoint );
863 return;
864 }
865
866 QgsVertexId vertexToRemove;
867 vertexToRemove.part = 0;
868 vertexToRemove.ring = 0;
869 vertexToRemove.vertex = size() - 1;
870 if ( mCaptureCurve.numPoints() == 2 && mCaptureCurve.nCurves() == 1 )
871 {
872 // store the first vertex to restore if after deleting the curve
873 // because when only two vertices, removing a point remove all the curve
874 const QgsPoint fp = mCaptureCurve.startPoint();
875 mCaptureCurve.deleteVertex( vertexToRemove );
876 mCaptureCurve.addVertex( fp );
877 }
878 else
879 {
880 const int curvesBefore = mCaptureCurve.nCurves();
881 const bool lastCurveIsLineString = qgsgeometry_cast< QgsLineString * >( mCaptureCurve.curveAt( curvesBefore - 1 ) );
882
883 const int pointsCountBefore = mCaptureCurve.numPoints();
884 mCaptureCurve.deleteVertex( vertexToRemove );
885 int pointsCountAfter = mCaptureCurve.numPoints();
886 for ( ; pointsCountAfter < pointsCountBefore; pointsCountAfter++ )
887 if ( !mSnappingMatches.empty() )
888 mSnappingMatches.removeLast();
889
890 // if we have removed the last point in a linestring curve, then we "stick" here and ignore subsequent
891 // autorepeat undo actions until the user releases the undo key and holds it down again. This allows
892 // users to selectively remove portions of the geometry captured with the streaming mode by holding down
893 // the undo key, without risking accidental undo of non-streamed portions.
894 if ( mCaptureCurve.nCurves() < curvesBefore && lastCurveIsLineString )
895 mIgnoreSubsequentAutoRepeatUndo = true;
896 }
897
898 updateExtraSnapLayer();
899
900 resetRubberBand();
901
902 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry, mLineDigitizingType, mCaptureFirstPoint );
903
904 if ( mCaptureCurve.numPoints() > 0 )
905 {
906 const QgsPoint lastPt = mCaptureCurve.endPoint();
907 mCaptureLastPoint = toMapCoordinates( layer(), lastPt );
908 mTempRubberBand->addPoint( mCaptureLastPoint );
909 mTempRubberBand->movePoint( lastPoint );
910 }
911
913 validateGeometry();
914 }
915}
916
918{
919 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
920 {
921 mCurrentShapeMapTool->keyPressEvent( e );
922 if ( e->isAccepted() )
923 return;
924 }
925
926 // this is backwards, but we can't change now without breaking api because
927 // forever QgsMapTools have had to explicitly mark events as ignored in order to
928 // indicate that they've consumed the event and that the default behavior should not
929 // be applied..!
930 // see QgsMapCanvas::keyPressEvent
931 e->accept();
932
933 if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete )
934 {
935 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
936 {
937 if ( !e->isAutoRepeat() )
938 {
939 mCurrentShapeMapTool->undo();
940 }
941 }
942 else
943 {
944 undo( e->isAutoRepeat() );
945 }
946
947 // Override default shortcut management in MapCanvas
948 e->ignore();
949 }
950 else if ( e->key() == Qt::Key_Escape )
951 {
952 if ( mCurrentShapeMapTool )
953 mCurrentShapeMapTool->clean();
954
956
957 // Override default shortcut management in MapCanvas
958 e->ignore();
959 }
960}
961
963{
964 mCapturing = true;
965}
966
968{
969 return mCapturing;
970}
971
973{
974 mRubberBand.reset();
975
977
978 qDeleteAll( mGeomErrorMarkers );
979 mGeomErrorMarkers.clear();
980 mGeomErrors.clear();
981
982 mCaptureFirstPoint = QgsPoint();
983 mCaptureLastPoint = QgsPoint();
984
985 mTracingStartPoint = QgsPointXY();
986
987 mCapturing = false;
988 mCaptureCurve.clear();
989 updateExtraSnapLayer();
990 mSnappingMatches.clear();
991 if ( auto *lCurrentVectorLayer = currentVectorLayer() )
992 lCurrentVectorLayer->triggerRepaint();
993}
994
996{
997 mTempRubberBand.reset();
998}
999
1001{
1002 stopCapturing();
1003 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
1004 mCurrentShapeMapTool->clean();
1005
1006 clearCurve();
1007}
1008
1010{
1011 mCaptureCurve.close();
1012 updateExtraSnapLayer();
1013}
1014
1015void QgsMapToolCapture::validateGeometry()
1016{
1019 )
1020 return;
1021
1022 if ( mValidator )
1023 {
1024 mValidator->deleteLater();
1025 mValidator = nullptr;
1026 }
1027
1028 mGeomErrors.clear();
1029 while ( !mGeomErrorMarkers.isEmpty() )
1030 {
1031 delete mGeomErrorMarkers.takeFirst();
1032 }
1033
1034 QgsGeometry geom;
1035
1036 switch ( mCaptureMode )
1037 {
1038 case CaptureNone:
1039 case CapturePoint:
1040 return;
1041 case CaptureLine:
1042 if ( size() < 2 )
1043 return;
1044 geom = QgsGeometry( mCaptureCurve.curveToLine() );
1045 break;
1046 case CapturePolygon:
1047 if ( size() < 3 )
1048 return;
1049 QgsLineString *exteriorRing = mCaptureCurve.curveToLine();
1050 exteriorRing->close();
1051 QgsPolygon *polygon = new QgsPolygon();
1052 polygon->setExteriorRing( exteriorRing );
1053 geom = QgsGeometry( polygon );
1054 break;
1055 }
1056
1057 if ( geom.isNull() )
1058 return;
1059
1060 Qgis::GeometryValidationEngine method = Qgis::GeometryValidationEngine::QgisInternal;
1062 method = Qgis::GeometryValidationEngine::Geos;
1063 mValidator = new QgsGeometryValidator( geom, nullptr, method );
1064 connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError );
1065 mValidator->start();
1066 QgsDebugMsgLevel( QStringLiteral( "Validation started" ), 4 );
1067}
1068
1069void QgsMapToolCapture::addError( const QgsGeometry::Error &e )
1070{
1071 mGeomErrors << e;
1072 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1073 if ( !vlayer )
1074 return;
1075
1076 if ( e.hasWhere() )
1077 {
1079 vm->setCenter( mCanvas->mapSettings().layerToMapCoordinates( vlayer, e.where() ) );
1081 vm->setPenWidth( 2 );
1082 vm->setToolTip( e.what() );
1083 vm->setColor( Qt::green );
1084 vm->setZValue( vm->zValue() + 1 );
1085 mGeomErrorMarkers << vm;
1086 }
1087}
1088
1090{
1091 return mCaptureCurve.numPoints();
1092}
1093
1094QVector<QgsPointXY> QgsMapToolCapture::points() const
1095{
1096 QVector<QgsPointXY> pointsXY;
1098
1099 return pointsXY;
1100}
1101
1103{
1104 QgsPointSequence pts;
1105 mCaptureCurve.points( pts );
1106 return pts;
1107}
1108
1109void QgsMapToolCapture::setPoints( const QVector<QgsPointXY> &pointList )
1110{
1111 QgsLineString *line = new QgsLineString( pointList );
1112 mCaptureCurve.clear();
1113 mCaptureCurve.addCurve( line );
1114 updateExtraSnapLayer();
1115 mSnappingMatches.clear();
1116 for ( int i = 0; i < line->length(); ++i )
1117 mSnappingMatches.append( QgsPointLocator::Match() );
1118 resetRubberBand();
1119}
1120
1122{
1123 QgsLineString *line = new QgsLineString( pointList );
1124 mCaptureCurve.clear();
1125 mCaptureCurve.addCurve( line );
1126 updateExtraSnapLayer();
1127 mSnappingMatches.clear();
1128 for ( int i = 0; i < line->length(); ++i )
1129 mSnappingMatches.append( QgsPointLocator::Match() );
1130 resetRubberBand();
1131}
1132
1134{
1135 QgsPoint newPoint( QgsWkbTypes::Point, point.x(), point.y() );
1136
1137 // get current layer
1138 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1139 if ( !vlayer )
1140 {
1141 return newPoint;
1142 }
1143
1144 // convert to the corresponding type for a full ZM support
1145 const QgsWkbTypes::Type type = vlayer->wkbType();
1146 if ( QgsWkbTypes::hasZ( type ) && !QgsWkbTypes::hasM( type ) )
1147 {
1148 newPoint.convertTo( QgsWkbTypes::PointZ );
1149 }
1150 else if ( !QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1151 {
1152 newPoint.convertTo( QgsWkbTypes::PointM );
1153 }
1154 else if ( QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1155 {
1156 newPoint.convertTo( QgsWkbTypes::PointZM );
1157 }
1158
1159 // set z value if necessary
1160 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) )
1161 {
1163 }
1164 // set m value if necessary
1165 if ( QgsWkbTypes::hasM( newPoint.wkbType() ) )
1166 {
1168 }
1169 return newPoint;
1170}
1171
1173{
1174 QgsPoint newPoint = mapPoint( e.mapPoint() );
1175
1176 // set z or m value from snapped point if necessary
1177 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) || QgsWkbTypes::hasM( newPoint.wkbType() ) )
1178 {
1179 // if snapped, z and m dimension are taken from the corresponding snapped
1180 // point.
1181 if ( e.isSnapped() )
1182 {
1183 const QgsPointLocator::Match match = e.mapPointMatch();
1184
1185 if ( match.layer() )
1186 {
1187 const QgsFeature ft = match.layer()->getFeature( match.featureId() );
1188 if ( QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
1189 {
1190 newPoint.setZ( ft.geometry().vertexAt( match.vertexIndex() ).z() );
1191 }
1192 if ( QgsWkbTypes::hasM( match.layer()->wkbType() ) )
1193 {
1194 newPoint.setM( ft.geometry().vertexAt( match.vertexIndex() ).m() );
1195 }
1196 }
1197 }
1198 }
1199
1200 return newPoint;
1201}
1202
1203void QgsMapToolCapture::updateExtraSnapLayer()
1204{
1205 if ( !mExtraSnapLayer )
1206 return;
1207
1208 if ( canvas()->snappingUtils()->config().selfSnapping() && layer() && mCaptureCurve.numPoints() >= 2 )
1209 {
1210 // the current layer may have changed
1211 mExtraSnapLayer->setCrs( layer()->crs() );
1212 QgsGeometry geom = QgsGeometry( mCaptureCurve.clone() );
1213 // we close the curve to allow snapping on last segment
1214 if ( mCaptureMode == CapturePolygon && mCaptureCurve.numPoints() >= 3 )
1215 {
1216 qgsgeometry_cast<QgsCompoundCurve *>( geom.get() )->close();
1217 }
1218 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1219 }
1220 else
1221 {
1222 QgsGeometry geom;
1223 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1224 }
1225}
1226
1227
1229{
1230 // POINT CAPTURING
1231 if ( mode() == CapturePoint )
1232 {
1233 if ( e->button() != Qt::LeftButton )
1234 return;
1235
1236 QgsPoint savePoint; //point in layer coordinates
1237 bool isMatchPointZ = false;
1238 bool isMatchPointM = false;
1239 try
1240 {
1241 QgsPoint fetchPoint;
1242 int res = fetchLayerPoint( e->mapPointMatch(), fetchPoint );
1243 isMatchPointZ = QgsWkbTypes::hasZ( fetchPoint.wkbType() );
1244 isMatchPointM = QgsWkbTypes::hasM( fetchPoint.wkbType() );
1245
1246 if ( res == 0 )
1247 {
1248 QgsWkbTypes::Type geomType = QgsWkbTypes::Type::Point;
1249 if ( isMatchPointM && isMatchPointZ )
1250 {
1251 geomType = QgsWkbTypes::Type::PointZM;
1252 }
1253 else if ( isMatchPointM )
1254 {
1255 geomType = QgsWkbTypes::Type::PointM;
1256 }
1257 else if ( isMatchPointZ )
1258 {
1259 geomType = QgsWkbTypes::Type::PointZ;
1260 }
1261 savePoint = QgsPoint( geomType, fetchPoint.x(), fetchPoint.y(), fetchPoint.z(), fetchPoint.m() );
1262 }
1263 else
1264 {
1265 QgsPointXY point = mCanvas->mapSettings().mapToLayerCoordinates( layer(), e->mapPoint() );
1266
1267 savePoint = QgsPoint( point.x(), point.y(), fetchPoint.z(), fetchPoint.m() );
1268 }
1269 }
1270 catch ( QgsCsException &cse )
1271 {
1272 Q_UNUSED( cse )
1273 emit messageEmitted( tr( "Cannot transform the point to the layer's coordinate system" ), Qgis::MessageLevel::Warning );
1274 return;
1275 }
1276
1277 QgsGeometry g( std::make_unique<QgsPoint>( savePoint ) );
1278
1279 // The snapping result needs to be added so it's available in the @snapping_results variable of default value etc. expression contexts
1280 addVertex( e->mapPoint(), e->mapPointMatch() );
1281
1282 geometryCaptured( g );
1283 pointCaptured( savePoint );
1284
1285 stopCapturing();
1286
1287 // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points
1289 }
1290
1291 // LINE AND POLYGON CAPTURING
1292 else if ( mode() == CaptureLine || mode() == CapturePolygon )
1293 {
1294 bool digitizingFinished = false;
1295
1296 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
1297 {
1298 if ( !mCurrentShapeMapTool )
1299 {
1300 emit messageEmitted( tr( "Cannot capture a shape without a shape tool defined" ), Qgis::MessageLevel::Warning );
1301 return;
1302 }
1303 else
1304 {
1305 if ( !mTempRubberBand )
1306 {
1307 mTempRubberBand.reset( createCurveRubberBand() );
1308 mTempRubberBand->setStringType( mLineDigitizingType );
1309 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
1310 }
1311
1312 digitizingFinished = mCurrentShapeMapTool->cadCanvasReleaseEvent( e, mCaptureMode );
1313 if ( digitizingFinished )
1314 mCurrentShapeMapTool->clean();
1315 }
1316 }
1317 else // i.e. not shape
1318 {
1319 //add point to list and to rubber band
1320 if ( e->button() == Qt::LeftButton )
1321 {
1322 const int error = addVertex( e->mapPoint(), e->mapPointMatch() );
1323 if ( error == 2 )
1324 {
1325 //problem with coordinate transformation
1326 emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::MessageLevel::Warning );
1327 return;
1328 }
1329
1331 }
1332 else if ( e->button() == Qt::RightButton )
1333 {
1334 // End of string
1336
1337 //lines: bail out if there are not at least two vertices
1338 if ( mode() == CaptureLine && size() < 2 )
1339 {
1340 stopCapturing();
1341 return;
1342 }
1343
1344 //polygons: bail out if there are not at least two vertices
1345 if ( mode() == CapturePolygon && size() < 3 )
1346 {
1347 stopCapturing();
1348 return;
1349 }
1350
1351 if ( mode() == CapturePolygon || e->modifiers() == Qt::ShiftModifier )
1352 {
1353 closePolygon();
1354 }
1355
1356 digitizingFinished = true;
1357 }
1358 }
1359
1360 if ( digitizingFinished )
1361 {
1362 QgsGeometry g;
1363 QgsCurve *curveToAdd = captureCurve()->clone();
1364
1365 if ( mode() == CaptureLine )
1366 {
1367 g = QgsGeometry( curveToAdd );
1368 geometryCaptured( g );
1369 lineCaptured( curveToAdd );
1370 }
1371 else
1372 {
1373 QgsCurvePolygon *poly = new QgsCurvePolygon();
1374 poly->setExteriorRing( curveToAdd );
1375 g = QgsGeometry( poly );
1376 geometryCaptured( g );
1377 polygonCaptured( poly );
1378 }
1379
1380 stopCapturing();
1381 }
1382 }
1383}
void setParentOwner(QObject *parent)
Sets the parent object.
void reset(T *p=nullptr)
Will reset the managed pointer to p.
void reset(T *p=nullptr)
Will reset the managed pointer to p.
T * release()
Clears the pointer and returns it.
CaptureTechnique
Capture technique.
Definition: qgis.h:153
@ Shape
Digitize shapes.
@ StraightSegments
Default capture mode - capture occurs with straight line segments.
@ CircularString
Capture in circular strings.
@ Streaming
Streaming points digitizing mode (points are automatically added as the mouse cursor moves).
GeometryValidationEngine
Available engines for validating geometries.
Definition: qgis.h:976
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
virtual QgsPoint vertexAt(QgsVertexId id) const =0
Returns the point corresponding to a specified vertex id.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
The QgsAdvancedDigitizingDockWidget class is a dockable widget used to handle the CAD tools on top of...
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
void switchZM()
Determines if Z or M will be enabled.
double getLineM() const
Convenient method to get the M value from the line edit wiget.
void clearPoints()
Removes all points from the CAD point list.
void removePreviousPoint()
Remove previous point in the CAD point list.
double getLineZ() const
Convenient method to get the Z value from the line edit wiget.
QgsPoint currentPointV2(bool *exists=nullptr) const
The last point.
QgsPoint currentPointLayerCoordinates(QgsMapLayer *layer) const
Returns the last CAD point, in a map layer's coordinates.
static QCursor getThemeCursor(Cursor cursor)
Helper to get a theme cursor.
QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
void close()
Appends first point if not already closed.
QgsPoint endPoint() const override SIP_HOLDGIL
Returns the end point of the curve.
const QgsCurve * curveAt(int i) const SIP_HOLDGIL
Returns the curve at the specified index.
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
void removeCurve(int i)
Removes a curve from the geometry.
void addCurve(QgsCurve *c, bool extendPrevious=false)
Adds a curve to the geometry (takes ownership).
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
QgsCompoundCurve * clone() const override
Clones the geometry by performing a deep copy.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
void clear() override
Clears the geometry, ie reset it to a null geometry.
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
void addVertex(const QgsPoint &pt)
Adds a vertex to the end of the geometry.
int nCurves() const SIP_HOLDGIL
Returns the number of curves in the geometry.
Class for doing transforms between two map coordinate systems.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Curve polygon geometry type.
virtual void setExteriorRing(QgsCurve *ring)
Sets the exterior ring of the polygon.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
QgsCurve * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
Definition: qgscurve.cpp:175
int vertexCount(int part=0, int ring=0) const override
Returns the number of vertices of which this geometry is built.
Definition: qgscurve.cpp:180
virtual QgsPoint endPoint() const =0
Returns the end point of the curve.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterFid(QgsFeatureId fid)
Sets the feature ID that should be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
static QgsPoint closestPoint(const QgsAbstractGeometry &geometry, const QgsPoint &point)
Returns the nearest point on a segment of a geometry for the specified point.
void errorFound(const QgsGeometry::Error &error)
Sent when an error has been found during the validation process.
A geometry error.
Definition: qgsgeometry.h:2406
bool hasWhere() const
true if the location available from
QgsPointXY where() const
The coordinates at which the error is located and should be visualized.
QString what() const
A human readable error message containing details about the error.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
bool vertexIdFromVertexNr(int number, QgsVertexId &id) const
Calculates the vertex ID from a vertex number.
QgsPoint vertexAt(int atVertex) const
Returns coordinates of a vertex.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
QgsGeometry convertToCurves(double distanceTolerance=1e-8, double angleTolerance=1e-8) const
Attempts to convert a non-curved geometry into a curved geometry type (e.g.
static void convertPointList(const QVector< QgsPointXY > &input, QgsPointSequence &output)
Upgrades a point list from QgsPointXY to QgsPoint.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
double length() const override SIP_HOLDGIL
Returns the planar, 2-dimensional length of the geometry.
void close()
Closes the line string by appending the first point to the end of the line, if it is not already clos...
Extension of QgsTracer that provides extra functionality:
QAction * actionEnableSnapping() const
Access to action that user may use to toggle snapping on/off.
void reportError(PathError err, bool addingVertex)
Report a path finding error to the user.
QAction * actionEnableTracing() const
Access to action that user may use to toggle tracing on/off. May be nullptr if no action was associat...
static QgsMapCanvasTracer * tracerForCanvas(QgsMapCanvas *canvas)
Retrieve instance of this class associated with given canvas (if any).
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
void currentLayerChanged(QgsMapLayer *layer)
Emitted when the current layer is changed.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
void setCrs(const QgsCoordinateReferenceSystem &srs, bool emitSignal=true)
Sets layer's spatial reference system.
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
bool isSnapped() const
Returns true if there is a snapped point cached.
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
QgsPointLocator::Match mapPointMatch() const
Returns the matching data from the most recently snapped point.
The QgsMapToolAdvancedDigitizing class is a QgsMapTool which gives event directly in map coordinates ...
virtual void cadCanvasMoveEvent(QgsMapMouseEvent *e)
Override this method when subclassing this class.
QgsAdvancedDigitizingDockWidget * mCadDockWidget
void deactivate() override
Unregisters this maptool from the cad dock widget.
virtual QgsMapLayer * layer() const
Returns the layer associated with the map tool.
QgsAdvancedDigitizingDockWidget * cadDockWidget() const
void activate() override
Registers this maptool with the cad dock widget.
void deactivate() override
Unregisters this maptool from the cad dock widget.
void stopCapturing()
Stop capturing.
int size()
Number of points digitized.
CaptureMode mode() const
The capture mode.
QgsMapToolCapture(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget, CaptureMode mode)
constructor
void undo(bool isAutoRepeat=false)
Removes the last vertex from mRubberBand and mCaptureList.
QgsPoint mapPoint(const QgsMapMouseEvent &e) const
Creates a QgsPoint with ZM support if necessary (according to the WkbType of the current layer).
void keyPressEvent(QKeyEvent *e) override
Intercept key events like Esc or Del to delete the last point.
void activate() override
Registers this maptool with the cad dock widget.
CaptureMode
Different capture modes.
@ CapturePolygon
Capture polygons.
@ CaptureNone
Do not capture / determine mode from layer geometry type.
@ CapturePoint
Capture points.
@ CaptureLine
Capture lines.
Q_DECL_DEPRECATED void setCircularDigitizingEnabled(bool enable)
Enable the digitizing with curve.
void deleteTempRubberBand()
Clean a temporary rubberband.
void clean() override
convenient method to clean members
~QgsMapToolCapture() override
void closePolygon()
Close an open polygon.
int addCurve(QgsCurve *c)
Adds a whole curve (e.g. circularstring) to the captured geometry. Curve must be in map CRS.
int fetchLayerPoint(const QgsPointLocator::Match &match, QgsPoint &layerPoint)
Fetches the original point from the source layer if it has the same CRS as the current layer.
QgsPointSequence pointsZM() const
List of digitized points.
Q_DECL_DEPRECATED void setPoints(const QVector< QgsPointXY > &pointList)
Set the points on which to work.
const QgsCompoundCurve * captureCurve() const
Gets the capture curve.
QList< QgsPointLocator::Match > snappingMatches() const
Returns a list of matches for each point on the captureCurve.
Q_DECL_DEPRECATED QVector< QgsPointXY > points() const
List of digitized points.
bool isCapturing() const
Are we currently capturing?
virtual bool supportsTechnique(Qgis::CaptureTechnique technique) const
Returns true if the tool supports the specified capture technique.
void setCurrentShapeMapTool(const QgsMapToolShapeMetadata *shapeMapToolMetadata)
Sets the current shape tool.
int addVertex(const QgsPointXY &point)
Adds a point to the rubber band (in map coordinates) and to the capture list (in layer coordinates)
@ ValidateGeometries
Tool supports geometry validation (since QGIS 3.22)
void setCurrentCaptureTechnique(Qgis::CaptureTechnique technique)
Sets the current capture if it is supported by the map tool.
virtual QgsMapToolCapture::Capabilities capabilities() const
Returns flags containing the supported capabilities.
void clearCurve()
Clear capture curve.
int nextPoint(const QgsPoint &mapPoint, QgsPoint &layerPoint)
Converts a map point to layer coordinates.
Q_DECL_DEPRECATED void setStreamDigitizingEnabled(bool enable)
Toggles the stream digitizing mode.
void cadCanvasMoveEvent(QgsMapMouseEvent *e) override
Override this method when subclassing this class.
void startCapturing()
Start capturing.
QgsRubberBand * takeRubberBand()
Returns the rubberBand currently owned by this map tool and transfers ownership to the caller.
void cadCanvasReleaseEvent(QgsMapMouseEvent *e) override
Override this method when subclassing this class.
static double defaultMValue()
Returns default M value.
QgsVectorLayer * currentVectorLayer()
Returns the current vector layer of the map canvas or 0.
static QColor digitizingFillColor()
Returns fill color for rubber bands (from global settings)
static double defaultZValue()
Returns default Z value.
QgsRubberBand * createRubberBand(QgsWkbTypes::GeometryType geometryType=QgsWkbTypes::LineGeometry, bool alternativeBand=false)
Creates a rubber band with the color/line width from the QGIS settings.
static QColor digitizingStrokeColor()
Returns stroke color for rubber bands (from global settings)
static int digitizingStrokeWidth()
Returns stroke width for rubber bands (from global settings)
virtual void deactivate()
Deactivates the map tool.
virtual void cadCanvasMoveEvent(QgsMapMouseEvent *e, QgsMapToolCapture::CaptureMode mode)=0
Called for a mouse move event.
QString id() const
Returns the id of the shape tool (equivalent to the one from the metadata)
virtual void undo()
Called to undo last action (last point added)
virtual void activate(QgsMapToolCapture::CaptureMode mode, const QgsPoint &lastCapturedMapPoint)
Activates the map tool with the last captured map point.
virtual void clean()
Called to clean the map tool (after canceling the operation or when the digitization has finished)
virtual bool cadCanvasReleaseEvent(QgsMapMouseEvent *e, QgsMapToolCapture::CaptureMode mode)=0
Called for a mouse release event Must return true if the digitization has ended and the geometry is c...
virtual void keyPressEvent(QKeyEvent *e)
Filters a key press event Ignores the event in default implementation.
QgsMapToolShapeMetadata is a base class for shape map tools metadata to be used in QgsMapToolShapeReg...
virtual QgsMapToolShapeAbstract * factory(QgsMapToolCapture *parentlTool) const SIP_FACTORY=0
Creates the shape map tool for the given parentTool Caller takes ownership of the returned object.
virtual QString id() const =0
Unique ID for the shape map tool.
QgsPoint toLayerCoordinates(const QgsMapLayer *layer, const QgsPoint &point)
Transforms a point from map coordinates to layer coordinates.
Definition: qgsmaptool.cpp:62
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition: qgsmaptool.h:336
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
Definition: qgsmaptool.cpp:215
QgsPointXY toMapCoordinates(QPoint point)
Transforms a point from screen coordinates to map coordinates.
Definition: qgsmaptool.cpp:41
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
Definition: qgsmaptool.cpp:160
void messageEmitted(const QString &message, Qgis::MessageLevel=Qgis::MessageLevel::Info)
emit a message
QPoint toCanvasCoordinates(const QgsPointXY &point) const
Transforms a point from map coordinates to screen coordinates.
Definition: qgsmaptool.cpp:77
bool isActive() const
Returns if the current map tool active on the map canvas.
Definition: qgsmaptool.cpp:145
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:562
bool dropMValue() override
Drops any measure values which exist in the geometry.
Definition: qgspoint.cpp:603
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspoint.cpp:767
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:551
Q_GADGET double x
Definition: qgspoint.h:52
bool convertTo(QgsWkbTypes::Type type) override
Converts the geometry to a specified type.
Definition: qgspoint.cpp:620
void setZ(double z) SIP_HOLDGIL
Sets the point's z-coordinate.
Definition: qgspoint.h:304
double z
Definition: qgspoint.h:54
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
Definition: qgspoint.cpp:592
double m
Definition: qgspoint.h:55
double y
Definition: qgspoint.h:53
void setM(double m) SIP_HOLDGIL
Sets the point's m-value.
Definition: qgspoint.h:319
Polygon geometry type.
Definition: qgspolygon.h:34
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:219
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:478
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
bool topologicalEditing
Definition: qgsproject.h:120
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:52
void movePoint(const QgsPointXY &p, int geometryIndex=0, int ringIndex=0)
Moves the rubber band point specified by index.
const QgsPointXY * getPoint(int i, int j=0, int ringIndex=0) const
Returns a vertex.
void reset(QgsWkbTypes::GeometryType geometryType=QgsWkbTypes::LineGeometry)
Clears all the geometries in this rubberband.
int numberOfVertices() const
Returns count of vertices in all lists of mPoint.
void addGeometry(const QgsGeometry &geometry, QgsMapLayer *layer, bool doUpdate=true)
Adds the geometry of an existing feature to a rubberband This is useful for multi feature highlightin...
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
static const QgsSettingsEntryBool settingsDigitizingConvertToCurve
Settings entry digitizing convert to curve.
static const QgsSettingsEntryInteger settingsDigitizingStreamTolerance
Settings entry digitizing stream tolerance.
static const QgsSettingsEntryDouble settingsDigitizingConvertToCurveDistanceTolerance
Settings entry digitizing convert to curve distance tolerance.
static const QgsSettingsEntryInteger settingsDigitizingValidateGeometries
Settings entry digitizing validate geometries.
static const QgsSettingsEntryDouble settingsDigitizingConvertToCurveAngleTolerance
Settings entry digitizing convert to curve angle tolerance.
static const QgsSettingsEntryDouble settingsDigitizingLineColorAlphaScale
Settings entry digitizing line color alpha scale.
Class that shows snapping marker on map canvas for the current snapping match.
bool isPointSnapped(const QgsPointXY &pt)
Find out whether the point is snapped to a vertex or edge (i.e. it can be used for tracing start/stop...
Definition: qgstracer.cpp:803
QVector< QgsPointXY > findShortestPath(const QgsPointXY &p1, const QgsPointXY &p2, PathError *error=nullptr)
Given two points, find the shortest path and return points on the way.
Definition: qgstracer.cpp:736
PathError
Possible errors that may happen when calling findShortestPath()
Definition: qgstracer.h:138
@ ErrNone
No error.
Definition: qgstracer.h:139
@ ErrTooManyFeatures
Max feature count threshold was reached while reading features.
Definition: qgstracer.h:140
bool init()
Build the internal data structures.
Definition: qgstracer.cpp:675
virtual Q_INVOKABLE QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
Q_INVOKABLE bool startEditing()
Makes the layer editable.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
A class for marking vertices of features using e.g.
void setPenWidth(int width)
void setCenter(const QgsPointXY &point)
Sets the center point of the marker, in map coordinates.
void setIconType(int iconType)
void setColor(const QColor &color)
Sets the stroke color for the marker.
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1130
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1080
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define BUILTIN_UNREACHABLE
Definition: qgis.h:3046
QVector< QgsPoint > QgsPointSequence
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
QgsFeatureId featureId() const
The id of the feature to which the snapped geometry belongs.
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
bool hasEdge() const
Returns true if the Match is an edge.
bool hasLineEndpoint() const
Returns true if the Match is a line endpoint (start or end vertex).
bool hasMiddleSegment() const
Returns true if the Match is the middle of a segment.
int vertexIndex() const
for vertex / edge match (first vertex of the edge)
bool hasVertex() const
Returns true if the Match is a vertex.
Setting options for loading vector layers.
bool skipCrsValidation
Controls whether the layer is allowed to have an invalid/unknown CRS.
bool loadDefaultStyle
Set to true if the default layer style should be loaded.
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:31
int vertex
Vertex number.
Definition: qgsvertexid.h:95
int part
Part number.
Definition: qgsvertexid.h:89
int ring
Ring number.
Definition: qgsvertexid.h:92