QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgscomposernodesitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposernodesitem.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 "qgscomposernodesitem.h"
18 #include "qgscomposition.h"
19 #include "qgscomposerutils.h"
20 #include "qgssymbollayerv2utils.h"
21 #include "qgssymbolv2.h"
22 #include <limits>
23 #include <math.h>
24 
26  QgsComposition* c )
27  : QgsComposerItem( c )
28  , mTagName( tagName )
29  , mSelectedNode( -1 )
30  , mDrawNodes( false )
31 {
32 }
33 
35  QPolygonF polygon,
36  QgsComposition* c )
37  : QgsComposerItem( c )
38  , mTagName( tagName )
39  , mSelectedNode( -1 )
40  , mDrawNodes( false )
41 {
42  const QRectF boundingRect = polygon.boundingRect();
43  setSceneRect( boundingRect );
44 
45  const QPointF topLeft = boundingRect.topLeft();
46  mPolygon = polygon.translated( -topLeft );
47 }
48 
50 {
51 }
52 
54  const QPointF &pt2 ) const
55 {
56  return sqrt( pow( pt1.x() - pt2.x(), 2 ) + pow( pt1.y() - pt2.y(), 2 ) );
57 }
58 
60  const bool checkArea,
61  const double radius )
62 {
63  const QPointF start = mapFromScene( pt );
64  double minDistance = std::numeric_limits<double>::max();
65  double maxDistance = ( checkArea ) ? radius : minDistance;
66  bool rc = false;
67  int idx = -1;
68 
69  for ( int i = 0; i != mPolygon.size(); i++ )
70  {
71  // get nodes of polyline
72  const QPointF pt1 = mPolygon.at( i );
73  QPointF pt2 = mPolygon.first();
74  if (( i + 1 ) != mPolygon.size() )
75  pt2 = mPolygon.at( i + 1 );
76 
77  // compute line eq
78  const double coef = ( pt2.y() - pt1.y() ) / ( pt2.x() - pt1.x() );
79  const double b = pt1.y() - coef * pt1.x();
80 
81  double distance = std::numeric_limits<double>::max();
82  if ( qIsInf( coef ) )
83  distance = qAbs( pt1.x() - start.x() );
84  else
85  {
86  const double coef2 = ( -1 / coef );
87  const double b2 = start.y() - coef2 * start.x();
88 
89  QPointF inter;
90  if ( qIsInf( coef2 ) )
91  {
92  distance = qAbs( pt1.y() - start.y() );
93  inter.setX( start.x() );
94  inter.setY( pt1.y() );
95  }
96  else
97  {
98  const double interx = ( b - b2 ) / ( coef2 - coef );
99  const double intery = interx * coef2 + b2;
100  inter.setX( interx );
101  inter.setY( intery );
102  }
103 
104  // check if intersection is within the line
105  const double length1 = computeDistance( inter, pt1 );
106  const double length2 = computeDistance( inter, pt2 );
107  const double length3 = computeDistance( pt1, pt2 );
108  const double length4 = length1 + length2;
109 
110  if ( qAbs( length3 - length4 ) < std::numeric_limits<float>::epsilon() )
111  distance = computeDistance( inter, start );
112  }
113 
114  if ( distance < minDistance && distance < maxDistance )
115  {
116  minDistance = distance;
117  idx = i;
118  }
119  }
120 
121  if ( idx >= 0 )
122  {
123  rc = _addNode( idx, start, maxDistance );
124  updateSceneRect();
125  }
126 
127  return rc;
128 }
129 
130 void QgsComposerNodesItem::drawNodes( QPainter *painter ) const
131 {
132  double rectSize = 3.0 / horizontalViewScaleFactor();
133 
134  QgsStringMap properties;
135  properties.insert( "name", "cross" );
136  properties.insert( "color_border", "red" );
137 
139  symbol.reset( QgsMarkerSymbolV2::createSimple( properties ) );
140  symbol.data()->setSize( rectSize );
141  symbol.data()->setAngle( 45 );
142 
144  ms.setOutputDpi( painter->device()->logicalDpiX() );
145 
147  context.setPainter( painter );
148  context.setForceVectorOutput( true );
149 
150  QScopedPointer<QgsExpressionContext> expressionContext;
151  expressionContext.reset( createExpressionContext() );
152  context.setExpressionContext( *expressionContext.data() );
153 
154  symbol.data()->startRender( context );
155 
156  Q_FOREACH ( QPointF pt, mPolygon )
157  symbol.data()->renderPoint( pt, nullptr, context );
158 
159  symbol.data()->stopRender( context );
160 
161  if ( mSelectedNode >= 0 && mSelectedNode < mPolygon.size() )
162  drawSelectedNode( painter );
163 }
164 
165 void QgsComposerNodesItem::drawSelectedNode( QPainter *painter ) const
166 {
167  double rectSize = 3.0 / horizontalViewScaleFactor();
168 
169  QgsStringMap properties;
170  properties.insert( "name", "square" );
171  properties.insert( "color", "0, 0, 0, 0" );
172  properties.insert( "color_border", "blue" );
173  properties.insert( "width_border", "4" );
174 
176  symbol.reset( QgsMarkerSymbolV2::createSimple( properties ) );
177  symbol.data()->setSize( rectSize );
178 
180  ms.setOutputDpi( painter->device()->logicalDpiX() );
181 
183  context.setPainter( painter );
184  context.setForceVectorOutput( true );
185 
186  QScopedPointer<QgsExpressionContext> expressionContext;
187  expressionContext.reset( createExpressionContext() );
188  context.setExpressionContext( *expressionContext.data() );
189 
190  symbol.data()->startRender( context );
191  symbol.data()->renderPoint( mPolygon.at( mSelectedNode ), nullptr, context );
192  symbol.data()->stopRender( context );
193 }
194 
196  const QStyleOptionGraphicsItem* itemStyle,
197  QWidget* pWidget )
198 {
199  Q_UNUSED( itemStyle );
200  Q_UNUSED( pWidget );
201 
202  if ( !painter )
203  return;
204 
205  if ( !shouldDrawItem() )
206  {
207  return;
208  }
209 
210  painter->save();
211  painter->setPen( Qt::NoPen );
212  painter->setBrush( Qt::NoBrush );
213  painter->setRenderHint( QPainter::Antialiasing, true );
214 
216  _draw( painter );
217 
218  if ( mDrawNodes && composition()->plotStyle() == QgsComposition::Preview )
219  drawNodes( painter );
220 
221  painter->restore();
222 }
223 
225  const bool searchInRadius,
226  const double radius )
227 {
228  const QPointF pt = mapFromScene( node );
229  double nearestDistance = std::numeric_limits<double>::max();
230  double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
231  double distance = 0;
232  int idx = -1;
233 
235  for ( ; it != mPolygon.constEnd(); ++it )
236  {
237  distance = computeDistance( pt, *it );
238  if ( distance < nearestDistance && distance < maxDistance )
239  {
240  nearestDistance = distance;
241  idx = it - mPolygon.constBegin();
242  }
243  }
244 
245  return idx;
246 }
247 
249 {
250  bool rc( false );
251 
252  if ( index >= 0 && index < mPolygon.size() )
253  {
254  position = mapToScene( mPolygon.at( index ) );
255  rc = true;
256  }
257 
258  return rc;
259 }
260 
262 {
263  bool rc = _removeNode( index );
264  if ( rc )
265  updateSceneRect();
266  return rc;
267 }
268 
269 bool QgsComposerNodesItem::moveNode( const int index, const QPointF &pt )
270 {
271  bool rc( false );
272 
273  if ( index >= 0 && index < mPolygon.size() )
274  {
275  QPointF nodeItem = mapFromScene( pt );
276  mPolygon.replace( index, nodeItem );
277  updateSceneRect();
278 
279  rc = true;
280  }
281 
282  return rc;
283 }
284 
286  const QDomDocument& doc )
287 {
288  // restore general composer item properties
289  const QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
290  if ( !composerItemList.isEmpty() )
291  {
292  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
293 
294  if ( !qgsDoubleNear( composerItemElem.attribute( "rotation", "0" ).toDouble(), 0.0 ) )
295  setItemRotation( composerItemElem.attribute( "rotation", "0" ).toDouble() );
296 
297  _readXML( composerItemElem, doc );
298  }
299 
300  // restore style
301  QDomElement styleSymbolElem = itemElem.firstChildElement( "symbol" );
302  if ( !styleSymbolElem.isNull() )
303  _readXMLStyle( styleSymbolElem );
304 
305  // restore nodes
306  mPolygon.clear();
307  QDomNodeList nodesList = itemElem.elementsByTagName( "node" );
308  for ( int i = 0; i < nodesList.size(); i++ )
309  {
310  QDomElement nodeElem = nodesList.at( i ).toElement();
311  QPointF newPt;
312  newPt.setX( nodeElem.attribute( "x" ).toDouble() );
313  newPt.setY( nodeElem.attribute( "y" ).toDouble() );
314  mPolygon.append( newPt );
315  }
316 
317  emit itemChanged();
318  return true;
319 }
320 
322 {
323  // get the bounding rect for the polygon currently displayed
325 
326  // compute x/y ratio
327  const float ratioX = rect().width() / boundingRect.width();
328  const float ratioY = rect().height() / boundingRect.height();
329 
330  // scaling
331  QTransform trans;
332  trans = trans.scale( ratioX, ratioY );
333  mPolygon = trans.map( mPolygon );
334 }
335 
337 {
338  bool rc = false;
339 
340  if ( index >= 0 && index < mPolygon.size() )
341  {
342  mSelectedNode = index;
343  rc = true;
344  }
345 
346  return rc;
347 }
348 
350 {
351  // set the new scene rectangle
352  const QRectF br = mPolygon.boundingRect();
353 
354  const QPointF topLeft = mapToScene( br.topLeft() );
355  setSceneRect( QRectF( topLeft.x(), topLeft.y(), br.width(), br.height() ) );
356 
357  // update polygon position
358  mPolygon.translate( -br.topLeft().x(), -br.topLeft().y() );
359 
360  // update
362  update();
363  emit itemChanged();
364 }
365 
367 {
368  QDomElement composerPolygonElem = doc.createElement( mTagName );
369 
370  // style
371  _writeXMLStyle( doc, composerPolygonElem );
372 
373  // write nodes
374  QDomElement nodesElem = doc.createElement( "nodes" );
375  Q_FOREACH ( QPointF pt, mPolygon )
376  {
377  QDomElement nodeElem = doc.createElement( "node" );
378  nodeElem.setAttribute( "x", QString::number( pt.x() ) );
379  nodeElem.setAttribute( "y", QString::number( pt.y() ) );
380  nodesElem.appendChild( nodeElem );
381  }
382  composerPolygonElem.appendChild( nodesElem );
383 
384  elem.appendChild( composerPolygonElem );
385 
386  return _writeXML( composerPolygonElem, doc );
387 }
bool moveNode(const int index, const QPointF &node)
Move a node to a new position.
void setForceVectorOutput(bool force)
QDomNodeList elementsByTagName(const QString &tagname) const
static unsigned index
static QgsMarkerSymbolV2 * createSimple(const QgsStringMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
QPolygonF mPolygon
Storage meaning for shape&#39;s nodes.
double horizontalViewScaleFactor() const
Returns the zoom factor of the graphics view.
bool removeNode(const int index)
Remove a node from the shape.
int nodeAtPosition(QPointF node, const bool searchInRadius=true, const double radius=10)
Search the nearest node in shape within a maximal area.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
void setRenderHint(RenderHint hint, bool on)
virtual void _readXMLStyle(const QDomElement &elmt)=0
Method called in readXML.
QDomNode appendChild(const QDomNode &newChild)
void append(const T &value)
QString attribute(const QString &name, const QString &defValue) const
virtual void _writeXMLStyle(QDomDocument &doc, QDomElement &elmt) const =0
Method called in writeXML.
void itemChanged()
Emitted when the item changes.
QPoint map(const QPoint &point) const
virtual bool _removeNode(const int nodeIndex)=0
Method called in removeNode.
const_iterator constEnd() const
A item that forms part of a map composition.
QPolygonF translated(qreal dx, qreal dy) const
void setOutputDpi(double dpi)
Set DPI used for conversion between real world units (e.g. mm) and pixels.
void save()
T & first()
bool _writeXML(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document.
double toDouble(bool *ok) const
virtual QgsExpressionContext * createExpressionContext() const override
Creates an expression context relating to the item&#39;s current state.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
void update(const QRectF &rect)
double computeDistance(const QPointF &pt1, const QPointF &pt2) const
Compute an euclidian distance between 2 nodes.
bool readXML(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
void reset(T *other)
The QgsMapSettings class contains configuration for rendering of the map.
bool _readXML(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document.
QDomElement toElement() const
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
bool isEmpty() const
virtual QRectF boundingRect() const
void clear()
void updateSceneRect()
Update the current scene rectangle for this item.
QString number(int n, int base)
qreal x() const
qreal y() const
QTransform & scale(qreal sx, qreal sy)
bool addNode(const QPointF &pt, const bool checkArea=true, const double radius=10)
Add a node in current shape.
void setPen(const QColor &color)
void setAttribute(const QString &name, const QString &value)
bool nodePosition(const int index, QPointF &position)
Gets the position of a node in scene coordinate.
QPointF topLeft() const
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
Reimplementation of QCanvasItem::paint - draw on canvas.
QPointF mapFromScene(const QPointF &point) const
QPaintDevice * device() const
virtual bool _addNode(const int nodeIndex, const QPointF &newNode, const double radius)=0
Method called in addNode.
void translate(qreal dx, qreal dy)
void setBrush(const QBrush &brush)
void setPainter(QPainter *p)
bool setSelectedNode(const int index)
Select a node.
virtual void _draw(QPainter *painter)=0
Method called in paint.
void rescaleToFitBoundingBox()
Rescale the current shape according to the boudning box.
void prepareGeometryChange()
Graphics scene for map printing.
const QgsMapSettings & mapSettings() const
Return setting of QGIS map canvas.
int logicalDpiX() const
T * data() const
bool isNull() const
void restore()
QgsComposition * mComposition
QgsComposerNodesItem(QString mTagName, QgsComposition *c)
Constructor.
const T & at(int i) const
const_iterator constBegin() const
Contains information about the context of a rendering operation.
QRectF boundingRect() const
QPointF mapToScene(const QPointF &point) const
qreal width() const
const QgsComposition * composition() const
Returns the composition the item is attached to.
virtual void setItemRotation(const double r, const bool adjustPosition=false)
Sets the item rotation.
~QgsComposerNodesItem()
Destructor.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setX(qreal x)
void setY(qreal y)
QDomElement firstChildElement(const QString &tagName) const
virtual void setSceneRect(const QRectF &rectangle)
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
void replace(int i, const T &value)
qreal height() const
iterator insert(const Key &key, const T &value)
bool writeXML(QDomElement &elem, QDomDocument &doc) const override
Stores state in Dom element.
int size() const
QDomElement createElement(const QString &tagName)
int size() const
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QDomNode at(int index) const
QRectF rect() const