QGIS API Documentation  3.23.0-Master (22c16f2067)
qgslayoutitemnodeitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitemnodeitem.cpp
3  begin : March 2016
4  copyright : (C) 2016 Paul Blottiere, Oslandia
5  email : paul dot blottiere at oslandia dot com
6  ***************************************************************************/
7 
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgslayoutitemnodeitem.h"
18 #include "qgssymbollayerutils.h"
19 #include "qgssymbol.h"
20 #include "qgsmapsettings.h"
21 #include "qgslayout.h"
22 #include "qgslayoututils.h"
23 #include "qgsmarkersymbol.h"
24 
25 #include <limits>
26 #include <cmath>
27 #include <QStyleOptionGraphicsItem>
28 
29 void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
30 {
31  mPolygon = nodes;
33  emit clipPathChanged();
34 }
35 
37 {
38  return mCurrentRectangle;
39 }
40 
42 {
43  return mMaxSymbolBleed;
44 }
45 
47  : QgsLayoutItem( layout )
48 {
49  init();
50 }
51 
52 QgsLayoutNodesItem::QgsLayoutNodesItem( const QPolygonF &polygon,
53  QgsLayout *layout )
54  : QgsLayoutItem( layout )
55 {
56  init();
57 
58  const QRectF boundingRect = polygon.boundingRect();
60 
61  const QPointF topLeft = boundingRect.topLeft();
62  mPolygon = polygon.translated( -topLeft );
63 }
64 
65 void QgsLayoutNodesItem::init()
66 {
67  // no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
68  // this blocks use of the pixmap based cache for these
69  setCacheMode( QGraphicsItem::NoCache );
70  setBackgroundEnabled( false );
71  setFrameEnabled( false );
72 
74 }
75 
77 {
78  QPainter *painter = context.renderContext().painter();
79  painter->setPen( Qt::NoPen );
80  painter->setBrush( Qt::NoBrush );
81 
82  context.renderContext().setForceVectorOutput( true );
84  _draw( context );
85 
86  if ( mDrawNodes && layout()->renderContext().isPreviewRender() )
87  drawNodes( context );
88 }
89 
91  QPointF pt2 ) const
92 {
93  return std::sqrt( std::pow( pt1.x() - pt2.x(), 2 ) + std::pow( pt1.y() - pt2.y(), 2 ) );
94 }
95 
96 bool QgsLayoutNodesItem::addNode( QPointF pt,
97  const bool checkArea,
98  const double radius )
99 {
100  const QPointF start = mapFromScene( pt );
101  double minDistance = std::numeric_limits<double>::max();
102  const double maxDistance = ( checkArea ) ? radius : minDistance;
103  bool rc = false;
104  int idx = -1;
105 
106  for ( int i = 0; i != mPolygon.size(); i++ )
107  {
108  // get nodes of polyline
109  const QPointF pt1 = mPolygon.at( i );
110  QPointF pt2 = mPolygon.at( 0 );
111  if ( ( i + 1 ) != mPolygon.size() )
112  pt2 = mPolygon.at( i + 1 );
113 
114  // compute line eq
115  const double coef = ( pt2.y() - pt1.y() ) / ( pt2.x() - pt1.x() );
116  const double b = pt1.y() - coef * pt1.x();
117 
118  double distance = std::numeric_limits<double>::max();
119  if ( std::isinf( coef ) )
120  distance = std::fabs( pt1.x() - start.x() );
121  else
122  {
123  const double coef2 = ( -1 / coef );
124  const double b2 = start.y() - coef2 * start.x();
125 
126  QPointF inter;
127  if ( std::isinf( coef2 ) )
128  {
129  distance = std::fabs( pt1.y() - start.y() );
130  inter.setX( start.x() );
131  inter.setY( pt1.y() );
132  }
133  else
134  {
135  const double interx = ( b - b2 ) / ( coef2 - coef );
136  const double intery = interx * coef2 + b2;
137  inter.setX( interx );
138  inter.setY( intery );
139  }
140 
141  // check if intersection is within the line
142  const double length1 = computeDistance( inter, pt1 );
143  const double length2 = computeDistance( inter, pt2 );
144  const double length3 = computeDistance( pt1, pt2 );
145  const double length4 = length1 + length2;
146 
147  if ( std::fabs( length3 - length4 ) < std::numeric_limits<float>::epsilon() )
148  distance = computeDistance( inter, start );
149  }
150 
151  if ( distance < minDistance && distance < maxDistance )
152  {
153  minDistance = distance;
154  idx = i;
155  }
156  }
157 
158  if ( idx >= 0 )
159  {
160  rc = _addNode( idx, start, maxDistance );
161  updateSceneRect();
162  emit clipPathChanged();
163  }
164 
165  return rc;
166 }
167 
168 void QgsLayoutNodesItem::drawNodes( QgsLayoutItemRenderContext &context ) const
169 {
170  context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
171 
172  const double rectSize = 9.0 / context.viewScaleFactor();
173 
174  QVariantMap properties;
175  properties.insert( QStringLiteral( "name" ), QStringLiteral( "cross" ) );
176  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "red" ) );
177 
178  std::unique_ptr<QgsMarkerSymbol> symbol;
179  symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
180  symbol->setSize( rectSize );
181  symbol->setAngle( 45 );
182 
183  symbol->startRender( context.renderContext() );
184  for ( const QPointF pt : std::as_const( mPolygon ) )
185  symbol->renderPoint( pt * context.viewScaleFactor(), nullptr, context.renderContext() );
186  symbol->stopRender( context.renderContext() );
187 
188  if ( mSelectedNode >= 0 && mSelectedNode < mPolygon.size() )
189  drawSelectedNode( context );
190 }
191 
192 void QgsLayoutNodesItem::drawSelectedNode( QgsLayoutItemRenderContext &context ) const
193 {
194  const double rectSize = 9.0 / context.viewScaleFactor();
195 
196  QVariantMap properties;
197  properties.insert( QStringLiteral( "name" ), QStringLiteral( "square" ) );
198  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0, 0, 0, 0" ) );
199  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "blue" ) );
200  properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "4" ) );
201 
202  std::unique_ptr<QgsMarkerSymbol> symbol;
203  symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
204  symbol->setSize( rectSize );
205 
206  symbol->startRender( context.renderContext() );
207  symbol->renderPoint( mPolygon.at( mSelectedNode ) * context.viewScaleFactor(), nullptr, context.renderContext() );
208  symbol->stopRender( context.renderContext() );
209 }
210 
212  const bool searchInRadius,
213  const double radius ) const
214 {
215  const QPointF pt = mapFromScene( node );
216  double nearestDistance = std::numeric_limits<double>::max();
217  const double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
218  double distance = 0;
219  int idx = -1;
220 
221  int i = 0;
222  for ( const QPointF polyPt : std::as_const( mPolygon ) )
223  {
224  distance = computeDistance( pt, polyPt );
225  if ( distance < nearestDistance && distance < maxDistance )
226  {
227  nearestDistance = distance;
228  idx = i;
229  }
230  i++;
231  }
232 
233  return idx;
234 }
235 
236 bool QgsLayoutNodesItem::nodePosition( const int index, QPointF &position ) const
237 {
238  bool rc( false );
239 
240  if ( index >= 0 && index < mPolygon.size() )
241  {
242  position = mapToScene( mPolygon.at( index ) );
243  rc = true;
244  }
245 
246  return rc;
247 }
248 
249 bool QgsLayoutNodesItem::removeNode( const int index )
250 {
251  const bool rc = _removeNode( index );
252  if ( rc )
253  {
254  updateSceneRect();
255  emit clipPathChanged();
256  }
257  return rc;
258 }
259 
260 bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
261 {
262  bool rc( false );
263 
264  if ( index >= 0 && index < mPolygon.size() )
265  {
266  const QPointF nodeItem = mapFromScene( pt );
267  mPolygon.replace( index, nodeItem );
268  updateSceneRect();
269  emit clipPathChanged();
270  rc = true;
271  }
272 
273  return rc;
274 }
275 
276 bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
277  const QDomDocument &, const QgsReadWriteContext &context )
278 {
279  // restore style
280  const QDomElement styleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
281  if ( !styleSymbolElem.isNull() )
282  _readXmlStyle( styleSymbolElem, context );
283 
284  // restore nodes
285  mPolygon.clear();
286  const QDomNodeList nodesList = itemElem.elementsByTagName( QStringLiteral( "node" ) );
287  for ( int i = 0; i < nodesList.size(); i++ )
288  {
289  const QDomElement nodeElem = nodesList.at( i ).toElement();
290  QPointF newPt;
291  newPt.setX( nodeElem.attribute( QStringLiteral( "x" ) ).toDouble() );
292  newPt.setY( nodeElem.attribute( QStringLiteral( "y" ) ).toDouble() );
293  mPolygon.append( newPt );
294  }
295 
296  emit changed();
297  emit clipPathChanged();
298  return true;
299 }
300 
302 {
303  // get the bounding rect for the polygon currently displayed
304  const QRectF boundingRect = mPolygon.boundingRect();
305 
306  // compute x/y ratio
307  const float ratioX = !qgsDoubleNear( boundingRect.width(), 0.0 )
308  ? rect().width() / boundingRect.width() : 0;
309  const float ratioY = !qgsDoubleNear( boundingRect.height(), 0.0 )
310  ? rect().height() / boundingRect.height() : 0;
311 
312  // scaling
313  QTransform trans;
314  trans = trans.scale( ratioX, ratioY );
315  mPolygon = trans.map( mPolygon );
316  emit clipPathChanged();
317 }
318 
319 bool QgsLayoutNodesItem::setSelectedNode( const int index )
320 {
321  bool rc = false;
322 
323  if ( index >= 0 && index < mPolygon.size() )
324  {
325  mSelectedNode = index;
326  rc = true;
327  }
328 
329  return rc;
330 }
331 
333 {
334  // set the new scene rectangle
335  const QRectF br = mPolygon.boundingRect();
336 
337  const QPointF topLeft = mapToScene( br.topLeft() );
338  //will trigger updateBoundingRect if necessary
339  attemptSetSceneRect( QRectF( topLeft.x(), topLeft.y(), br.width(), br.height() ) );
340 
341  // update polygon position
342  mPolygon.translate( -br.topLeft().x(), -br.topLeft().y() );
343 }
344 
346 {
347  QRectF br = rect();
349  mCurrentRectangle = br;
350 
351  // update
352  prepareGeometryChange();
353  update();
354 }
355 
356 bool QgsLayoutNodesItem::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
357 {
358  // style
359  _writeXmlStyle( doc, elem, context );
360 
361  // write nodes
362  QDomElement nodesElem = doc.createElement( QStringLiteral( "nodes" ) );
363  for ( const QPointF pt : std::as_const( mPolygon ) )
364  {
365  QDomElement nodeElem = doc.createElement( QStringLiteral( "node" ) );
366  nodeElem.setAttribute( QStringLiteral( "x" ), QString::number( pt.x() ) );
367  nodeElem.setAttribute( QStringLiteral( "y" ), QString::number( pt.y() ) );
368  nodesElem.appendChild( nodeElem );
369  }
370  elem.appendChild( nodesElem );
371 
372  return true;
373 }
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:45
double viewScaleFactor() const
Returns the current view zoom (scale factor).
Definition: qgslayoutitem.h:94
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
Base class for graphical items within a QgsLayout.
virtual void setFrameEnabled(bool drawFrame)
Sets whether this item has a frame drawn around it or not.
void sizePositionChanged()
Emitted when the item's size or position changes.
void clipPathChanged()
Emitted when the item's clipping path has changed.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
virtual void updateBoundingRect()
Called when the bounding rect of the item should recalculated.
virtual bool _removeNode(int nodeIndex)=0
Method called in removeNode.
QPolygonF nodes() const
Returns the nodes the shape consists of.
double mMaxSymbolBleed
Max symbol bleed.
virtual void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const =0
Method called in writeXml.
QRectF mCurrentRectangle
Current bounding rectangle of shape.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
bool setSelectedNode(int index)
Selects a node by index.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
void updateSceneRect()
Update the current scene rectangle for this item.
virtual void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context)=0
Method called in readXml.
bool removeNode(int index)
Remove a node with specified index from the shape.
QRectF boundingRect() const override
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidean distance between 2 nodes.
bool nodePosition(int index, QPointF &position) const
Gets the position of a node in scene coordinates.
bool addNode(QPointF point, bool checkArea=true, double radius=10)
Add a node in current shape.
bool moveNode(int index, QPointF node)
Moves a node to a new position.
double estimatedFrameBleed() const override
Returns the estimated amount the item's frame bleeds outside the item's actual rectangle.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
virtual void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr)=0
Method called in paint.
QgsLayoutNodesItem(QgsLayout *layout)
Constructor for QgsLayoutNodesItem, attached to the specified layout.
virtual bool _addNode(int nodeIndex, QPointF newNode, double radius)=0
Method called in addNode.
void rescaleToFitBoundingBox()
Rescale the current shape according to the item's bounding box.
void setNodes(const QPolygonF &nodes)
Sets the nodes the shape consists of.
QPolygonF mPolygon
Shape's nodes.
int nodeAtPosition(QPointF point, bool searchInRadius=true, double radius=10) const
Search for the nearest node in the shape within a maximal area.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
The class is used as a container of context for various read/write operations on other objects.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
QPainter * painter()
Returns the destination QPainter for the render operation.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1491