QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 <limits>
24 #include <cmath>
25 #include <QStyleOptionGraphicsItem>
26 
27 void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
28 {
29  mPolygon = nodes;
31  emit clipPathChanged();
32 }
33 
35 {
36  return mCurrentRectangle;
37 }
38 
40 {
41  return mMaxSymbolBleed;
42 }
43 
45  : QgsLayoutItem( layout )
46 {
47  init();
48 }
49 
50 QgsLayoutNodesItem::QgsLayoutNodesItem( const QPolygonF &polygon,
51  QgsLayout *layout )
52  : QgsLayoutItem( layout )
53 {
54  init();
55 
56  const QRectF boundingRect = polygon.boundingRect();
58 
59  const QPointF topLeft = boundingRect.topLeft();
60  mPolygon = polygon.translated( -topLeft );
61 }
62 
63 void QgsLayoutNodesItem::init()
64 {
65  // no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
66  // this blocks use of the pixmap based cache for these
67  setCacheMode( QGraphicsItem::NoCache );
68  setBackgroundEnabled( false );
69  setFrameEnabled( false );
70 
72 }
73 
75 {
76  QPainter *painter = context.renderContext().painter();
77  painter->setPen( Qt::NoPen );
78  painter->setBrush( Qt::NoBrush );
79 
80  context.renderContext().setForceVectorOutput( true );
82  _draw( context );
83 
84  if ( mDrawNodes && layout()->renderContext().isPreviewRender() )
85  drawNodes( context );
86 }
87 
89  QPointF pt2 ) const
90 {
91  return std::sqrt( std::pow( pt1.x() - pt2.x(), 2 ) + std::pow( pt1.y() - pt2.y(), 2 ) );
92 }
93 
94 bool QgsLayoutNodesItem::addNode( QPointF pt,
95  const bool checkArea,
96  const double radius )
97 {
98  const QPointF start = mapFromScene( pt );
99  double minDistance = std::numeric_limits<double>::max();
100  double maxDistance = ( checkArea ) ? radius : minDistance;
101  bool rc = false;
102  int idx = -1;
103 
104  for ( int i = 0; i != mPolygon.size(); i++ )
105  {
106  // get nodes of polyline
107  const QPointF pt1 = mPolygon.at( i );
108  QPointF pt2 = mPolygon.at( 0 );
109  if ( ( i + 1 ) != mPolygon.size() )
110  pt2 = mPolygon.at( i + 1 );
111 
112  // compute line eq
113  const double coef = ( pt2.y() - pt1.y() ) / ( pt2.x() - pt1.x() );
114  const double b = pt1.y() - coef * pt1.x();
115 
116  double distance = std::numeric_limits<double>::max();
117  if ( std::isinf( coef ) )
118  distance = std::fabs( pt1.x() - start.x() );
119  else
120  {
121  const double coef2 = ( -1 / coef );
122  const double b2 = start.y() - coef2 * start.x();
123 
124  QPointF inter;
125  if ( std::isinf( coef2 ) )
126  {
127  distance = std::fabs( pt1.y() - start.y() );
128  inter.setX( start.x() );
129  inter.setY( pt1.y() );
130  }
131  else
132  {
133  const double interx = ( b - b2 ) / ( coef2 - coef );
134  const double intery = interx * coef2 + b2;
135  inter.setX( interx );
136  inter.setY( intery );
137  }
138 
139  // check if intersection is within the line
140  const double length1 = computeDistance( inter, pt1 );
141  const double length2 = computeDistance( inter, pt2 );
142  const double length3 = computeDistance( pt1, pt2 );
143  const double length4 = length1 + length2;
144 
145  if ( std::fabs( length3 - length4 ) < std::numeric_limits<float>::epsilon() )
146  distance = computeDistance( inter, start );
147  }
148 
149  if ( distance < minDistance && distance < maxDistance )
150  {
151  minDistance = distance;
152  idx = i;
153  }
154  }
155 
156  if ( idx >= 0 )
157  {
158  rc = _addNode( idx, start, maxDistance );
159  updateSceneRect();
160  emit clipPathChanged();
161  }
162 
163  return rc;
164 }
165 
166 void QgsLayoutNodesItem::drawNodes( QgsLayoutItemRenderContext &context ) const
167 {
168  context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
169 
170  double rectSize = 9.0 / context.viewScaleFactor();
171 
172  QVariantMap properties;
173  properties.insert( QStringLiteral( "name" ), QStringLiteral( "cross" ) );
174  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "red" ) );
175 
176  std::unique_ptr<QgsMarkerSymbol> symbol;
177  symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
178  symbol->setSize( rectSize );
179  symbol->setAngle( 45 );
180 
181  symbol->startRender( context.renderContext() );
182  for ( QPointF pt : mPolygon )
183  symbol->renderPoint( pt * context.viewScaleFactor(), nullptr, context.renderContext() );
184  symbol->stopRender( context.renderContext() );
185 
186  if ( mSelectedNode >= 0 && mSelectedNode < mPolygon.size() )
187  drawSelectedNode( context );
188 }
189 
190 void QgsLayoutNodesItem::drawSelectedNode( QgsLayoutItemRenderContext &context ) const
191 {
192  double rectSize = 9.0 / context.viewScaleFactor();
193 
194  QVariantMap properties;
195  properties.insert( QStringLiteral( "name" ), QStringLiteral( "square" ) );
196  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0, 0, 0, 0" ) );
197  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "blue" ) );
198  properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "4" ) );
199 
200  std::unique_ptr<QgsMarkerSymbol> symbol;
201  symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
202  symbol->setSize( rectSize );
203 
204  symbol->startRender( context.renderContext() );
205  symbol->renderPoint( mPolygon.at( mSelectedNode ) * context.viewScaleFactor(), nullptr, context.renderContext() );
206  symbol->stopRender( context.renderContext() );
207 }
208 
210  const bool searchInRadius,
211  const double radius ) const
212 {
213  const QPointF pt = mapFromScene( node );
214  double nearestDistance = std::numeric_limits<double>::max();
215  double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
216  double distance = 0;
217  int idx = -1;
218 
219  int i = 0;
220  for ( QPointF polyPt : mPolygon )
221  {
222  distance = computeDistance( pt, polyPt );
223  if ( distance < nearestDistance && distance < maxDistance )
224  {
225  nearestDistance = distance;
226  idx = i;
227  }
228  i++;
229  }
230 
231  return idx;
232 }
233 
234 bool QgsLayoutNodesItem::nodePosition( const int index, QPointF &position ) const
235 {
236  bool rc( false );
237 
238  if ( index >= 0 && index < mPolygon.size() )
239  {
240  position = mapToScene( mPolygon.at( index ) );
241  rc = true;
242  }
243 
244  return rc;
245 }
246 
247 bool QgsLayoutNodesItem::removeNode( const int index )
248 {
249  bool rc = _removeNode( index );
250  if ( rc )
251  {
252  updateSceneRect();
253  emit clipPathChanged();
254  }
255  return rc;
256 }
257 
258 bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
259 {
260  bool rc( false );
261 
262  if ( index >= 0 && index < mPolygon.size() )
263  {
264  QPointF nodeItem = mapFromScene( pt );
265  mPolygon.replace( index, nodeItem );
266  updateSceneRect();
267  emit clipPathChanged();
268  rc = true;
269  }
270 
271  return rc;
272 }
273 
274 bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
275  const QDomDocument &, const QgsReadWriteContext &context )
276 {
277  // restore style
278  QDomElement styleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
279  if ( !styleSymbolElem.isNull() )
280  _readXmlStyle( styleSymbolElem, context );
281 
282  // restore nodes
283  mPolygon.clear();
284  QDomNodeList nodesList = itemElem.elementsByTagName( QStringLiteral( "node" ) );
285  for ( int i = 0; i < nodesList.size(); i++ )
286  {
287  QDomElement nodeElem = nodesList.at( i ).toElement();
288  QPointF newPt;
289  newPt.setX( nodeElem.attribute( QStringLiteral( "x" ) ).toDouble() );
290  newPt.setY( nodeElem.attribute( QStringLiteral( "y" ) ).toDouble() );
291  mPolygon.append( newPt );
292  }
293 
294  emit changed();
295  emit clipPathChanged();
296  return true;
297 }
298 
300 {
301  // get the bounding rect for the polygon currently displayed
302  const QRectF boundingRect = mPolygon.boundingRect();
303 
304  // compute x/y ratio
305  const float ratioX = !qgsDoubleNear( boundingRect.width(), 0.0 )
306  ? rect().width() / boundingRect.width() : 0;
307  const float ratioY = !qgsDoubleNear( boundingRect.height(), 0.0 )
308  ? rect().height() / boundingRect.height() : 0;
309 
310  // scaling
311  QTransform trans;
312  trans = trans.scale( ratioX, ratioY );
313  mPolygon = trans.map( mPolygon );
314  emit clipPathChanged();
315 }
316 
317 bool QgsLayoutNodesItem::setSelectedNode( const int index )
318 {
319  bool rc = false;
320 
321  if ( index >= 0 && index < mPolygon.size() )
322  {
323  mSelectedNode = index;
324  rc = true;
325  }
326 
327  return rc;
328 }
329 
331 {
332  // set the new scene rectangle
333  const QRectF br = mPolygon.boundingRect();
334 
335  const QPointF topLeft = mapToScene( br.topLeft() );
336  //will trigger updateBoundingRect if necessary
337  attemptSetSceneRect( QRectF( topLeft.x(), topLeft.y(), br.width(), br.height() ) );
338 
339  // update polygon position
340  mPolygon.translate( -br.topLeft().x(), -br.topLeft().y() );
341 }
342 
344 {
345  QRectF br = rect();
347  mCurrentRectangle = br;
348 
349  // update
350  prepareGeometryChange();
351  update();
352 }
353 
354 bool QgsLayoutNodesItem::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
355 {
356  // style
357  _writeXmlStyle( doc, elem, context );
358 
359  // write nodes
360  QDomElement nodesElem = doc.createElement( QStringLiteral( "nodes" ) );
361  for ( QPointF pt : mPolygon )
362  {
363  QDomElement nodeElem = doc.createElement( QStringLiteral( "node" ) );
364  nodeElem.setAttribute( QStringLiteral( "x" ), QString::number( pt.x() ) );
365  nodeElem.setAttribute( QStringLiteral( "y" ), QString::number( pt.y() ) );
366  nodesElem.appendChild( nodeElem );
367  }
368  elem.appendChild( nodesElem );
369 
370  return true;
371 }
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:50
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
Definition: qgssymbol.cpp:1556
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:316