QGIS API Documentation  3.25.0-Master (349eb7cb1e)
qgsadvanceddigitizingcanvasitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsadvanceddigitizingcanvasitem.cpp - map canvas item for CAD tools
3  ----------------------
4  begin : October 2014
5  copyright : (C) Denis Rouzaud
6  email : [email protected]
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 <QPainter>
17 
20 #include "qgsmapcanvas.h"
21 #include "qgscadutils.h"
22 
23 
25  : QgsMapCanvasItem( canvas )
26  , mLockedPen( QPen( QColor( 0, 127, 0, 255 ), 1, Qt::DashLine ) )
27  , mConstruction1Pen( QPen( QColor( 127, 127, 127, 150 ), 1, Qt::DashLine ) )
28  , mConstruction2Pen( QPen( QColor( 127, 127, 127, 255 ), 1, Qt::DashLine ) )
29  , mSnapPen( QPen( QColor( 127, 0, 0, 150 ), 1 ) )
30  , mSnapLinePen( QPen( QColor( 127, 0, 0, 150 ), 1, Qt::DashLine ) )
31  , mCursorPen( QPen( QColor( 127, 127, 127, 255 ), 1 ) )
32  , mAdvancedDigitizingDockWidget( cadDockWidget )
33 {
34 }
35 
36 void QgsAdvancedDigitizingCanvasItem::paint( QPainter *painter )
37 {
38  if ( !mAdvancedDigitizingDockWidget->cadEnabled() )
39  return;
40 
41  // Use visible polygon rather than extent to properly handle rotated maps
42  QPolygonF mapPoly = mMapCanvas->mapSettings().visiblePolygon();
43  const double canvasWidth = QLineF( mapPoly[0], mapPoly[1] ).length();
44  const double canvasHeight = QLineF( mapPoly[0], mapPoly[3] ).length();
45 
46  const int nPoints = mAdvancedDigitizingDockWidget->pointsCount();
47  if ( !nPoints )
48  return;
49 
50  bool previousPointExist, penulPointExist;
51  const QgsPointXY curPoint = mAdvancedDigitizingDockWidget->currentPointV2();
52  const QgsPointXY prevPoint = mAdvancedDigitizingDockWidget->previousPointV2( &previousPointExist );
53  const QgsPointXY penulPoint = mAdvancedDigitizingDockWidget->penultimatePointV2( &penulPointExist );
54  const bool snappedToVertex = mAdvancedDigitizingDockWidget->snappedToVertex();
55  const QList<QgsPointXY> snappedSegment = mAdvancedDigitizingDockWidget->snappedSegment();
56  const bool hasSnappedSegment = snappedSegment.count() == 2;
57 
58  const bool curPointExist = mapPoly.containsPoint( curPoint.toQPointF(), Qt::OddEvenFill );
59 
60  const double mupp = mMapCanvas->getCoordinateTransform()->mapUnitsPerPixel();
61  if ( mupp == 0 )
62  return;
63 
64  const double canvasRotationRad = mMapCanvas->rotation() * M_PI / 180;
65  const double canvasDiagonalDimension = ( canvasWidth + canvasHeight ) / mupp ;
66 
67  QPointF curPointPix, prevPointPix, penulPointPix, snapSegmentPix1, snapSegmentPix2;
68 
69  if ( curPointExist )
70  {
71  curPointPix = toCanvasCoordinates( curPoint );
72  }
73  if ( previousPointExist )
74  {
75  prevPointPix = toCanvasCoordinates( prevPoint );
76  }
77  if ( penulPointExist )
78  {
79  penulPointPix = toCanvasCoordinates( penulPoint );
80  }
81  if ( hasSnappedSegment )
82  {
83  snapSegmentPix1 = toCanvasCoordinates( snappedSegment[0] );
84  snapSegmentPix2 = toCanvasCoordinates( snappedSegment[1] );
85  }
86 
87  painter->setRenderHint( QPainter::Antialiasing );
88  painter->setCompositionMode( QPainter::CompositionMode_Difference );
89 
90  // Draw point snap
91  if ( curPointExist && snappedToVertex )
92  {
93  painter->setPen( mSnapPen );
94  painter->drawEllipse( curPointPix, 10, 10 );
95  }
96 
97  // Draw segment snap
98  if ( hasSnappedSegment && !snappedToVertex )
99  {
100  painter->setPen( mSnapPen );
101  painter->drawLine( snapSegmentPix1, snapSegmentPix2 );
102 
103  if ( curPointExist )
104  {
105  painter->setPen( mSnapLinePen );
106  painter->drawLine( snapSegmentPix1, curPointPix );
107  }
108  }
109 
110  // Draw segment par/per input
111  if ( mAdvancedDigitizingDockWidget->betweenLineConstraint() != Qgis::BetweenLineConstraint::NoConstraint && hasSnappedSegment )
112  {
113  painter->setPen( mConstruction2Pen );
114  painter->drawLine( snapSegmentPix1, snapSegmentPix2 );
115  }
116 
117  // Draw angle
118  if ( nPoints > 1 )
119  {
120  double a0, a;
121  if ( mAdvancedDigitizingDockWidget->constraintAngle()->relative() && nPoints > 2 )
122  {
123  a0 = std::atan2( -( prevPoint.y() - penulPoint.y() ), prevPoint.x() - penulPoint.x() );
124  }
125  else
126  {
127  a0 = 0;
128  }
129  if ( mAdvancedDigitizingDockWidget->constraintAngle()->isLocked() )
130  {
131  a = a0 - mAdvancedDigitizingDockWidget->constraintAngle()->value() * M_PI / 180;
132  }
133  else
134  {
135  a = std::atan2( -( curPoint.y() - prevPoint.y() ), curPoint.x() - prevPoint.x() );
136  }
137 
138  a0 += canvasRotationRad;
139  a += canvasRotationRad;
140 
141  painter->setPen( mConstruction2Pen );
142  painter->drawArc( QRectF( prevPointPix.x() - 20,
143  prevPointPix.y() - 20,
144  40, 40 ),
145  static_cast<int>( 16 * -a0 * 180 / M_PI ),
146  static_cast<int>( 16 * ( a0 - a ) * 180 / M_PI ) );
147  painter->drawLine( prevPointPix,
148  prevPointPix + 60 * QPointF( std::cos( a0 ), std::sin( a0 ) ) );
149 
150 
151  if ( mAdvancedDigitizingDockWidget->constraintAngle()->isLocked() )
152  {
153  painter->setPen( mLockedPen );
154  const double canvasPadding = QLineF( prevPointPix, curPointPix ).length();
155  painter->drawLine( prevPointPix + ( canvasPadding - canvasDiagonalDimension ) * QPointF( std::cos( a ), std::sin( a ) ),
156  prevPointPix + ( canvasPadding + canvasDiagonalDimension ) * QPointF( std::cos( a ), std::sin( a ) ) );
157  }
158  }
159 
160  // Draw distance
161  if ( nPoints > 1 && mAdvancedDigitizingDockWidget->constraintDistance()->isLocked() )
162  {
163  painter->setPen( mLockedPen );
164  const double r = mAdvancedDigitizingDockWidget->constraintDistance()->value() / mupp;
165  painter->drawEllipse( prevPointPix, r, r );
166  }
167 
168  // Draw x
169  if ( mAdvancedDigitizingDockWidget->constraintX()->isLocked() )
170  {
171  double x = 0.0;
172  bool draw = true;
173  painter->setPen( mLockedPen );
174  if ( mAdvancedDigitizingDockWidget->constraintX()->relative() )
175  {
176  if ( nPoints > 1 )
177  {
178  x = mAdvancedDigitizingDockWidget->constraintX()->value() + prevPoint.x();
179  }
180  else
181  {
182  draw = false;
183  }
184  }
185  else
186  {
187  x = mAdvancedDigitizingDockWidget->constraintX()->value();
188  }
189  if ( draw )
190  {
191  painter->drawLine( toCanvasCoordinates( QgsPointXY( x, mapPoly[0].y() ) ) - canvasDiagonalDimension * QPointF( std::sin( -canvasRotationRad ), std::cos( -canvasRotationRad ) ),
192  toCanvasCoordinates( QgsPointXY( x, mapPoly[0].y() ) ) + canvasDiagonalDimension * QPointF( std::sin( -canvasRotationRad ), std::cos( -canvasRotationRad ) ) );
193  }
194  }
195 
196  // Draw y
197  if ( mAdvancedDigitizingDockWidget->constraintY()->isLocked() )
198  {
199  double y = 0.0;
200  bool draw = true;
201  painter->setPen( mLockedPen );
202  if ( mAdvancedDigitizingDockWidget->constraintY()->relative() )
203  {
204  if ( nPoints > 1 )
205  {
206  y = mAdvancedDigitizingDockWidget->constraintY()->value() + prevPoint.y();
207  }
208  else
209  {
210  draw = false;
211  }
212  }
213  else
214  {
215  y = mAdvancedDigitizingDockWidget->constraintY()->value();
216  }
217  if ( draw )
218  {
219  painter->drawLine( toCanvasCoordinates( QgsPointXY( mapPoly[0].x(), y ) ) - canvasDiagonalDimension * QPointF( std::cos( -canvasRotationRad ), -std::sin( -canvasRotationRad ) ),
220  toCanvasCoordinates( QgsPointXY( mapPoly[0].x(), y ) ) + canvasDiagonalDimension * QPointF( std::cos( -canvasRotationRad ), -std::sin( -canvasRotationRad ) ) );
221 
222  }
223  }
224 
225  // Draw constr
226  if ( mAdvancedDigitizingDockWidget->betweenLineConstraint() == Qgis::BetweenLineConstraint::NoConstraint )
227  {
228  if ( curPointExist && previousPointExist )
229  {
230  painter->setPen( mConstruction2Pen );
231  painter->drawLine( prevPointPix, curPointPix );
232  }
233 
234  if ( previousPointExist && penulPointExist )
235  {
236  painter->setPen( mConstruction1Pen );
237  painter->drawLine( penulPointPix, prevPointPix );
238  }
239  }
240 
241  if ( curPointExist )
242  {
243  painter->setPen( mCursorPen );
244  painter->drawLine( curPointPix + QPointF( -5, -5 ),
245  curPointPix + QPointF( +5, +5 ) );
246  painter->drawLine( curPointPix + QPointF( -5, +5 ),
247  curPointPix + QPointF( +5, -5 ) );
248  }
249 
250  auto lineExtensionSide = mAdvancedDigitizingDockWidget->lineExtensionSide();
251  if ( mAdvancedDigitizingDockWidget->constraintLineExtension()->isLocked() &&
252  lineExtensionSide != Qgis::LineExtensionSide::NoVertex &&
253  !mAdvancedDigitizingDockWidget->lockedSnapVertices().isEmpty() )
254  {
255  painter->setPen( mLockedPen );
256 
257  const QgsPointLocator::Match snap = mAdvancedDigitizingDockWidget->lockedSnapVertices().last();
258  const QPointF snappedPoint = toCanvasCoordinates( snap.point() );
259 
260  const QgsFeature feature = snap.layer()->getFeature( snap.featureId() );
261  const QgsGeometry geom = feature.geometry();
262 
263  QgsPoint vertex;
264  if ( lineExtensionSide == Qgis::LineExtensionSide::BeforeVertex )
265  {
266  vertex = geom.vertexAt( snap.vertexIndex() - 1 );
267  }
268  else
269  {
270  vertex = geom.vertexAt( snap.vertexIndex() + 1 );
271  }
272 
273  if ( !vertex.isEmpty() )
274  {
275  const QPointF point = toCanvasCoordinates( vertex );
276  const double angle = std::atan2( snappedPoint.y() - point.y(), snappedPoint.x() - point.x() );
277 
278  const double canvasPadding = QLineF( snappedPoint, curPointPix ).length();
279  painter->drawLine( snappedPoint + ( canvasPadding - canvasDiagonalDimension ) * QPointF( std::cos( angle ), std::sin( angle ) ),
280  snappedPoint + ( canvasPadding + canvasDiagonalDimension ) * QPointF( std::cos( angle ), std::sin( angle ) ) );
281  }
282 
283  }
284 
285  if ( mAdvancedDigitizingDockWidget->constraintXyVertex()->isLocked() )
286  {
287  painter->setPen( mLockedPen );
288 
289  double coordinateExtension = mAdvancedDigitizingDockWidget->softLockX();
290  if ( coordinateExtension != std::numeric_limits<double>::quiet_NaN() )
291  {
292  const QgsPointXY point( coordinateExtension, mapPoly[0].y() );
293  const QPointF rotation( std::sin( -canvasRotationRad ), std::cos( -canvasRotationRad ) );
294  painter->drawLine( toCanvasCoordinates( point ) - canvasDiagonalDimension * rotation,
295  toCanvasCoordinates( point ) + canvasDiagonalDimension * rotation );
296  }
297 
298  coordinateExtension = mAdvancedDigitizingDockWidget->softLockY();
299  if ( coordinateExtension != std::numeric_limits<double>::quiet_NaN() )
300  {
301  const QgsPointXY point( mapPoly[0].x(), coordinateExtension );
302  const QPointF rotation( std::cos( -canvasRotationRad ), -std::sin( -canvasRotationRad ) );
303  painter->drawLine( toCanvasCoordinates( point ) - canvasDiagonalDimension * rotation,
304  toCanvasCoordinates( point ) + canvasDiagonalDimension * rotation );
305  }
306  }
307 
308  painter->setPen( mCursorPen );
309 
310  const QList< QgsPointLocator::Match > lockedSnapVertices = mAdvancedDigitizingDockWidget->lockedSnapVertices();
311  for ( QgsPointLocator::Match snapMatch : lockedSnapVertices )
312  {
313  const QgsPointXY point = snapMatch.point();
314  const QPointF canvasPoint = toCanvasCoordinates( point );
315 
316  painter->drawLine( canvasPoint + QPointF( 5, 5 ),
317  canvasPoint - QPointF( 5, 5 ) );
318  painter->drawLine( canvasPoint + QPointF( -5, 5 ),
319  canvasPoint - QPointF( -5, 5 ) );
320  }
321 
322  if ( !lockedSnapVertices.isEmpty() )
323  {
324  const QgsPointXY point = lockedSnapVertices.last().point();
325  const QPointF canvasPoint = toCanvasCoordinates( point );
326 
327  painter->drawLine( canvasPoint + QPointF( 0, 5 ),
328  canvasPoint - QPointF( 0, 5 ) );
329  painter->drawLine( canvasPoint + QPointF( 5, 0 ),
330  canvasPoint - QPointF( 5, 0 ) );
331  }
332 }
333 
335 {
336  // Use visible polygon rather than extent to properly handle rotated maps
337  QPolygonF mapPoly = mMapCanvas->mapSettings().visiblePolygon();
338  const double canvasWidth = QLineF( mapPoly[0], mapPoly[1] ).length();
339  const double canvasHeight = QLineF( mapPoly[0], mapPoly[3] ).length();
340  const QgsRectangle mapRect = QgsRectangle( mapPoly[0],
341  QgsPointXY(
342  mapPoly[0].x() + canvasWidth,
343  mapPoly[0].y() - canvasHeight
344  )
345  );
346  if ( rect() != mapRect )
347  setRect( mapRect );
348 }
@ NoConstraint
No additional constraint.
@ NoVertex
Don't lock to vertex.
@ BeforeVertex
Lock to previous vertex.
void paint(QPainter *painter) override
function to be implemented by derived classes
void updatePosition() override
called on changed extent or resize event to update position of the item
QgsAdvancedDigitizingCanvasItem(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
bool isLocked() const
Is any kind of lock mode enabled.
double value() const
The value of the constraint.
bool relative() const
Is the constraint in relative mode.
The QgsAdvancedDigitizingDockWidget class is a dockable widget used to handle the CAD tools on top of...
const CadConstraint * constraintDistance() const
Returns the CadConstraint on the distance.
double softLockY() const
Returns the Y value of the Y soft lock. The value is NaN is the constraint isn't magnetized to a line...
bool cadEnabled() const
determines if CAD tools are enabled or if map tools behaves "nomally"
int pointsCount() const
The number of points in the CAD point helper list.
bool snappedToVertex() const
Is it snapped to a vertex.
QList< QgsPointLocator::Match > lockedSnapVertices() const
Returns the snap matches whose vertices have been locked.
QgsPoint previousPointV2(bool *exists=nullptr) const
The previous point.
const CadConstraint * constraintX() const
Returns the CadConstraint on the X coordinate.
QgsPoint penultimatePointV2(bool *exists=nullptr) const
The penultimate point.
double softLockX() const
Returns the X value of the X soft lock. The value is NaN is the constraint isn't magnetized to a line...
Qgis::BetweenLineConstraint betweenLineConstraint() const
Returns the between line constraints which are used to place perpendicular/parallel segments to snapp...
const CadConstraint * constraintXyVertex() const
Returns the CadConstraint.
const CadConstraint * constraintAngle() const
Returns the CadConstraint on the angle.
const CadConstraint * constraintY() const
Returns the CadConstraint on the Y coordinate.
QgsPoint currentPointV2(bool *exists=nullptr) const
The last point.
const CadConstraint * constraintLineExtension() const
Returns the CadConstraint.
Qgis::LineExtensionSide lineExtensionSide() const
Returns on which side of the constraint line extension point, the line was created.
QList< QgsPointXY > snappedSegment() const
Snapped to a segment.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
QgsPoint vertexAt(int atVertex) const
Returns coordinates of a vertex.
An abstract class for items that can be placed on the map canvas.
QgsRectangle rect() const
returns canvas item rectangle in map units
QPointF toCanvasCoordinates(const QgsPointXY &point) const
transformation from map coordinates to screen coordinates
QgsMapCanvas * mMapCanvas
pointer to map canvas
void setRect(const QgsRectangle &r, bool resetRotation=true)
sets canvas item rectangle in map units
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
double rotation() const
Gets the current map canvas rotation in clockwise degrees.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated)
double mapUnitsPerPixel() const
Returns the current map units per pixel.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
QPointF toQPointF() const
Converts a point to a QPointF.
Definition: qgspointxy.h:169
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspoint.cpp:767
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
QgsVectorLayer * layer() const
The vector layer where the snap occurred.
QgsFeatureId featureId() const
The id of the feature to which the snapped geometry belongs.
QgsPointXY point() const
for vertex / edge match coords depending on what class returns it (geom.cache: layer coords,...
int vertexIndex() const
for vertex / edge match (first vertex of the edge)