QGIS API Documentation 4.3.0-Master (6402a64e93b)
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 {
445 connect( mCurrentShapeMapTool, &QgsMapToolShapeAbstract::transientGeometryChanged, this, &QgsMapToolCapture::onTransientGeometryChanged );
446 mCurrentShapeMapTool->activate( mCaptureMode, mCaptureLastPoint );
447 }
448 else
449 {
450 disconnect( mCurrentShapeMapTool, &QgsMapToolShapeAbstract::transientGeometryChanged, this, &QgsMapToolCapture::onTransientGeometryChanged );
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
1505 mValidator = new QgsGeometryValidator( geom, nullptr, method );
1506 connect( mValidator, &QgsGeometryValidator::errorFound, this, &QgsMapToolCapture::addError );
1507 mValidator->start();
1508 QgsDebugMsgLevel( u"Validation started"_s, 4 );
1509}
1510
1511void QgsMapToolCapture::addError( const QgsGeometry::Error &e )
1512{
1513 mGeomErrors << e;
1514 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1515 if ( !vlayer )
1516 return;
1517
1518 if ( e.hasWhere() )
1519 {
1520 QgsVertexMarker *vm = new QgsVertexMarker( mCanvas );
1521 vm->setCenter( mCanvas->mapSettings().layerToMapCoordinates( vlayer, e.where() ) );
1523 vm->setPenWidth( 2 );
1524 vm->setToolTip( e.what() );
1525 vm->setColor( Qt::green );
1526 vm->setZValue( vm->zValue() + 1 );
1527 mGeomErrorMarkers << vm;
1528 }
1529}
1530
1532{
1533 return mCaptureCurve.numPoints();
1534}
1535
1536QVector<QgsPointXY> QgsMapToolCapture::points() const
1537{
1538 QVector<QgsPointXY> pointsXY;
1540
1541 return pointsXY;
1542}
1543
1545{
1546 QgsPointSequence pts;
1547 mCaptureCurve.points( pts );
1548 return pts;
1549}
1550
1551void QgsMapToolCapture::setPoints( const QVector<QgsPointXY> &pointList )
1552{
1553 QgsLineString *line = new QgsLineString( pointList );
1554 mCaptureCurve.clear();
1555 mCaptureCurve.addCurve( line );
1556 updateExtraSnapLayer();
1557 mSnappingMatches.clear();
1558 for ( int i = 0; i < line->length(); ++i )
1559 mSnappingMatches.append( QgsPointLocator::Match() );
1560 resetRubberBand();
1561}
1562
1564{
1565 QgsLineString *line = new QgsLineString( pointList );
1566 mCaptureCurve.clear();
1567 mCaptureCurve.addCurve( line );
1568 updateExtraSnapLayer();
1569 mSnappingMatches.clear();
1570 for ( int i = 0; i < line->length(); ++i )
1571 mSnappingMatches.append( QgsPointLocator::Match() );
1572 resetRubberBand();
1573}
1574
1576{
1577 QgsPoint newPoint( Qgis::WkbType::Point, point.x(), point.y() );
1578
1579 // get current layer
1580 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1581 if ( !vlayer )
1582 {
1583 return newPoint;
1584 }
1585
1586 // convert to the corresponding type for a full ZM support
1587 const Qgis::WkbType type = vlayer->wkbType();
1588 if ( QgsWkbTypes::hasZ( type ) && !QgsWkbTypes::hasM( type ) )
1589 {
1590 newPoint.convertTo( Qgis::WkbType::PointZ );
1591 }
1592 else if ( !QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1593 {
1594 newPoint.convertTo( Qgis::WkbType::PointM );
1595 }
1596 else if ( QgsWkbTypes::hasZ( type ) && QgsWkbTypes::hasM( type ) )
1597 {
1599 }
1600
1601 // set z value if necessary
1602 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) )
1603 {
1604 newPoint.setZ( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->getLineZ() : defaultZValue() );
1605 }
1606 // set m value if necessary
1607 if ( QgsWkbTypes::hasM( newPoint.wkbType() ) )
1608 {
1609 newPoint.setM( mCadDockWidget && mCadDockWidget->cadEnabled() ? mCadDockWidget->getLineM() : defaultMValue() );
1610 }
1611 return newPoint;
1612}
1613
1615{
1616 QgsPoint newPoint = mapPoint( e.mapPoint() );
1617
1618 // set z or m value from snapped point if necessary
1619 if ( QgsWkbTypes::hasZ( newPoint.wkbType() ) || QgsWkbTypes::hasM( newPoint.wkbType() ) )
1620 {
1621 // if snapped, z and m dimension are taken from the corresponding snapped
1622 // point.
1623 if ( e.isSnapped() )
1624 {
1625 const QgsPointLocator::Match match = e.mapPointMatch();
1626
1627 if ( match.layer() )
1628 {
1629 const QgsFeature ft = match.layer()->getFeature( match.featureId() );
1630 if ( QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
1631 {
1632 newPoint.setZ( ft.geometry().vertexAt( match.vertexIndex() ).z() );
1633 }
1634 if ( QgsWkbTypes::hasM( match.layer()->wkbType() ) )
1635 {
1636 newPoint.setM( ft.geometry().vertexAt( match.vertexIndex() ).m() );
1637 }
1638 }
1639 }
1640 }
1641
1642 return newPoint;
1643}
1644
1645void QgsMapToolCapture::updateExtraSnapLayer()
1646{
1647 if ( !mExtraSnapLayer )
1648 return;
1649
1650 if ( canvas()->snappingUtils()->config().selfSnapping() && layer() )
1651 {
1652 // the current layer may have changed
1653 mExtraSnapLayer->setCrs( layer()->crs() );
1654
1655 QgsGeometry geom;
1656
1657 // For NURBS curves, include both the evaluated curve and control points for snapping
1658 if ( mLineDigitizingType == Qgis::WkbType::NurbsCurve && mTempRubberBand && mTempRubberBand->pointsCount() >= 2 )
1659 {
1660 // Create a GeometryCollection containing control points and evaluated curve
1661 auto collection = std::make_unique<QgsGeometryCollection>();
1662
1663 // Add control points as individual Point geometries
1664 const int pointCount = mTempRubberBand->pointsCount();
1665 // Exclude the last point (cursor position)
1666 for ( int i = 0; i < pointCount - 1; ++i )
1667 {
1668 collection->addGeometry( new QgsPoint( mTempRubberBand->pointFromEnd( pointCount - 1 - i ) ) );
1669 }
1670
1671 // Add the evaluated curve as a LineString
1672 std::unique_ptr<QgsCurve> nurbsCurve( mTempRubberBand->curve() );
1673 if ( nurbsCurve )
1674 {
1675 std::unique_ptr<QgsLineString> curvePoints( nurbsCurve->curveToLine() );
1676 if ( curvePoints )
1677 {
1678 // For polygon mode, close the curve to allow snapping to first point
1679 if ( mCaptureMode == CapturePolygon && curvePoints->numPoints() >= 3 )
1680 {
1681 curvePoints->close();
1682 }
1683 collection->addGeometry( curvePoints.release() );
1684 }
1685 }
1686
1687 geom = QgsGeometry( collection.release() );
1688 }
1689 else if ( mBezierData && mBezierData->anchorCount() >= 2 )
1690 {
1691 // Poly-Bézier mode: create a GeometryCollection containing anchors, handles, and interpolated curve
1692 auto collection = std::make_unique<QgsGeometryCollection>();
1693
1694 // Add all anchors as individual Point geometries
1695 const QVector<QgsPoint> anchors = mBezierData->anchors();
1696 for ( const QgsPoint &point : anchors )
1697 {
1698 collection->addGeometry( new QgsPoint( point ) );
1699 }
1700
1701 // Add all handles as individual Point geometries
1702 const QVector<QgsPoint> handles = mBezierData->handles();
1703 for ( const QgsPoint &point : handles )
1704 {
1705 collection->addGeometry( new QgsPoint( point ) );
1706 }
1707
1708 // Add interpolated curve as a LineString
1709 const QgsPointSequence interpolated = mBezierData->interpolateLine();
1710 if ( !interpolated.isEmpty() )
1711 {
1712 auto curveLineString = std::make_unique<QgsLineString>( interpolated );
1713 // For polygon mode, close the curve to allow snapping to first point
1714 if ( mCaptureMode == CapturePolygon && curveLineString->numPoints() >= 3 )
1715 {
1716 curveLineString->close();
1717 }
1718 collection->addGeometry( curveLineString.release() );
1719 }
1720
1721 geom = QgsGeometry( collection.release() );
1722 }
1723 else if ( mCaptureCurve.numPoints() >= 2 )
1724 {
1725 // Standard capture curve
1726 geom = QgsGeometry( mCaptureCurve.clone() );
1727 // we close the curve to allow snapping on last segment
1728 if ( mCaptureMode == CapturePolygon && mCaptureCurve.numPoints() >= 3 )
1729 {
1730 qgsgeometry_cast<QgsCompoundCurve *>( geom.get() )->close();
1731 }
1732 }
1733
1734 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1735 }
1736 else
1737 {
1738 QgsGeometry geom;
1739 mExtraSnapLayer->changeGeometry( mExtraSnapFeatureId, geom );
1740 }
1741}
1742
1743void QgsMapToolCapture::onTransientGeometryChanged( const QgsReferencedGeometry &geometry )
1744{
1745 QgsReferencedGeometry correctedGeometry = geometry;
1746
1747 // ensure geometry type is consistent with expected type
1748 if ( mCaptureMode == CapturePolygon )
1749 {
1750 if ( const auto curve = qgsgeometry_cast< const QgsCurve * >( correctedGeometry.constGet() ) )
1751 {
1752 auto convertedToPolygon = std::make_unique< QgsCurvePolygon >();
1753 convertedToPolygon->setExteriorRing( curve->clone() );
1754 correctedGeometry = QgsReferencedGeometry( QgsGeometry( std::move( convertedToPolygon ) ), correctedGeometry.crs() );
1755 }
1756 }
1757 else if ( mCaptureMode == CaptureLine )
1758 {
1759 if ( const auto polygon = qgsgeometry_cast< const QgsCurvePolygon * >( correctedGeometry.constGet() ) )
1760 {
1761 std::unique_ptr< QgsCurve > exterior( polygon->exteriorRing()->clone() );
1762 correctedGeometry = QgsReferencedGeometry( QgsGeometry( std::move( exterior ) ), correctedGeometry.crs() );
1763 }
1764 }
1765
1766 emit transientGeometryChanged( correctedGeometry );
1767}
1768
1770{
1771 // POINT CAPTURING
1772 if ( mode() == CapturePoint )
1773 {
1774 if ( e->button() != Qt::LeftButton )
1775 return;
1776
1777 QgsPoint savePoint; //point in layer coordinates
1778 bool isMatchPointZ = false;
1779 bool isMatchPointM = false;
1780 try
1781 {
1782 QgsPoint fetchPoint;
1783 int res = fetchLayerPoint( e->mapPointMatch(), fetchPoint );
1784 isMatchPointZ = QgsWkbTypes::hasZ( fetchPoint.wkbType() );
1785 isMatchPointM = QgsWkbTypes::hasM( fetchPoint.wkbType() );
1786
1787 if ( res == 0 )
1788 {
1790 if ( isMatchPointM && isMatchPointZ )
1791 {
1792 geomType = Qgis::WkbType::PointZM;
1793 }
1794 else if ( isMatchPointM )
1795 {
1796 geomType = Qgis::WkbType::PointM;
1797 }
1798 else if ( isMatchPointZ )
1799 {
1800 geomType = Qgis::WkbType::PointZ;
1801 }
1802 savePoint = QgsPoint( geomType, fetchPoint.x(), fetchPoint.y(), fetchPoint.z(), fetchPoint.m() );
1803 }
1804 else
1805 {
1806 QgsPointXY point = mCanvas->mapSettings().mapToLayerCoordinates( layer(), e->mapPoint() );
1807
1808 savePoint = QgsPoint( point.x(), point.y(), fetchPoint.z(), fetchPoint.m() );
1809 }
1810 }
1811 catch ( QgsCsException &cse )
1812 {
1813 Q_UNUSED( cse )
1814 emit messageEmitted( tr( "Cannot transform the point to the layer's coordinate system" ), Qgis::MessageLevel::Warning );
1815 return;
1816 }
1817
1818 QgsGeometry g( std::make_unique<QgsPoint>( savePoint ) );
1819
1820 // The snapping result needs to be added so it's available in the @snapping_results variable of default value etc. expression contexts
1821 addVertex( e->mapPoint(), e->mapPointMatch() );
1822
1823 geometryCaptured( g );
1824 pointCaptured( savePoint );
1825
1826 stopCapturing();
1827
1828 // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points
1830 }
1831
1832 // LINE AND POLYGON CAPTURING
1833 else if ( mode() == CaptureLine || mode() == CapturePolygon )
1834 {
1835 bool digitizingFinished = false;
1836 QgsPointSequence nurbsControlPoints;
1837 QVector<double> nurbsWeights;
1838
1839 // Poly-Bézier mode handling
1840 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::PolyBezier )
1841 {
1842 if ( e->button() == Qt::LeftButton )
1843 {
1844 // End dragging on mouse release
1845 mBezierDragging = false;
1846 mBezierDragAnchorIndex = -1;
1847 mBezierDragHandleIndex = -1;
1848 mBezierMoveAnchorIndex = -1;
1849
1850 // Clear highlights
1851 if ( mBezierMarker )
1852 {
1853 mBezierMarker->setHighlightedAnchor( -1 );
1854 mBezierMarker->setHighlightedHandle( -1 );
1855 mBezierMarker->updateFromData( *mBezierData );
1856 }
1857
1858 return;
1859 }
1860 else if ( e->button() == Qt::RightButton )
1861 {
1862 // End dragging
1863 mBezierDragging = false;
1864 mBezierDragAnchorIndex = -1;
1865 mBezierDragHandleIndex = -1;
1866 mBezierMoveAnchorIndex = -1;
1867
1868 if ( mBezierData && mBezierData->anchorCount() >= 2 )
1869 {
1870 // Convert Poly-Bézier to NurbsCurve
1871 std::unique_ptr<QgsNurbsCurve> nurbsCurve = mBezierData->asNurbsCurve();
1872 if ( nurbsCurve )
1873 {
1874 // Transform to layer coordinates if a layer is present
1875 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1876 if ( vlayer )
1877 {
1878 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( vlayer );
1879 if ( ct.isValid() && !ct.isShortCircuited() )
1880 {
1881 try
1882 {
1883 nurbsCurve->transform( ct, Qgis::TransformDirection::Reverse );
1884 }
1885 catch ( QgsCsException & )
1886 {
1887 emit messageEmitted( tr( "Cannot transform the geometry to layer coordinates" ), Qgis::MessageLevel::Warning );
1888 stopCapturing();
1889 return;
1890 }
1891 }
1892 }
1893
1894 std::unique_ptr<QgsCurve> curveToAdd;
1895
1896 // Close for polygon if needed
1897 if ( mode() == CapturePolygon && !nurbsCurve->isClosed() )
1898 {
1899 // For polygon, wrap in compound curve and add closing segment
1900 auto compound = std::make_unique<QgsCompoundCurve>();
1901 compound->addCurve( nurbsCurve.release() );
1902 // Add closing line segment from end to start
1903 auto closingSegment = std::make_unique<QgsLineString>();
1904 closingSegment->addVertex( compound->endPoint() );
1905 closingSegment->addVertex( compound->startPoint() );
1906 compound->addCurve( closingSegment.release() );
1907 curveToAdd = std::move( compound );
1908 }
1909 else
1910 {
1911 curveToAdd.reset( nurbsCurve.release() );
1912 }
1913 QgsGeometry g;
1914
1915 if ( mode() == CaptureLine )
1916 {
1917 g = QgsGeometry( curveToAdd->clone() );
1918 geometryCaptured( g );
1919 lineCaptured( curveToAdd.release() );
1920 }
1921 else // CapturePolygon
1922 {
1923 auto poly = std::make_unique<QgsCurvePolygon>();
1924 poly->setExteriorRing( curveToAdd.release() );
1925 g = QgsGeometry( poly->clone() );
1926 geometryCaptured( g );
1927 polygonCaptured( poly.get() );
1928 }
1929
1930 digitizingFinished = true;
1931 }
1932 }
1933
1934 // Clean up Bézier data
1935 if ( mBezierMarker )
1936 mBezierMarker->clear();
1937 mBezierData.reset();
1938 mBezierMarker.reset();
1939 stopCapturing();
1940 return;
1941 }
1942 return;
1943 }
1944 else if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
1945 {
1946 if ( !mCurrentShapeMapTool )
1947 {
1948 emit messageEmitted( tr( "Select an option from the Shape Digitizing Toolbar in order to capture shapes" ), Qgis::MessageLevel::Warning );
1949 return;
1950 }
1951 else
1952 {
1953 if ( !mTempRubberBand )
1954 {
1955 mTempRubberBand.reset( createCurveRubberBand() );
1956 mTempRubberBand->setStringType( mLineDigitizingType );
1957 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
1958 }
1959
1960 digitizingFinished = mCurrentShapeMapTool->cadCanvasReleaseEvent( e, mCaptureMode );
1961 if ( digitizingFinished )
1962 mCurrentShapeMapTool->clean();
1963 }
1964 }
1965 else // i.e. not shape
1966 {
1967 //add point to list and to rubber band
1968 if ( e->button() == Qt::LeftButton )
1969 {
1970 const int error = addVertex( e->mapPoint(), e->mapPointMatch() );
1971 if ( error == 2 )
1972 {
1973 //problem with coordinate transformation
1974 emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::MessageLevel::Warning );
1975 return;
1976 }
1977
1979 }
1980 else if ( e->button() == Qt::RightButton )
1981 {
1982 // End of string
1983
1984 // Extract NURBS control points and weights from the rubberband before deleting it
1985 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && mTempRubberBand )
1986 {
1987 const int rbPointCount = mTempRubberBand->pointsCount();
1988 if ( rbPointCount > 1 )
1989 {
1990 // Exclude the last point (cursor position)
1991 for ( int i = 0; i < rbPointCount - 1; ++i )
1992 {
1993 nurbsControlPoints.append( mTempRubberBand->pointFromEnd( rbPointCount - 1 - i ) );
1994 }
1995 // Also extract weights (in correct order)
1996 const QVector<double> &rbWeights = mTempRubberBand->weights();
1997 for ( int i = 0; i < rbPointCount - 1; ++i )
1998 {
1999 if ( i < rbWeights.size() )
2000 nurbsWeights.append( rbWeights[i] );
2001 else
2002 nurbsWeights.append( 1.0 );
2003 }
2004 }
2005 }
2006
2008
2009 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve )
2010 {
2011 // Minimum 4 control points required for degree 3 NURBS
2012 if ( mode() == CaptureLine && nurbsControlPoints.count() < 4 )
2013 {
2014 stopCapturing();
2015 return;
2016 }
2017 if ( mode() == CapturePolygon && nurbsControlPoints.count() < 4 )
2018 {
2019 stopCapturing();
2020 return;
2021 }
2022 }
2023 else
2024 {
2025 //lines: bail out if there are not at least two vertices
2026 if ( mode() == CaptureLine && size() < 2 )
2027 {
2028 stopCapturing();
2029 return;
2030 }
2031
2032 //polygons: bail out if there are not at least two vertices
2033 if ( mode() == CapturePolygon && size() < 3 )
2034 {
2035 stopCapturing();
2036 return;
2037 }
2038 }
2039
2040 if ( mode() == CapturePolygon || e->modifiers() == Qt::ShiftModifier )
2041 {
2042 // Close NURBS curve by adding first control point at the end
2043 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && !nurbsControlPoints.isEmpty() )
2044 {
2045 nurbsControlPoints.append( nurbsControlPoints.first() );
2046 if ( !nurbsWeights.isEmpty() )
2047 nurbsWeights.append( nurbsWeights.first() );
2048 }
2049 else
2050 {
2051 closePolygon();
2052 }
2053 }
2054
2055 digitizingFinished = true;
2056 }
2057 }
2058
2059 if ( digitizingFinished )
2060 {
2061 QgsGeometry g;
2062 std::unique_ptr<QgsCurve> curveToAdd;
2063
2064 // Create a single NurbsCurve from all control points
2065 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve )
2066 {
2067 // Get degree from settings
2069 const int n = nurbsControlPoints.size();
2070
2071 // Adapt degree if not enough control points
2072 if ( n < degree + 1 )
2073 {
2074 degree = std::max( 1, n - 1 );
2075 if ( n < 2 )
2076 {
2077 curveToAdd = std::make_unique<QgsLineString>( nurbsControlPoints );
2078 }
2079 }
2080
2081 if ( !curveToAdd )
2082 {
2083 // Generate uniform clamped knot vector (size = n + degree + 1)
2084 const int knotCount = n + degree + 1;
2085 QVector<double> knots( knotCount );
2086
2087 // First (degree + 1) knots are 0
2088 for ( int i = 0; i <= degree; ++i )
2089 knots[i] = 0.0;
2090
2091 // Last (degree + 1) knots are 1
2092 for ( int i = knotCount - degree - 1; i < knotCount; ++i )
2093 knots[i] = 1.0;
2094
2095 // Middle knots are uniformly spaced
2096 const int numMiddleKnots = n - degree - 1;
2097 for ( int i = 0; i < numMiddleKnots; ++i )
2098 {
2099 knots[degree + 1 + i] = static_cast<double>( i + 1 ) / ( numMiddleKnots + 1 );
2100 }
2101
2102 // Ensure we have the right number of weights
2103 QVector<double> weights = nurbsWeights;
2104 while ( weights.size() < n )
2105 weights.append( 1.0 );
2106 weights.resize( n );
2107
2108 curveToAdd = std::make_unique<QgsNurbsCurve>( nurbsControlPoints, degree, knots, weights );
2109 }
2110
2111 // Transform to layer coordinates if a layer is present
2112 if ( curveToAdd )
2113 {
2114 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
2115 if ( vlayer )
2116 {
2117 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( vlayer );
2118 if ( ct.isValid() && !ct.isShortCircuited() )
2119 {
2120 try
2121 {
2122 curveToAdd->transform( ct, Qgis::TransformDirection::Reverse );
2123 }
2124 catch ( QgsCsException & )
2125 {
2126 emit messageEmitted( tr( "Cannot transform the geometry to layer coordinates" ), Qgis::MessageLevel::Warning );
2127 stopCapturing();
2128 return;
2129 }
2130 }
2131 }
2132 }
2133 }
2134 else
2135 {
2136 curveToAdd.reset( captureCurve()->clone() );
2137 }
2138
2139 if ( mode() == CaptureLine )
2140 {
2141 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
2142 {
2144 {
2146 {
2147 // if there is only one segment the compound curve will be casted to circular string
2148 // otherwise the user will see a warning on the message bar saying that a compound
2149 // curve can't be added on a circular string layer
2150 if ( compound->nCurves() == 1 )
2151 {
2152 if ( const QgsCircularString *circularPart = qgsgeometry_cast<const QgsCircularString *>( compound->curveAt( 0 ) ) )
2153 {
2154 curveToAdd.reset( circularPart->clone() );
2155 }
2156 }
2157 }
2158 }
2159 }
2160
2161 g = QgsGeometry( curveToAdd->clone() );
2162 geometryCaptured( g );
2163 lineCaptured( curveToAdd.release() );
2164 }
2165 else
2166 {
2167 // For NURBS curves, keep the already-created curve
2168 // For other curves, check provider support for curved segments
2169 if ( mCurrentCaptureTechnique != Qgis::CaptureTechnique::NurbsCurve )
2170 {
2171 //does compoundcurve contain circular strings?
2172 //does provider support circular strings?
2173 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
2174 {
2175 const bool hasCurvedSegments = captureCurve()->hasCurvedSegments();
2176 const bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & Qgis::VectorProviderCapability::CircularGeometries;
2177
2178 if ( hasCurvedSegments && providerSupportsCurvedSegments )
2179 {
2180 curveToAdd.reset( captureCurve()->clone() );
2181 }
2182 else
2183 {
2184 curveToAdd.reset( captureCurve()->curveToLine() );
2185 }
2186 }
2187 else
2188 {
2189 curveToAdd.reset( captureCurve()->clone() );
2190 }
2191 }
2192 auto poly = std::make_unique<QgsCurvePolygon>();
2193 poly->setExteriorRing( curveToAdd.release() );
2194 g = QgsGeometry( poly->clone() );
2195 geometryCaptured( g );
2196 polygonCaptured( poly.get() );
2197 }
2198
2199 stopCapturing();
2200 }
2201 }
2202}
@ 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:2235
@ 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:2864
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.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
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:63
QgsGeometry geometry
Definition qgsfeature.h:66
void errorFound(const QgsGeometry::Error &error)
Sent when an error has been found during the validation process.
static Qgis::GeometryValidationEngine defaultValidationEngine()
Returns the geometry validation engine configured in the application settings.
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 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 respecting the user's settings.
static double defaultMValue()
Returns default M value.
QgsVectorLayer * currentVectorLayer()
Returns the current vector layer for the map canvas or nullptr if none is set.
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:403
friend class QgsMapCanvas
Definition qgsmaptool.h:423
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:614
bool dropMValue() override
Drops any measure values which exist in the geometry.
Definition qgspoint.cpp:655
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:603
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
Definition qgspoint.cpp:495
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:672
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:418
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:644
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:121
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
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 transform(QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback=nullptr) override
Transforms the vertices from the geometry in place, using the specified geometry transformer object.
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:8122
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
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:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:96
int ring
Ring number.
Definition qgsvertexid.h:99