QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgslayoutitempolyline.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitempolyline.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 "qgslayoutitempolyline.h"
18 #include "qgslayoutitemregistry.h"
19 #include "qgssymbollayerutils.h"
20 #include "qgssymbol.h"
21 #include "qgslayout.h"
22 #include "qgsmapsettings.h"
23 #include "qgslayoututils.h"
24 #include "qgsreadwritecontext.h"
25 #include "qgssvgcache.h"
26 #include <QSvgRenderer>
27 #include <limits>
28 #include <QGraphicsPathItem>
29 #include <QVector2D>
30 
32  : QgsLayoutNodesItem( layout )
33 {
34  createDefaultPolylineStyleSymbol();
35 }
36 
38  : QgsLayoutNodesItem( polyline, layout )
39 {
40  createDefaultPolylineStyleSymbol();
41 }
42 
44 {
45  return new QgsLayoutItemPolyline( layout );
46 }
47 
49 {
51 }
52 
54 {
55  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPolyline.svg" ) );
56 }
57 
58 bool QgsLayoutItemPolyline::_addNode( const int indexPoint,
59  QPointF newPoint,
60  const double radius )
61 {
62  const double distStart = computeDistance( newPoint, mPolygon[0] );
63  const double distEnd = computeDistance( newPoint, mPolygon[mPolygon.size() - 1] );
64 
65  if ( indexPoint == ( mPolygon.size() - 1 ) )
66  {
67  if ( distEnd < radius )
68  mPolygon.append( newPoint );
69  else if ( distStart < radius )
70  mPolygon.insert( 0, newPoint );
71  }
72  else
73  mPolygon.insert( indexPoint + 1, newPoint );
74 
75  return true;
76 }
77 
78 bool QgsLayoutItemPolyline::_removeNode( const int index )
79 {
80  if ( index < 0 || index >= mPolygon.size() )
81  return false;
82 
83  mPolygon.remove( index );
84 
85  if ( mPolygon.size() < 2 )
86  mPolygon.clear();
87  else
88  {
89  int newSelectNode = index;
90  if ( index >= mPolygon.size() )
91  newSelectNode = mPolygon.size() - 1;
92  setSelectedNode( newSelectNode );
93  }
94 
95  return true;
96 }
97 
98 void QgsLayoutItemPolyline::createDefaultPolylineStyleSymbol()
99 {
100  QgsStringMap properties;
101  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0,0,0,255" ) );
102  properties.insert( QStringLiteral( "width" ), QStringLiteral( "0.3" ) );
103  properties.insert( QStringLiteral( "capstyle" ), QStringLiteral( "square" ) );
104 
105  mPolylineStyleSymbol.reset( QgsLineSymbol::createSimple( properties ) );
106  refreshSymbol();
107 }
108 
109 void QgsLayoutItemPolyline::refreshSymbol()
110 {
111  if ( layout() )
112  {
113  QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( layout(), nullptr, layout()->renderContext().dpi() );
114  mMaxSymbolBleed = ( 25.4 / layout()->renderContext().dpi() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mPolylineStyleSymbol.get(), rc );
115  }
116 
117  updateSceneRect();
118 
119  emit frameChanged();
120 }
121 
122 void QgsLayoutItemPolyline::drawStartMarker( QPainter *painter )
123 {
124  if ( mPolygon.size() < 2 )
125  return;
126 
127  switch ( mStartMarker )
128  {
129  case MarkerMode::NoMarker:
130  break;
131 
132  case MarkerMode::ArrowHead:
133  {
134  // calculate angle at start of line
135  QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
136  double angle = startLine.angle();
137  drawArrow( painter, mPolygon.at( 0 ), angle );
138  break;
139  }
140 
141  case MarkerMode::SvgMarker:
142  {
143  // calculate angle at start of line
144  QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
145  double angle = startLine.angle();
146  drawSvgMarker( painter, mPolygon.at( 0 ), angle, mStartMarkerFile, mStartArrowHeadHeight );
147  break;
148  }
149  }
150 
151 }
152 
153 void QgsLayoutItemPolyline::drawEndMarker( QPainter *painter )
154 {
155  if ( mPolygon.size() < 2 )
156  return;
157 
158  switch ( mEndMarker )
159  {
160  case MarkerMode::NoMarker:
161  break;
162 
163  case MarkerMode::ArrowHead:
164  {
165  // calculate angle at end of line
166  QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
167  double angle = endLine.angle();
168 
169  //move end point depending on arrow width
170  QVector2D dir = QVector2D( endLine.dx(), endLine.dy() ).normalized();
171  QPointF endPoint = endLine.p2();
172  endPoint += ( dir * 0.5 * mArrowHeadWidth ).toPointF();
173 
174  drawArrow( painter, endPoint, angle );
175  break;
176  }
177  case MarkerMode::SvgMarker:
178  {
179  // calculate angle at end of line
180  QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
181  double angle = endLine.angle();
182  drawSvgMarker( painter, endLine.p2(), angle, mEndMarkerFile, mEndArrowHeadHeight );
183  break;
184  }
185  }
186 }
187 
188 void QgsLayoutItemPolyline::drawArrow( QPainter *painter, QPointF center, double angle )
189 {
190  // translate angle from ccw from axis to cw from north
191  angle = 90 - angle;
192  QPen p;
193  p.setColor( mArrowHeadStrokeColor );
194  p.setWidthF( mArrowHeadStrokeWidth );
195  painter->setPen( p );
196  QBrush b;
197  b.setColor( mArrowHeadFillColor );
198  painter->setBrush( b );
199 
200  drawArrowHead( painter, center.x(), center.y(), angle, mArrowHeadWidth );
201 }
202 
203 void QgsLayoutItemPolyline::updateMarkerSvgSizes()
204 {
205  setStartSvgMarkerPath( mStartMarkerFile );
206  setEndSvgMarkerPath( mEndMarkerFile );
207 }
208 
209 void QgsLayoutItemPolyline::drawArrowHead( QPainter *p, const double x, const double y, const double angle, const double arrowHeadWidth )
210 {
211  if ( !p )
212  return;
213 
214  double angleRad = angle / 180.0 * M_PI;
215  QPointF middlePoint( x, y );
216 
217  //rotate both arrow points
218  QPointF p1 = QPointF( -arrowHeadWidth / 2.0, arrowHeadWidth );
219  QPointF p2 = QPointF( arrowHeadWidth / 2.0, arrowHeadWidth );
220 
221  QPointF p1Rotated, p2Rotated;
222  p1Rotated.setX( p1.x() * std::cos( angleRad ) + p1.y() * -std::sin( angleRad ) );
223  p1Rotated.setY( p1.x() * std::sin( angleRad ) + p1.y() * std::cos( angleRad ) );
224  p2Rotated.setX( p2.x() * std::cos( angleRad ) + p2.y() * -std::sin( angleRad ) );
225  p2Rotated.setY( p2.x() * std::sin( angleRad ) + p2.y() * std::cos( angleRad ) );
226 
227  QPolygonF arrowHeadPoly;
228  arrowHeadPoly << middlePoint;
229  arrowHeadPoly << QPointF( middlePoint.x() + p1Rotated.x(), middlePoint.y() + p1Rotated.y() );
230  arrowHeadPoly << QPointF( middlePoint.x() + p2Rotated.x(), middlePoint.y() + p2Rotated.y() );
231  QPen arrowPen = p->pen();
232  arrowPen.setJoinStyle( Qt::RoundJoin );
233  QBrush arrowBrush = p->brush();
234  arrowBrush.setStyle( Qt::SolidPattern );
235  p->setPen( arrowPen );
236  p->setBrush( arrowBrush );
237  arrowBrush.setStyle( Qt::SolidPattern );
238  p->drawPolygon( arrowHeadPoly );
239 }
240 
241 void QgsLayoutItemPolyline::drawSvgMarker( QPainter *p, QPointF point, double angle, const QString &markerPath, double height ) const
242 {
243  // translate angle from ccw from axis to cw from north
244  angle = 90 - angle;
245 
246  if ( mArrowHeadWidth <= 0 || height <= 0 )
247  {
248  //bad image size
249  return;
250  }
251 
252  if ( markerPath.isEmpty() )
253  return;
254 
255  QSvgRenderer r;
256  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( markerPath, mArrowHeadWidth, mArrowHeadFillColor, mArrowHeadStrokeColor, mArrowHeadStrokeWidth,
257  1.0 );
258  r.load( svgContent );
259 
260  p->save();
261  p->translate( point.x(), point.y() );
262  p->rotate( angle );
263  p->translate( -mArrowHeadWidth / 2.0, -height / 2.0 );
264  r.render( p, QRectF( 0, 0, mArrowHeadWidth, height ) );
265  p->restore();
266 }
267 
269 {
270  if ( !id().isEmpty() )
271  return id();
272 
273  return tr( "<Polyline>" );
274 }
275 
276 void QgsLayoutItemPolyline::_draw( QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem * )
277 {
278  context.renderContext().painter()->save();
279  //setup painter scaling to dots so that raster symbology is drawn to scale
281  QTransform t = QTransform::fromScale( scale, scale );
282 
283  mPolylineStyleSymbol->startRender( context.renderContext() );
284  mPolylineStyleSymbol->renderPolyline( t.map( mPolygon ), nullptr, context.renderContext() );
285  mPolylineStyleSymbol->stopRender( context.renderContext() );
286 
287  // painter is scaled to dots, so scale back to layout units
288  context.renderContext().painter()->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
289 
290  drawStartMarker( context.renderContext().painter() );
291  drawEndMarker( context.renderContext().painter() );
292  context.renderContext().painter()->restore();
293 }
294 
295 void QgsLayoutItemPolyline::_readXmlStyle( const QDomElement &elmt, const QgsReadWriteContext &context )
296 {
297  mPolylineStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol>( elmt, context ) );
298 }
299 
301 {
302  mPolylineStyleSymbol.reset( static_cast<QgsLineSymbol *>( symbol->clone() ) );
303  refreshSymbol();
304 }
305 
307 {
308  mStartMarker = mode;
309  update();
310 }
311 
313 {
314  mEndMarker = mode;
315  update();
316 }
317 
319 {
320  mArrowHeadWidth = width;
321  updateMarkerSvgSizes();
322  update();
323 }
324 
325 QPainterPath QgsLayoutItemPolyline::shape() const
326 {
327  QPainterPath path;
328  path.addPolygon( mPolygon );
329 
330  QPainterPathStroker ps;
331 
332  ps.setWidth( 2 * mMaxSymbolBleed );
333  QPainterPath strokedOutline = ps.createStroke( path );
334 
335  return strokedOutline;
336 }
337 
339 {
340  QSvgRenderer r;
341  mStartMarkerFile = path;
342  if ( path.isEmpty() || !r.load( path ) )
343  {
344  mStartArrowHeadHeight = 0;
345  }
346  else
347  {
348  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
349  QRect viewBox = r.viewBox();
350  mStartArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
351  }
353 }
354 
356 {
357  QSvgRenderer r;
358  mEndMarkerFile = path;
359  if ( path.isEmpty() || !r.load( path ) )
360  {
361  mEndArrowHeadHeight = 0;
362  }
363  else
364  {
365  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
366  QRect viewBox = r.viewBox();
367  mEndArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
368  }
370 }
371 
373 {
374  mArrowHeadStrokeColor = color;
375  update();
376 }
377 
379 {
380  mArrowHeadFillColor = color;
381  update();
382 }
383 
385 {
386  mArrowHeadStrokeWidth = width;
388  update();
389 }
390 
391 void QgsLayoutItemPolyline::_writeXmlStyle( QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context ) const
392 {
393  const QDomElement pe = QgsSymbolLayerUtils::saveSymbol( QString(),
394  mPolylineStyleSymbol.get(),
395  doc,
396  context );
397  elmt.appendChild( pe );
398 }
399 
400 bool QgsLayoutItemPolyline::writePropertiesToElement( QDomElement &elmt, QDomDocument &doc, const QgsReadWriteContext &context ) const
401 {
402  QgsLayoutNodesItem::writePropertiesToElement( elmt, doc, context );
403 
404  // absolute paths to relative
405  QString startMarkerPath = QgsSymbolLayerUtils::svgSymbolPathToName( mStartMarkerFile, context.pathResolver() );
406  QString endMarkerPath = QgsSymbolLayerUtils::svgSymbolPathToName( mEndMarkerFile, context.pathResolver() );
407 
408  elmt.setAttribute( QStringLiteral( "arrowHeadWidth" ), QString::number( mArrowHeadWidth ) );
409  elmt.setAttribute( QStringLiteral( "arrowHeadFillColor" ), QgsSymbolLayerUtils::encodeColor( mArrowHeadFillColor ) );
410  elmt.setAttribute( QStringLiteral( "arrowHeadOutlineColor" ), QgsSymbolLayerUtils::encodeColor( mArrowHeadStrokeColor ) );
411  elmt.setAttribute( QStringLiteral( "outlineWidth" ), QString::number( mArrowHeadStrokeWidth ) );
412  elmt.setAttribute( QStringLiteral( "markerMode" ), mEndMarker );
413  elmt.setAttribute( QStringLiteral( "startMarkerMode" ), mStartMarker );
414  elmt.setAttribute( QStringLiteral( "startMarkerFile" ), startMarkerPath );
415  elmt.setAttribute( QStringLiteral( "endMarkerFile" ), endMarkerPath );
416 
417  return true;
418 }
419 
420 bool QgsLayoutItemPolyline::readPropertiesFromElement( const QDomElement &elmt, const QDomDocument &doc, const QgsReadWriteContext &context )
421 {
422  mArrowHeadWidth = elmt.attribute( QStringLiteral( "arrowHeadWidth" ), QStringLiteral( "2.0" ) ).toDouble();
423  mArrowHeadFillColor = QgsSymbolLayerUtils::decodeColor( elmt.attribute( QStringLiteral( "arrowHeadFillColor" ), QStringLiteral( "0,0,0,255" ) ) );
424  mArrowHeadStrokeColor = QgsSymbolLayerUtils::decodeColor( elmt.attribute( QStringLiteral( "arrowHeadOutlineColor" ), QStringLiteral( "0,0,0,255" ) ) );
425  mArrowHeadStrokeWidth = elmt.attribute( QStringLiteral( "outlineWidth" ), QStringLiteral( "1.0" ) ).toDouble();
426  // relative paths to absolute
427  QString startMarkerPath = elmt.attribute( QStringLiteral( "startMarkerFile" ), QString() );
428  QString endMarkerPath = elmt.attribute( QStringLiteral( "endMarkerFile" ), QString() );
431  mEndMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( QStringLiteral( "markerMode" ), QStringLiteral( "0" ) ).toInt() );
432  mStartMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( QStringLiteral( "startMarkerMode" ), QStringLiteral( "0" ) ).toInt() );
433 
434  QgsLayoutNodesItem::readPropertiesFromElement( elmt, doc, context );
435 
437  return true;
438 }
439 
441 {
442  QRectF br = rect();
443 
444  double margin = std::max( mMaxSymbolBleed, computeMarkerMargin() );
445  if ( mEndMarker == ArrowHead )
446  {
447  margin += 0.5 * mArrowHeadWidth;
448  }
449  br.adjust( -margin, -margin, margin, margin );
450  mCurrentRectangle = br;
451 
452  // update
453  prepareGeometryChange();
454  update();
455 }
456 
457 
458 double QgsLayoutItemPolyline::computeMarkerMargin() const
459 {
460  double margin = 0;
461 
462  if ( mStartMarker == ArrowHead || mEndMarker == ArrowHead )
463  {
464  margin = mArrowHeadStrokeWidth / 2.0 + mArrowHeadWidth * M_SQRT2;
465  }
466 
467  if ( mStartMarker == SvgMarker )
468  {
469  double startMarkerMargin = std::sqrt( 0.25 * ( mStartArrowHeadHeight * mStartArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
470  margin = std::max( startMarkerMargin, margin );
471  }
472 
473  if ( mEndMarker == SvgMarker )
474  {
475  double endMarkerMargin = std::sqrt( 0.25 * ( mEndArrowHeadHeight * mEndArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
476  margin = std::max( endMarkerMargin, margin );
477  }
478 
479  return margin;
480 }
The class is used as a container of context for various read/write operations on other objects...
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
void setStartMarker(MarkerMode mode)
Sets the start marker mode, which controls what marker is drawn at the start of the line...
void updateSceneRect()
Update the current scene rectangle for this item.
bool _removeNode(int nodeIndex) override
Method called in removeNode.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
void setArrowHeadWidth(double width)
Sets the width of line arrow heads in mm.
void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const override
Method called in writeXml.
QRectF mCurrentRectangle
Current bounding rectangle of shape.
void setArrowHeadFillColor(const QColor &color)
Sets the color used to fill the arrow head.
static QgsLineSymbol * createSimple(const QgsStringMap &properties)
Create a line symbol with one symbol layer: SimpleLine with specified properties. ...
Definition: qgssymbol.cpp:1139
void setStartSvgMarkerPath(const QString &path)
Sets the path to a SVG marker to draw at the start of the line.
MarkerMode
Vertex marker mode.
An abstract layout item that provides generic methods for node based shapes such as polygon or polyli...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QString displayName() const override
Gets item display name.
double mMaxSymbolBleed
Max symbol bleed.
QPainterPath shape() const override
void setEndSvgMarkerPath(const QString &path)
Sets the path to a SVG marker to draw at the end of the line.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPolygonF mPolygon
Shape&#39;s nodes.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:356
QMap< QString, QString > QgsStringMap
Definition: qgis.h:577
bool _addNode(int indexPoint, QPointF newPoint, double radius) override
Method called in addNode.
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
void setSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the shape.
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgssymbol.h:920
static QString encodeColor(const QColor &color)
int type() const override
QgsLineSymbol * symbol()
Returns the line symbol used to draw the shape.
void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context) override
Method called in readXml.
void setArrowHeadStrokeColor(const QColor &color)
Sets the color used to draw the stroke around the arrow head.
void setEndMarker(MarkerMode mode)
Sets the end marker mode, which controls what marker is drawn at the end of the line.
void frameChanged()
Emitted if the item&#39;s frame style changes.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s name from its path.
void setArrowHeadStrokeWidth(double width)
Sets the pen width in millimeters for the stroke of the arrow head.
Layout item for node based polyline shapes.
QIcon icon() const override
Returns the item&#39;s icon.
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidian distance between 2 nodes.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:71
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr) override
Method called in paint.
static QgsLayoutItemPolyline * create(QgsLayout *layout)
Returns a new polyline item for the specified layout.
bool setSelectedNode(int index)
Selects a node by index.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:43
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0)
Gets SVG content.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
QString id() const
Returns the item&#39;s ID name.
QgsLayoutItemPolyline(QgsLayout *layout)
Constructor for QgsLayoutItemPolyline for the specified layout.
const QgsLayout * layout() const
Returns the layout the object is attached to.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s path from its name.
double dpi() const
Returns the dpi for outputting the layout.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
static QColor decodeColor(const QString &str)
double arrowHeadWidth() const
Returns the width of line arrow heads in mm.
QgsLineSymbol * clone() const override
Returns a deep copy of this symbol.
Definition: qgssymbol.cpp:1828