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