QGIS API Documentation 4.1.0-Master (3b8ef1f72a3)
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
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
1745void QgsMapToolCapture::onTransientGeometryChanged( const QgsReferencedGeometry &geometry )
1746{
1747 QgsReferencedGeometry correctedGeometry = geometry;
1748
1749 // ensure geometry type is consistent with expected type
1750 if ( mCaptureMode == CapturePolygon )
1751 {
1752 if ( const auto curve = qgsgeometry_cast< const QgsCurve * >( correctedGeometry.constGet() ) )
1753 {
1754 auto convertedToPolygon = std::make_unique< QgsCurvePolygon >();
1755 convertedToPolygon->setExteriorRing( curve->clone() );
1756 correctedGeometry = QgsReferencedGeometry( QgsGeometry( std::move( convertedToPolygon ) ), correctedGeometry.crs() );
1757 }
1758 }
1759 else if ( mCaptureMode == CaptureLine )
1760 {
1761 if ( const auto polygon = qgsgeometry_cast< const QgsCurvePolygon * >( correctedGeometry.constGet() ) )
1762 {
1763 std::unique_ptr< QgsCurve > exterior( polygon->exteriorRing()->clone() );
1764 correctedGeometry = QgsReferencedGeometry( QgsGeometry( std::move( exterior ) ), correctedGeometry.crs() );
1765 }
1766 }
1767
1768 emit transientGeometryChanged( correctedGeometry );
1769}
1770
1772{
1773 // POINT CAPTURING
1774 if ( mode() == CapturePoint )
1775 {
1776 if ( e->button() != Qt::LeftButton )
1777 return;
1778
1779 QgsPoint savePoint; //point in layer coordinates
1780 bool isMatchPointZ = false;
1781 bool isMatchPointM = false;
1782 try
1783 {
1784 QgsPoint fetchPoint;
1785 int res = fetchLayerPoint( e->mapPointMatch(), fetchPoint );
1786 isMatchPointZ = QgsWkbTypes::hasZ( fetchPoint.wkbType() );
1787 isMatchPointM = QgsWkbTypes::hasM( fetchPoint.wkbType() );
1788
1789 if ( res == 0 )
1790 {
1792 if ( isMatchPointM && isMatchPointZ )
1793 {
1794 geomType = Qgis::WkbType::PointZM;
1795 }
1796 else if ( isMatchPointM )
1797 {
1798 geomType = Qgis::WkbType::PointM;
1799 }
1800 else if ( isMatchPointZ )
1801 {
1802 geomType = Qgis::WkbType::PointZ;
1803 }
1804 savePoint = QgsPoint( geomType, fetchPoint.x(), fetchPoint.y(), fetchPoint.z(), fetchPoint.m() );
1805 }
1806 else
1807 {
1808 QgsPointXY point = mCanvas->mapSettings().mapToLayerCoordinates( layer(), e->mapPoint() );
1809
1810 savePoint = QgsPoint( point.x(), point.y(), fetchPoint.z(), fetchPoint.m() );
1811 }
1812 }
1813 catch ( QgsCsException &cse )
1814 {
1815 Q_UNUSED( cse )
1816 emit messageEmitted( tr( "Cannot transform the point to the layer's coordinate system" ), Qgis::MessageLevel::Warning );
1817 return;
1818 }
1819
1820 QgsGeometry g( std::make_unique<QgsPoint>( savePoint ) );
1821
1822 // The snapping result needs to be added so it's available in the @snapping_results variable of default value etc. expression contexts
1823 addVertex( e->mapPoint(), e->mapPointMatch() );
1824
1825 geometryCaptured( g );
1826 pointCaptured( savePoint );
1827
1828 stopCapturing();
1829
1830 // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points
1832 }
1833
1834 // LINE AND POLYGON CAPTURING
1835 else if ( mode() == CaptureLine || mode() == CapturePolygon )
1836 {
1837 bool digitizingFinished = false;
1838 QgsPointSequence nurbsControlPoints;
1839 QVector<double> nurbsWeights;
1840
1841 // Poly-Bézier mode handling
1842 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::PolyBezier )
1843 {
1844 if ( e->button() == Qt::LeftButton )
1845 {
1846 // End dragging on mouse release
1847 mBezierDragging = false;
1848 mBezierDragAnchorIndex = -1;
1849 mBezierDragHandleIndex = -1;
1850 mBezierMoveAnchorIndex = -1;
1851
1852 // Clear highlights
1853 if ( mBezierMarker )
1854 {
1855 mBezierMarker->setHighlightedAnchor( -1 );
1856 mBezierMarker->setHighlightedHandle( -1 );
1857 mBezierMarker->updateFromData( *mBezierData );
1858 }
1859
1860 return;
1861 }
1862 else if ( e->button() == Qt::RightButton )
1863 {
1864 // End dragging
1865 mBezierDragging = false;
1866 mBezierDragAnchorIndex = -1;
1867 mBezierDragHandleIndex = -1;
1868 mBezierMoveAnchorIndex = -1;
1869
1870 if ( mBezierData && mBezierData->anchorCount() >= 2 )
1871 {
1872 // Convert Poly-Bézier to NurbsCurve
1873 std::unique_ptr<QgsNurbsCurve> nurbsCurve = mBezierData->asNurbsCurve();
1874 if ( nurbsCurve )
1875 {
1876 // Transform to layer coordinates if a layer is present
1877 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
1878 if ( vlayer )
1879 {
1880 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( vlayer );
1881 if ( ct.isValid() && !ct.isShortCircuited() )
1882 {
1883 try
1884 {
1885 nurbsCurve->transform( ct, Qgis::TransformDirection::Reverse );
1886 }
1887 catch ( QgsCsException & )
1888 {
1889 emit messageEmitted( tr( "Cannot transform the geometry to layer coordinates" ), Qgis::MessageLevel::Warning );
1890 stopCapturing();
1891 return;
1892 }
1893 }
1894 }
1895
1896 std::unique_ptr<QgsCurve> curveToAdd;
1897
1898 // Close for polygon if needed
1899 if ( mode() == CapturePolygon && !nurbsCurve->isClosed() )
1900 {
1901 // For polygon, wrap in compound curve and add closing segment
1902 auto compound = std::make_unique<QgsCompoundCurve>();
1903 compound->addCurve( nurbsCurve.release() );
1904 // Add closing line segment from end to start
1905 auto closingSegment = std::make_unique<QgsLineString>();
1906 closingSegment->addVertex( compound->endPoint() );
1907 closingSegment->addVertex( compound->startPoint() );
1908 compound->addCurve( closingSegment.release() );
1909 curveToAdd = std::move( compound );
1910 }
1911 else
1912 {
1913 curveToAdd.reset( nurbsCurve.release() );
1914 }
1915 QgsGeometry g;
1916
1917 if ( mode() == CaptureLine )
1918 {
1919 g = QgsGeometry( curveToAdd->clone() );
1920 geometryCaptured( g );
1921 lineCaptured( curveToAdd.release() );
1922 }
1923 else // CapturePolygon
1924 {
1925 auto poly = std::make_unique<QgsCurvePolygon>();
1926 poly->setExteriorRing( curveToAdd.release() );
1927 g = QgsGeometry( poly->clone() );
1928 geometryCaptured( g );
1929 polygonCaptured( poly.get() );
1930 }
1931
1932 digitizingFinished = true;
1933 }
1934 }
1935
1936 // Clean up Bézier data
1937 if ( mBezierMarker )
1938 mBezierMarker->clear();
1939 mBezierData.reset();
1940 mBezierMarker.reset();
1941 stopCapturing();
1942 return;
1943 }
1944 return;
1945 }
1946 else if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::Shape )
1947 {
1948 if ( !mCurrentShapeMapTool )
1949 {
1950 emit messageEmitted( tr( "Select an option from the Shape Digitizing Toolbar in order to capture shapes" ), Qgis::MessageLevel::Warning );
1951 return;
1952 }
1953 else
1954 {
1955 if ( !mTempRubberBand )
1956 {
1957 mTempRubberBand.reset( createCurveRubberBand() );
1958 mTempRubberBand->setStringType( mLineDigitizingType );
1959 mTempRubberBand->setRubberBandGeometryType( mCaptureMode == CapturePolygon ? Qgis::GeometryType::Polygon : Qgis::GeometryType::Line );
1960 }
1961
1962 digitizingFinished = mCurrentShapeMapTool->cadCanvasReleaseEvent( e, mCaptureMode );
1963 if ( digitizingFinished )
1964 mCurrentShapeMapTool->clean();
1965 }
1966 }
1967 else // i.e. not shape
1968 {
1969 //add point to list and to rubber band
1970 if ( e->button() == Qt::LeftButton )
1971 {
1972 const int error = addVertex( e->mapPoint(), e->mapPointMatch() );
1973 if ( error == 2 )
1974 {
1975 //problem with coordinate transformation
1976 emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::MessageLevel::Warning );
1977 return;
1978 }
1979
1981 }
1982 else if ( e->button() == Qt::RightButton )
1983 {
1984 // End of string
1985
1986 // Extract NURBS control points and weights from the rubberband before deleting it
1987 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && mTempRubberBand )
1988 {
1989 const int rbPointCount = mTempRubberBand->pointsCount();
1990 if ( rbPointCount > 1 )
1991 {
1992 // Exclude the last point (cursor position)
1993 for ( int i = 0; i < rbPointCount - 1; ++i )
1994 {
1995 nurbsControlPoints.append( mTempRubberBand->pointFromEnd( rbPointCount - 1 - i ) );
1996 }
1997 // Also extract weights (in correct order)
1998 const QVector<double> &rbWeights = mTempRubberBand->weights();
1999 for ( int i = 0; i < rbPointCount - 1; ++i )
2000 {
2001 if ( i < rbWeights.size() )
2002 nurbsWeights.append( rbWeights[i] );
2003 else
2004 nurbsWeights.append( 1.0 );
2005 }
2006 }
2007 }
2008
2010
2011 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve )
2012 {
2013 // Minimum 4 control points required for degree 3 NURBS
2014 if ( mode() == CaptureLine && nurbsControlPoints.count() < 4 )
2015 {
2016 stopCapturing();
2017 return;
2018 }
2019 if ( mode() == CapturePolygon && nurbsControlPoints.count() < 4 )
2020 {
2021 stopCapturing();
2022 return;
2023 }
2024 }
2025 else
2026 {
2027 //lines: bail out if there are not at least two vertices
2028 if ( mode() == CaptureLine && size() < 2 )
2029 {
2030 stopCapturing();
2031 return;
2032 }
2033
2034 //polygons: bail out if there are not at least two vertices
2035 if ( mode() == CapturePolygon && size() < 3 )
2036 {
2037 stopCapturing();
2038 return;
2039 }
2040 }
2041
2042 if ( mode() == CapturePolygon || e->modifiers() == Qt::ShiftModifier )
2043 {
2044 // Close NURBS curve by adding first control point at the end
2045 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve && !nurbsControlPoints.isEmpty() )
2046 {
2047 nurbsControlPoints.append( nurbsControlPoints.first() );
2048 if ( !nurbsWeights.isEmpty() )
2049 nurbsWeights.append( nurbsWeights.first() );
2050 }
2051 else
2052 {
2053 closePolygon();
2054 }
2055 }
2056
2057 digitizingFinished = true;
2058 }
2059 }
2060
2061 if ( digitizingFinished )
2062 {
2063 QgsGeometry g;
2064 std::unique_ptr<QgsCurve> curveToAdd;
2065
2066 // Create a single NurbsCurve from all control points
2067 if ( mCurrentCaptureTechnique == Qgis::CaptureTechnique::NurbsCurve )
2068 {
2069 // Get degree from settings
2071 const int n = nurbsControlPoints.size();
2072
2073 // Adapt degree if not enough control points
2074 if ( n < degree + 1 )
2075 {
2076 degree = std::max( 1, n - 1 );
2077 if ( n < 2 )
2078 {
2079 curveToAdd = std::make_unique<QgsLineString>( nurbsControlPoints );
2080 }
2081 }
2082
2083 if ( !curveToAdd )
2084 {
2085 // Generate uniform clamped knot vector (size = n + degree + 1)
2086 const int knotCount = n + degree + 1;
2087 QVector<double> knots( knotCount );
2088
2089 // First (degree + 1) knots are 0
2090 for ( int i = 0; i <= degree; ++i )
2091 knots[i] = 0.0;
2092
2093 // Last (degree + 1) knots are 1
2094 for ( int i = knotCount - degree - 1; i < knotCount; ++i )
2095 knots[i] = 1.0;
2096
2097 // Middle knots are uniformly spaced
2098 const int numMiddleKnots = n - degree - 1;
2099 for ( int i = 0; i < numMiddleKnots; ++i )
2100 {
2101 knots[degree + 1 + i] = static_cast<double>( i + 1 ) / ( numMiddleKnots + 1 );
2102 }
2103
2104 // Ensure we have the right number of weights
2105 QVector<double> weights = nurbsWeights;
2106 while ( weights.size() < n )
2107 weights.append( 1.0 );
2108 weights.resize( n );
2109
2110 curveToAdd = std::make_unique<QgsNurbsCurve>( nurbsControlPoints, degree, knots, weights );
2111 }
2112
2113 // Transform to layer coordinates if a layer is present
2114 if ( curveToAdd )
2115 {
2116 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() );
2117 if ( vlayer )
2118 {
2119 const QgsCoordinateTransform ct = mCanvas->mapSettings().layerTransform( vlayer );
2120 if ( ct.isValid() && !ct.isShortCircuited() )
2121 {
2122 try
2123 {
2124 curveToAdd->transform( ct, Qgis::TransformDirection::Reverse );
2125 }
2126 catch ( QgsCsException & )
2127 {
2128 emit messageEmitted( tr( "Cannot transform the geometry to layer coordinates" ), Qgis::MessageLevel::Warning );
2129 stopCapturing();
2130 return;
2131 }
2132 }
2133 }
2134 }
2135 }
2136 else
2137 {
2138 curveToAdd.reset( captureCurve()->clone() );
2139 }
2140
2141 if ( mode() == CaptureLine )
2142 {
2143 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
2144 {
2146 {
2148 {
2149 // if there is only one segment the compound curve will be casted to circular string
2150 // otherwise the user will see a warning on the message bar saying that a compound
2151 // curve can't be added on a circular string layer
2152 if ( compound->nCurves() == 1 )
2153 {
2154 if ( const QgsCircularString *circularPart = qgsgeometry_cast<const QgsCircularString *>( compound->curveAt( 0 ) ) )
2155 {
2156 curveToAdd.reset( circularPart->clone() );
2157 }
2158 }
2159 }
2160 }
2161 }
2162
2163 g = QgsGeometry( curveToAdd->clone() );
2164 geometryCaptured( g );
2165 lineCaptured( curveToAdd.release() );
2166 }
2167 else
2168 {
2169 // For NURBS curves, keep the already-created curve
2170 // For other curves, check provider support for curved segments
2171 if ( mCurrentCaptureTechnique != Qgis::CaptureTechnique::NurbsCurve )
2172 {
2173 //does compoundcurve contain circular strings?
2174 //does provider support circular strings?
2175 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer() ) )
2176 {
2177 const bool hasCurvedSegments = captureCurve()->hasCurvedSegments();
2178 const bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & Qgis::VectorProviderCapability::CircularGeometries;
2179
2180 if ( hasCurvedSegments && providerSupportsCurvedSegments )
2181 {
2182 curveToAdd.reset( captureCurve()->clone() );
2183 }
2184 else
2185 {
2186 curveToAdd.reset( captureCurve()->curveToLine() );
2187 }
2188 }
2189 else
2190 {
2191 curveToAdd.reset( captureCurve()->clone() );
2192 }
2193 }
2194 auto poly = std::make_unique<QgsCurvePolygon>();
2195 poly->setExteriorRing( curveToAdd.release() );
2196 g = QgsGeometry( poly->clone() );
2197 geometryCaptured( g );
2198 polygonCaptured( poly.get() );
2199 }
2200
2201 stopCapturing();
2202 }
2203 }
2204}
@ 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:2204
@ QgisInternal
Use internal QgsGeometryValidator method.
Definition qgis.h:2205
@ Geos
Use GEOS validation methods.
Definition qgis.h:2206
@ 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:2818
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: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:380
friend class QgsMapCanvas
Definition qgsmaptool.h:400
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
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 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:7657
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