QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
18#include <algorithm>
19#include <memory>
20
22#include "qgsapplication.h"
23#include "qgsexception.h"
24#include "qgsfeatureiterator.h"
26#include "qgslinestring.h"
27#include "qgslogger.h"
28#include "qgsmapcanvas.h"
29#include "qgsmapcanvastracer.h"
30#include "qgsmapmouseevent.h"
34#include "qgspolygon.h"
35#include "qgsproject.h"
36#include "qgsrubberband.h"
38#include "qgssnapindicator.h"
39#include "qgssnappingutils.h"
40#include "qgsvectorlayer.h"
41#include "qgsvertexmarker.h"
42
43#include <QAction>
44#include <QCursor>
45#include <QPixmap>
46#include <QStatusBar>
47
48#include "moc_qgsmaptoolcapture.cpp"
49
52 , mCaptureMode( mode )
53 , mCaptureModeFromLayer( mode == CaptureNone )
54{
55 mTempRubberBand.setParentOwner( canvas );
56
57 mSnapIndicator = std::make_unique<QgsSnapIndicator>( canvas );
58
60
61 connect( canvas, &QgsMapCanvas::currentLayerChanged, this, &QgsMapToolCapture::currentLayerChanged );
62
64 layerOptions.skipCrsValidation = true;
65 layerOptions.loadDefaultStyle = false;
66 mExtraSnapLayer = new QgsVectorLayer( QStringLiteral( "LineString?crs=" ), QStringLiteral( "extra snap" ), QStringLiteral( "memory" ), layerOptions );
67 mExtraSnapLayer->startEditing();
68 QgsFeature f;
69 mExtraSnapLayer->addFeature( f );
70 mExtraSnapFeatureId = f.id();
71
72 connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, &QgsMapToolCapture::updateExtraSnapLayer );
73
74 currentLayerChanged( canvas->currentLayer() );
75}
76
78{
79 // during tear down we have to clean up mExtraSnapLayer first, before
80 // we call stop capturing. Otherwise stopCapturing tries to access members
81 // from the mapcanvas, which is likely already being destroyed and triggering
82 // the deletion of this object...
83 if ( mCanvas )
84 {
85 mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
86 }
87 mExtraSnapLayer->deleteLater();
88 mExtraSnapLayer = nullptr;
89
91
92 if ( mValidator )
93 {
94 mValidator->deleteLater();
95 mValidator = nullptr;
96 }
97}
98
103
105{
106 switch ( technique )
107 {
109 return true;
113 return false;
114 }
116}
117
119{
120 if ( mTempRubberBand )
121 mTempRubberBand->show();
122
123 mCanvas->snappingUtils()->addExtraSnapLayer( mExtraSnapLayer );
125
126 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
127 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
128}
129
131{
132 if ( mTempRubberBand )
133 mTempRubberBand->hide();
134
135 mSnapIndicator->setMatch( QgsPointLocator::Match() );
136
137 mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
138
139 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
140 mCurrentShapeMapTool->deactivate();
141
143}
144
145void QgsMapToolCapture::currentLayerChanged( QgsMapLayer *layer )
146{
147 if ( !mCaptureModeFromLayer )
148 return;
149
150 mCaptureMode = CaptureNone;
151
152 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
153 if ( !vlayer )
154 {
155 return;
156 }
157
158 if ( vlayer->isSpatial() )
159 {
161 }
162 else
163 {
164 setCursor( QCursor( Qt::ArrowCursor ) );
165 }
166
167 switch ( vlayer->geometryType() )
168 {
170 mCaptureMode = CapturePoint;
171 break;
173 mCaptureMode = CaptureLine;
174 break;
176 mCaptureMode = CapturePolygon;
177 break;
178 default:
179 mCaptureMode = CaptureNone;
180 break;
181 }
182
183 if ( mTempRubberBand )
184 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
185
186 resetRubberBand();
188}
189
190
191bool QgsMapToolCapture::tracingEnabled()
192{
193 QgsMapCanvasTracer *tracer = QgsMapCanvasTracer::tracerForCanvas( mCanvas );
194 return tracer && ( !tracer->actionEnableTracing() || tracer->actionEnableTracing()->isChecked() )
195 && ( !tracer->actionEnableSnapping() || tracer->actionEnableSnapping()->isChecked() );
196}
197
198
199QgsPointXY QgsMapToolCapture::tracingStartPoint()
200{
201 // if we have starting point from previous trace, then preferably use that one
202 // (useful when tracing with offset)
203 if ( mTracingStartPoint != QgsPointXY() )
204 return mTracingStartPoint;
205
206 return mCaptureLastPoint;
207}
208
209
210bool QgsMapToolCapture::tracingMouseMove( QgsMapMouseEvent *e )
211{
212 if ( !e->isSnapped() )
213 return false;
214
215 QgsPointXY pt0 = tracingStartPoint();
216 if ( pt0 == QgsPointXY() )
217 return false;
218
219 QgsMapCanvasTracer *tracer = QgsMapCanvasTracer::tracerForCanvas( mCanvas );
220 if ( !tracer )
221 return false; // this should not happen!
222
224 QVector<QgsPointXY> points = tracer->findShortestPath( pt0, e->mapPoint(), &err );
225 if ( points.isEmpty() )
226 {
227 tracer->reportError( err, false );
228 return false;
229 }
230
231 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, Qgis::WkbType::LineString, mCaptureFirstPoint );
232 mTempRubberBand->addPoint( mCaptureLastPoint );
233
234 // if there is offset, we need to fix the rubber bands to make sure they are aligned correctly.
235 // There are two cases we need to sort out:
236 // 1. the last point of mRubberBand may need to be moved off the traced curve to respect the offset
237 // 2. first point of mTempRubberBand may be needed to be moved to the beginning of the offset trace
238 const QgsPoint lastPoint = mCaptureLastPoint;
239 QgsPointXY lastPointXY( lastPoint );
240 if ( lastPointXY == pt0 && points[0] != lastPointXY )
241 {
242 if ( mRubberBand->numberOfVertices() != 0 )
243 {
244 // if rubber band had just one point, for some strange reason it contains the point twice
245 // we only want to move the last point if there are multiple points already
246 if ( mRubberBand->numberOfVertices() > 2 || ( mRubberBand->numberOfVertices() == 2 && *mRubberBand->getPoint( 0, 0 ) != *mRubberBand->getPoint( 0, 1 ) ) )
247 mRubberBand->movePoint( points[0] );
248 }
249
250 mTempRubberBand->movePoint( 0, QgsPoint( points[0] ) );
251 }
252
253 mTempRubberBand->movePoint( QgsPoint( points[0] ) );
254
255 // update temporary rubberband
256 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
257 mTempRubberBand->addPoint( QgsPoint( points.at( i ) ), i == points.count() - 1 );
258
259
260 mTempRubberBand->addPoint( QgsPoint( points[points.size() - 1] ) );
261
262 tracer->reportError( QgsTracer::ErrNone, false ); // clear messagebar if there was any error
263 return true;
264}
265
266
267bool QgsMapToolCapture::tracingAddVertex( const QgsPointXY &point )
268{
269 QgsMapCanvasTracer *tracer = QgsMapCanvasTracer::tracerForCanvas( mCanvas );
270 if ( !tracer )
271 return false; // this should not happen!
272
273 if ( mTempRubberBand->pointsCount() == 0 )
274 {
275 if ( !tracer->init() )
276 {
278 return false;
279 }
280
281 // only accept first point if it is snapped to the graph (to vertex or edge)
282 const bool res = tracer->isPointSnapped( point );
283 if ( res )
284 {
285 mTracingStartPoint = point;
286 }
287 return false;
288 }
289
290 QgsPointXY pt0 = tracingStartPoint();
291 if ( pt0 == QgsPointXY() )
292 return false;
293
295 const QVector<QgsPointXY> tracedPointsInMapCrs = tracer->findShortestPath( pt0, point, &err );
296 if ( tracedPointsInMapCrs.isEmpty() )
297 return false; // ignore the vertex - can't find path to the end point!
298
299 // transform points
300 QgsPointSequence layerPoints;
301 layerPoints.reserve( tracedPointsInMapCrs.size() );
302 QgsPointSequence mapPoints;
303 mapPoints.reserve( tracedPointsInMapCrs.size() );
304 for ( const QgsPointXY &tracedPointMapCrs : tracedPointsInMapCrs )
305 {
306 QgsPoint mapPoint( tracedPointMapCrs );
307
308 QgsPoint lp; // in layer coords
309 if ( nextPoint( mapPoint, lp ) != 0 )
310 return false;
311
312 // copy z and m from layer point back to mapPoint, as nextPoint() call will populate these based
313 // on the context of the trace
314 if ( lp.is3D() )
315 mapPoint.addZValue( lp.z() );
316 if ( lp.isMeasure() )
317 mapPoint.addMValue( lp.m() );
318
319 mapPoints << mapPoint;
320 layerPoints << lp;
321 }
322
323 // Move the last point of the captured curve to the first point on the trace string (necessary if there is offset)
324 const QgsVertexId lastVertexId( 0, 0, mCaptureCurve.numPoints() - 1 );
325 mCaptureCurve.moveVertex( lastVertexId, layerPoints.first() );
326 mSnappingMatches.removeLast();
327 mSnappingMatches.append( QgsPointLocator::Match() );
328
329 addCurve( new QgsLineString( mapPoints ) );
330
331 resetRubberBand();
332
333 // Curves de-approximation
335 {
336#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
337 int pointBefore = mCaptureCurve.numPoints();
338#endif
339
340 // If the tool and the layer support curves
341 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
343 {
344 const QgsGeometry linear = QgsGeometry( mCaptureCurve.segmentize() );
345 const QgsGeometry curved = linear.convertToCurves(
348 );
350 {
351 mCaptureCurve.clear();
352 mCaptureCurve.addCurve( qgsgeometry_cast<const QgsCurve *>( curved.constGet() )->clone() );
353 }
354 else
355 {
356 mCaptureCurve = *qgsgeometry_cast<const QgsCompoundCurve *>( curved.constGet() );
357 }
358 }
359
360#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
361 // sync the snapping matches list
362 const int pointAfter = mCaptureCurve.numPoints();
363
364 for ( ; pointBefore < pointAfter; ++pointBefore )
365 mSnappingMatches.append( QgsPointLocator::Match() );
366
367 for ( ; pointBefore > pointAfter; --pointBefore )
368 mSnappingMatches.removeLast();
369#else
370 mSnappingMatches.resize( mCaptureCurve.numPoints() );
371#endif
372 }
373
374 tracer->reportError( QgsTracer::ErrNone, true ); // clear messagebar if there was any error
375
376 // adjust last captured point
377 const QgsPoint lastPt = mCaptureCurve.endPoint();
378 mCaptureLastPoint = toMapCoordinates( layer(), lastPt );
379
380 return true;
381}
382
383QgsMapToolCaptureRubberBand *QgsMapToolCapture::createCurveRubberBand() const
384{
385 QgsMapToolCaptureRubberBand *rb = new QgsMapToolCaptureRubberBand( mCanvas );
386 rb->setStrokeWidth( digitizingStrokeWidth() );
387 QColor color = digitizingStrokeColor();
388
390 color.setAlphaF( color.alphaF() * alphaScale );
391 rb->setLineStyle( Qt::DotLine );
392 rb->setStrokeColor( color );
393
394 const QColor fillColor = digitizingFillColor();
395 rb->setFillColor( fillColor );
396 rb->show();
397 return rb;
398}
399
400void QgsMapToolCapture::resetRubberBand()
401{
402 if ( !mRubberBand )
403 return;
404 QgsLineString *lineString = mCaptureCurve.curveToLine();
405
406 mRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
407 mRubberBand->addGeometry( QgsGeometry( lineString ), layer() );
408}
409
411{
412 return mRubberBand.release();
413}
414
422
430
432{
433 if ( mCurrentCaptureTechnique == technique )
434 return;
435
436 mStartNewCurve = true;
437
438 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
439 {
440 mCurrentShapeMapTool->deactivate();
441 clean();
442 }
443
444 switch ( technique )
445 {
447 mLineDigitizingType = Qgis::WkbType::LineString;
448 break;
450 mLineDigitizingType = Qgis::WkbType::CircularString;
451 break;
453 mLineDigitizingType = Qgis::WkbType::LineString;
454 mStreamingToleranceInPixels = QgsSettingsRegistryCore::settingsDigitizingStreamTolerance->value();
455 break;
457 mLineDigitizingType = Qgis::WkbType::LineString;
458 break;
459 }
460
461 if ( mTempRubberBand )
462 mTempRubberBand->setStringType( mLineDigitizingType );
463
464 mCurrentCaptureTechnique = technique;
465
466 if ( technique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool && isActive() )
467 {
468 clean();
469 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
470 }
471}
472
474{
475 if ( mCurrentShapeMapTool )
476 {
477 if ( shapeMapToolMetadata && mCurrentShapeMapTool->id() == shapeMapToolMetadata->id() )
478 return;
479 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
480 mCurrentShapeMapTool->deactivate();
481 mCurrentShapeMapTool->deleteLater();
482 }
483
484 mCurrentShapeMapTool.reset( shapeMapToolMetadata ? shapeMapToolMetadata->factory( this ) : nullptr );
485
486 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && isActive() )
487 {
488 clean();
489 if ( mCurrentShapeMapTool )
490 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
491 }
492}
493
495{
496 // If we are adding a record to a non-spatial layer, just return
497 if ( mCaptureModeFromLayer && ( !canvas()->currentLayer() || !canvas()->currentLayer()->isSpatial() ) )
498 return;
499
501
502 const QgsPointXY point = e->mapPoint();
503
504 mSnapIndicator->setMatch( e->mapPointMatch() );
505
506 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
507 {
508 if ( !mCurrentShapeMapTool )
509 {
510 emit messageEmitted( tr( "Select an option from the Shape Digitizing Toolbar in order to capture shapes" ), Qgis::MessageLevel::Warning );
511 }
512 else
513 {
514 if ( !mTempRubberBand )
515 {
516 mTempRubberBand.reset( createCurveRubberBand() );
517 mTempRubberBand->setStringType( mLineDigitizingType );
518 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
519 }
520
521 mCurrentShapeMapTool->cadCanvasMoveEvent( e, mCaptureMode );
522 return;
523 }
524 }
525 else
526 {
527 const QgsPoint mapPoint = QgsPoint( point );
528
529 if ( mCaptureMode != CapturePoint && mTempRubberBand && mCapturing )
530 {
531 bool hasTrace = false;
532
533 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Streaming )
534 {
535 if ( !mCaptureCurve.isEmpty() )
536 {
537 const QgsPoint prevPoint = mCaptureCurve.curveAt( mCaptureCurve.nCurves() - 1 )->endPoint();
538 if ( QgsPointXY( toCanvasCoordinates( toMapCoordinates( layer(), prevPoint ) ) ).distance( toCanvasCoordinates( point ) ) < mStreamingToleranceInPixels )
539 return;
540 }
541
542 mAllowAddingStreamingPoints = true;
544 mAllowAddingStreamingPoints = false;
545 }
546 else if ( tracingEnabled() && mCaptureCurve.numPoints() != 0 )
547 {
548 // Store the intermediate point for circular string to retrieve after tracing mouse move if
549 // the digitizing type is circular and the temp rubber band is effectivly circular and if this point is existing
550 // Store an empty point if the digitizing type is linear ot the point is not existing (curve not complete)
551 if ( mLineDigitizingType == Qgis::WkbType::CircularString && mTempRubberBand->stringType() == Qgis::WkbType::CircularString && mTempRubberBand->curveIsComplete() )
552 mCircularItermediatePoint = mTempRubberBand->pointFromEnd( 1 );
553 else if ( mLineDigitizingType == Qgis::WkbType::LineString || !mTempRubberBand->curveIsComplete() )
554 mCircularItermediatePoint = QgsPoint();
555
556 hasTrace = tracingMouseMove( e );
557
558 if ( !hasTrace )
559 {
560 // Restore the temp rubber band
561 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
562 mTempRubberBand->addPoint( mCaptureLastPoint );
563 if ( !mCircularItermediatePoint.isEmpty() )
564 {
565 mTempRubberBand->movePoint( mCircularItermediatePoint );
566 mTempRubberBand->addPoint( mCircularItermediatePoint );
567 }
568 }
569 }
570
571 if ( mCurrentCaptureTechnique != Qgis::CaptureTechnique::Streaming && !hasTrace )
572 {
573 if ( mCaptureCurve.numPoints() > 0 )
574 {
575 const QgsPoint mapPt = mCaptureLastPoint;
576
577 if ( mTempRubberBand )
578 {
579 mTempRubberBand->movePoint( mapPoint );
580 mTempRubberBand->movePoint( 0, mapPt );
581 }
582
583 // fix existing rubber band after tracing - the last point may have been moved if using offset
584 if ( mRubberBand->numberOfVertices() )
585 mRubberBand->movePoint( mapPt );
586 }
587 else if ( mTempRubberBand )
588 mTempRubberBand->movePoint( mapPoint );
589 }
590 }
591 }
592} // mouseMoveEvent
593
594
596{
597 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
598 {
599 try
600 {
601 QgsPointXY mapP( mapPoint.x(), mapPoint.y() ); //#spellok
602 const bool is3D = layerPoint.is3D();
603 const bool isMeasure = layerPoint.isMeasure();
604 mapP = toLayerCoordinates( vlayer, mapP ); //transform snapped point back to layer crs //#spellok
605 layerPoint = QgsPoint( layerPoint.wkbType(), mapP.x(), mapP.y(), layerPoint.z(), layerPoint.m() ); //#spellok
606 if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !is3D )
607 layerPoint.addZValue( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->currentPointV2().z() : defaultZValue() );
608 if ( QgsWkbTypes::hasM( vlayer->wkbType() ) && !isMeasure )
609 layerPoint.addMValue( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->currentPointV2().m() : defaultMValue() );
610 }
611 catch ( QgsCsException & )
612 {
613 QgsDebugError( QStringLiteral( "transformation to layer coordinate failed" ) );
614 return 2;
615 }
616 }
617 else
618 {
619 layerPoint = QgsPoint( toLayerCoordinates( layer(), mapPoint ) );
620 }
621
622 return 0;
623}
624
626{
628 return nextPoint( mapPoint, layerPoint );
629}
630
632{
633 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
634 QgsVectorLayer *sourceLayer = match.layer();
635 if ( mCadDockWidget && mCadDockWidget->cadEnabled() )
636 {
637 layerPoint = mCadDockWidget->currentPointLayerCoordinates( layer() );
638 return 0;
639 }
640 else if ( !vlayer )
641 {
642 return 1;
643 }
644
645 if ( match.isValid() && sourceLayer )
646 {
647 if ( ( match.hasVertex() || match.hasLineEndpoint() ) )
648 {
649 if ( sourceLayer->crs() != vlayer->crs() )
650 {
651 layerPoint = match.interpolatedPoint();
652 return 1;
653 }
654 QgsFeature f;
655 QgsFeatureRequest request;
656 request.setFilterFid( match.featureId() );
657 const bool fetched = match.layer()->getFeatures( request ).nextFeature( f );
658 if ( fetched )
659 {
660 QgsVertexId vId;
661 if ( !f.geometry().vertexIdFromVertexNr( match.vertexIndex(), vId ) )
662 {
663 return 2;
664 }
665 layerPoint = f.geometry().constGet()->vertexAt( vId );
666 if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !layerPoint.is3D() )
667 layerPoint.addZValue( defaultZValue() );
668 if ( QgsWkbTypes::hasM( vlayer->wkbType() ) && !layerPoint.isMeasure() )
669 layerPoint.addMValue( defaultMValue() );
670
671 // ZM support depends on the target layer
672 if ( !QgsWkbTypes::hasZ( vlayer->wkbType() ) )
673 {
674 layerPoint.dropZValue();
675 }
676
677 if ( !QgsWkbTypes::hasM( vlayer->wkbType() ) )
678 {
679 layerPoint.dropMValue();
680 }
681
682 return 0;
683 }
684 return 2;
685 }
686 else if ( QgsProject::instance()->topologicalEditing() && ( match.hasEdge() || match.hasMiddleSegment() ) )
687 {
688 layerPoint = toLayerCoordinates( vlayer, match.interpolatedPoint( mCanvas->mapSettings().destinationCrs() ) );
689 return 0;
690 }
691 }
692 return 2;
693}
694
696{
697 return addVertex( point, QgsPointLocator::Match() );
698}
699
701{
702 if ( mode() == CaptureNone )
703 {
704 QgsDebugError( QStringLiteral( "invalid capture mode" ) );
705 return 2;
706 }
707
708 if ( mCapturing && mCurrentCaptureTechnique == Qgis::CaptureTechnique::Streaming && !mAllowAddingStreamingPoints )
709 return 0;
710
711 QgsPoint layerPoint;
712 if ( layer() )
713 {
714 int res = fetchLayerPoint( match, layerPoint );
715 if ( res != 0 )
716 {
717 res = nextPoint( QgsPoint( point ), layerPoint );
718 if ( res != 0 )
719 {
720 return res;
721 }
722 }
723 }
724 else
725 {
726 layerPoint = QgsPoint( point );
727 }
728 const QgsPoint mapPoint = toMapCoordinates( layer(), layerPoint );
729
730 if ( mCaptureMode == CapturePoint )
731 {
732 mCaptureCurve.addVertex( layerPoint );
733 mSnappingMatches.append( match );
734 }
735 else
736 {
737 if ( mCaptureFirstPoint.isEmpty() )
738 {
739 mCaptureFirstPoint = mapPoint;
740 }
741
742 if ( !mRubberBand )
744
745 if ( !mTempRubberBand )
746 {
747 mTempRubberBand.reset( createCurveRubberBand() );
748 mTempRubberBand->setStringType( mLineDigitizingType );
749 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mapPoint );
750 }
751
752 bool traceCreated = false;
753 if ( tracingEnabled() )
754 {
755 traceCreated = tracingAddVertex( mapPoint );
756 }
757
758 // keep new tracing start point if we created a trace. This is useful when tracing with
759 // offset so that the user stays "snapped"
760 mTracingStartPoint = traceCreated ? point : QgsPointXY();
761
762 if ( !traceCreated )
763 {
764 // ordinary digitizing
765 mTempRubberBand->movePoint( mapPoint ); //move the last point of the temp rubberband before operating with it
766 if ( mTempRubberBand->curveIsComplete() ) //2 points for line and 3 points for circular
767 {
768 if ( QgsCurve *curve = mTempRubberBand->curve() )
769 {
770 addCurve( curve );
771 // add curve append only invalid match to mSnappingMatches,
772 // so we need to remove them and add the one from here if it is valid
773 if ( match.isValid() && mSnappingMatches.count() > 0 && !mSnappingMatches.last().isValid() )
774 {
775 mSnappingMatches.removeLast();
776 if ( mTempRubberBand->stringType() == Qgis::WkbType::CircularString )
777 {
778 // for circular string two points are added and match for intermediate point is stored
779 mSnappingMatches.removeLast();
780 mSnappingMatches.append( mCircularIntermediateMatch );
781 }
782 mSnappingMatches.append( match );
783 }
784 }
785 mCaptureLastPoint = mapPoint;
786 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
787 }
788 else if ( mTempRubberBand->pointsCount() == 0 )
789 {
790 mCaptureLastPoint = mapPoint;
791 mCaptureCurve.addVertex( layerPoint );
792 mSnappingMatches.append( match );
793 }
794 else
795 {
796 if ( mTempRubberBand->stringType() == Qgis::WkbType::CircularString )
797 {
798 mCircularIntermediateMatch = match;
799 }
800 }
801
802 mTempRubberBand->addPoint( mapPoint );
803 }
804 else
805 {
806 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
807 mTempRubberBand->addPoint( mCaptureLastPoint );
808 }
809 }
810
811 updateExtraSnapLayer();
812 validateGeometry();
813
814 return 0;
815}
816
818{
819 if ( !c )
820 {
821 return 1;
822 }
823
824 if ( !mRubberBand )
825 {
827 }
828
829 if ( mTempRubberBand )
830 {
831 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
832 const QgsPoint endPt = c->endPoint();
833 mTempRubberBand->addPoint( endPt ); //add last point of c
834 }
835
836 const int countBefore = mCaptureCurve.vertexCount();
837 //if there is only one point, this the first digitized point that are in the this first curve added --> remove the point
838 if ( mCaptureCurve.numPoints() == 1 )
839 mCaptureCurve.removeCurve( 0 );
840
841 // Transform back to layer CRS in case map CRS and layer CRS are different
842 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( layer() );
843 if ( ct.isValid() && !ct.isShortCircuited() )
844 {
845 QgsLineString *segmented = c->curveToLine();
847 // Curve geometries will be converted to segments, so we explicitly set extentPrevious to false
848 // to be able to remove the whole curve in undo
849 mCaptureCurve.addCurve( segmented, false );
850 delete c;
851 }
852 else
853 {
854 // we set the extendPrevious option to true to avoid creating compound curves with many 2 vertex linestrings -- instead we prefer
855 // to extend linestring curves so that they continue the previous linestring wherever possible...
856 mCaptureCurve.addCurve( c, !mStartNewCurve );
857 }
858
859 mStartNewCurve = false;
860
861 const int countAfter = mCaptureCurve.vertexCount();
862 const int addedPoint = countAfter - countBefore;
863
864 updateExtraSnapLayer();
865
866 for ( int i = 0; i < addedPoint; ++i )
867 mSnappingMatches.append( QgsPointLocator::Match() );
868
869 resetRubberBand();
870
871 return 0;
872}
873
875{
876 mCaptureCurve.clear();
877 updateExtraSnapLayer();
878}
879
880QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
881{
882 return mSnappingMatches;
883}
884
885void QgsMapToolCapture::undo( bool isAutoRepeat )
886{
887 mTracingStartPoint = QgsPointXY();
888
889 if ( mTempRubberBand )
890 {
891 if ( size() <= 1 && mTempRubberBand->pointsCount() != 0 )
892 return;
893
894 if ( isAutoRepeat && mIgnoreSubsequentAutoRepeatUndo )
895 return;
896 mIgnoreSubsequentAutoRepeatUndo = false;
897
898 const QgsPoint lastPoint = mTempRubberBand->lastPoint();
899
900 if ( mTempRubberBand->stringType() == Qgis::WkbType::CircularString && mTempRubberBand->pointsCount() > 2 )
901 {
902 mTempRubberBand->removeLastPoint();
903 mTempRubberBand->movePoint( lastPoint );
904 return;
905 }
906
907 QgsVertexId vertexToRemove;
908 vertexToRemove.part = 0;
909 vertexToRemove.ring = 0;
910 vertexToRemove.vertex = size() - 1;
911
912 // If the geometry was reprojected, remove the entire last curve.
913 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( layer() );
914 if ( ct.isValid() && !ct.isShortCircuited() )
915 {
916 mCaptureCurve.removeCurve( mCaptureCurve.nCurves() - 1 );
917 }
918 if ( mCaptureCurve.numPoints() == 2 && mCaptureCurve.nCurves() == 1 )
919 {
920 // store the first vertex to restore if after deleting the curve
921 // because when only two vertices, removing a point remove all the curve
922 const QgsPoint fp = mCaptureCurve.startPoint();
923 mCaptureCurve.deleteVertex( vertexToRemove );
924 mCaptureCurve.addVertex( fp );
925 }
926 else
927 {
928 const int curvesBefore = mCaptureCurve.nCurves();
929 const bool lastCurveIsLineString = qgsgeometry_cast<const QgsLineString *>( mCaptureCurve.curveAt( curvesBefore - 1 ) );
930
931 const int pointsCountBefore = mCaptureCurve.numPoints();
932 mCaptureCurve.deleteVertex( vertexToRemove );
933 int pointsCountAfter = mCaptureCurve.numPoints();
934 for ( ; pointsCountAfter < pointsCountBefore; pointsCountAfter++ )
935 if ( !mSnappingMatches.empty() )
936 mSnappingMatches.removeLast();
937
938 // if we have removed the last point in a linestring curve, then we "stick" here and ignore subsequent
939 // autorepeat undo actions until the user releases the undo key and holds it down again. This allows
940 // users to selectively remove portions of the geometry captured with the streaming mode by holding down
941 // the undo key, without risking accidental undo of non-streamed portions.
942 if ( mCaptureCurve.nCurves() < curvesBefore && lastCurveIsLineString )
943 mIgnoreSubsequentAutoRepeatUndo = true;
944 }
945
946 updateExtraSnapLayer();
947
948 resetRubberBand();
949
950 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
951
952 if ( mCaptureCurve.numPoints() > 0 )
953 {
954 const QgsPoint lastPt = mCaptureCurve.endPoint();
955 mCaptureLastPoint = toMapCoordinates( layer(), lastPt );
956 mTempRubberBand->addPoint( mCaptureLastPoint );
957 mTempRubberBand->movePoint( lastPoint );
958 }
959
960 mCadDockWidget->removePreviousPoint();
961 validateGeometry();
962 }
963}
964
966{
967 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
968 {
969 mCurrentShapeMapTool->keyPressEvent( e );
970 if ( e->isAccepted() )
971 return;
972 }
973
974 // this is backwards, but we can't change now without breaking api because
975 // forever QgsMapTools have had to explicitly mark events as ignored in order to
976 // indicate that they've consumed the event and that the default behavior should not
977 // be applied..!
978 // see QgsMapCanvas::keyPressEvent
979 e->accept();
980
981 if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete )
982 {
983 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
984 {
985 if ( !e->isAutoRepeat() )
986 {
987 mCurrentShapeMapTool->undo();
988 }
989 }
990 else
991 {
992 undo( e->isAutoRepeat() );
993 }
994
995 // Override default shortcut management in MapCanvas
996 e->ignore();
997 }
998 else if ( e->key() == Qt::Key_Escape )
999 {
1000 if ( mCurrentShapeMapTool )
1001 mCurrentShapeMapTool->clean();
1002
1003 stopCapturing();
1004
1005 // Override default shortcut management in MapCanvas
1006 e->ignore();
1007 }
1008}
1009
1011{
1012 mCapturing = true;
1013}
1014
1016{
1017 return mCapturing;
1018}
1019
1021{
1022 mRubberBand.reset();
1023
1025
1026 qDeleteAll( mGeomErrorMarkers );
1027 mGeomErrorMarkers.clear();
1028 mGeomErrors.clear();
1029
1030 mCaptureFirstPoint = QgsPoint();
1031 mCaptureLastPoint = QgsPoint();
1032
1033 mTracingStartPoint = QgsPointXY();
1034
1035 mCapturing = false;
1036 mCaptureCurve.clear();
1037 updateExtraSnapLayer();
1038 mSnappingMatches.clear();
1039 if ( auto *lCurrentVectorLayer = currentVectorLayer() )
1040 lCurrentVectorLayer->triggerRepaint();
1041}
1042
1044{
1045 mTempRubberBand.reset();
1046}
1047
1049{
1050 stopCapturing();
1051 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
1052 mCurrentShapeMapTool->clean();
1053
1054 clearCurve();
1055}
1056
1058{
1059 mCaptureCurve.close();
1060 updateExtraSnapLayer();
1061}
1062
1063void QgsMapToolCapture::validateGeometry()
1064{
1066 || !( capabilities() & ValidateGeometries ) )
1067 return;
1068
1069 if ( mValidator )
1070 {
1071 mValidator->deleteLater();
1072 mValidator = nullptr;
1073 }
1074
1075 mGeomErrors.clear();
1076 while ( !mGeomErrorMarkers.isEmpty() )
1077 {
1078 delete mGeomErrorMarkers.takeFirst();
1079 }
1080
1081 QgsGeometry geom;
1082
1083 switch ( mCaptureMode )
1084 {
1085 case CaptureNone:
1086 case CapturePoint:
1087 return;
1088 case CaptureLine:
1089 if ( size() < 2 )
1090 return;
1091 geom = QgsGeometry( mCaptureCurve.curveToLine() );
1092 break;
1093 case CapturePolygon:
1094 if ( size() < 3 )
1095 return;
1096 QgsLineString *exteriorRing = mCaptureCurve.curveToLine();
1097 exteriorRing->close();
1098 QgsPolygon *polygon = new QgsPolygon();
1099 polygon->setExteriorRing( exteriorRing );
1100 geom = QgsGeometry( polygon );
1101 break;
1102 }
1103
1104 if ( geom.isNull() )
1105 return;
1106
1110 mValidator = new QgsGeometryValidator( geom, nullptr, method );
1111 connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError );
1112 mValidator->start();
1113 QgsDebugMsgLevel( QStringLiteral( "Validation started" ), 4 );
1114}
1115
1116void QgsMapToolCapture::addError( const QgsGeometry::Error &e )
1117{
1118 mGeomErrors << e;
1119 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1120 if ( !vlayer )
1121 return;
1122
1123 if ( e.hasWhere() )
1124 {
1125 QgsVertexMarker *vm = new QgsVertexMarker( mCanvas );
1126 vm->setCenter( mCanvas->mapSettings().layerToMapCoordinates( vlayer, e.where() ) );
1128 vm->setPenWidth( 2 );
1129 vm->setToolTip( e.what() );
1130 vm->setColor( Qt::green );
1131 vm->setZValue( vm->zValue() + 1 );
1132 mGeomErrorMarkers << vm;
1133 }
1134}
1135
1137{
1138 return mCaptureCurve.numPoints();
1139}
1140
1141QVector<QgsPointXY> QgsMapToolCapture::points() const
1142{
1143 QVector<QgsPointXY> pointsXY;
1145
1146 return pointsXY;
1147}
1148
1150{
1151 QgsPointSequence pts;
1152 mCaptureCurve.points( pts );
1153 return pts;
1154}
1155
1156void QgsMapToolCapture::setPoints( const QVector<QgsPointXY> &pointList )
1157{
1158 QgsLineString *line = new QgsLineString( pointList );
1159 mCaptureCurve.clear();
1160 mCaptureCurve.addCurve( line );
1161 updateExtraSnapLayer();
1162 mSnappingMatches.clear();
1163 for ( int i = 0; i < line->length(); ++i )
1164 mSnappingMatches.append( QgsPointLocator::Match() );
1165 resetRubberBand();
1166}
1167
1169{
1170 QgsLineString *line = new QgsLineString( pointList );
1171 mCaptureCurve.clear();
1172 mCaptureCurve.addCurve( line );
1173 updateExtraSnapLayer();
1174 mSnappingMatches.clear();
1175 for ( int i = 0; i < line->length(); ++i )
1176 mSnappingMatches.append( QgsPointLocator::Match() );
1177 resetRubberBand();
1178}
1179
1181{
1182 QgsPoint newPoint( Qgis::WkbType::Point, point.x(), point.y() );
1183
1184 // get current layer
1185 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1186 if ( !vlayer )
1187 {
1188 return newPoint;
1189 }
1190
1191 // convert to the corresponding type for a full ZM support
1192 const Qgis::WkbType type = vlayer->wkbType();
1193 if ( QgsWkbTypes::hasZ( type ) && !QgsWkbTypes::hasM( type ) )
1194 {
1195 newPoint.convertTo( Qgis::WkbType::PointZ );
1196 }
1197 else if ( !QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1198 {
1199 newPoint.convertTo( Qgis::WkbType::PointM );
1200 }
1201 else if ( QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1202 {
1204 }
1205
1206 // set z value if necessary
1207 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) )
1208 {
1209 newPoint.setZ( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->getLineZ() : defaultZValue() );
1210 }
1211 // set m value if necessary
1212 if ( QgsWkbTypes::hasM( newPoint.wkbType() ) )
1213 {
1214 newPoint.setM( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->getLineM() : defaultMValue() );
1215 }
1216 return newPoint;
1217}
1218
1220{
1221 QgsPoint newPoint = mapPoint( e.mapPoint() );
1222
1223 // set z or m value from snapped point if necessary
1224 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) || QgsWkbTypes::hasM( newPoint.wkbType() ) )
1225 {
1226 // if snapped, z and m dimension are taken from the corresponding snapped
1227 // point.
1228 if ( e.isSnapped() )
1229 {
1230 const QgsPointLocator::Match match = e.mapPointMatch();
1231
1232 if ( match.layer() )
1233 {
1234 const QgsFeature ft = match.layer()->getFeature( match.featureId() );
1235 if ( QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
1236 {
1237 newPoint.setZ( ft.geometry().vertexAt( match.vertexIndex() ).z() );
1238 }
1239 if ( QgsWkbTypes::hasM( match.layer()->wkbType() ) )
1240 {
1241 newPoint.setM( ft.geometry().vertexAt( match.vertexIndex() ).m() );
1242 }
1243 }
1244 }
1245 }
1246
1247 return newPoint;
1248}
1249
1250void QgsMapToolCapture::updateExtraSnapLayer()
1251{
1252 if ( !mExtraSnapLayer )
1253 return;
1254
1255 if ( canvas()->snappingUtils()->config().selfSnapping() && layer() && mCaptureCurve.numPoints() >= 2 )
1256 {
1257 // the current layer may have changed
1258 mExtraSnapLayer->setCrs( layer()->crs() );
1259 QgsGeometry geom = QgsGeometry( mCaptureCurve.clone() );
1260 // we close the curve to allow snapping on last segment
1261 if ( mCaptureMode == CapturePolygon && mCaptureCurve.numPoints() >= 3 )
1262 {
1263 qgsgeometry_cast<QgsCompoundCurve *>( geom.get() )->close();
1264 }
1265 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1266 }
1267 else
1268 {
1269 QgsGeometry geom;
1270 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1271 }
1272}
1273
1274
1276{
1277 // POINT CAPTURING
1278 if ( mode() == CapturePoint )
1279 {
1280 if ( e->button() != Qt::LeftButton )
1281 return;
1282
1283 QgsPoint savePoint; //point in layer coordinates
1284 bool isMatchPointZ = false;
1285 bool isMatchPointM = false;
1286 try
1287 {
1288 QgsPoint fetchPoint;
1289 int res = fetchLayerPoint( e->mapPointMatch(), fetchPoint );
1290 isMatchPointZ = QgsWkbTypes::hasZ( fetchPoint.wkbType() );
1291 isMatchPointM = QgsWkbTypes::hasM( fetchPoint.wkbType() );
1292
1293 if ( res == 0 )
1294 {
1296 if ( isMatchPointM && isMatchPointZ )
1297 {
1298 geomType = Qgis::WkbType::PointZM;
1299 }
1300 else if ( isMatchPointM )
1301 {
1302 geomType = Qgis::WkbType::PointM;
1303 }
1304 else if ( isMatchPointZ )
1305 {
1306 geomType = Qgis::WkbType::PointZ;
1307 }
1308 savePoint = QgsPoint( geomType, fetchPoint.x(), fetchPoint.y(), fetchPoint.z(), fetchPoint.m() );
1309 }
1310 else
1311 {
1312 QgsPointXY point = mCanvas->mapSettings().mapToLayerCoordinates( layer(), e->mapPoint() );
1313
1314 savePoint = QgsPoint( point.x(), point.y(), fetchPoint.z(), fetchPoint.m() );
1315 }
1316 }
1317 catch ( QgsCsException &cse )
1318 {
1319 Q_UNUSED( cse )
1320 emit messageEmitted( tr( "Cannot transform the point to the layer's coordinate system" ), Qgis::MessageLevel::Warning );
1321 return;
1322 }
1323
1324 QgsGeometry g( std::make_unique<QgsPoint>( savePoint ) );
1325
1326 // The snapping result needs to be added so it's available in the @snapping_results variable of default value etc. expression contexts
1327 addVertex( e->mapPoint(), e->mapPointMatch() );
1328
1329 geometryCaptured( g );
1330 pointCaptured( savePoint );
1331
1332 stopCapturing();
1333
1334 // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points
1336 }
1337
1338 // LINE AND POLYGON CAPTURING
1339 else if ( mode() == CaptureLine || mode() == CapturePolygon )
1340 {
1341 bool digitizingFinished = false;
1342
1343 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
1344 {
1345 if ( !mCurrentShapeMapTool )
1346 {
1347 emit messageEmitted( tr( "Select an option from the Shape Digitizing Toolbar in order to capture shapes" ), Qgis::MessageLevel::Warning );
1348 return;
1349 }
1350 else
1351 {
1352 if ( !mTempRubberBand )
1353 {
1354 mTempRubberBand.reset( createCurveRubberBand() );
1355 mTempRubberBand->setStringType( mLineDigitizingType );
1356 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
1357 }
1358
1359 digitizingFinished = mCurrentShapeMapTool->cadCanvasReleaseEvent( e, mCaptureMode );
1360 if ( digitizingFinished )
1361 mCurrentShapeMapTool->clean();
1362 }
1363 }
1364 else // i.e. not shape
1365 {
1366 //add point to list and to rubber band
1367 if ( e->button() == Qt::LeftButton )
1368 {
1369 const int error = addVertex( e->mapPoint(), e->mapPointMatch() );
1370 if ( error == 2 )
1371 {
1372 //problem with coordinate transformation
1373 emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::MessageLevel::Warning );
1374 return;
1375 }
1376
1378 }
1379 else if ( e->button() == Qt::RightButton )
1380 {
1381 // End of string
1383
1384 //lines: bail out if there are not at least two vertices
1385 if ( mode() == CaptureLine && size() < 2 )
1386 {
1387 stopCapturing();
1388 return;
1389 }
1390
1391 //polygons: bail out if there are not at least two vertices
1392 if ( mode() == CapturePolygon && size() < 3 )
1393 {
1394 stopCapturing();
1395 return;
1396 }
1397
1398 if ( mode() == CapturePolygon || e->modifiers() == Qt::ShiftModifier )
1399 {
1400 closePolygon();
1401 }
1402
1403 digitizingFinished = true;
1404 }
1405 }
1406
1407 if ( digitizingFinished )
1408 {
1409 QgsGeometry g;
1410 std::unique_ptr<QgsCurve> curveToAdd( captureCurve()->clone() );
1411
1412 if ( mode() == CaptureLine )
1413 {
1414 g = QgsGeometry( curveToAdd->clone() );
1415 geometryCaptured( g );
1416 lineCaptured( curveToAdd.release() );
1417 }
1418 else
1419 {
1420 //does compoundcurve contain circular strings?
1421 //does provider support circular strings?
1422 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
1423 {
1424 const bool hasCurvedSegments = captureCurve()->hasCurvedSegments();
1425 const bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & Qgis::VectorProviderCapability::CircularGeometries;
1426
1427 if ( hasCurvedSegments && providerSupportsCurvedSegments )
1428 {
1429 curveToAdd.reset( captureCurve()->clone() );
1430 }
1431 else
1432 {
1433 curveToAdd.reset( captureCurve()->curveToLine() );
1434 }
1435 }
1436 else
1437 {
1438 curveToAdd.reset( captureCurve()->clone() );
1439 }
1440 std::unique_ptr<QgsCurvePolygon> poly { new QgsCurvePolygon() };
1441 poly->setExteriorRing( curveToAdd.release() );
1442 g = QgsGeometry( poly->clone() );
1443 geometryCaptured( g );
1444 polygonCaptured( poly.get() );
1445 }
1446
1447 stopCapturing();
1448 }
1449 }
1450}
@ CircularGeometries
Supports circular geometry types (circularstring, compoundcurve, curvepolygon).
Definition qgis.h:514
CaptureTechnique
Capture technique.
Definition qgis.h:397
@ Shape
Digitize shapes.
Definition qgis.h:401
@ StraightSegments
Default capture mode - capture occurs with straight line segments.
Definition qgis.h:398
@ CircularString
Capture in circular strings.
Definition qgis.h:399
@ Streaming
Streaming points digitizing mode (points are automatically added as the mouse cursor moves).
Definition qgis.h:400
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:2084
@ QgisInternal
Use internal QgsGeometryValidator method.
Definition qgis.h:2085
@ Geos
Use GEOS validation methods.
Definition qgis.h:2086
@ Warning
Warning message.
Definition qgis.h:158
@ Point
Points.
Definition qgis.h:359
@ Line
Lines.
Definition qgis.h:360
@ Polygon
Polygons.
Definition qgis.h:361
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:277
@ CompoundCurve
CompoundCurve.
Definition qgis.h:288
@ Point
Point.
Definition qgis.h:279
@ LineString
LineString.
Definition qgis.h:280
@ PointM
PointM.
Definition qgis.h:310
@ CircularString
CircularString.
Definition qgis.h:287
@ PointZ
PointZ.
Definition qgis.h:295
@ PointZM
PointZM.
Definition qgis.h:325
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
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.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
A dockable widget used to handle the CAD tools on top of a selection of map tools.
void switchZM()
Determines if Z or M will be enabled.
void clearPoints()
Removes all points from the CAD point list.
static QCursor getThemeCursor(Cursor cursor)
Helper to get a theme cursor.
@ CapturePoint
Select and capture a point or a feature.
QgsCompoundCurve * clone() const override
Clones the geometry by performing a deep copy.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
int numPoints() const override
Returns the number of points in the curve.
Handles coordinate transforms between two coordinate systems.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
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.
Curve polygon geometry type.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
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:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
void errorFound(const QgsGeometry::Error &error)
Sent when an error has been found during the validation process.
A geometry error.
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.
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.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (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.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.).
Line string geometry type, with support for z-dimension and m-values.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
void close()
Closes the line string by appending the first point to the end of the line, if it is not already clos...
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).
void currentLayerChanged(QgsMapLayer *layer)
Emitted when the current layer is changed.
Base class for all map layer types.
Definition qgsmaplayer.h:80
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
void setCrs(const QgsCoordinateReferenceSystem &srs, bool emitSignal=true)
Sets layer's spatial reference system.
A mouse event which is the result of a user interaction with 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.
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
QgsMapToolAdvancedDigitizing(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Creates an advanced digitizing maptool.
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
virtual void geometryCaptured(const QgsGeometry &geometry)
Called when the geometry is captured.
void undo(bool isAutoRepeat=false)
Removes the last vertex from mRubberBand and mCaptureList.
QFlags< Capability > Capabilities
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
virtual void polygonCaptured(const QgsCurvePolygon *polygon)
Called when a polygon is captured.
void closePolygon()
Close an open polygon.
virtual void pointCaptured(const QgsPoint &point)
Called when a point is captured.
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.
virtual void lineCaptured(const QgsCurve *line)
Called when a line is captured.
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.
@ SupportsCurves
Supports curved geometries input.
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.
QgsRubberBand * createRubberBand(Qgis::GeometryType geometryType=Qgis::GeometryType::Line, bool alternativeBand=false)
Creates a rubber band with the color/line width from the QGIS settings.
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.
static QColor digitizingStrokeColor()
Returns stroke color for rubber bands (from global settings).
static int digitizingStrokeWidth()
Returns stroke width for rubber bands (from global settings).
Base class for shape map tools metadata to be used in QgsMapToolShapeRegistry.
virtual QgsMapToolShapeAbstract * factory(QgsMapToolCapture *parentlTool) const =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.
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
QgsPointXY toMapCoordinates(QPoint point)
Transforms a point from screen coordinates to map coordinates.
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition qgsmaptool.h:360
friend class QgsMapCanvas
Definition qgsmaptool.h:380
void messageEmitted(const QString &message, Qgis::MessageLevel level=Qgis::MessageLevel::Info)
Emitted when a message should be shown to the user in the application message bar.
QPoint toCanvasCoordinates(const QgsPointXY &point) const
Transforms a point from map coordinates to screen coordinates.
bool isActive() const
Returns if the current map tool active on the map canvas.
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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:572
bool dropMValue() override
Drops any measure values which exist in the geometry.
Definition qgspoint.cpp:613
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:561
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
Definition qgspoint.cpp:464
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
void setM(double m)
Sets the point's m-value.
Definition qgspoint.h:365
bool convertTo(Qgis::WkbType type) override
Converts the geometry to a specified type.
Definition qgspoint.cpp:630
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:350
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
Definition qgspoint.cpp:602
double m
Definition qgspoint.h:55
double y
Definition qgspoint.h:53
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
static QgsProject * instance()
Returns the QgsProject singleton instance.
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
Responsible for drawing transient features (e.g.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
static const QgsSettingsEntryInteger * settingsDigitizingStreamTolerance
Settings entry digitizing stream tolerance.
static const QgsSettingsEntryDouble * settingsDigitizingLineColorAlphaScale
Settings entry digitizing line color alpha scale.
static const QgsSettingsEntryDouble * settingsDigitizingConvertToCurveAngleTolerance
Settings entry digitizing convert to curve angle tolerance.
static const QgsSettingsEntryDouble * settingsDigitizingConvertToCurveDistanceTolerance
Settings entry digitizing convert to curve distance tolerance.
static const QgsSettingsEntryInteger * settingsDigitizingValidateGeometries
Settings entry digitizing validate geometries.
static const QgsSettingsEntryBool * settingsDigitizingConvertToCurve
Settings entry digitizing convert to curve.
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...
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.
PathError
Possible errors that may happen when calling findShortestPath().
Definition qgstracer.h:133
@ ErrNone
No error.
Definition qgstracer.h:134
@ ErrTooManyFeatures
Max feature count threshold was reached while reading features.
Definition qgstracer.h:135
bool init()
Build the internal data structures.
virtual Q_INVOKABLE Qgis::VectorProviderCapabilities capabilities() const
Returns flags containing the supported capabilities.
Represents a vector layer which manages a vector based dataset.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Q_INVOKABLE Qgis::WkbType wkbType() const final
Returns the WKBType or WKBUnknown in case of error.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
Q_INVOKABLE QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
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 Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
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:7208
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57
QgsFeatureId featureId() const
The id of the feature to which the snapped geometry belongs.
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsPoint interpolatedPoint(const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem()) const
Convenient method to return a point on an edge with linear interpolation of the Z value.
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:30
int vertex
Vertex number.
Definition qgsvertexid.h:94
int part
Part number.
Definition qgsvertexid.h:88
int ring
Ring number.
Definition qgsvertexid.h:91