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