QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 }
QgsLayoutItemRenderContext::viewScaleFactor
double viewScaleFactor() const
Returns the current view zoom (scale factor).
Definition: qgslayoutitem.h:94
QgsLayoutObject::layout
const QgsLayout * layout() const
Returns the layout the object is attached to.
Definition: qgslayoutobject.cpp:216
QgsLayoutNodesItem::setNodes
void setNodes(const QPolygonF &nodes)
Sets the nodes the shape consists of.
Definition: qgslayoutitemnodeitem.cpp:29
QgsLayoutNodesItem::QgsLayoutNodesItem
QgsLayoutNodesItem(QgsLayout *layout)
Constructor for QgsLayoutNodesItem, attached to the specified layout.
Definition: qgslayoutitemnodeitem.cpp:46
QgsLayoutNodesItem::mMaxSymbolBleed
double mMaxSymbolBleed
Max symbol bleed.
Definition: qgslayoutitemnodeitem.h:150
QgsLayoutNodesItem::boundingRect
QRectF boundingRect() const override
Definition: qgslayoutitemnodeitem.cpp:36
QgsLayoutItem::sizePositionChanged
void sizePositionChanged()
Emitted when the item's size or position changes.
QgsLayoutItem::setFrameEnabled
virtual void setFrameEnabled(bool drawFrame)
Sets whether this item has a frame drawn around it or not.
Definition: qgslayoutitem.cpp:834
QgsReadWriteContext
The class is used as a container of context for various read/write operations on other objects.
Definition: qgsreadwritecontext.h:34
QgsLayoutNodesItem::computeDistance
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidean distance between 2 nodes.
Definition: qgslayoutitemnodeitem.cpp:90
QgsLayoutNodesItem::readPropertiesFromElement
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
Definition: qgslayoutitemnodeitem.cpp:276
qgssymbollayerutils.h
QgsLayoutItemRenderContext
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:44
QgsLayoutItem::attemptSetSceneRect
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.
Definition: qgslayoutitem.cpp:511
QgsLayoutNodesItem::_removeNode
virtual bool _removeNode(int nodeIndex)=0
Method called in removeNode.
QgsLayoutNodesItem::setSelectedNode
bool setSelectedNode(int index)
Selects a node by index.
Definition: qgslayoutitemnodeitem.cpp:319
qgsmapsettings.h
QgsLayoutObject::changed
void changed()
Emitted when the object's properties change.
QgsLayoutNodesItem::mCurrentRectangle
QRectF mCurrentRectangle
Current bounding rectangle of shape.
Definition: qgslayoutitemnodeitem.h:180
qgslayoutitemnodeitem.h
QgsLayoutNodesItem::updateSceneRect
void updateSceneRect()
Update the current scene rectangle for this item.
Definition: qgslayoutitemnodeitem.cpp:332
QgsLayoutNodesItem::mPolygon
QPolygonF mPolygon
Shape's nodes.
Definition: qgslayoutitemnodeitem.h:147
qgslayoututils.h
QgsLayoutNodesItem::estimatedFrameBleed
double estimatedFrameBleed() const override
Returns the estimated amount the item's frame bleeds outside the item's actual rectangle.
Definition: qgslayoutitemnodeitem.cpp:41
QgsLayoutNodesItem::draw
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
Definition: qgslayoutitemnodeitem.cpp:76
QgsLayoutNodesItem::_draw
virtual void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr)=0
Method called in paint.
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2265
QgsLayoutNodesItem::nodes
QPolygonF nodes() const
Returns the nodes the shape consists of.
Definition: qgslayoutitemnodeitem.h:57
QgsLayoutItem::setBackgroundEnabled
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
Definition: qgslayoutitem.cpp:887
QgsLayoutNodesItem::addNode
bool addNode(QPointF point, bool checkArea=true, double radius=10)
Add a node in current shape.
Definition: qgslayoutitemnodeitem.cpp:96
QgsLayoutNodesItem::rescaleToFitBoundingBox
void rescaleToFitBoundingBox()
Rescale the current shape according to the item's bounding box.
Definition: qgslayoutitemnodeitem.cpp:301
QgsLayoutItem
Base class for graphical items within a QgsLayout.
Definition: qgslayoutitem.h:112
QgsLayoutNodesItem::writePropertiesToElement
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
Definition: qgslayoutitemnodeitem.cpp:356
qgslayout.h
QgsLayoutNodesItem::removeNode
bool removeNode(int index)
Remove a node with specified index from the shape.
Definition: qgslayoutitemnodeitem.cpp:249
QgsLayoutNodesItem::nodePosition
bool nodePosition(int index, QPointF &position) const
Gets the position of a node in scene coordinates.
Definition: qgslayoutitemnodeitem.cpp:236
QgsLayoutNodesItem::_readXmlStyle
virtual void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context)=0
Method called in readXml.
QgsRenderContext::setForceVectorOutput
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
Definition: qgsrendercontext.cpp:330
QgsLayoutItemRenderContext::renderContext
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
qgsmarkersymbol.h
QgsLayoutNodesItem::moveNode
bool moveNode(int index, QPointF node)
Moves a node to a new position.
Definition: qgslayoutitemnodeitem.cpp:260
QgsLayoutNodesItem::updateBoundingRect
virtual void updateBoundingRect()
Called when the bounding rect of the item should recalculated.
Definition: qgslayoutitemnodeitem.cpp:345
QgsLayoutNodesItem::nodeAtPosition
int nodeAtPosition(QPointF point, bool searchInRadius=true, double radius=10) const
Search for the nearest node in the shape within a maximal area.
Definition: qgslayoutitemnodeitem.cpp:211
QgsRenderContext::painter
QPainter * painter()
Returns the destination QPainter for the render operation.
Definition: qgsrendercontext.h:112
QgsLayoutNodesItem::_writeXmlStyle
virtual void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const =0
Method called in writeXml.
QgsMarkerSymbol::createSimple
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
Definition: qgsmarkersymbol.cpp:22
QgsLayoutNodesItem::_addNode
virtual bool _addNode(int nodeIndex, QPointF newNode, double radius)=0
Method called in addNode.
qgssymbol.h
QgsLayoutItem::clipPathChanged
void clipPathChanged()
Emitted when the item's clipping path has changed.