QGIS API Documentation 3.99.0-Master (c22de0620c0)
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
23#include "qgsapplication.h"
24#include "qgsbezierdata.h"
25#include "qgsbeziermarker.h"
26#include "qgscircularstring.h"
27#include "qgscompoundcurve.h"
28#include "qgsexception.h"
29#include "qgsfeatureiterator.h"
32#include "qgslinestring.h"
33#include "qgslogger.h"
34#include "qgsmapcanvas.h"
35#include "qgsmapcanvastracer.h"
36#include "qgsmapmouseevent.h"
40#include "qgsnurbscurve.h"
41#include "qgspolygon.h"
42#include "qgsproject.h"
43#include "qgsrubberband.h"
46#include "qgssnapindicator.h"
47#include "qgssnappingutils.h"
48#include "qgsvectorlayer.h"
49#include "qgsvertexmarker.h"
50
51#include <QAction>
52#include <QCursor>
53#include <QPixmap>
54#include <QStatusBar>
55#include <QString>
56#include <QWheelEvent>
57
58#include "moc_qgsmaptoolcapture.cpp"
59
60using namespace Qt::StringLiterals;
61
64 , mCaptureMode( mode )
65 , mCaptureModeFromLayer( mode == CaptureNone )
66{
67 mTempRubberBand.setParentOwner( canvas );
68
69 mSnapIndicator = std::make_unique<QgsSnapIndicator>( canvas );
70
72
73 connect( canvas, &QgsMapCanvas::currentLayerChanged, this, &QgsMapToolCapture::currentLayerChanged );
74
76 layerOptions.skipCrsValidation = true;
77 layerOptions.loadDefaultStyle = false;
78 mExtraSnapLayer = new QgsVectorLayer( u"LineString?crs="_s, u"extra snap"_s, u"memory"_s, layerOptions );
79 mExtraSnapLayer->startEditing();
80 QgsFeature f;
81 mExtraSnapLayer->addFeature( f );
82 mExtraSnapFeatureId = f.id();
83
84 connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, this, &QgsMapToolCapture::updateExtraSnapLayer );
85
86 currentLayerChanged( canvas->currentLayer() );
87}
88
90{
91 // during tear down we have to clean up mExtraSnapLayer first, before
92 // we call stop capturing. Otherwise stopCapturing tries to access members
93 // from the mapcanvas, which is likely already being destroyed and triggering
94 // the deletion of this object...
95 if ( mCanvas )
96 {
97 mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
98 }
99 mExtraSnapLayer->deleteLater();
100 mExtraSnapLayer = nullptr;
101
103
104 if ( mValidator )
105 {
106 mValidator->deleteLater();
107 mValidator = nullptr;
108 }
109}
110
115
131
133{
134 if ( mTempRubberBand )
135 mTempRubberBand->show();
136
137 mCanvas->snappingUtils()->addExtraSnapLayer( mExtraSnapLayer );
139
140 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
141 {
142 setCurrentShapeMapToolIsActivated( true );
143 }
144}
145
147{
148 if ( mTempRubberBand )
149 mTempRubberBand->hide();
150
151 mSnapIndicator->setMatch( QgsPointLocator::Match() );
152
153 mCanvas->snappingUtils()->removeExtraSnapLayer( mExtraSnapLayer );
154
155 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
156 {
157 setCurrentShapeMapToolIsActivated( false );
158 }
159
161}
162
163void QgsMapToolCapture::currentLayerChanged( QgsMapLayer *layer )
164{
165 if ( !mCaptureModeFromLayer )
166 return;
167
168 mCaptureMode = CaptureNone;
169
170 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
171 if ( !vlayer )
172 {
173 return;
174 }
175
176 if ( vlayer->isSpatial() )
177 {
179 }
180 else
181 {
182 setCursor( QCursor( Qt::ArrowCursor ) );
183 }
184
185 switch ( vlayer->geometryType() )
186 {
188 mCaptureMode = CapturePoint;
189 break;
191 mCaptureMode = CaptureLine;
192 break;
194 mCaptureMode = CapturePolygon;
195 break;
196 default:
197 mCaptureMode = CaptureNone;
198 break;
199 }
200
201 if ( mTempRubberBand )
202 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
203
204 resetRubberBand();
206}
207
208
209bool QgsMapToolCapture::tracingEnabled()
210{
211 QgsMapCanvasTracer *tracer = QgsMapCanvasTracer::tracerForCanvas( mCanvas );
212 return tracer && ( !tracer->actionEnableTracing() || tracer->actionEnableTracing()->isChecked() )
213 && ( !tracer->actionEnableSnapping() || tracer->actionEnableSnapping()->isChecked() );
214}
215
216
217QgsPointXY QgsMapToolCapture::tracingStartPoint()
218{
219 // if we have starting point from previous trace, then preferably use that one
220 // (useful when tracing with offset)
221 if ( mTracingStartPoint != QgsPointXY() )
222 return mTracingStartPoint;
223
224 return mCaptureLastPoint;
225}
226
227
228bool QgsMapToolCapture::tracingMouseMove( QgsMapMouseEvent *e )
229{
230 if ( !e->isSnapped() )
231 return false;
232
233 QgsPointXY pt0 = tracingStartPoint();
234 if ( pt0 == QgsPointXY() )
235 return false;
236
237 QgsMapCanvasTracer *tracer = QgsMapCanvasTracer::tracerForCanvas( mCanvas );
238 if ( !tracer )
239 return false; // this should not happen!
240
242 QVector<QgsPointXY> points = tracer->findShortestPath( pt0, e->mapPoint(), &err );
243 if ( points.isEmpty() )
244 {
245 tracer->reportError( err, false );
246 return false;
247 }
248
249 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, Qgis::WkbType::LineString, mCaptureFirstPoint );
250 mTempRubberBand->addPoint( mCaptureLastPoint );
251
252 // if there is offset, we need to fix the rubber bands to make sure they are aligned correctly.
253 // There are two cases we need to sort out:
254 // 1. the last point of mRubberBand may need to be moved off the traced curve to respect the offset
255 // 2. first point of mTempRubberBand may be needed to be moved to the beginning of the offset trace
256 const QgsPoint lastPoint = mCaptureLastPoint;
257 QgsPointXY lastPointXY( lastPoint );
258 if ( lastPointXY == pt0 && points[0] != lastPointXY )
259 {
260 if ( mRubberBand->numberOfVertices() != 0 )
261 {
262 // if rubber band had just one point, for some strange reason it contains the point twice
263 // we only want to move the last point if there are multiple points already
264 if ( mRubberBand->numberOfVertices() > 2 || ( mRubberBand->numberOfVertices() == 2 && *mRubberBand->getPoint( 0, 0 ) != *mRubberBand->getPoint( 0, 1 ) ) )
265 mRubberBand->movePoint( points[0] );
266 }
267
268 mTempRubberBand->movePoint( 0, QgsPoint( points[0] ) );
269 }
270
271 mTempRubberBand->movePoint( QgsPoint( points[0] ) );
272
273 // update temporary rubberband
274 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
275 mTempRubberBand->addPoint( QgsPoint( points.at( i ) ), i == points.count() - 1 );
276
277
278 mTempRubberBand->addPoint( QgsPoint( points[points.size() - 1] ) );
279
280 tracer->reportError( QgsTracer::ErrNone, false ); // clear messagebar if there was any error
281
282 QgsCoordinateReferenceSystem targetCrs = mCanvas->mapSettings().destinationCrs();
283 if ( QgsMapLayer *l = layer() )
284 {
285 // if we have a layer, then the geometry will be in the layer's CRS, not the canvas'
286 targetCrs = l->crs();
287 }
288
289 std::unique_ptr< QgsCompoundCurve > tempCurve( mCaptureCurve.clone() );
290 try
291 {
292 std::unique_ptr< QgsCurve > tracedCurve( mTempRubberBand->curve() );
293 tracedCurve->transform( QgsCoordinateTransform( mCanvas->mapSettings().destinationCrs(), targetCrs, QgsProject::instance()->transformContext() ) );
294 tempCurve->addCurve( tracedCurve.release() );
295 if ( mCaptureMode == CapturePolygon )
296 {
297 tempCurve->close();
298 auto curvePolygon = std::make_unique< QgsCurvePolygon >();
299 curvePolygon->setExteriorRing( tempCurve.release() );
300 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( curvePolygon ) ), targetCrs ) );
301 }
302 else
303 {
304 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( tempCurve ) ), targetCrs ) );
305 }
306 }
307 catch ( QgsCsException &e )
308 {
309 QgsDebugError( e.what() );
310 }
311
312 return true;
313}
314
315
316bool QgsMapToolCapture::tracingAddVertex( const QgsPointXY &point )
317{
318 QgsMapCanvasTracer *tracer = QgsMapCanvasTracer::tracerForCanvas( mCanvas );
319 if ( !tracer )
320 return false; // this should not happen!
321
322 if ( mTempRubberBand->pointsCount() == 0 )
323 {
324 if ( !tracer->init() )
325 {
327 return false;
328 }
329
330 // only accept first point if it is snapped to the graph (to vertex or edge)
331 const bool res = tracer->isPointSnapped( point );
332 if ( res )
333 {
334 mTracingStartPoint = point;
335 }
336 return false;
337 }
338
339 QgsPointXY pt0 = tracingStartPoint();
340 if ( pt0 == QgsPointXY() )
341 return false;
342
344 const QVector<QgsPointXY> tracedPointsInMapCrs = tracer->findShortestPath( pt0, point, &err );
345 if ( tracedPointsInMapCrs.isEmpty() )
346 return false; // ignore the vertex - can't find path to the end point!
347
348 // transform points
349 QgsPointSequence layerPoints;
350 layerPoints.reserve( tracedPointsInMapCrs.size() );
351 QgsPointSequence mapPoints;
352 mapPoints.reserve( tracedPointsInMapCrs.size() );
353 for ( const QgsPointXY &tracedPointMapCrs : tracedPointsInMapCrs )
354 {
355 QgsPoint mapPoint( tracedPointMapCrs );
356
357 QgsPoint lp; // in layer coords
358 if ( nextPoint( mapPoint, lp ) != 0 )
359 return false;
360
361 // copy z and m from layer point back to mapPoint, as nextPoint() call will populate these based
362 // on the context of the trace
363 if ( lp.is3D() )
364 mapPoint.addZValue( lp.z() );
365 if ( lp.isMeasure() )
366 mapPoint.addMValue( lp.m() );
367
368 mapPoints << mapPoint;
369 layerPoints << lp;
370 }
371
372 // Move the last point of the captured curve to the first point on the trace string (necessary if there is offset)
373 const QgsVertexId lastVertexId( 0, 0, mCaptureCurve.numPoints() - 1 );
374 mCaptureCurve.moveVertex( lastVertexId, layerPoints.first() );
375 mSnappingMatches.removeLast();
376 mSnappingMatches.append( QgsPointLocator::Match() );
377
378 addCurve( new QgsLineString( mapPoints ) );
379
380 resetRubberBand();
381
382 // Curves de-approximation
384 {
385 // If the tool and the layer support curves
386 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
388 {
389 const QgsGeometry linear = QgsGeometry( mCaptureCurve.segmentize() );
390 const QgsGeometry curved = linear.convertToCurves(
393 );
395 {
396 mCaptureCurve.clear();
397 mCaptureCurve.addCurve( qgsgeometry_cast<const QgsCurve *>( curved.constGet() )->clone() );
398 }
399 else
400 {
401 mCaptureCurve = *qgsgeometry_cast<const QgsCompoundCurve *>( curved.constGet() );
402 }
403 }
404
405 mSnappingMatches.resize( mCaptureCurve.numPoints() );
406 }
407
408 tracer->reportError( QgsTracer::ErrNone, true ); // clear messagebar if there was any error
409
410 // adjust last captured point
411 const QgsPoint lastPt = mCaptureCurve.endPoint();
412 mCaptureLastPoint = toMapCoordinates( layer(), lastPt );
413
414 return true;
415}
416
417QgsMapToolCaptureRubberBand *QgsMapToolCapture::createCurveRubberBand() const
418{
419 QgsMapToolCaptureRubberBand *rb = new QgsMapToolCaptureRubberBand( mCanvas );
420 rb->setStrokeWidth( digitizingStrokeWidth() );
421 QColor color = digitizingStrokeColor();
422
424 color.setAlphaF( color.alphaF() * alphaScale );
425 rb->setLineStyle( Qt::DotLine );
426 rb->setStrokeColor( color );
427
428 const QColor fillColor = digitizingFillColor();
429 rb->setFillColor( fillColor );
430 rb->show();
431 return rb;
432}
433
434void QgsMapToolCapture::resetRubberBand()
435{
436 if ( !mRubberBand )
437 return;
438 QgsLineString *lineString = mCaptureCurve.curveToLine();
439
440 mRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
441 mRubberBand->addGeometry( QgsGeometry( lineString ), layer() );
442}
443
444void QgsMapToolCapture::setCurrentShapeMapToolIsActivated( bool activated )
445{
446 if ( activated )
447 {
449 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
450 }
451 else
452 {
454 mCurrentShapeMapTool->deactivate();
455 }
456}
457
459{
460 return mRubberBand.release();
461}
462
470
478
480{
481 if ( mCurrentCaptureTechnique == technique )
482 return;
483
484 mStartNewCurve = true;
485
486 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
487 {
488 setCurrentShapeMapToolIsActivated( false );
489 clean();
490 }
491
492 switch ( technique )
493 {
495 mLineDigitizingType = Qgis::WkbType::LineString;
496 break;
498 mLineDigitizingType = Qgis::WkbType::CircularString;
499 break;
501 mLineDigitizingType = Qgis::WkbType::LineString;
502 mStreamingToleranceInPixels = QgsSettingsRegistryCore::settingsDigitizingStreamTolerance->value();
503 break;
505 mLineDigitizingType = Qgis::WkbType::LineString;
506 break;
509 mLineDigitizingType = Qgis::WkbType::NurbsCurve;
510 break;
511 }
512
513 if ( mTempRubberBand )
514 mTempRubberBand->setStringType( mLineDigitizingType );
515
516 mCurrentCaptureTechnique = technique;
517
518 if ( technique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool && isActive() )
519 {
520 clean();
521 setCurrentShapeMapToolIsActivated( true );
522 }
523}
524
526{
527 if ( mCurrentShapeMapTool )
528 {
529 if ( shapeMapToolMetadata && mCurrentShapeMapTool->id() == shapeMapToolMetadata->id() )
530 return;
531 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
532 {
533 setCurrentShapeMapToolIsActivated( false );
534 }
535 mCurrentShapeMapTool->deleteLater();
536 }
537
538 mCurrentShapeMapTool.reset( shapeMapToolMetadata ? shapeMapToolMetadata->factory( this ) : nullptr );
539
540 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && isActive() )
541 {
542 clean();
543 if ( mCurrentShapeMapTool )
544 {
545 setCurrentShapeMapToolIsActivated( true );
546 }
547 }
548}
549
551{
552 // Poly-Bézier mode: handle press to add anchor and start drag
553 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::PolyBezier
554 && ( mode() == CaptureLine || mode() == CapturePolygon ) )
555 {
556 if ( e->button() == Qt::LeftButton )
557 {
558 const QgsPoint mapPoint = QgsPoint( e->mapPoint() );
559
560 // Initialize Bézier structures if needed
561 if ( !mBezierData )
562 mBezierData = std::make_unique<QgsBezierData>();
563 if ( !mBezierMarker )
564 mBezierMarker = std::make_unique<QgsBezierMarker>( mCanvas );
565
566 const double tolerance = searchRadiusMU( mCanvas );
567
568 // Reset drag indices
569 mBezierDragAnchorIndex = -1;
570 mBezierDragHandleIndex = -1;
571 mBezierMoveAnchorIndex = -1;
572
573 // First, check if clicking on an existing handle
574 const int handleIdx = mBezierData->findClosestHandle( mapPoint, tolerance );
575 if ( handleIdx >= 0 )
576 {
577 // Start dragging this handle independently
578 mBezierDragHandleIndex = handleIdx;
579 mBezierDragging = true;
580 mBezierMarker->setHighlightedHandle( handleIdx );
581 return;
582 }
583
584 // Second, check if clicking on an existing anchor
585 const int anchorIdx = mBezierData->findClosestAnchor( mapPoint, tolerance );
586 if ( anchorIdx >= 0 )
587 {
588 if ( e->modifiers() & Qt::AltModifier )
589 {
590 // Alt+click on anchor: extend handles symmetrically (like creating new anchor)
591 mBezierDragAnchorIndex = anchorIdx;
592 mBezierDragging = true;
593 mBezierMarker->setHighlightedAnchor( anchorIdx );
594 }
595 else
596 {
597 // Normal click: start moving this anchor
598 mBezierMoveAnchorIndex = anchorIdx;
599 mBezierDragging = true;
600 mBezierMarker->setHighlightedAnchor( anchorIdx );
601 }
602 return;
603 }
604
605 // Otherwise, add new anchor and start symmetric handle drag
606 mBezierData->addAnchor( mapPoint );
607 mBezierDragAnchorIndex = mBezierData->anchorCount() - 1;
608 mBezierDragging = true;
609
610 // Update visualization
611 mBezierMarker->updateFromData( *mBezierData );
612
614 return;
615 }
616 }
617
618 // Default handling for other modes
620}
621
623{
624 // If we are adding a record to a non-spatial layer, just return
625 if ( mCaptureModeFromLayer && ( !canvas()->currentLayer() || !canvas()->currentLayer()->isSpatial() ) )
626 return;
627
629
630 const QgsPointXY point = e->mapPoint();
631
632 mSnapIndicator->setMatch( e->mapPointMatch() );
633
634 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
635 {
636 if ( !mCurrentShapeMapTool )
637 {
638 emit messageEmitted( tr( "Select an option from the Shape Digitizing Toolbar in order to capture shapes" ), Qgis::MessageLevel::Warning );
639 }
640 else
641 {
642 if ( !mTempRubberBand )
643 {
644 mTempRubberBand.reset( createCurveRubberBand() );
645 mTempRubberBand->setStringType( mLineDigitizingType );
646 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
647 }
648
649 mCurrentShapeMapTool->cadCanvasMoveEvent( e, mCaptureMode );
650 return;
651 }
652 }
653 else if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::PolyBezier )
654 {
655 // Poly-Bézier mode handling
656 const QgsPoint mapPoint = QgsPoint( point );
657
658 // Check if we are hovering over a handle or anchor to change cursor
659 if ( mBezierData )
660 {
661 const double tolerance = searchRadiusMU( mCanvas );
662
663 // Check if mouse is near any handle
664 const int handleIdx = mBezierData->findClosestHandle( mapPoint, tolerance );
665 // Check if mouse is near any anchor
666 const int anchorIdx = mBezierData->findClosestAnchor( mapPoint, tolerance );
667
668 if ( handleIdx >= 0 || anchorIdx >= 0 )
669 {
670 // Change cursor to hand pointer when hovering over a handle or anchor
671 setCursor( Qt::PointingHandCursor );
672 }
673 else
674 {
675 // Reset cursor to the default CapturePoint
677 }
678 }
679
680 if ( mBezierDragging && mBezierData )
681 {
682 if ( mBezierDragHandleIndex >= 0 )
683 {
684 // Dragging an existing handle independently
685 mBezierData->moveHandle( mBezierDragHandleIndex, mapPoint );
686 }
687 else if ( mBezierDragAnchorIndex >= 0 )
688 {
689 // Creating new anchor: update both handles symmetrically
690 mBezierData->calculateSymmetricHandles( mBezierDragAnchorIndex, mapPoint );
691 }
692
693 // Update visualization
694 if ( mBezierMarker )
695 mBezierMarker->updateFromData( *mBezierData );
696 }
697 // For Polygon preview
698 else if ( mBezierData && mBezierData->anchorCount() > 0 && mBezierMarker && mCapturing )
699 {
700 QgsBezierData previewData = *mBezierData;
701 previewData.addAnchor( mapPoint );
702
703 mBezierMarker->updateCurve( previewData );
704
705 QgsPointSequence points = previewData.interpolateLine();
706
707 if ( !mTempRubberBand )
708 {
709 mTempRubberBand.reset( createCurveRubberBand() );
710 mTempRubberBand->setStringType( mLineDigitizingType );
711 }
712
713 QgsPoint firstPoint = points.isEmpty() ? QgsPoint() : points.first();
714 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, Qgis::WkbType::LineString, firstPoint );
715
716 for ( const QgsPoint &pt : std::as_const( points ) )
717 {
718 mTempRubberBand->addPoint( pt );
719 }
720
721 QgsCoordinateReferenceSystem targetCrs = mCanvas->mapSettings().destinationCrs();
722 if ( QgsMapLayer *l = layer() )
723 {
724 targetCrs = l->crs();
725 }
726
727 if ( points.size() >= 2 )
728 {
729 auto lineString = std::make_unique<QgsLineString>( points );
730
731 if ( mCaptureMode == CapturePolygon )
732 {
733 auto curvePolygon = std::make_unique<QgsCurvePolygon>();
734 lineString->close();
735 curvePolygon->setExteriorRing( lineString.release() );
736 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( curvePolygon ) ), targetCrs ) );
737 }
738 else
739 {
740 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( lineString ) ), targetCrs ) );
741 }
742 }
743 }
744 return;
745 }
746 else
747 {
748 const QgsPoint mapPoint = QgsPoint( point );
749
750 QgsCoordinateReferenceSystem targetCrs = mCanvas->mapSettings().destinationCrs();
751 if ( QgsMapLayer *l = layer() )
752 {
753 // if we have a layer, then the geometry will be in the layer's CRS, not the canvas'
754 targetCrs = l->crs();
755 }
756
757 if ( mCaptureMode != CapturePoint && mTempRubberBand && mCapturing )
758 {
759 bool hasTrace = false;
760
761 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Streaming )
762 {
763 if ( !mCaptureCurve.isEmpty() )
764 {
765 const QgsPoint prevPoint = mCaptureCurve.curveAt( mCaptureCurve.nCurves() - 1 )->endPoint();
766 if ( QgsPointXY( toCanvasCoordinates( toMapCoordinates( layer(), prevPoint ) ) ).distance( toCanvasCoordinates( point ) ) < mStreamingToleranceInPixels )
767 return;
768 }
769
770 mAllowAddingStreamingPoints = true;
772 mAllowAddingStreamingPoints = false;
773
774 std::unique_ptr< QgsCompoundCurve > tempCurve( mCaptureCurve.clone() );
775 if ( mCaptureMode == CapturePolygon )
776 {
777 auto curvePolygon = std::make_unique< QgsCurvePolygon >();
778 tempCurve->close();
779 curvePolygon->setExteriorRing( tempCurve.release() );
780 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( curvePolygon ) ), targetCrs ) );
781 }
782 else
783 {
784 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( tempCurve ) ), targetCrs ) );
785 }
786 }
787 else if ( tracingEnabled() && mCaptureCurve.numPoints() != 0 )
788 {
789 // Store the intermediate point for circular string to retrieve after tracing mouse move if
790 // the digitizing type is circular and the temp rubber band is effectively circular and if this point is existing
791 // Store an empty point if the digitizing type is linear ot the point is not existing (curve not complete)
792 if ( mLineDigitizingType == Qgis::WkbType::CircularString && mTempRubberBand->stringType() == Qgis::WkbType::CircularString && mTempRubberBand->curveIsComplete() )
793 mCircularItermediatePoint = mTempRubberBand->pointFromEnd( 1 );
794 else if ( mLineDigitizingType == Qgis::WkbType::LineString || !mTempRubberBand->curveIsComplete() )
795 mCircularItermediatePoint = QgsPoint();
796
797 hasTrace = tracingMouseMove( e );
798
799 if ( !hasTrace )
800 {
801 // Restore the temp rubber band
802 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
803 mTempRubberBand->addPoint( mCaptureLastPoint );
804 if ( !mCircularItermediatePoint.isEmpty() )
805 {
806 mTempRubberBand->movePoint( mCircularItermediatePoint );
807 mTempRubberBand->addPoint( mCircularItermediatePoint );
808 }
809 }
810 }
811
812 if ( mCurrentCaptureTechnique != Qgis::CaptureTechnique::Streaming && !hasTrace )
813 {
814 if ( mCaptureCurve.numPoints() > 0 )
815 {
816 const QgsPoint mapPt = mCaptureLastPoint;
817
818 if ( mTempRubberBand )
819 {
820 mTempRubberBand->movePoint( mapPoint );
821 mTempRubberBand->movePoint( 0, mapPt );
822 }
823
824 // fix existing rubber band after tracing - the last point may have been moved if using offset
825 if ( mRubberBand->numberOfVertices() )
826 mRubberBand->movePoint( mapPt );
827
828 std::unique_ptr< QgsCompoundCurve > tempCurve( mCaptureCurve.clone() );
829
830 // add mouse hover point to current captured geometry
831 try
832 {
833 QgsPoint hoverPointTargetCrs = mapPoint;
834 hoverPointTargetCrs.transform( QgsCoordinateTransform( mCanvas->mapSettings().destinationCrs(), targetCrs, QgsProject::instance()->transformContext() ) );
835 tempCurve->addCurve( new QgsLineString( tempCurve->endPoint(), hoverPointTargetCrs ) );
836 }
837 catch ( QgsCsException &e )
838 {
839 QgsDebugError( e.what() );
840 }
841
842 if ( mCaptureMode == CapturePolygon )
843 {
844 auto curvePolygon = std::make_unique< QgsCurvePolygon >();
845 tempCurve->close();
846 curvePolygon->setExteriorRing( tempCurve.release() );
847 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( curvePolygon ) ), targetCrs ) );
848 }
849 else
850 {
851 emit transientGeometryChanged( QgsReferencedGeometry( QgsGeometry( std::move( tempCurve ) ), targetCrs ) );
852 }
853 }
854 else if ( mTempRubberBand )
855 mTempRubberBand->movePoint( mapPoint );
856 }
857 }
858 }
859} // mouseMoveEvent
860
861
863{
864 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
865 {
866 try
867 {
868 QgsPointXY mapP( mapPoint.x(), mapPoint.y() ); //#spellok
869 const bool is3D = layerPoint.is3D();
870 const bool isMeasure = layerPoint.isMeasure();
871 mapP = toLayerCoordinates( vlayer, mapP ); //transform snapped point back to layer crs //#spellok
872 layerPoint = QgsPoint( layerPoint.wkbType(), mapP.x(), mapP.y(), layerPoint.z(), layerPoint.m() ); //#spellok
873 if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !is3D )
874 layerPoint.addZValue( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->currentPointV2().z() : defaultZValue() );
875 if ( QgsWkbTypes::hasM( vlayer->wkbType() ) && !isMeasure )
876 layerPoint.addMValue( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->currentPointV2().m() : defaultMValue() );
877 }
878 catch ( QgsCsException & )
879 {
880 QgsDebugError( u"transformation to layer coordinate failed"_s );
881 return 2;
882 }
883 }
884 else
885 {
886 layerPoint = QgsPoint( toLayerCoordinates( layer(), mapPoint ) );
887 }
888
889 return 0;
890}
891
893{
895 return nextPoint( mapPoint, layerPoint );
896}
897
899{
900 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
901 QgsVectorLayer *sourceLayer = match.layer();
902 if ( mCadDockWidget && mCadDockWidget->cadEnabled() )
903 {
904 layerPoint = mCadDockWidget->currentPointLayerCoordinates( layer() );
905 return 0;
906 }
907 else if ( !vlayer )
908 {
909 return 1;
910 }
911
912 if ( match.isValid() && sourceLayer )
913 {
914 if ( ( match.hasVertex() || match.hasLineEndpoint() ) )
915 {
916 if ( sourceLayer->crs() != vlayer->crs() )
917 {
918 layerPoint = match.interpolatedPoint();
919 return 1;
920 }
921 QgsFeature f;
922 QgsFeatureRequest request;
923 request.setFilterFid( match.featureId() );
924 const bool fetched = match.layer()->getFeatures( request ).nextFeature( f );
925 if ( fetched )
926 {
927 QgsVertexId vId;
928 if ( !f.geometry().vertexIdFromVertexNr( match.vertexIndex(), vId ) )
929 {
930 return 2;
931 }
932 layerPoint = f.geometry().constGet()->vertexAt( vId );
933 if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !layerPoint.is3D() )
934 layerPoint.addZValue( defaultZValue() );
935 if ( QgsWkbTypes::hasM( vlayer->wkbType() ) && !layerPoint.isMeasure() )
936 layerPoint.addMValue( defaultMValue() );
937
938 // ZM support depends on the target layer
939 if ( !QgsWkbTypes::hasZ( vlayer->wkbType() ) )
940 {
941 layerPoint.dropZValue();
942 }
943
944 if ( !QgsWkbTypes::hasM( vlayer->wkbType() ) )
945 {
946 layerPoint.dropMValue();
947 }
948
949 return 0;
950 }
951 return 2;
952 }
953 else if ( QgsProject::instance()->topologicalEditing() && ( match.hasEdge() || match.hasMiddleSegment() ) )
954 {
955 layerPoint = toLayerCoordinates( vlayer, match.interpolatedPoint( mCanvas->mapSettings().destinationCrs() ) );
956 return 0;
957 }
958 }
959 return 2;
960}
961
963{
964 return addVertex( point, QgsPointLocator::Match() );
965}
966
968{
969 if ( mode() == CaptureNone )
970 {
971 QgsDebugError( u"invalid capture mode"_s );
972 return 2;
973 }
974
975 if ( mCapturing && mCurrentCaptureTechnique == Qgis::CaptureTechnique::Streaming && !mAllowAddingStreamingPoints )
976 return 0;
977
978 QgsPoint layerPoint;
979 if ( layer() )
980 {
981 int res = fetchLayerPoint( match, layerPoint );
982 if ( res != 0 )
983 {
984 res = nextPoint( QgsPoint( point ), layerPoint );
985 if ( res != 0 )
986 {
987 return res;
988 }
989 }
990 }
991 else
992 {
993 layerPoint = QgsPoint( point );
994 }
995 const QgsPoint mapPoint = toMapCoordinates( layer(), layerPoint );
996
997 if ( mCaptureMode == CapturePoint )
998 {
999 mCaptureCurve.addVertex( layerPoint );
1000 mSnappingMatches.append( match );
1001 }
1002 else
1003 {
1004 if ( mCaptureFirstPoint.isEmpty() )
1005 {
1006 mCaptureFirstPoint = mapPoint;
1007 }
1008
1009 if ( !mRubberBand )
1010 mRubberBand.reset( createRubberBand( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line ) );
1011
1012 if ( !mTempRubberBand )
1013 {
1014 mTempRubberBand.reset( createCurveRubberBand() );
1015 mTempRubberBand->setStringType( mLineDigitizingType );
1016 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mapPoint );
1017 }
1018
1019 bool traceCreated = false;
1020 if ( tracingEnabled() )
1021 {
1022 traceCreated = tracingAddVertex( mapPoint );
1023 }
1024
1025 // keep new tracing start point if we created a trace. This is useful when tracing with
1026 // offset so that the user stays "snapped"
1027 mTracingStartPoint = traceCreated ? point : QgsPointXY();
1028
1029 if ( !traceCreated )
1030 {
1031 // ordinary digitizing
1032 mTempRubberBand->movePoint( mapPoint ); //move the last point of the temp rubberband before operating with it
1033 if ( mTempRubberBand->curveIsComplete() ) //2 points for line and 3 points for circular
1034 {
1035 if ( QgsCurve *curve = mTempRubberBand->curve() )
1036 {
1037 addCurve( curve );
1038 // add curve append only invalid match to mSnappingMatches,
1039 // so we need to remove them and add the one from here if it is valid
1040 if ( match.isValid() && mSnappingMatches.count() > 0 && !mSnappingMatches.last().isValid() )
1041 {
1042 mSnappingMatches.removeLast();
1043 if ( mTempRubberBand->stringType() == Qgis::WkbType::CircularString )
1044 {
1045 // for circular string two points are added and match for intermediate point is stored
1046 mSnappingMatches.removeLast();
1047 mSnappingMatches.append( mCircularIntermediateMatch );
1048 }
1049 mSnappingMatches.append( match );
1050 }
1051 }
1052 mCaptureLastPoint = mapPoint;
1053 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
1054 }
1055 else if ( mTempRubberBand->pointsCount() == 0 )
1056 {
1057 mCaptureLastPoint = mapPoint;
1058 mCaptureCurve.addVertex( layerPoint );
1059 mSnappingMatches.append( match );
1060 }
1061 else
1062 {
1063 if ( mTempRubberBand->stringType() == Qgis::WkbType::CircularString )
1064 {
1065 mCircularIntermediateMatch = match;
1066 }
1067 }
1068
1069 mTempRubberBand->addPoint( mapPoint );
1070 }
1071 else
1072 {
1073 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
1074 mTempRubberBand->addPoint( mCaptureLastPoint );
1075 }
1076 }
1077
1078 updateExtraSnapLayer();
1079 validateGeometry();
1080
1081 return 0;
1082}
1083
1085{
1086 if ( !c )
1087 {
1088 return 1;
1089 }
1090
1091 if ( !mRubberBand )
1092 {
1093 mRubberBand.reset( createRubberBand( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line ) );
1094 }
1095
1096 if ( mTempRubberBand )
1097 {
1098 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
1099 const QgsPoint endPt = c->endPoint();
1100 mTempRubberBand->addPoint( endPt ); //add last point of c
1101 }
1102
1103 const int countBefore = mCaptureCurve.vertexCount();
1104 //if there is only one point, this the first digitized point that are in the this first curve added --> remove the point
1105 if ( mCaptureCurve.numPoints() == 1 )
1106 mCaptureCurve.removeCurve( 0 );
1107
1108 // Transform back to layer CRS in case map CRS and layer CRS are different
1109 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( layer() );
1110 if ( ct.isValid() && !ct.isShortCircuited() )
1111 {
1112 QgsLineString *segmented = c->curveToLine();
1114 // Curve geometries will be converted to segments, so we explicitly set extentPrevious to false
1115 // to be able to remove the whole curve in undo
1116 mCaptureCurve.addCurve( segmented, false );
1117 delete c;
1118 }
1119 else
1120 {
1121 // we set the extendPrevious option to true to avoid creating compound curves with many 2 vertex linestrings -- instead we prefer
1122 // to extend linestring curves so that they continue the previous linestring wherever possible...
1123 mCaptureCurve.addCurve( c, !mStartNewCurve );
1124 }
1125
1126 mStartNewCurve = false;
1127
1128 const int countAfter = mCaptureCurve.vertexCount();
1129 const int addedPoint = countAfter - countBefore;
1130
1131 updateExtraSnapLayer();
1132
1133 for ( int i = 0; i < addedPoint; ++i )
1134 mSnappingMatches.append( QgsPointLocator::Match() );
1135
1136 resetRubberBand();
1137
1138 return 0;
1139}
1140
1142{
1143 mCaptureCurve.clear();
1144 updateExtraSnapLayer();
1145}
1146
1147QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
1148{
1149 return mSnappingMatches;
1150}
1151
1152void QgsMapToolCapture::undo( bool isAutoRepeat )
1153{
1154 mTracingStartPoint = QgsPointXY();
1155
1156 // Handle Poly-Bézier mode: delete the last anchor with its handles
1157 // This must be checked before the standard size() check since Poly-Bézier
1158 // doesn't use mCaptureCurve during capture
1159 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::PolyBezier
1160 && mBezierData && mBezierData->anchorCount() > 0 )
1161 {
1162 mBezierData->deleteAnchor( mBezierData->anchorCount() - 1 );
1163 if ( mBezierMarker )
1164 mBezierMarker->updateFromData( *mBezierData );
1165 // Reset drag state
1166 mBezierDragging = false;
1167 mBezierDragAnchorIndex = -1;
1168 mBezierDragHandleIndex = -1;
1169 mBezierMoveAnchorIndex = -1;
1170 mCadDockWidget->removePreviousPoint();
1171 return;
1172 }
1173
1174 if ( mTempRubberBand )
1175 {
1176 // Handle NURBS ControlPoints mode: remove last control point
1177 // This must be checked before the standard size() check since NURBS ControlPoints
1178 // doesn't use mCaptureCurve during capture
1179 if ( mTempRubberBand->stringType() == Qgis::WkbType::NurbsCurve && mTempRubberBand->pointsCount() > 1 )
1180 {
1181 const QgsPoint lastPoint = mTempRubberBand->lastPoint();
1182 mTempRubberBand->removeLastPoint();
1183 mTempRubberBand->movePoint( lastPoint );
1184 mCadDockWidget->removePreviousPoint();
1185 return;
1186 }
1187
1188 if ( size() <= 1 && mTempRubberBand->pointsCount() != 0 )
1189 return;
1190
1191 if ( isAutoRepeat && mIgnoreSubsequentAutoRepeatUndo )
1192 return;
1193 mIgnoreSubsequentAutoRepeatUndo = false;
1194
1195 const QgsPoint lastPoint = mTempRubberBand->lastPoint();
1196
1197 if ( mTempRubberBand->stringType() == Qgis::WkbType::CircularString && mTempRubberBand->pointsCount() > 2 )
1198 {
1199 mTempRubberBand->removeLastPoint();
1200 mTempRubberBand->movePoint( lastPoint );
1201 return;
1202 }
1203
1204 // Handle NURBS ControlPoints mode: remove last control point
1205 if ( QgsWkbTypes::isNurbsType( mTempRubberBand->stringType() ) && mTempRubberBand->pointsCount() > 1 )
1206 {
1207 mTempRubberBand->removeLastPoint();
1208 mTempRubberBand->movePoint( lastPoint );
1209 mCadDockWidget->removePreviousPoint();
1210 return;
1211 }
1212
1213 QgsVertexId vertexToRemove;
1214 vertexToRemove.part = 0;
1215 vertexToRemove.ring = 0;
1216 vertexToRemove.vertex = size() - 1;
1217
1218 // If the geometry was reprojected, remove the entire last curve.
1219 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( layer() );
1220 if ( ct.isValid() && !ct.isShortCircuited() )
1221 {
1222 mCaptureCurve.removeCurve( mCaptureCurve.nCurves() - 1 );
1223 }
1224 if ( mCaptureCurve.numPoints() == 2 && mCaptureCurve.nCurves() == 1 )
1225 {
1226 // store the first vertex to restore if after deleting the curve
1227 // because when only two vertices, removing a point remove all the curve
1228 const QgsPoint fp = mCaptureCurve.startPoint();
1229 mCaptureCurve.deleteVertex( vertexToRemove );
1230 mCaptureCurve.addVertex( fp );
1231 }
1232 else
1233 {
1234 const int curvesBefore = mCaptureCurve.nCurves();
1235 const bool lastCurveIsLineString = qgsgeometry_cast<const QgsLineString *>( mCaptureCurve.curveAt( curvesBefore - 1 ) );
1236
1237 const int pointsCountBefore = mCaptureCurve.numPoints();
1238 mCaptureCurve.deleteVertex( vertexToRemove );
1239 int pointsCountAfter = mCaptureCurve.numPoints();
1240 for ( ; pointsCountAfter < pointsCountBefore; pointsCountAfter++ )
1241 if ( !mSnappingMatches.empty() )
1242 mSnappingMatches.removeLast();
1243
1244 // if we have removed the last point in a linestring curve, then we "stick" here and ignore subsequent
1245 // autorepeat undo actions until the user releases the undo key and holds it down again. This allows
1246 // users to selectively remove portions of the geometry captured with the streaming mode by holding down
1247 // the undo key, without risking accidental undo of non-streamed portions.
1248 if ( mCaptureCurve.nCurves() < curvesBefore && lastCurveIsLineString )
1249 mIgnoreSubsequentAutoRepeatUndo = true;
1250 }
1251
1252 updateExtraSnapLayer();
1253
1254 resetRubberBand();
1255
1256 mTempRubberBand->reset( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line, mLineDigitizingType, mCaptureFirstPoint );
1257
1258 if ( mCaptureCurve.numPoints() > 0 )
1259 {
1260 const QgsPoint lastPt = mCaptureCurve.endPoint();
1261 mCaptureLastPoint = toMapCoordinates( layer(), lastPt );
1262 mTempRubberBand->addPoint( mCaptureLastPoint );
1263 mTempRubberBand->movePoint( lastPoint );
1264 }
1265
1266 mCadDockWidget->removePreviousPoint();
1267 validateGeometry();
1268 }
1269}
1270
1272{
1273 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
1274 {
1275 mCurrentShapeMapTool->keyPressEvent( e );
1276 if ( e->isAccepted() )
1277 return;
1278 }
1279
1280 // this is backwards, but we can't change now without breaking api because
1281 // forever QgsMapTools have had to explicitly mark events as ignored in order to
1282 // indicate that they've consumed the event and that the default behavior should not
1283 // be applied..!
1284 // see QgsMapCanvas::keyPressEvent
1285 e->accept();
1286
1287 if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete )
1288 {
1289 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
1290 {
1291 if ( !e->isAutoRepeat() )
1292 {
1293 mCurrentShapeMapTool->undo();
1294 }
1295 }
1296 else
1297 {
1298 undo( e->isAutoRepeat() );
1299 }
1300
1301 // Override default shortcut management in MapCanvas
1302 e->ignore();
1303 }
1304 else if ( e->key() == Qt::Key_Escape )
1305 {
1306 if ( mCurrentShapeMapTool )
1307 mCurrentShapeMapTool->clean();
1308
1309 stopCapturing();
1310
1311 // Override default shortcut management in MapCanvas
1312 e->ignore();
1313 }
1314 else if ( e->key() == Qt::Key_W && !e->isAutoRepeat() )
1315 {
1316 // Enable NURBS weight editing mode when W is pressed
1317 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && mTempRubberBand && mTempRubberBand->pointsCount() >= 2 )
1318 {
1319 mWeightEditMode = true;
1320 // Edit the last control point by default (the one being digitized)
1321 mWeightEditControlPointIndex = mTempRubberBand->pointsCount() - 2; // -2 because last point is the cursor position
1322
1323 // Enable and update weight via CAD dock widget (which will notify the floater)
1324 if ( cadDockWidget() )
1325 {
1326 cadDockWidget()->setWeight( QString::number( mTempRubberBand->weight( mWeightEditControlPointIndex ), 'f', 2 ), true );
1327 }
1328 e->ignore();
1329 }
1330 }
1331}
1332
1334{
1335 if ( e->key() == Qt::Key_W && !e->isAutoRepeat() )
1336 {
1337 if ( mWeightEditMode )
1338 {
1339 mWeightEditMode = false;
1340 mWeightEditControlPointIndex = -1;
1341
1342 // Disable weight editing via CAD dock widget
1343 if ( cadDockWidget() )
1344 {
1345 cadDockWidget()->setWeight( QString(), false );
1346 }
1347
1348 e->accept();
1349 return;
1350 }
1351 }
1352
1354}
1355
1356void QgsMapToolCapture::wheelEvent( QWheelEvent *e )
1357{
1358 if ( mWeightEditMode )
1359 {
1360 // Adjust weight with mouse wheel
1361 // Base adjustment: 0.1 per wheel step
1362 // Ctrl modifier: fine adjustment (0.01 per step)
1363 // Shift modifier: coarse adjustment (1.0 per step)
1364 double adjustment = e->angleDelta().y() > 0 ? 0.1 : -0.1;
1365 if ( e->modifiers() & Qt::ControlModifier )
1366 adjustment *= 0.1;
1367 else if ( e->modifiers() & Qt::ShiftModifier )
1368 adjustment *= 10.0;
1369
1370 const double currentWeight = mTempRubberBand->weight( mWeightEditControlPointIndex );
1371 const double newWeight = std::max( 0.01, currentWeight + adjustment );
1372
1373 if ( mTempRubberBand->setWeight( mWeightEditControlPointIndex, newWeight ) )
1374 {
1375 if ( cadDockWidget() )
1376 {
1377 cadDockWidget()->setWeight( QString::number( newWeight, 'f', 2 ), true );
1378 }
1379 }
1380
1381 e->accept();
1382 return;
1383 }
1384
1386}
1387
1389{
1390 mCapturing = true;
1391}
1392
1394{
1395 return mCapturing;
1396}
1397
1399{
1400 mRubberBand.reset();
1401
1403
1404 // Reset weight editing mode when stopping capture
1405 if ( mWeightEditMode )
1406 {
1407 mWeightEditMode = false;
1408 mWeightEditControlPointIndex = -1;
1409 if ( cadDockWidget() )
1410 {
1411 cadDockWidget()->setWeight( QString(), false );
1412 }
1413 }
1414
1415 qDeleteAll( mGeomErrorMarkers );
1416 mGeomErrorMarkers.clear();
1417 mGeomErrors.clear();
1418
1419 mCaptureFirstPoint = QgsPoint();
1420 mCaptureLastPoint = QgsPoint();
1421
1422 mTracingStartPoint = QgsPointXY();
1423
1424 mCapturing = false;
1425 mCaptureCurve.clear();
1426 updateExtraSnapLayer();
1427 mSnappingMatches.clear();
1428
1429 // Clean up Bézier digitizing data
1430 if ( mBezierMarker )
1431 mBezierMarker->clear();
1432 mBezierData.reset();
1433 mBezierMarker.reset();
1434 mBezierDragging = false;
1435 mBezierDragAnchorIndex = -1;
1436
1437 if ( auto *lCurrentVectorLayer = currentVectorLayer() )
1438 lCurrentVectorLayer->triggerRepaint();
1439
1441}
1442
1444{
1445 mTempRubberBand.reset();
1446}
1447
1449{
1450 stopCapturing();
1451 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape && mCurrentShapeMapTool )
1452 mCurrentShapeMapTool->clean();
1453
1454 clearCurve();
1455}
1456
1458{
1459 mCaptureCurve.close();
1460 updateExtraSnapLayer();
1461}
1462
1463void QgsMapToolCapture::validateGeometry()
1464{
1466 || !( capabilities() & ValidateGeometries ) )
1467 return;
1468
1469 if ( mValidator )
1470 {
1471 mValidator->deleteLater();
1472 mValidator = nullptr;
1473 }
1474
1475 mGeomErrors.clear();
1476 while ( !mGeomErrorMarkers.isEmpty() )
1477 {
1478 delete mGeomErrorMarkers.takeFirst();
1479 }
1480
1481 QgsGeometry geom;
1482
1483 switch ( mCaptureMode )
1484 {
1485 case CaptureNone:
1486 case CapturePoint:
1487 return;
1488 case CaptureLine:
1489 if ( size() < 2 )
1490 return;
1491 geom = QgsGeometry( mCaptureCurve.curveToLine() );
1492 break;
1493 case CapturePolygon:
1494 if ( size() < 3 )
1495 return;
1496 QgsLineString *exteriorRing = mCaptureCurve.curveToLine();
1497 exteriorRing->close();
1498 QgsPolygon *polygon = new QgsPolygon();
1499 polygon->setExteriorRing( exteriorRing );
1500 geom = QgsGeometry( polygon );
1501 break;
1502 }
1503
1504 if ( geom.isNull() )
1505 return;
1506
1510 mValidator = new QgsGeometryValidator( geom, nullptr, method );
1511 connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError );
1512 mValidator->start();
1513 QgsDebugMsgLevel( u"Validation started"_s, 4 );
1514}
1515
1516void QgsMapToolCapture::addError( const QgsGeometry::Error &e )
1517{
1518 mGeomErrors << e;
1519 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1520 if ( !vlayer )
1521 return;
1522
1523 if ( e.hasWhere() )
1524 {
1525 QgsVertexMarker *vm = new QgsVertexMarker( mCanvas );
1526 vm->setCenter( mCanvas->mapSettings().layerToMapCoordinates( vlayer, e.where() ) );
1528 vm->setPenWidth( 2 );
1529 vm->setToolTip( e.what() );
1530 vm->setColor( Qt::green );
1531 vm->setZValue( vm->zValue() + 1 );
1532 mGeomErrorMarkers << vm;
1533 }
1534}
1535
1537{
1538 return mCaptureCurve.numPoints();
1539}
1540
1541QVector<QgsPointXY> QgsMapToolCapture::points() const
1542{
1543 QVector<QgsPointXY> pointsXY;
1545
1546 return pointsXY;
1547}
1548
1550{
1551 QgsPointSequence pts;
1552 mCaptureCurve.points( pts );
1553 return pts;
1554}
1555
1556void QgsMapToolCapture::setPoints( const QVector<QgsPointXY> &pointList )
1557{
1558 QgsLineString *line = new QgsLineString( pointList );
1559 mCaptureCurve.clear();
1560 mCaptureCurve.addCurve( line );
1561 updateExtraSnapLayer();
1562 mSnappingMatches.clear();
1563 for ( int i = 0; i < line->length(); ++i )
1564 mSnappingMatches.append( QgsPointLocator::Match() );
1565 resetRubberBand();
1566}
1567
1569{
1570 QgsLineString *line = new QgsLineString( pointList );
1571 mCaptureCurve.clear();
1572 mCaptureCurve.addCurve( line );
1573 updateExtraSnapLayer();
1574 mSnappingMatches.clear();
1575 for ( int i = 0; i < line->length(); ++i )
1576 mSnappingMatches.append( QgsPointLocator::Match() );
1577 resetRubberBand();
1578}
1579
1581{
1582 QgsPoint newPoint( Qgis::WkbType::Point, point.x(), point.y() );
1583
1584 // get current layer
1585 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1586 if ( !vlayer )
1587 {
1588 return newPoint;
1589 }
1590
1591 // convert to the corresponding type for a full ZM support
1592 const Qgis::WkbType type = vlayer->wkbType();
1593 if ( QgsWkbTypes::hasZ( type ) && !QgsWkbTypes::hasM( type ) )
1594 {
1595 newPoint.convertTo( Qgis::WkbType::PointZ );
1596 }
1597 else if ( !QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1598 {
1599 newPoint.convertTo( Qgis::WkbType::PointM );
1600 }
1601 else if ( QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1602 {
1604 }
1605
1606 // set z value if necessary
1607 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) )
1608 {
1609 newPoint.setZ( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->getLineZ() : defaultZValue() );
1610 }
1611 // set m value if necessary
1612 if ( QgsWkbTypes::hasM( newPoint.wkbType() ) )
1613 {
1614 newPoint.setM( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->getLineM() : defaultMValue() );
1615 }
1616 return newPoint;
1617}
1618
1620{
1621 QgsPoint newPoint = mapPoint( e.mapPoint() );
1622
1623 // set z or m value from snapped point if necessary
1624 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) || QgsWkbTypes::hasM( newPoint.wkbType() ) )
1625 {
1626 // if snapped, z and m dimension are taken from the corresponding snapped
1627 // point.
1628 if ( e.isSnapped() )
1629 {
1630 const QgsPointLocator::Match match = e.mapPointMatch();
1631
1632 if ( match.layer() )
1633 {
1634 const QgsFeature ft = match.layer()->getFeature( match.featureId() );
1635 if ( QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
1636 {
1637 newPoint.setZ( ft.geometry().vertexAt( match.vertexIndex() ).z() );
1638 }
1639 if ( QgsWkbTypes::hasM( match.layer()->wkbType() ) )
1640 {
1641 newPoint.setM( ft.geometry().vertexAt( match.vertexIndex() ).m() );
1642 }
1643 }
1644 }
1645 }
1646
1647 return newPoint;
1648}
1649
1650void QgsMapToolCapture::updateExtraSnapLayer()
1651{
1652 if ( !mExtraSnapLayer )
1653 return;
1654
1655 if ( canvas()->snappingUtils()->config().selfSnapping() && layer() )
1656 {
1657 // the current layer may have changed
1658 mExtraSnapLayer->setCrs( layer()->crs() );
1659
1660 QgsGeometry geom;
1661
1662 // For NURBS curves, include both the evaluated curve and control points for snapping
1663 if ( mLineDigitizingType == Qgis::WkbType::NurbsCurve && mTempRubberBand && mTempRubberBand->pointsCount() >= 2 )
1664 {
1665 // Create a GeometryCollection containing control points and evaluated curve
1666 auto collection = std::make_unique<QgsGeometryCollection>();
1667
1668 // Add control points as individual Point geometries
1669 const int pointCount = mTempRubberBand->pointsCount();
1670 // Exclude the last point (cursor position)
1671 for ( int i = 0; i < pointCount - 1; ++i )
1672 {
1673 collection->addGeometry( new QgsPoint( mTempRubberBand->pointFromEnd( pointCount - 1 - i ) ) );
1674 }
1675
1676 // Add the evaluated curve as a LineString
1677 std::unique_ptr<QgsCurve> nurbsCurve( mTempRubberBand->curve() );
1678 if ( nurbsCurve )
1679 {
1680 std::unique_ptr<QgsLineString> curvePoints( nurbsCurve->curveToLine() );
1681 if ( curvePoints )
1682 {
1683 // For polygon mode, close the curve to allow snapping to first point
1684 if ( mCaptureMode == CapturePolygon && curvePoints->numPoints() >= 3 )
1685 {
1686 curvePoints->close();
1687 }
1688 collection->addGeometry( curvePoints.release() );
1689 }
1690 }
1691
1692 geom = QgsGeometry( collection.release() );
1693 }
1694 else if ( mBezierData && mBezierData->anchorCount() >= 2 )
1695 {
1696 // Poly-Bézier mode: create a GeometryCollection containing anchors, handles, and interpolated curve
1697 auto collection = std::make_unique<QgsGeometryCollection>();
1698
1699 // Add all anchors as individual Point geometries
1700 const QVector<QgsPoint> anchors = mBezierData->anchors();
1701 for ( const QgsPoint &point : anchors )
1702 {
1703 collection->addGeometry( new QgsPoint( point ) );
1704 }
1705
1706 // Add all handles as individual Point geometries
1707 const QVector<QgsPoint> handles = mBezierData->handles();
1708 for ( const QgsPoint &point : handles )
1709 {
1710 collection->addGeometry( new QgsPoint( point ) );
1711 }
1712
1713 // Add interpolated curve as a LineString
1714 const QgsPointSequence interpolated = mBezierData->interpolateLine();
1715 if ( !interpolated.isEmpty() )
1716 {
1717 auto curveLineString = std::make_unique<QgsLineString>( interpolated );
1718 // For polygon mode, close the curve to allow snapping to first point
1719 if ( mCaptureMode == CapturePolygon && curveLineString->numPoints() >= 3 )
1720 {
1721 curveLineString->close();
1722 }
1723 collection->addGeometry( curveLineString.release() );
1724 }
1725
1726 geom = QgsGeometry( collection.release() );
1727 }
1728 else if ( mCaptureCurve.numPoints() >= 2 )
1729 {
1730 // Standard capture curve
1731 geom = QgsGeometry( mCaptureCurve.clone() );
1732 // we close the curve to allow snapping on last segment
1733 if ( mCaptureMode == CapturePolygon && mCaptureCurve.numPoints() >= 3 )
1734 {
1735 qgsgeometry_cast<QgsCompoundCurve *>( geom.get() )->close();
1736 }
1737 }
1738
1739 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1740 }
1741 else
1742 {
1743 QgsGeometry geom;
1744 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1745 }
1746}
1747
1748
1750{
1751 // POINT CAPTURING
1752 if ( mode() == CapturePoint )
1753 {
1754 if ( e->button() != Qt::LeftButton )
1755 return;
1756
1757 QgsPoint savePoint; //point in layer coordinates
1758 bool isMatchPointZ = false;
1759 bool isMatchPointM = false;
1760 try
1761 {
1762 QgsPoint fetchPoint;
1763 int res = fetchLayerPoint( e->mapPointMatch(), fetchPoint );
1764 isMatchPointZ = QgsWkbTypes::hasZ( fetchPoint.wkbType() );
1765 isMatchPointM = QgsWkbTypes::hasM( fetchPoint.wkbType() );
1766
1767 if ( res == 0 )
1768 {
1770 if ( isMatchPointM && isMatchPointZ )
1771 {
1772 geomType = Qgis::WkbType::PointZM;
1773 }
1774 else if ( isMatchPointM )
1775 {
1776 geomType = Qgis::WkbType::PointM;
1777 }
1778 else if ( isMatchPointZ )
1779 {
1780 geomType = Qgis::WkbType::PointZ;
1781 }
1782 savePoint = QgsPoint( geomType, fetchPoint.x(), fetchPoint.y(), fetchPoint.z(), fetchPoint.m() );
1783 }
1784 else
1785 {
1786 QgsPointXY point = mCanvas->mapSettings().mapToLayerCoordinates( layer(), e->mapPoint() );
1787
1788 savePoint = QgsPoint( point.x(), point.y(), fetchPoint.z(), fetchPoint.m() );
1789 }
1790 }
1791 catch ( QgsCsException &cse )
1792 {
1793 Q_UNUSED( cse )
1794 emit messageEmitted( tr( "Cannot transform the point to the layer's coordinate system" ), Qgis::MessageLevel::Warning );
1795 return;
1796 }
1797
1798 QgsGeometry g( std::make_unique<QgsPoint>( savePoint ) );
1799
1800 // The snapping result needs to be added so it's available in the @snapping_results variable of default value etc. expression contexts
1801 addVertex( e->mapPoint(), e->mapPointMatch() );
1802
1803 geometryCaptured( g );
1804 pointCaptured( savePoint );
1805
1806 stopCapturing();
1807
1808 // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points
1810 }
1811
1812 // LINE AND POLYGON CAPTURING
1813 else if ( mode() == CaptureLine || mode() == CapturePolygon )
1814 {
1815 bool digitizingFinished = false;
1816 QgsPointSequence nurbsControlPoints;
1817 QVector<double> nurbsWeights;
1818
1819 // Poly-Bézier mode handling
1820 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::PolyBezier )
1821 {
1822 if ( e->button() == Qt::LeftButton )
1823 {
1824 // End dragging on mouse release
1825 mBezierDragging = false;
1826 mBezierDragAnchorIndex = -1;
1827 mBezierDragHandleIndex = -1;
1828 mBezierMoveAnchorIndex = -1;
1829
1830 // Clear highlights
1831 if ( mBezierMarker )
1832 {
1833 mBezierMarker->setHighlightedAnchor( -1 );
1834 mBezierMarker->setHighlightedHandle( -1 );
1835 mBezierMarker->updateFromData( *mBezierData );
1836 }
1837
1838 return;
1839 }
1840 else if ( e->button() == Qt::RightButton )
1841 {
1842 // End dragging
1843 mBezierDragging = false;
1844 mBezierDragAnchorIndex = -1;
1845 mBezierDragHandleIndex = -1;
1846 mBezierMoveAnchorIndex = -1;
1847
1848 if ( mBezierData && mBezierData->anchorCount() >= 2 )
1849 {
1850 // Convert Poly-Bézier to NurbsCurve
1851 std::unique_ptr<QgsNurbsCurve> nurbsCurve = mBezierData->asNurbsCurve();
1852 if ( nurbsCurve )
1853 {
1854 // Transform to layer coordinates if a layer is present
1855 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1856 if ( vlayer )
1857 {
1858 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( vlayer );
1859 if ( ct.isValid() && !ct.isShortCircuited() )
1860 {
1861 try
1862 {
1863 nurbsCurve->transform( ct, Qgis::TransformDirection::Reverse );
1864 }
1865 catch ( QgsCsException & )
1866 {
1867 emit messageEmitted( tr( "Cannot transform the geometry to layer coordinates" ), Qgis::MessageLevel::Warning );
1868 stopCapturing();
1869 return;
1870 }
1871 }
1872 }
1873
1874 std::unique_ptr<QgsCurve> curveToAdd;
1875
1876 // Close for polygon if needed
1877 if ( mode() == CapturePolygon && !nurbsCurve->isClosed() )
1878 {
1879 // For polygon, wrap in compound curve and add closing segment
1880 auto compound = std::make_unique<QgsCompoundCurve>();
1881 compound->addCurve( nurbsCurve.release() );
1882 // Add closing line segment from end to start
1883 auto closingSegment = std::make_unique<QgsLineString>();
1884 closingSegment->addVertex( compound->endPoint() );
1885 closingSegment->addVertex( compound->startPoint() );
1886 compound->addCurve( closingSegment.release() );
1887 curveToAdd = std::move( compound );
1888 }
1889 else
1890 {
1891 curveToAdd.reset( nurbsCurve.release() );
1892 }
1893 QgsGeometry g;
1894
1895 if ( mode() == CaptureLine )
1896 {
1897 g = QgsGeometry( curveToAdd->clone() );
1898 geometryCaptured( g );
1899 lineCaptured( curveToAdd.release() );
1900 }
1901 else // CapturePolygon
1902 {
1903 auto poly = std::make_unique<QgsCurvePolygon>();
1904 poly->setExteriorRing( curveToAdd.release() );
1905 g = QgsGeometry( poly->clone() );
1906 geometryCaptured( g );
1907 polygonCaptured( poly.get() );
1908 }
1909
1910 digitizingFinished = true;
1911 }
1912 }
1913
1914 // Clean up Bézier data
1915 if ( mBezierMarker )
1916 mBezierMarker->clear();
1917 mBezierData.reset();
1918 mBezierMarker.reset();
1919 stopCapturing();
1920 return;
1921 }
1922 return;
1923 }
1924 else if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
1925 {
1926 if ( !mCurrentShapeMapTool )
1927 {
1928 emit messageEmitted( tr( "Select an option from the Shape Digitizing Toolbar in order to capture shapes" ), Qgis::MessageLevel::Warning );
1929 return;
1930 }
1931 else
1932 {
1933 if ( !mTempRubberBand )
1934 {
1935 mTempRubberBand.reset( createCurveRubberBand() );
1936 mTempRubberBand->setStringType( mLineDigitizingType );
1937 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
1938 }
1939
1940 digitizingFinished = mCurrentShapeMapTool->cadCanvasReleaseEvent( e, mCaptureMode );
1941 if ( digitizingFinished )
1942 mCurrentShapeMapTool->clean();
1943 }
1944 }
1945 else // i.e. not shape
1946 {
1947 //add point to list and to rubber band
1948 if ( e->button() == Qt::LeftButton )
1949 {
1950 const int error = addVertex( e->mapPoint(), e->mapPointMatch() );
1951 if ( error == 2 )
1952 {
1953 //problem with coordinate transformation
1954 emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::MessageLevel::Warning );
1955 return;
1956 }
1957
1959 }
1960 else if ( e->button() == Qt::RightButton )
1961 {
1962 // End of string
1963
1964 // Extract NURBS control points and weights from the rubberband before deleting it
1965 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && mTempRubberBand )
1966 {
1967 const int rbPointCount = mTempRubberBand->pointsCount();
1968 if ( rbPointCount > 1 )
1969 {
1970 // Exclude the last point (cursor position)
1971 for ( int i = 0; i < rbPointCount - 1; ++i )
1972 {
1973 nurbsControlPoints.append( mTempRubberBand->pointFromEnd( rbPointCount - 1 - i ) );
1974 }
1975 // Also extract weights (in correct order)
1976 const QVector<double> &rbWeights = mTempRubberBand->weights();
1977 for ( int i = 0; i < rbPointCount - 1; ++i )
1978 {
1979 if ( i < rbWeights.size() )
1980 nurbsWeights.append( rbWeights[i] );
1981 else
1982 nurbsWeights.append( 1.0 );
1983 }
1984 }
1985 }
1986
1988
1989 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve )
1990 {
1991 // Minimum 4 control points required for degree 3 NURBS
1992 if ( mode() == CaptureLine && nurbsControlPoints.count() < 4 )
1993 {
1994 stopCapturing();
1995 return;
1996 }
1997 if ( mode() == CapturePolygon && nurbsControlPoints.count() < 4 )
1998 {
1999 stopCapturing();
2000 return;
2001 }
2002 }
2003 else
2004 {
2005 //lines: bail out if there are not at least two vertices
2006 if ( mode() == CaptureLine && size() < 2 )
2007 {
2008 stopCapturing();
2009 return;
2010 }
2011
2012 //polygons: bail out if there are not at least two vertices
2013 if ( mode() == CapturePolygon && size() < 3 )
2014 {
2015 stopCapturing();
2016 return;
2017 }
2018 }
2019
2020 if ( mode() == CapturePolygon || e->modifiers() == Qt::ShiftModifier )
2021 {
2022 // Close NURBS curve by adding first control point at the end
2023 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && !nurbsControlPoints.isEmpty() )
2024 {
2025 nurbsControlPoints.append( nurbsControlPoints.first() );
2026 if ( !nurbsWeights.isEmpty() )
2027 nurbsWeights.append( nurbsWeights.first() );
2028 }
2029 else
2030 {
2031 closePolygon();
2032 }
2033 }
2034
2035 digitizingFinished = true;
2036 }
2037 }
2038
2039 if ( digitizingFinished )
2040 {
2041 QgsGeometry g;
2042 std::unique_ptr<QgsCurve> curveToAdd;
2043
2044 // Create a single NurbsCurve from all control points
2045 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve )
2046 {
2047 // Get degree from settings
2049 const int n = nurbsControlPoints.size();
2050
2051 // Adapt degree if not enough control points
2052 if ( n < degree + 1 )
2053 {
2054 degree = std::max( 1, n - 1 );
2055 if ( n < 2 )
2056 {
2057 curveToAdd = std::make_unique<QgsLineString>( nurbsControlPoints );
2058 }
2059 }
2060
2061 if ( !curveToAdd )
2062 {
2063 // Generate uniform clamped knot vector (size = n + degree + 1)
2064 const int knotCount = n + degree + 1;
2065 QVector<double> knots( knotCount );
2066
2067 // First (degree + 1) knots are 0
2068 for ( int i = 0; i <= degree; ++i )
2069 knots[i] = 0.0;
2070
2071 // Last (degree + 1) knots are 1
2072 for ( int i = knotCount - degree - 1; i < knotCount; ++i )
2073 knots[i] = 1.0;
2074
2075 // Middle knots are uniformly spaced
2076 const int numMiddleKnots = n - degree - 1;
2077 for ( int i = 0; i < numMiddleKnots; ++i )
2078 {
2079 knots[degree + 1 + i] = static_cast<double>( i + 1 ) / ( numMiddleKnots + 1 );
2080 }
2081
2082 // Ensure we have the right number of weights
2083 QVector<double> weights = nurbsWeights;
2084 while ( weights.size() < n )
2085 weights.append( 1.0 );
2086 weights.resize( n );
2087
2088 curveToAdd = std::make_unique<QgsNurbsCurve>( nurbsControlPoints, degree, knots, weights );
2089 }
2090
2091 // Transform to layer coordinates if a layer is present
2092 if ( curveToAdd )
2093 {
2094 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
2095 if ( vlayer )
2096 {
2097 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( vlayer );
2098 if ( ct.isValid() && !ct.isShortCircuited() )
2099 {
2100 try
2101 {
2102 curveToAdd->transform( ct, Qgis::TransformDirection::Reverse );
2103 }
2104 catch ( QgsCsException & )
2105 {
2106 emit messageEmitted( tr( "Cannot transform the geometry to layer coordinates" ), Qgis::MessageLevel::Warning );
2107 stopCapturing();
2108 return;
2109 }
2110 }
2111 }
2112 }
2113 }
2114 else
2115 {
2116 curveToAdd.reset( captureCurve()->clone() );
2117 }
2118
2119 if ( mode() == CaptureLine )
2120 {
2121 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
2122 {
2124 {
2126 {
2127 // if there is only one segment the compound curve will be casted to circular string
2128 // otherwise the user will see a warning on the message bar saying that a compound
2129 // curve can't be added on a circular string layer
2130 if ( compound->nCurves() == 1 )
2131 {
2132 if ( const QgsCircularString *circularPart = qgsgeometry_cast<const QgsCircularString *>( compound->curveAt( 0 ) ) )
2133 {
2134 curveToAdd.reset( circularPart->clone() );
2135 }
2136 }
2137 }
2138 }
2139 }
2140
2141 g = QgsGeometry( curveToAdd->clone() );
2142 geometryCaptured( g );
2143 lineCaptured( curveToAdd.release() );
2144 }
2145 else
2146 {
2147 // For NURBS curves, keep the already-created curve
2148 // For other curves, check provider support for curved segments
2149 if ( mCurrentCaptureTechnique != Qgis::CaptureTechnique::NurbsCurve )
2150 {
2151 //does compoundcurve contain circular strings?
2152 //does provider support circular strings?
2153 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
2154 {
2155 const bool hasCurvedSegments = captureCurve()->hasCurvedSegments();
2156 const bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & Qgis::VectorProviderCapability::CircularGeometries;
2157
2158 if ( hasCurvedSegments && providerSupportsCurvedSegments )
2159 {
2160 curveToAdd.reset( captureCurve()->clone() );
2161 }
2162 else
2163 {
2164 curveToAdd.reset( captureCurve()->curveToLine() );
2165 }
2166 }
2167 else
2168 {
2169 curveToAdd.reset( captureCurve()->clone() );
2170 }
2171 }
2172 auto poly = std::make_unique<QgsCurvePolygon>();
2173 poly->setExteriorRing( curveToAdd.release() );
2174 g = QgsGeometry( poly->clone() );
2175 geometryCaptured( g );
2176 polygonCaptured( poly.get() );
2177 }
2178
2179 stopCapturing();
2180 }
2181 }
2182}
@ CircularGeometries
Supports circular geometry types (circularstring, compoundcurve, curvepolygon).
Definition qgis.h:535
CaptureTechnique
Capture technique.
Definition qgis.h:415
@ NurbsCurve
Digitizes NURBS curves with control points (curve is attracted to but does not pass through control p...
Definition qgis.h:421
@ Shape
Digitize shapes.
Definition qgis.h:419
@ StraightSegments
Default capture mode - capture occurs with straight line segments.
Definition qgis.h:416
@ CircularString
Capture in circular strings.
Definition qgis.h:417
@ Streaming
Streaming points digitizing mode (points are automatically added as the mouse cursor moves).
Definition qgis.h:418
@ PolyBezier
Digitizes poly-Bézier curves with anchors and tangent handles (curve passes through anchor points).
Definition qgis.h:420
GeometryValidationEngine
Available engines for validating geometries.
Definition qgis.h:2157
@ QgisInternal
Use internal QgsGeometryValidator method.
Definition qgis.h:2158
@ Geos
Use GEOS validation methods.
Definition qgis.h:2159
@ Warning
Warning message.
Definition qgis.h:160
@ Point
Points.
Definition qgis.h:377
@ Line
Lines.
Definition qgis.h:378
@ Polygon
Polygons.
Definition qgis.h:379
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:291
@ CompoundCurve
CompoundCurve.
Definition qgis.h:302
@ Point
Point.
Definition qgis.h:293
@ LineString
LineString.
Definition qgis.h:294
@ NurbsCurve
NurbsCurve.
Definition qgis.h:308
@ PointM
PointM.
Definition qgis.h:326
@ CircularString
CircularString.
Definition qgis.h:301
@ PointZ
PointZ.
Definition qgis.h:310
@ PointZM
PointZM.
Definition qgis.h:342
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2746
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 setWeight(const QString &value, bool enabled)
Set the weight value for NURBS curves.
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.
Circular string geometry type.
Compound curve geometry type.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
Represents a coordinate reference system (CRS).
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.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
QString what() const
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:60
QgsFeatureId id
Definition qgsfeature.h:68
QgsGeometry geometry
Definition qgsfeature.h:71
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:83
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
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 void cadCanvasPressEvent(QgsMapMouseEvent *e)
Override this method when subclassing this class.
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 transientGeometryChanged(const QgsReferencedGeometry &geometry)
Emitted whenever the geometry associated with the tool is changed, including transient (i....
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.
void wheelEvent(QWheelEvent *e) override
Handles wheel events for NURBS weight editing.
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.
void keyReleaseEvent(QKeyEvent *e) override
Handles key release events for NURBS weight editing mode.
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 cadCanvasPressEvent(QgsMapMouseEvent *e) override
Override this method when subclassing this class.
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).
void transientGeometryChanged(const QgsReferencedGeometry &geometry)
Emitted whenever the geometry associated with the tool is changed, including transient (i....
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:369
friend class QgsMapCanvas
Definition qgsmaptool.h:389
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.
void activated()
Emitted when the map tool is activated.
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
virtual void keyReleaseEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
QPoint toCanvasCoordinates(const QgsPointXY &point) const
Transforms a point from map coordinates to screen coordinates.
virtual void wheelEvent(QWheelEvent *e)
Mouse wheel event for overriding. Default implementation does nothing.
bool isActive() const
Returns if the current map tool active on the map canvas.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition qgspoint.cpp:593
bool dropMValue() override
Drops any measure values which exist in the geometry.
Definition qgspoint.cpp:634
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:582
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
Definition qgspoint.cpp:485
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
void setM(double m)
Sets the point's m-value.
Definition qgspoint.h:398
bool convertTo(Qgis::WkbType type) override
Converts the geometry to a specified type.
Definition qgspoint.cpp:651
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
Definition qgspoint.cpp:408
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:383
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
Definition qgspoint.cpp:623
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
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.
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:120
A QgsGeometry with associated coordinate reference system.
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 QgsSettingsEntryInteger * settingsDigitizingNurbsDegree
Settings entry digitizing NURBS curve degree.
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 Q_INVOKABLE bool isNurbsType(Qgis::WkbType type)
Returns true if the WKB type is a NURBS curve type.
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:7539
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
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:34
int vertex
Vertex number.
Definition qgsvertexid.h:98
int part
Part number.
Definition qgsvertexid.h:92
int ring
Ring number.
Definition qgsvertexid.h:95