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