QGIS API Documentation  3.27.0-Master (aef1b1ec20)
qgslinesymbollayer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslinesymbollayer.cpp
3  ---------------------
4  begin : November 2009
5  copyright : (C) 2009 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgslinesymbollayer.h"
17 #include "qgscurve.h"
18 #include "qgscurvepolygon.h"
19 #include "qgsdxfexport.h"
20 #include "qgssymbollayerutils.h"
21 #include "qgsexpression.h"
22 #include "qgsrendercontext.h"
23 #include "qgslogger.h"
24 #include "qgsvectorlayer.h"
25 #include "qgsgeometrysimplifier.h"
26 #include "qgsunittypes.h"
27 #include "qgsproperty.h"
29 #include "qgsmarkersymbol.h"
30 #include "qgslinesymbol.h"
31 #include "qgsapplication.h"
32 #include "qgsimagecache.h"
33 #include "qgsfeedback.h"
34 #include "qgsimageoperation.h"
35 #include "qgscolorrampimpl.h"
36 
37 #include <algorithm>
38 #include <QPainter>
39 #include <QDomDocument>
40 #include <QDomElement>
41 
42 #include <cmath>
43 
44 QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
45  : mPenStyle( penStyle )
46 {
47  mColor = color;
48  mWidth = width;
49  mCustomDashVector << 5 << 2;
50 }
51 
53 
55 {
57  mWidthUnit = unit;
58  mOffsetUnit = unit;
59  mCustomDashPatternUnit = unit;
60  mDashPatternOffsetUnit = unit;
61  mTrimDistanceStartUnit = unit;
62  mTrimDistanceEndUnit = unit;
63 }
64 
66 {
68  if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
69  {
71  }
72  return unit;
73 }
74 
76 {
79 }
80 
82 {
84  mWidthMapUnitScale = scale;
85  mOffsetMapUnitScale = scale;
86  mCustomDashPatternMapUnitScale = scale;
87 }
88 
90 {
93  mOffsetMapUnitScale == mCustomDashPatternMapUnitScale )
94  {
95  return mWidthMapUnitScale;
96  }
97  return QgsMapUnitScale();
98 }
99 
101 {
104  Qt::PenStyle penStyle = DEFAULT_SIMPLELINE_PENSTYLE;
105 
106  if ( props.contains( QStringLiteral( "line_color" ) ) )
107  {
108  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
109  }
110  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
111  {
112  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
113  }
114  else if ( props.contains( QStringLiteral( "color" ) ) )
115  {
116  //pre 2.5 projects used "color"
117  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
118  }
119  if ( props.contains( QStringLiteral( "line_width" ) ) )
120  {
121  width = props[QStringLiteral( "line_width" )].toDouble();
122  }
123  else if ( props.contains( QStringLiteral( "outline_width" ) ) )
124  {
125  width = props[QStringLiteral( "outline_width" )].toDouble();
126  }
127  else if ( props.contains( QStringLiteral( "width" ) ) )
128  {
129  //pre 2.5 projects used "width"
130  width = props[QStringLiteral( "width" )].toDouble();
131  }
132  if ( props.contains( QStringLiteral( "line_style" ) ) )
133  {
134  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
135  }
136  else if ( props.contains( QStringLiteral( "outline_style" ) ) )
137  {
138  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
139  }
140  else if ( props.contains( QStringLiteral( "penstyle" ) ) )
141  {
142  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
143  }
144 
146  if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
147  {
148  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
149  }
150  else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
151  {
152  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
153  }
154  else if ( props.contains( QStringLiteral( "width_unit" ) ) )
155  {
156  //pre 2.5 projects used "width_unit"
157  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
158  }
159  if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
160  l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
161  if ( props.contains( QStringLiteral( "offset" ) ) )
162  l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
163  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
164  l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
165  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
166  l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
167  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
168  l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
169  if ( props.contains( QStringLiteral( "capstyle" ) ) )
170  l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
171 
172  if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
173  {
174  l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
175  }
176  if ( props.contains( QStringLiteral( "customdash" ) ) )
177  {
178  l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
179  }
180  if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
181  {
182  l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
183  }
184  if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
185  {
186  l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
187  }
188 
189  if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
190  {
191  l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
192  }
193 
194  if ( props.contains( QStringLiteral( "ring_filter" ) ) )
195  {
196  l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
197  }
198 
199  if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
200  l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
201  if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
202  l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
203  if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
204  l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
205 
206  if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
207  l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
208  if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
209  l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
210  if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
211  l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
212  if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
213  l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
214  if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
215  l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
216  if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
217  l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
218 
219  if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
220  l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
221 
222  if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
223  l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
224 
226 
227  return l;
228 }
229 
231 {
232  return QStringLiteral( "SimpleLine" );
233 }
234 
236 {
237  QColor penColor = mColor;
238  penColor.setAlphaF( mColor.alphaF() * context.opacity() );
239  mPen.setColor( penColor );
240  double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
241  mPen.setWidthF( scaledWidth );
242 
243  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
244  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
245  const double dashWidthDiv = std::max( 1.0, scaledWidth );
246  if ( mUseCustomDashPattern )
247  {
248  mPen.setStyle( Qt::CustomDashLine );
249 
250  //scale pattern vector
251 
252  QVector<qreal> scaledVector;
253  QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
254  for ( ; it != mCustomDashVector.constEnd(); ++it )
255  {
256  //the dash is specified in terms of pen widths, therefore the division
257  scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
258  }
259  mPen.setDashPattern( scaledVector );
260  }
261  else
262  {
263  mPen.setStyle( mPenStyle );
264  }
265 
266  if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
267  {
268  mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
269  }
270 
271  mPen.setJoinStyle( mPenJoinStyle );
272  mPen.setCapStyle( mPenCapStyle );
273 
274  mSelPen = mPen;
275  QColor selColor = context.renderContext().selectionColor();
276  if ( ! SELECTION_IS_OPAQUE )
277  selColor.setAlphaF( context.opacity() );
278  mSelPen.setColor( selColor );
279 }
280 
282 {
283  Q_UNUSED( context )
284 }
285 
286 void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
287 {
288  QPainter *p = context.renderContext().painter();
289  if ( !p )
290  {
291  return;
292  }
293 
294  QgsExpressionContextScope *scope = nullptr;
295  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
296  if ( hasDataDefinedProperties() )
297  {
298  scope = new QgsExpressionContextScope();
299  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
300  }
301 
302  if ( mDrawInsidePolygon )
303  p->save();
304 
305  switch ( mRingFilter )
306  {
307  case AllRings:
308  case ExteriorRingOnly:
309  {
310  if ( mDrawInsidePolygon )
311  {
312  //only drawing the line on the interior of the polygon, so set clip path for painter
313  QPainterPath clipPath;
314  clipPath.addPolygon( points );
315 
316  if ( rings )
317  {
318  //add polygon rings
319  for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
320  {
321  QPolygonF ring = *it;
322  clipPath.addPolygon( ring );
323  }
324  }
325 
326  //use intersect mode, as a clip path may already exist (e.g., for composer maps)
327  p->setClipPath( clipPath, Qt::IntersectClip );
328  }
329 
330  if ( scope )
332 
333  renderPolyline( points, context );
334  }
335  break;
336 
337  case InteriorRingsOnly:
338  break;
339  }
340 
341  if ( rings )
342  {
343  switch ( mRingFilter )
344  {
345  case AllRings:
346  case InteriorRingsOnly:
347  {
348  mOffset = -mOffset; // invert the offset for rings!
349  int ringIndex = 1;
350  for ( const QPolygonF &ring : std::as_const( *rings ) )
351  {
352  if ( scope )
354 
355  renderPolyline( ring, context );
356  ringIndex++;
357  }
358  mOffset = -mOffset;
359  }
360  break;
361  case ExteriorRingOnly:
362  break;
363  }
364  }
365 
366  if ( mDrawInsidePolygon )
367  {
368  //restore painter to reset clip path
369  p->restore();
370  }
371 
372 }
373 
375 {
376  QPainter *p = context.renderContext().painter();
377  if ( !p )
378  {
379  return;
380  }
381 
382  QPolygonF points = pts;
383 
384  double startTrim = mTrimDistanceStart;
386  {
387  context.setOriginalValueVariable( startTrim );
389  }
390  double endTrim = mTrimDistanceEnd;
392  {
393  context.setOriginalValueVariable( endTrim );
395  }
396 
397  double totalLength = -1;
398  if ( mTrimDistanceStartUnit == QgsUnitTypes::RenderPercentage )
399  {
400  totalLength = QgsSymbolLayerUtils::polylineLength( points );
401  startTrim = startTrim * 0.01 * totalLength;
402  }
403  else
404  {
405  startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
406  }
407  if ( mTrimDistanceEndUnit == QgsUnitTypes::RenderPercentage )
408  {
409  if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
410  totalLength = QgsSymbolLayerUtils::polylineLength( points );
411  endTrim = endTrim * 0.01 * totalLength;
412  }
413  else
414  {
415  endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
416  }
417  if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
418  {
419  points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
420  }
421 
422  QColor penColor = mColor;
423  penColor.setAlphaF( mColor.alphaF() * context.opacity() );
424  mPen.setColor( penColor );
425 
426  double offset = mOffset;
427  applyDataDefinedSymbology( context, mPen, mSelPen, offset );
428 
429  const QPen pen = context.selected() ? mSelPen : mPen;
430 
431  if ( !pen.dashPattern().isEmpty() )
432  {
433  // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
434  const QVector<double> pattern = pen.dashPattern();
435  bool foundNonNull = false;
436  for ( int i = 0; i < pattern.size(); ++i )
437  {
438  if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
439  {
440  foundNonNull = true;
441  break;
442  }
443  }
444  if ( !foundNonNull )
445  return;
446  }
447 
448  p->setBrush( Qt::NoBrush );
449 
450  // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
451  std::unique_ptr< QgsScopedQPainterState > painterState;
452  if ( points.size() <= 2 &&
455  ( p->renderHints() & QPainter::Antialiasing ) )
456  {
457  painterState = std::make_unique< QgsScopedQPainterState >( p );
458  p->setRenderHint( QPainter::Antialiasing, false );
459  }
460 
461  const bool applyPatternTweaks = mAlignDashPattern
462  && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
463  && pen.dashOffset() == 0;
464 
465  if ( qgsDoubleNear( offset, 0 ) )
466  {
467  if ( applyPatternTweaks )
468  {
469  drawPathWithDashPatternTweaks( p, points, pen );
470  }
471  else
472  {
473  p->setPen( pen );
474  QPainterPath path;
475  path.addPolygon( points );
476  p->drawPath( path );
477  }
478  }
479  else
480  {
481  double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
483  {
484  // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
485  // and clamp it to a reasonable range. It's the best we can do in this situation!
486  scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
487  }
488 
489  QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
490  for ( const QPolygonF &part : mline )
491  {
492  if ( applyPatternTweaks )
493  {
494  drawPathWithDashPatternTweaks( p, part, pen );
495  }
496  else
497  {
498  p->setPen( pen );
499  QPainterPath path;
500  path.addPolygon( part );
501  p->drawPath( path );
502  }
503  }
504  }
505 }
506 
508 {
509  QVariantMap map;
510  map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
511  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
512  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
513  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
514  map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
515  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
516  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
517  map[QStringLiteral( "offset" )] = QString::number( mOffset );
518  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
519  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
520  map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
521  map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
522  map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
523  map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
524  map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
525  map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
526  map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
527  map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
528  map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
529  map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
530  map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
531  map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
532  map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
533  map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
534  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
535  map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
536  map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
537  return map;
538 }
539 
541 {
543  l->setWidthUnit( mWidthUnit );
547  l->setCustomDashPatternUnit( mCustomDashPatternUnit );
548  l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
549  l->setOffset( mOffset );
550  l->setPenJoinStyle( mPenJoinStyle );
551  l->setPenCapStyle( mPenCapStyle );
552  l->setUseCustomDashPattern( mUseCustomDashPattern );
553  l->setCustomDashVector( mCustomDashVector );
554  l->setDrawInsidePolygon( mDrawInsidePolygon );
556  l->setDashPatternOffset( mDashPatternOffset );
557  l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
558  l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
559  l->setTrimDistanceStart( mTrimDistanceStart );
560  l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
561  l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
562  l->setTrimDistanceEnd( mTrimDistanceEnd );
563  l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
564  l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
565  l->setAlignDashPattern( mAlignDashPattern );
566  l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
567 
569  copyPaintEffect( l );
570  return l;
571 }
572 
573 void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
574 {
575  if ( mPenStyle == Qt::NoPen )
576  return;
577 
578  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
579  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
580  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
581  element.appendChild( symbolizerElem );
582 
583  // <Geometry>
584  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
585 
586  // <Stroke>
587  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
588  symbolizerElem.appendChild( strokeElem );
589 
590  Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
592  QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
594  &mPenJoinStyle, &mPenCapStyle, &customDashVector );
595 
596  // <se:PerpendicularOffset>
597  if ( !qgsDoubleNear( mOffset, 0.0 ) )
598  {
599  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
601  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
602  symbolizerElem.appendChild( perpOffsetElem );
603  }
604 }
605 
606 QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
607 {
608  if ( mUseCustomDashPattern )
609  {
610  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
611  mPen.color(), mPenJoinStyle,
612  mPenCapStyle, mOffset, &mCustomDashVector );
613  }
614  else
615  {
616  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
617  mPenCapStyle, mOffset );
618  }
619 }
620 
622 {
623  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
624 
625  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
626  if ( strokeElem.isNull() )
627  return nullptr;
628 
629  Qt::PenStyle penStyle;
630  QColor color;
631  double width;
632  Qt::PenJoinStyle penJoinStyle;
633  Qt::PenCapStyle penCapStyle;
634  QVector<qreal> customDashVector;
635 
636  if ( !QgsSymbolLayerUtils::lineFromSld( strokeElem, penStyle,
637  color, width,
639  &customDashVector ) )
640  return nullptr;
641 
642  double offset = 0.0;
643  QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
644  if ( !perpOffsetElem.isNull() )
645  {
646  bool ok;
647  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
648  if ( ok )
649  offset = d;
650  }
651 
652  double scaleFactor = 1.0;
653  const QString uom = element.attribute( QStringLiteral( "uom" ) );
654  QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
655  width = width * scaleFactor;
656  offset = offset * scaleFactor;
657 
659  l->setOutputUnit( sldUnitSize );
660  l->setOffset( offset );
663  l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
665  return l;
666 }
667 
668 void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
669 {
670  if ( !dataDefinedProperties().hasActiveProperties() )
671  return; // shortcut
672 
673  //data defined properties
674  bool hasStrokeWidthExpression = false;
676  {
677  context.setOriginalValueVariable( mWidth );
678  double scaledWidth = context.renderContext().convertToPainterUnits(
681  pen.setWidthF( scaledWidth );
682  selPen.setWidthF( scaledWidth );
683  hasStrokeWidthExpression = true;
684  }
685 
686  //color
688  {
690 
692  penColor.setAlphaF( context.opacity() * penColor.alphaF() );
693  pen.setColor( penColor );
694  }
695 
696  //offset
698  {
701  }
702 
703  //dash dot vector
704 
705  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
706  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
707  const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
708 
710  {
711  QVector<qreal> dashVector;
713  if ( !exprVal.isNull() )
714  {
715  QStringList dashList = exprVal.toString().split( ';' );
716  QStringList::const_iterator dashIt = dashList.constBegin();
717  for ( ; dashIt != dashList.constEnd(); ++dashIt )
718  {
719  dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
720  }
721  pen.setDashPattern( dashVector );
722  }
723  }
724  else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
725  {
726  //re-scale pattern vector after data defined pen width was applied
727 
728  QVector<qreal> scaledVector;
729  for ( double v : std::as_const( mCustomDashVector ) )
730  {
731  //the dash is specified in terms of pen widths, therefore the division
732  scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
733  }
734  mPen.setDashPattern( scaledVector );
735  }
736 
737  // dash pattern offset
738  double patternOffset = mDashPatternOffset;
739  if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
740  {
741  context.setOriginalValueVariable( patternOffset );
743  pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
744  }
745 
746  //line style
748  {
751  if ( !exprVal.isNull() )
752  pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
753  }
754 
755  //join style
757  {
760  if ( !exprVal.isNull() )
761  pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
762  }
763 
764  //cap style
766  {
769  if ( !exprVal.isNull() )
770  pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
771  }
772 }
773 
774 void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
775 {
776  if ( pen.dashPattern().empty() || points.size() < 2 )
777  return;
778 
779  QVector< qreal > sourcePattern = pen.dashPattern();
780  const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
781  // back to painter units
782  for ( int i = 0; i < sourcePattern.size(); ++ i )
783  sourcePattern[i] *= pen.widthF();
784 
785  if ( pen.widthF() <= 1.0 )
786  pen.setWidthF( 1.0001 );
787 
788  QVector< qreal > buffer;
789  QPolygonF bufferedPoints;
790  QPolygonF previousSegmentBuffer;
791  // we iterate through the line points, building a custom dash pattern and adding it to the buffer
792  // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
793  // and then append the buffer to the output pattern.
794 
795  auto ptIt = points.constBegin();
796  double totalBufferLength = 0;
797  int patternIndex = 0;
798  double currentRemainingDashLength = 0;
799  double currentRemainingGapLength = 0;
800 
801  auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
802  {
803  QVector< qreal > result;
804  result.reserve( buffer.size() );
805  for ( auto it = buffer.begin(); it != buffer.end(); )
806  {
807  qreal dash = *it++;
808  qreal gap = *it++;
809  while ( dash == 0 && !result.empty() )
810  {
811  result.last() += gap;
812 
813  if ( it == buffer.end() )
814  return result;
815  dash = *it++;
816  gap = *it++;
817  }
818  while ( gap == 0 && it != buffer.end() )
819  {
820  dash += *it++;
821  gap = *it++;
822  }
823  result << dash << gap;
824  }
825  return result;
826  };
827 
828  double currentBufferLineLength = 0;
829  auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
830  dashWidthDiv, &compressPattern]( QPointF * nextPoint )
831  {
832  if ( buffer.empty() || bufferedPoints.size() < 2 )
833  {
834  return;
835  }
836 
837  if ( currentRemainingDashLength )
838  {
839  // ended midway through a dash -- we want to finish this off
840  buffer << currentRemainingDashLength << 0.0;
841  totalBufferLength += currentRemainingDashLength;
842  }
843  QVector< qreal > compressed = compressPattern( buffer );
844  if ( !currentRemainingDashLength )
845  {
846  // ended midway through a gap -- we don't want this, we want to end at previous dash
847  totalBufferLength -= compressed.last();
848  compressed.last() = 0;
849  }
850 
851  // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
852  const double scaleFactor = currentBufferLineLength / totalBufferLength;
853 
854  bool shouldFlushPreviousSegmentBuffer = false;
855 
856  if ( !previousSegmentBuffer.empty() )
857  {
858  // add first dash from current buffer
859  QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
860  if ( !firstDashSubstring.empty() )
861  QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
862 
863  // then we skip over the first dash and gap for this segment
864  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
865 
866  compressed = compressed.mid( 2 );
867  shouldFlushPreviousSegmentBuffer = !compressed.empty();
868  }
869 
870  if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
871  {
872  QPen adjustedPen = pen;
873  adjustedPen.setStyle( Qt::SolidLine );
874  painter->setPen( adjustedPen );
875  QPainterPath path;
876  path.addPolygon( previousSegmentBuffer );
877  painter->drawPath( path );
878  previousSegmentBuffer.clear();
879  }
880 
881  double finalDash = 0;
882  if ( nextPoint )
883  {
884  // sharp bend:
885  // 1. rewind buffered points line by final dash and gap length
886  // (later) 2. draw the bend with a solid line of length 2 * final dash size
887 
888  if ( !compressed.empty() )
889  {
890  finalDash = compressed.at( compressed.size() - 2 );
891  const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
892 
893  const QPolygonF thisPoints = bufferedPoints;
894  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
895  previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
896  }
897  else
898  {
899  previousSegmentBuffer << bufferedPoints;
900  }
901  }
902 
903  currentBufferLineLength = 0;
904  currentRemainingDashLength = 0;
905  currentRemainingGapLength = 0;
906  totalBufferLength = 0;
907  buffer.clear();
908 
909  if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
910  {
911  QPen adjustedPen = pen;
912  if ( !compressed.empty() )
913  {
914  // maximum size of dash pattern is 32 elements
915  compressed = compressed.mid( 0, 32 );
916  std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
917  adjustedPen.setDashPattern( compressed );
918  }
919  else
920  {
921  adjustedPen.setStyle( Qt::SolidLine );
922  }
923 
924  painter->setPen( adjustedPen );
925  QPainterPath path;
926  path.addPolygon( bufferedPoints );
927  painter->drawPath( path );
928  }
929 
930  bufferedPoints.clear();
931  };
932 
933  QPointF p1;
934  QPointF p2 = *ptIt;
935  ptIt++;
936  bufferedPoints << p2;
937  for ( ; ptIt != points.constEnd(); ++ptIt )
938  {
939  p1 = *ptIt;
940  if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
941  {
942  continue;
943  }
944 
945  double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
946  currentBufferLineLength += remainingSegmentDistance;
947  while ( true )
948  {
949  // handle currentRemainingDashLength/currentRemainingGapLength
950  if ( currentRemainingDashLength > 0 )
951  {
952  // bit more of dash to insert
953  if ( remainingSegmentDistance >= currentRemainingDashLength )
954  {
955  // all of dash fits in
956  buffer << currentRemainingDashLength << 0.0;
957  totalBufferLength += currentRemainingDashLength;
958  remainingSegmentDistance -= currentRemainingDashLength;
959  patternIndex++;
960  currentRemainingDashLength = 0.0;
961  currentRemainingGapLength = sourcePattern.at( patternIndex );
962  }
963  else
964  {
965  // only part of remaining dash fits in
966  buffer << remainingSegmentDistance << 0.0;
967  totalBufferLength += remainingSegmentDistance;
968  currentRemainingDashLength -= remainingSegmentDistance;
969  break;
970  }
971  }
972  if ( currentRemainingGapLength > 0 )
973  {
974  // bit more of gap to insert
975  if ( remainingSegmentDistance >= currentRemainingGapLength )
976  {
977  // all of gap fits in
978  buffer << 0.0 << currentRemainingGapLength;
979  totalBufferLength += currentRemainingGapLength;
980  remainingSegmentDistance -= currentRemainingGapLength;
981  currentRemainingGapLength = 0.0;
982  patternIndex++;
983  }
984  else
985  {
986  // only part of remaining gap fits in
987  buffer << 0.0 << remainingSegmentDistance;
988  totalBufferLength += remainingSegmentDistance;
989  currentRemainingGapLength -= remainingSegmentDistance;
990  break;
991  }
992  }
993 
994  if ( patternIndex >= sourcePattern.size() )
995  patternIndex = 0;
996 
997  const double nextPatternDashLength = sourcePattern.at( patternIndex );
998  const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
999  if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1000  {
1001  buffer << nextPatternDashLength << nextPatternGapLength;
1002  remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1003  totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1004  patternIndex += 2;
1005  }
1006  else if ( nextPatternDashLength <= remainingSegmentDistance )
1007  {
1008  // can fit in "dash", but not "gap"
1009  buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1010  totalBufferLength += remainingSegmentDistance;
1011  currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1012  currentRemainingDashLength = 0;
1013  patternIndex++;
1014  break;
1015  }
1016  else
1017  {
1018  // can't fit in "dash"
1019  buffer << remainingSegmentDistance << 0.0;
1020  totalBufferLength += remainingSegmentDistance;
1021  currentRemainingGapLength = 0;
1022  currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1023  break;
1024  }
1025  }
1026 
1027  bufferedPoints << p1;
1028  if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1029  {
1030  QPointF nextPoint = *( ptIt + 1 );
1031 
1032  // extreme angles form more than 45 degree angle at a node
1033  if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1034  {
1035  // extreme angle. Rescale buffer and flush
1036  flushBuffer( &nextPoint );
1037  bufferedPoints << p1;
1038  // restart the line with the full length of the most recent dash element -- see
1039  // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1040  if ( patternIndex % 2 == 1 )
1041  {
1042  patternIndex--;
1043  }
1044  currentRemainingDashLength = sourcePattern.at( patternIndex );
1045  }
1046  }
1047 
1048  p2 = p1;
1049  }
1050 
1051  flushBuffer( nullptr );
1052  if ( !previousSegmentBuffer.empty() )
1053  {
1054  QPen adjustedPen = pen;
1055  adjustedPen.setStyle( Qt::SolidLine );
1056  painter->setPen( adjustedPen );
1057  QPainterPath path;
1058  path.addPolygon( previousSegmentBuffer );
1059  painter->drawPath( path );
1060  previousSegmentBuffer.clear();
1061  }
1062 }
1063 
1065 {
1066  if ( mDrawInsidePolygon )
1067  {
1068  //set to clip line to the interior of polygon, so we expect no bleed
1069  return 0;
1070  }
1071  else
1072  {
1073  return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1075  }
1076 }
1077 
1079 {
1080  unit = mCustomDashPatternUnit;
1081  return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1082 }
1083 
1085 {
1086  return mPenStyle;
1087 }
1088 
1090 {
1091  double width = mWidth;
1093  {
1094  context.setOriginalValueVariable( mWidth );
1096  }
1097 
1100  {
1102  }
1103  return width;
1104 }
1105 
1107 {
1109  {
1112  }
1113  return mColor;
1114 }
1115 
1117 {
1118  return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1119 }
1120 
1122 {
1123  return mAlignDashPattern;
1124 }
1125 
1127 {
1128  mAlignDashPattern = enabled;
1129 }
1130 
1132 {
1133  return mPatternCartographicTweakOnSharpCorners;
1134 }
1135 
1137 {
1138  mPatternCartographicTweakOnSharpCorners = enabled;
1139 }
1140 
1142 {
1143  double offset = mOffset;
1144 
1146  {
1147  context.setOriginalValueVariable( mOffset );
1149  }
1150 
1153  {
1155  }
1156  return -offset; //direction seems to be inverse to symbology offset
1157 }
1158 
1160 
1162 
1163 class MyLine
1164 {
1165  public:
1166  MyLine( QPointF p1, QPointF p2 )
1167  : mVertical( false )
1168  , mIncreasing( false )
1169  , mT( 0.0 )
1170  , mLength( 0.0 )
1171  {
1172  if ( p1 == p2 )
1173  return; // invalid
1174 
1175  // tangent and direction
1176  if ( qgsDoubleNear( p1.x(), p2.x() ) )
1177  {
1178  // vertical line - tangent undefined
1179  mVertical = true;
1180  mIncreasing = ( p2.y() > p1.y() );
1181  }
1182  else
1183  {
1184  mVertical = false;
1185  mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1186  mIncreasing = ( p2.x() > p1.x() );
1187  }
1188 
1189  // length
1190  double x = ( p2.x() - p1.x() );
1191  double y = ( p2.y() - p1.y() );
1192  mLength = std::sqrt( x * x + y * y );
1193  }
1194 
1195  // return angle in radians
1196  double angle()
1197  {
1198  double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1199 
1200  if ( !mIncreasing )
1201  a += M_PI;
1202  return a;
1203  }
1204 
1205  // return difference for x,y when going along the line with specified interval
1206  QPointF diffForInterval( double interval )
1207  {
1208  if ( mVertical )
1209  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1210 
1211  double alpha = std::atan( mT );
1212  double dx = std::cos( alpha ) * interval;
1213  double dy = std::sin( alpha ) * interval;
1214  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1215  }
1216 
1217  double length() const { return mLength; }
1218 
1219  protected:
1220  bool mVertical;
1221  bool mIncreasing;
1222  double mT;
1223  double mLength;
1224 };
1225 
1227 
1228 //
1229 // QgsTemplatedLineSymbolLayerBase
1230 //
1232  : mRotateSymbols( rotateSymbol )
1233  , mInterval( interval )
1234 {
1235 
1236 }
1237 
1239 {
1240  if ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1242  else if ( mPlacements & Qgis::MarkerLinePlacement::Vertex )
1244  else if ( ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1245  && ( mPlacements & Qgis::MarkerLinePlacement::InnerVertices )
1246  && ( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1247  return Qgis::MarkerLinePlacement::Vertex; // retain round trip for deprecated old API
1248  else if ( mPlacements & Qgis::MarkerLinePlacement::LastVertex )
1250  else if ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1252  else if ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1254  else if ( mPlacements & Qgis::MarkerLinePlacement::CurvePoint )
1256  else if ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter )
1258  else
1260 }
1261 
1263 {
1264  mPlacements = placement;
1265 }
1266 
1268 
1270 {
1271  if ( mRenderingFeature )
1272  {
1273  // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1274  // until after we've received the final part
1275  mFeatureSymbolOpacity = context.opacity();
1276  mCurrentFeatureIsSelected = context.selected();
1277  }
1278 
1279  double offset = mOffset;
1280 
1282  {
1283  context.setOriginalValueVariable( mOffset );
1285  }
1286 
1287  Qgis::MarkerLinePlacements placements = QgsTemplatedLineSymbolLayerBase::placements();
1288 
1290  {
1292  if ( !exprVal.isNull() )
1293  {
1294  QString placementString = exprVal.toString();
1295  if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1296  {
1298  }
1299  else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1300  {
1302  }
1303  else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1304  {
1306  }
1307  else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1308  {
1310  }
1311  else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1312  {
1314  }
1315  else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1316  {
1318  }
1319  else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1320  {
1322  }
1323  else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1324  {
1326  }
1327  else
1328  {
1330  }
1331  }
1332  }
1333 
1334  QgsScopedQPainterState painterState( context.renderContext().painter() );
1335 
1336  double averageOver = mAverageAngleLength;
1338  {
1339  context.setOriginalValueVariable( mAverageAngleLength );
1341  }
1342  averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1343 
1344  if ( qgsDoubleNear( offset, 0.0 ) )
1345  {
1347  renderPolylineInterval( points, context, averageOver );
1349  renderPolylineCentral( points, context, averageOver );
1351  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1353  && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1354  {
1355  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1356  mHasRenderedFirstPart = mRenderingFeature;
1357  }
1359  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1361  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1363  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1365  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1366  }
1367  else
1368  {
1369  context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1371 
1372  for ( int part = 0; part < mline.count(); ++part )
1373  {
1374  const QPolygonF &points2 = mline[ part ];
1375 
1377  renderPolylineInterval( points2, context, averageOver );
1379  renderPolylineCentral( points2, context, averageOver );
1381  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1383  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1385  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1387  && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1388  {
1389  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1390  mHasRenderedFirstPart = mRenderingFeature;
1391  }
1393  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1395  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1396  }
1397  }
1398 }
1399 
1400 void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1401 {
1402  const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1403 
1404  if ( curvePolygon )
1405  {
1406  context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1407  }
1408 
1409  QgsExpressionContextScope *scope = nullptr;
1410  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1411  if ( hasDataDefinedProperties() )
1412  {
1413  scope = new QgsExpressionContextScope();
1414  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1415  }
1416 
1417  switch ( mRingFilter )
1418  {
1419  case AllRings:
1420  case ExteriorRingOnly:
1421  {
1422  if ( scope )
1424 
1425  renderPolyline( points, context );
1426  break;
1427  }
1428  case InteriorRingsOnly:
1429  break;
1430  }
1431 
1432  if ( rings )
1433  {
1434  switch ( mRingFilter )
1435  {
1436  case AllRings:
1437  case InteriorRingsOnly:
1438  {
1439  mOffset = -mOffset; // invert the offset for rings!
1440  for ( int i = 0; i < rings->size(); ++i )
1441  {
1442  if ( curvePolygon )
1443  {
1444  context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1445  }
1446  if ( scope )
1448 
1449  renderPolyline( rings->at( i ), context );
1450  }
1451  mOffset = -mOffset;
1452  }
1453  break;
1454  case ExteriorRingOnly:
1455  break;
1456  }
1457  }
1458 }
1459 
1461 {
1463  if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1464  {
1466  }
1467  return unit;
1468 }
1469 
1471 {
1473  mIntervalUnit = unit;
1474  mOffsetAlongLineUnit = unit;
1475  mAverageAngleLengthUnit = unit;
1476 }
1477 
1479 {
1481  setIntervalMapUnitScale( scale );
1482  mOffsetMapUnitScale = scale;
1484 }
1485 
1487 {
1491  {
1492  return mOffsetMapUnitScale;
1493  }
1494  return QgsMapUnitScale();
1495 }
1496 
1498 {
1499  QVariantMap map;
1500  map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1501  map[QStringLiteral( "interval" )] = QString::number( interval() );
1502  map[QStringLiteral( "offset" )] = QString::number( mOffset );
1503  map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1504  map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1505  map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1506  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1507  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1508  map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1509  map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1510  map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1511  map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1512  map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1513 
1514  map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1515 
1516  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1517  map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1518  return map;
1519 }
1520 
1522 {
1523  return mPlaceOnEveryPart
1524  || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1525  || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1526  || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1527 }
1528 
1530 {
1531  mRenderingFeature = true;
1532  mHasRenderedFirstPart = false;
1533 }
1534 
1536 {
1537  mRenderingFeature = false;
1538  if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1539  return;
1540 
1541  const double prevOpacity = subSymbol()->opacity();
1542  subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1543 
1544  // render final point
1545  renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1546  mFeatureSymbolOpacity = 1;
1547  subSymbol()->setOpacity( prevOpacity );
1548 }
1549 
1551 {
1552  destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1553  destLayer->setOffset( mOffset );
1554  destLayer->setPlacements( placements() );
1555  destLayer->setOffsetUnit( mOffsetUnit );
1557  destLayer->setIntervalUnit( intervalUnit() );
1559  destLayer->setOffsetAlongLine( offsetAlongLine() );
1562  destLayer->setAverageAngleLength( mAverageAngleLength );
1563  destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1564  destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1565  destLayer->setRingFilter( mRingFilter );
1566  destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1567  copyDataDefinedProperties( destLayer );
1568  copyPaintEffect( destLayer );
1569 }
1570 
1572 {
1573  if ( properties.contains( QStringLiteral( "offset" ) ) )
1574  {
1575  destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1576  }
1577  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1578  {
1579  destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1580  }
1581  if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1582  {
1583  destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1584  }
1585  if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1586  {
1587  destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1588  }
1589  if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1590  {
1591  destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1592  }
1593  if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1594  {
1595  destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1596  }
1597 
1598  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1599  {
1600  destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1601  }
1602  if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1603  {
1604  destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1605  }
1606 
1607  if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1608  {
1609  destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1610  }
1611  if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1612  {
1613  destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1614  }
1615  if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1616  {
1617  destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1618  }
1619 
1620  if ( properties.contains( QStringLiteral( "placement" ) ) )
1621  {
1622  if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1624  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1626  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1628  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1630  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1632  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1634  else
1636  }
1637  else if ( properties.contains( QStringLiteral( "placements" ) ) )
1638  {
1639  Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1640  destLayer->setPlacements( placements );
1641  }
1642 
1643  if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1644  {
1645  destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1646  }
1647 
1648  destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1649 
1651 }
1652 
1653 void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1654 {
1655  if ( points.isEmpty() )
1656  return;
1657 
1658  double lengthLeft = 0; // how much is left until next marker
1659 
1660  QgsRenderContext &rc = context.renderContext();
1661  double interval = mInterval;
1662 
1664  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1665 
1667  {
1668  context.setOriginalValueVariable( mInterval );
1670  }
1671  if ( interval <= 0 )
1672  {
1673  interval = 0.1;
1674  }
1675  double offsetAlongLine = mOffsetAlongLine;
1677  {
1678  context.setOriginalValueVariable( mOffsetAlongLine );
1680  }
1681 
1682  double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1684  {
1685  // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1686  // and clamp it to a reasonable range. It's the best we can do in this situation!
1687  painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, QgsUnitTypes::RenderMillimeters ), 10.0 ), 100.0 );
1688  }
1689 
1690  if ( painterUnitInterval < 0 )
1691  return;
1692 
1693  double painterUnitOffsetAlongLine = 0;
1694 
1695  // only calculated if we need it!
1696  double totalLength = -1;
1697 
1698  if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1699  {
1700  switch ( offsetAlongLineUnit() )
1701  {
1710  break;
1712  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1713  painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1714  break;
1715  }
1716 
1717  if ( points.isClosed() )
1718  {
1719  if ( painterUnitOffsetAlongLine > 0 )
1720  {
1721  if ( totalLength < 0 )
1722  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1723  painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1724  }
1725  else if ( painterUnitOffsetAlongLine < 0 )
1726  {
1727  if ( totalLength < 0 )
1728  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1729  painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1730  }
1731  }
1732  }
1733 
1735  {
1736  // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1737  // and clamp it to a reasonable range. It's the best we can do in this situation!
1738  painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
1739  }
1740 
1741  lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1742 
1743  if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1744  {
1745  QVector< QPointF > angleStartPoints;
1746  QVector< QPointF > symbolPoints;
1747  QVector< QPointF > angleEndPoints;
1748 
1749  // we collect 3 arrays of points. These correspond to
1750  // 1. the actual point at which to render the symbol
1751  // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1752  // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1753  // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1754  // (or trace past the final point)
1755  collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1756 
1757  if ( symbolPoints.empty() )
1758  {
1759  // no symbols to draw, shortcut out early
1760  return;
1761  }
1762 
1763  if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1764  {
1765  // avoid duplicate points at start and end of closed rings
1766  symbolPoints.pop_back();
1767  }
1768 
1769  angleEndPoints.reserve( symbolPoints.size() );
1770  angleStartPoints.reserve( symbolPoints.size() );
1771  if ( averageOver <= painterUnitOffsetAlongLine )
1772  {
1773  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1774  }
1775  else
1776  {
1777  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1778  }
1779  collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1780 
1781  int pointNum = 0;
1782  for ( int i = 0; i < symbolPoints.size(); ++ i )
1783  {
1784  if ( context.renderContext().renderingStopped() )
1785  break;
1786 
1787  const QPointF pt = symbolPoints[i];
1788  const QPointF startPt = angleStartPoints[i];
1789  const QPointF endPt = angleEndPoints[i];
1790 
1791  MyLine l( startPt, endPt );
1792  // rotate marker (if desired)
1793  if ( rotateSymbols() )
1794  {
1795  setSymbolLineAngle( l.angle() * 180 / M_PI );
1796  }
1797 
1799  renderSymbol( pt, context.feature(), rc, -1, context.selected() );
1800  }
1801  }
1802  else
1803  {
1804  // not averaging line angle -- always use exact section angle
1805  int pointNum = 0;
1806  QPointF lastPt = points[0];
1807  for ( int i = 1; i < points.count(); ++i )
1808  {
1809  if ( context.renderContext().renderingStopped() )
1810  break;
1811 
1812  const QPointF &pt = points[i];
1813 
1814  if ( lastPt == pt ) // must not be equal!
1815  continue;
1816 
1817  // for each line, find out dx and dy, and length
1818  MyLine l( lastPt, pt );
1819  QPointF diff = l.diffForInterval( painterUnitInterval );
1820 
1821  // if there's some length left from previous line
1822  // use only the rest for the first point in new line segment
1823  double c = 1 - lengthLeft / painterUnitInterval;
1824 
1825  lengthLeft += l.length();
1826 
1827  // rotate marker (if desired)
1828  if ( rotateSymbols() )
1829  {
1830  setSymbolLineAngle( l.angle() * 180 / M_PI );
1831  }
1832 
1833  // while we're not at the end of line segment, draw!
1834  while ( lengthLeft > painterUnitInterval )
1835  {
1836  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1837  lastPt += c * diff;
1838  lengthLeft -= painterUnitInterval;
1840  renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
1841  c = 1; // reset c (if wasn't 1 already)
1842  }
1843 
1844  lastPt = pt;
1845  }
1846 
1847  }
1848 }
1849 
1850 static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1851 {
1852  // calc average angle between the previous and next point
1853  double a1 = MyLine( prevPt, pt ).angle();
1854  double a2 = MyLine( pt, nextPt ).angle();
1855  double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1856 
1857  return std::atan2( unitY, unitX );
1858 }
1859 
1860 void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1861 {
1862  if ( points.isEmpty() )
1863  return;
1864 
1865  QgsRenderContext &rc = context.renderContext();
1866 
1867  int i = -1, maxCount = 0;
1868  bool isRing = false;
1869 
1871  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1873 
1874  double offsetAlongLine = mOffsetAlongLine;
1876  {
1877  context.setOriginalValueVariable( mOffsetAlongLine );
1879  }
1880 
1881  // only calculated if we need it!!
1882  double totalLength = -1;
1883  if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1884  {
1885  //scale offset along line
1886  switch ( offsetAlongLineUnit() )
1887  {
1896  break;
1898  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1899  offsetAlongLine = offsetAlongLine / 100 * totalLength;
1900  break;
1901  }
1902  if ( points.isClosed() )
1903  {
1904  if ( offsetAlongLine > 0 )
1905  {
1906  if ( totalLength < 0 )
1907  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1908  offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1909  }
1910  else if ( offsetAlongLine < 0 )
1911  {
1912  if ( totalLength < 0 )
1913  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1914  offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1915  }
1916  }
1917  }
1918 
1919  if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1923  {
1925  const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1926 
1927  QgsVertexId vId;
1928  QgsPoint vPoint;
1929  double x, y, z;
1930  QPointF mapPoint;
1931  int pointNum = 0;
1932  const int numPoints = context.renderContext().geometry()->nCoordinates();
1933  while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1934  {
1935  if ( context.renderContext().renderingStopped() )
1936  break;
1937 
1939 
1940  if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1941  continue;
1942 
1943  if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1944  continue;
1945 
1946  if ( ( ( placement == Qgis::MarkerLinePlacement::Vertex || placement == Qgis::MarkerLinePlacement::InnerVertices ) && vId.type == Qgis::VertexType::Segment )
1947  || ( placement == Qgis::MarkerLinePlacement::CurvePoint && vId.type == Qgis::VertexType::Curve ) )
1948  {
1949  //transform
1950  x = vPoint.x();
1951  y = vPoint.y();
1952  z = 0.0;
1953  if ( ct.isValid() )
1954  {
1955  ct.transformInPlace( x, y, z );
1956  }
1957  mapPoint.setX( x );
1958  mapPoint.setY( y );
1959  mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1960  if ( rotateSymbols() )
1961  {
1962  double angle = context.renderContext().geometry()->vertexAngle( vId );
1963  setSymbolLineAngle( angle * 180 / M_PI );
1964  }
1965  renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() );
1966  }
1967  }
1968 
1969  return;
1970  }
1971 
1972  int pointNum = 0;
1973 
1974  switch ( placement )
1975  {
1977  {
1978  i = 0;
1979  maxCount = 1;
1980  break;
1981  }
1982 
1984  {
1985  i = points.count() - 1;
1986  pointNum = i;
1987  maxCount = points.count();
1988  break;
1989  }
1990 
1992  {
1993  i = 1;
1994  pointNum = 1;
1995  maxCount = points.count() - 1;
1996  break;
1997  }
1998 
2001  {
2003  maxCount = points.count();
2004  if ( points.first() == points.last() )
2005  isRing = true;
2006  break;
2007  }
2008 
2012  {
2013  return;
2014  }
2015  }
2016 
2018  {
2019  double distance;
2021  renderOffsetVertexAlongLine( points, i, distance, context, placement );
2022 
2023  return;
2024  }
2025 
2026  QPointF prevPoint;
2027  if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2028  prevPoint = points.at( 0 );
2029 
2030  QPointF symbolPoint;
2031  for ( ; i < maxCount; ++i )
2032  {
2034 
2035  if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2036  {
2037  continue; // don't draw the last marker - it has been drawn already
2038  }
2039 
2041  {
2042  QPointF currentPoint = points.at( i );
2043  symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2044  0.5 * ( currentPoint.y() + prevPoint.y() ) );
2045  if ( rotateSymbols() )
2046  {
2047  double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2048  currentPoint.x() - prevPoint.x() );
2049  setSymbolLineAngle( angle * 180 / M_PI );
2050  }
2051  prevPoint = currentPoint;
2052  }
2053  else
2054  {
2055  symbolPoint = points.at( i );
2056  // rotate marker (if desired)
2057  if ( rotateSymbols() )
2058  {
2059  double angle = markerAngle( points, isRing, i );
2060  setSymbolLineAngle( angle * 180 / M_PI );
2061  }
2062  }
2063 
2064  mFinalVertex = symbolPoint;
2065  if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2066  renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
2067  }
2068 }
2069 
2070 double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2071 {
2072  double angle = 0;
2073  const QPointF &pt = points[vertex];
2074 
2075  if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2076  {
2077  int prevIndex = vertex - 1;
2078  int nextIndex = vertex + 1;
2079 
2080  if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2081  {
2082  prevIndex = points.count() - 2;
2083  nextIndex = 1;
2084  }
2085 
2086  QPointF prevPoint, nextPoint;
2087  while ( prevIndex >= 0 )
2088  {
2089  prevPoint = points[ prevIndex ];
2090  if ( prevPoint != pt )
2091  {
2092  break;
2093  }
2094  --prevIndex;
2095  }
2096 
2097  while ( nextIndex < points.count() )
2098  {
2099  nextPoint = points[ nextIndex ];
2100  if ( nextPoint != pt )
2101  {
2102  break;
2103  }
2104  ++nextIndex;
2105  }
2106 
2107  if ( prevIndex >= 0 && nextIndex < points.count() )
2108  {
2109  angle = _averageAngle( prevPoint, pt, nextPoint );
2110  }
2111  }
2112  else //no ring and vertex is at start / at end
2113  {
2114  if ( vertex == 0 )
2115  {
2116  while ( vertex < points.size() - 1 )
2117  {
2118  const QPointF &nextPt = points[vertex + 1];
2119  if ( pt != nextPt )
2120  {
2121  angle = MyLine( pt, nextPt ).angle();
2122  return angle;
2123  }
2124  ++vertex;
2125  }
2126  }
2127  else
2128  {
2129  // use last segment's angle
2130  while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2131  {
2132  const QPointF &prevPt = points[vertex - 1];
2133  if ( pt != prevPt )
2134  {
2135  angle = MyLine( prevPt, pt ).angle();
2136  return angle;
2137  }
2138  --vertex;
2139  }
2140  }
2141  }
2142  return angle;
2143 }
2144 
2145 void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2146 {
2147  if ( points.isEmpty() )
2148  return;
2149 
2150  QgsRenderContext &rc = context.renderContext();
2151  if ( qgsDoubleNear( distance, 0.0 ) )
2152  {
2153  // rotate marker (if desired)
2154  if ( rotateSymbols() )
2155  {
2156  bool isRing = false;
2157  if ( points.first() == points.last() )
2158  isRing = true;
2159  double angle = markerAngle( points, isRing, vertex );
2160  setSymbolLineAngle( angle * 180 / M_PI );
2161  }
2162  mFinalVertex = points[vertex];
2163  if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2164  renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
2165  return;
2166  }
2167 
2168  int pointIncrement = distance > 0 ? 1 : -1;
2169  QPointF previousPoint = points[vertex];
2170  int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2171  int endPoint = distance > 0 ? points.count() - 1 : 0;
2172  double distanceLeft = std::fabs( distance );
2173 
2174  for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2175  {
2176  const QPointF &pt = points[i];
2177 
2178  if ( previousPoint == pt ) // must not be equal!
2179  continue;
2180 
2181  // create line segment
2182  MyLine l( previousPoint, pt );
2183 
2184  if ( distanceLeft < l.length() )
2185  {
2186  //destination point is in current segment
2187  QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2188  // rotate marker (if desired)
2189  if ( rotateSymbols() )
2190  {
2191  setSymbolLineAngle( l.angle() * 180 / M_PI );
2192  }
2193  mFinalVertex = markerPoint;
2194  if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2195  renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
2196  return;
2197  }
2198 
2199  distanceLeft -= l.length();
2200  previousPoint = pt;
2201  }
2202 
2203  //didn't find point
2204 }
2205 
2206 void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2207 {
2208  if ( p.empty() )
2209  return;
2210 
2211  QVector< QPointF > points = p;
2212  const bool closedRing = points.first() == points.last();
2213 
2214  double lengthLeft = initialOffset;
2215 
2216  double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2217  if ( initialLagLeft < 0 && closedRing )
2218  {
2219  // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2220  QPointF lastPt = points.constLast();
2221  QVector< QPointF > pseudoPoints;
2222  for ( int i = points.count() - 2; i > 0; --i )
2223  {
2224  if ( initialLagLeft >= 0 )
2225  {
2226  break;
2227  }
2228 
2229  const QPointF &pt = points[i];
2230 
2231  if ( lastPt == pt ) // must not be equal!
2232  continue;
2233 
2234  MyLine l( lastPt, pt );
2235  initialLagLeft += l.length();
2236  lastPt = pt;
2237 
2238  pseudoPoints << pt;
2239  }
2240  std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2241 
2242  points = pseudoPoints;
2243  points.append( p );
2244  }
2245  else
2246  {
2247  while ( initialLagLeft < 0 )
2248  {
2249  dest << points.constFirst();
2250  initialLagLeft += intervalPainterUnits;
2251  }
2252  }
2253  if ( initialLag > 0 )
2254  {
2255  lengthLeft += intervalPainterUnits - initialLagLeft;
2256  }
2257 
2258  QPointF lastPt = points[0];
2259  for ( int i = 1; i < points.count(); ++i )
2260  {
2261  const QPointF &pt = points[i];
2262 
2263  if ( lastPt == pt ) // must not be equal!
2264  {
2265  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2266  {
2267  lastPt = points[0];
2268  i = 0;
2269  }
2270  continue;
2271  }
2272 
2273  // for each line, find out dx and dy, and length
2274  MyLine l( lastPt, pt );
2275  QPointF diff = l.diffForInterval( intervalPainterUnits );
2276 
2277  // if there's some length left from previous line
2278  // use only the rest for the first point in new line segment
2279  double c = 1 - lengthLeft / intervalPainterUnits;
2280 
2281  lengthLeft += l.length();
2282 
2283 
2284  while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2285  {
2286  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2287  lastPt += c * diff;
2288  lengthLeft -= intervalPainterUnits;
2289  dest << lastPt;
2290  c = 1; // reset c (if wasn't 1 already)
2291  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2292  break;
2293  }
2294  lastPt = pt;
2295 
2296  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2297  break;
2298 
2299  // if a closed ring, we keep looping around the ring until we hit the required number of points
2300  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2301  {
2302  lastPt = points[0];
2303  i = 0;
2304  }
2305  }
2306 
2307  if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2308  {
2309  // pad with repeating last point to match desired size
2310  while ( dest.size() < numberPointsRequired )
2311  dest << points.constLast();
2312  }
2313 }
2314 
2315 void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2316 {
2317  if ( !points.isEmpty() )
2318  {
2319  // calc length
2320  qreal length = 0;
2321  QPolygonF::const_iterator it = points.constBegin();
2322  QPointF last = *it;
2323  for ( ++it; it != points.constEnd(); ++it )
2324  {
2325  length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2326  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2327  last = *it;
2328  }
2329  if ( qgsDoubleNear( length, 0.0 ) )
2330  return;
2331 
2332  const double midPoint = length / 2;
2333 
2334  QPointF pt;
2335  double thisSymbolAngle = 0;
2336 
2337  if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2338  {
2339  QVector< QPointF > angleStartPoints;
2340  QVector< QPointF > symbolPoints;
2341  QVector< QPointF > angleEndPoints;
2342  // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2343  collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2344  collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2345  collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2346 
2347  pt = symbolPoints.at( 1 );
2348  MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2349  thisSymbolAngle = l.angle();
2350  }
2351  else
2352  {
2353  // find the segment where the central point lies
2354  it = points.constBegin();
2355  last = *it;
2356  qreal last_at = 0, next_at = 0;
2357  QPointF next;
2358  int segment = 0;
2359  for ( ++it; it != points.constEnd(); ++it )
2360  {
2361  next = *it;
2362  next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2363  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2364  if ( next_at >= midPoint )
2365  break; // we have reached the center
2366  last = *it;
2367  last_at = next_at;
2368  segment++;
2369  }
2370 
2371  // find out the central point on segment
2372  MyLine l( last, next ); // for line angle
2373  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2374  pt = last + ( next - last ) * k;
2375  thisSymbolAngle = l.angle();
2376  }
2377 
2378  // draw the marker
2379  // rotate marker (if desired)
2380  if ( rotateSymbols() )
2381  {
2382  setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2383  }
2384 
2385  renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2386 
2387  }
2388 }
2389 
2391 {
2392  return mMarker.get();
2393 }
2394 
2396 {
2397  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2398  {
2399  delete symbol;
2400  return false;
2401  }
2402 
2403  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2404  mColor = mMarker->color();
2405  return true;
2406 }
2407 
2408 
2409 
2410 //
2411 // QgsMarkerLineSymbolLayer
2412 //
2413 
2414 QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2415  : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2416 {
2417  setSubSymbol( new QgsMarkerSymbol() );
2418 }
2419 
2421 
2423 {
2424  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2426 
2427  if ( props.contains( QStringLiteral( "interval" ) ) )
2428  interval = props[QStringLiteral( "interval" )].toDouble();
2429  if ( props.contains( QStringLiteral( "rotate" ) ) )
2430  rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2431 
2432  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2433  setCommonProperties( x.get(), props );
2434  return x.release();
2435 }
2436 
2438 {
2439  return QStringLiteral( "MarkerLine" );
2440 }
2441 
2442 void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2443 {
2444  mMarker->setColor( color );
2445  mColor = color;
2446 }
2447 
2449 {
2450  return mMarker ? mMarker->color() : mColor;
2451 }
2452 
2454 {
2455  // if being rotated, it gets initialized with every line segment
2456  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2457  if ( rotateSymbols() )
2459  mMarker->setRenderHints( hints );
2460 
2461  mMarker->startRender( context.renderContext(), context.fields() );
2462 }
2463 
2465 {
2466  mMarker->stopRender( context.renderContext() );
2467 }
2468 
2469 
2471 {
2472  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2473  copyTemplateSymbolProperties( x.get() );
2474  return x.release();
2475 }
2476 
2477 void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2478 {
2479  for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2480  {
2481  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2482  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2483  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2484  element.appendChild( symbolizerElem );
2485 
2486  // <Geometry>
2487  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2488 
2489  QString gap;
2491  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2493  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2495  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2497  // no way to get line/polygon's vertices, use a VendorOption
2498  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2499 
2501  {
2503  gap = qgsDoubleToString( interval );
2504  }
2505 
2506  if ( !rotateSymbols() )
2507  {
2508  // markers in LineSymbolizer must be drawn following the line orientation,
2509  // use a VendorOption when no marker rotation
2510  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2511  }
2512 
2513  // <Stroke>
2514  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2515  symbolizerElem.appendChild( strokeElem );
2516 
2517  // <GraphicStroke>
2518  QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2519  strokeElem.appendChild( graphicStrokeElem );
2520 
2521  QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2522  if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2523  {
2524  markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2525  }
2526  else if ( layer )
2527  {
2528  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2529  }
2530  else
2531  {
2532  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2533  }
2534 
2535  if ( !gap.isEmpty() )
2536  {
2537  QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2538  QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap );
2539  graphicStrokeElem.appendChild( gapElem );
2540  }
2541 
2542  if ( !qgsDoubleNear( mOffset, 0.0 ) )
2543  {
2544  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2546  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2547  symbolizerElem.appendChild( perpOffsetElem );
2548  }
2549  }
2550 }
2551 
2553 {
2554  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2555 
2556  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2557  if ( strokeElem.isNull() )
2558  return nullptr;
2559 
2560  QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2561  if ( graphicStrokeElem.isNull() )
2562  return nullptr;
2563 
2564  // retrieve vendor options
2565  bool rotateMarker = true;
2567 
2568  QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2569  for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2570  {
2571  if ( it.key() == QLatin1String( "placement" ) )
2572  {
2573  if ( it.value() == QLatin1String( "points" ) )
2575  else if ( it.value() == QLatin1String( "firstPoint" ) )
2577  else if ( it.value() == QLatin1String( "lastPoint" ) )
2579  else if ( it.value() == QLatin1String( "centralPoint" ) )
2581  }
2582  else if ( it.value() == QLatin1String( "rotateMarker" ) )
2583  {
2584  rotateMarker = it.value() == QLatin1String( "0" );
2585  }
2586  }
2587 
2588  std::unique_ptr< QgsMarkerSymbol > marker;
2589 
2591  if ( l )
2592  {
2593  QgsSymbolLayerList layers;
2594  layers.append( l );
2595  marker.reset( new QgsMarkerSymbol( layers ) );
2596  }
2597 
2598  if ( !marker )
2599  return nullptr;
2600 
2601  double interval = 0.0;
2602  QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2603  if ( !gapElem.isNull() )
2604  {
2605  bool ok;
2606  double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2607  if ( ok )
2608  interval = d;
2609  }
2610 
2611  double offset = 0.0;
2612  QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2613  if ( !perpOffsetElem.isNull() )
2614  {
2615  bool ok;
2616  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2617  if ( ok )
2618  offset = d;
2619  }
2620 
2621  double scaleFactor = 1.0;
2622  const QString uom = element.attribute( QStringLiteral( "uom" ) );
2623  QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2624  interval = interval * scaleFactor;
2625  offset = offset * scaleFactor;
2626 
2628  x->setOutputUnit( sldUnitSize );
2629  x->setPlacements( placement );
2630  x->setInterval( interval );
2631  x->setSubSymbol( marker.release() );
2632  x->setOffset( offset );
2633  return x;
2634 }
2635 
2637 {
2638  mMarker->setSize( width );
2639 }
2640 
2642 {
2643  if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2644  {
2645  mMarker->setDataDefinedSize( property );
2646  }
2648 }
2649 
2650 void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2651 {
2652  const double prevOpacity = mMarker->opacity();
2653  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2655  mMarker->setOpacity( prevOpacity );
2656 }
2657 
2659 {
2660  mMarker->setLineAngle( angle );
2661 }
2662 
2664 {
2665  return mMarker->angle();
2666 }
2667 
2669 {
2670  mMarker->setAngle( angle );
2671 }
2672 
2673 void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2674 {
2675  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2677 
2678  mMarker->renderPoint( point, feature, context, layer, selected );
2679 
2680  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2681 }
2682 
2684 {
2685  return mMarker->size();
2686 }
2687 
2689 {
2690  return mMarker->size( context );
2691 }
2692 
2694 {
2696  mMarker->setOutputUnit( unit );
2697 }
2698 
2700 {
2706  || ( mMarker && mMarker->usesMapUnits() );
2707 }
2708 
2709 QSet<QString> QgsMarkerLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2710 {
2711  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2712  if ( mMarker )
2713  attr.unite( mMarker->usedAttributes( context ) );
2714  return attr;
2715 }
2716 
2718 {
2720  return true;
2721  if ( mMarker && mMarker->hasDataDefinedProperties() )
2722  return true;
2723  return false;
2724 }
2725 
2727 {
2728  return ( mMarker->size( context ) / 2.0 ) +
2730 }
2731 
2732 
2733 //
2734 // QgsHashedLineSymbolLayer
2735 //
2736 
2737 QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2738  : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2739 {
2740  setSubSymbol( new QgsLineSymbol() );
2741 }
2742 
2744 
2746 {
2747  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2749 
2750  if ( props.contains( QStringLiteral( "interval" ) ) )
2751  interval = props[QStringLiteral( "interval" )].toDouble();
2752  if ( props.contains( QStringLiteral( "rotate" ) ) )
2753  rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2754 
2755  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2756  setCommonProperties( x.get(), props );
2757  if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2758  {
2759  x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2760  }
2761 
2762  if ( props.contains( QStringLiteral( "hash_length" ) ) )
2763  x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2764 
2765  if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2766  x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2767 
2768  if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2769  x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2770 
2771  return x.release();
2772 }
2773 
2775 {
2776  return QStringLiteral( "HashLine" );
2777 }
2778 
2780 {
2781  // if being rotated, it gets initialized with every line segment
2782  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2783  if ( rotateSymbols() )
2785  mHashSymbol->setRenderHints( hints );
2786 
2787  mHashSymbol->startRender( context.renderContext(), context.fields() );
2788 }
2789 
2791 {
2792  mHashSymbol->stopRender( context.renderContext() );
2793 }
2794 
2796 {
2797  QVariantMap map = QgsTemplatedLineSymbolLayerBase::properties();
2798  map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2799 
2800  map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2801  map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2802  map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2803 
2804  return map;
2805 }
2806 
2808 {
2809  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2810  copyTemplateSymbolProperties( x.get() );
2811  x->setHashAngle( mHashAngle );
2812  x->setHashLength( mHashLength );
2813  x->setHashLengthUnit( mHashLengthUnit );
2814  x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2815  return x.release();
2816 }
2817 
2818 void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2819 {
2820  mHashSymbol->setColor( color );
2821  mColor = color;
2822 }
2823 
2825 {
2826  return mHashSymbol ? mHashSymbol->color() : mColor;
2827 }
2828 
2830 {
2831  return mHashSymbol.get();
2832 }
2833 
2835 {
2836  if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2837  {
2838  delete symbol;
2839  return false;
2840  }
2841 
2842  mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2843  mColor = mHashSymbol->color();
2844  return true;
2845 }
2846 
2847 void QgsHashedLineSymbolLayer::setWidth( const double width )
2848 {
2849  mHashLength = width;
2850 }
2851 
2853 {
2854  return mHashLength;
2855 }
2856 
2858 {
2859  return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2860 }
2861 
2863 {
2864  return ( mHashSymbol->width( context ) / 2.0 )
2865  + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2866  + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
2867 }
2868 
2870 {
2872  mHashSymbol->setOutputUnit( unit );
2873 }
2874 
2875 QSet<QString> QgsHashedLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2876 {
2877  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2878  if ( mHashSymbol )
2879  attr.unite( mHashSymbol->usedAttributes( context ) );
2880  return attr;
2881 }
2882 
2884 {
2886  return true;
2887  if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2888  return true;
2889  return false;
2890 }
2891 
2893 {
2894  if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2895  {
2896  mHashSymbol->setDataDefinedWidth( property );
2897  }
2899 }
2900 
2902 {
2903  return mHashLengthUnit == QgsUnitTypes::RenderMapUnits || mHashLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
2909  || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2910 }
2911 
2913 {
2914  mSymbolLineAngle = angle;
2915 }
2916 
2918 {
2919  return mSymbolAngle;
2920 }
2921 
2923 {
2924  mSymbolAngle = angle;
2925 }
2926 
2927 void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2928 {
2929  double lineLength = mHashLength;
2931  {
2932  context.expressionContext().setOriginalValueVariable( mHashLength );
2934  }
2935  const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2936 
2937  double hashAngle = mHashAngle;
2939  {
2940  context.expressionContext().setOriginalValueVariable( mHashAngle );
2942  }
2943 
2944  QgsPointXY center( point );
2945  QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2946  QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2947 
2948  QPolygonF points;
2949  points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2950 
2951  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2953 
2954  mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2955 
2956  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2957 }
2958 
2960 {
2961  return mHashAngle;
2962 }
2963 
2965 {
2966  mHashAngle = angle;
2967 }
2968 
2969 void QgsHashedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2970 {
2971  const double prevOpacity = mHashSymbol->opacity();
2972  mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2974  mHashSymbol->setOpacity( prevOpacity );
2975 }
2976 
2977 //
2978 // QgsAbstractBrushedLineSymbolLayer
2979 //
2980 
2981 void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2982 {
2983  if ( !context.renderContext().painter() )
2984  return;
2985 
2986  double offset = mOffset;
2988  {
2989  context.setOriginalValueVariable( offset );
2991  }
2992 
2993  QPolygonF offsetPoints;
2994  if ( qgsDoubleNear( offset, 0 ) )
2995  {
2996  renderLine( points, context, patternThickness, patternLength, brush );
2997  }
2998  else
2999  {
3000  const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3001 
3002  const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
3003  for ( const QPolygonF &part : offsetLine )
3004  {
3005  renderLine( part, context, patternThickness, patternLength, brush );
3006  }
3007  }
3008 }
3009 
3010 void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3011  const double patternLength, const QBrush &sourceBrush )
3012 {
3013  QPainter *p = context.renderContext().painter();
3014  if ( !p )
3015  return;
3016 
3017  QBrush brush = sourceBrush;
3018 
3019  // duplicate points mess up the calculations, we need to remove them first
3020  // we'll calculate the min/max coordinate at the same time, since we're already looping
3021  // through the points
3022  QPolygonF inputPoints;
3023  inputPoints.reserve( points.size() );
3024  QPointF prev;
3025  double minX = std::numeric_limits< double >::max();
3026  double minY = std::numeric_limits< double >::max();
3027  double maxX = std::numeric_limits< double >::lowest();
3028  double maxY = std::numeric_limits< double >::lowest();
3029 
3030  for ( const QPointF &pt : std::as_const( points ) )
3031  {
3032  if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3033  continue;
3034 
3035  inputPoints << pt;
3036  prev = pt;
3037  minX = std::min( minX, pt.x() );
3038  minY = std::min( minY, pt.y() );
3039  maxX = std::max( maxX, pt.x() );
3040  maxY = std::max( maxY, pt.y() );
3041  }
3042 
3043  if ( inputPoints.size() < 2 ) // nothing to render
3044  return;
3045 
3046  // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3047  constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3048  // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3049  constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3050 
3051  // our temporary image needs to extend out by the line thickness on each side, and we'll also add an allowance for antialiasing effects on each side
3052  const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3053  const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3054 
3055  const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3056  && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3057 
3058  QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3059  if ( temporaryImage.isNull() )
3060  {
3061  QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3062  return;
3063  }
3064 
3065  // clear temporary image contents
3066  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3067  return;
3068  temporaryImage.fill( Qt::transparent );
3069  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3070  return;
3071 
3072  Qt::PenJoinStyle join = mPenJoinStyle;
3074  {
3077  if ( !exprVal.isNull() )
3078  join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3079  }
3080 
3081  Qt::PenCapStyle cap = mPenCapStyle;
3083  {
3086  if ( !exprVal.isNull() )
3087  cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3088  }
3089 
3090  // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3091  QPainterPathStroker stroker;
3092  stroker.setWidth( lineThickness );
3093  stroker.setCapStyle( cap );
3094  stroker.setJoinStyle( join );
3095 
3096  QPainterPath path;
3097  path.addPolygon( inputPoints );
3098  const QPainterPath stroke = stroker.createStroke( path ).simplified();
3099 
3100  // prepare temporary image
3101  QPainter imagePainter;
3102  imagePainter.begin( &temporaryImage );
3103  context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3104  imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3105 
3106  imagePainter.setClipPath( stroke, Qt::IntersectClip );
3107  imagePainter.setPen( Qt::NoPen );
3108 
3109  QPointF segmentStartPoint = inputPoints.at( 0 );
3110 
3111  // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3112  double progressThroughImage = 0;
3113 
3114  QgsPoint prevSegmentPolygonEndLeft;
3115  QgsPoint prevSegmentPolygonEndRight;
3116 
3117  // for closed rings this will store the left/right polygon points of the start/end of the line
3118  QgsPoint startLinePolygonLeft;
3119  QgsPoint startLinePolygonRight;
3120 
3121  for ( int i = 1; i < inputPoints.size(); ++i )
3122  {
3123  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3124  break;
3125 
3126  const QPointF segmentEndPoint = inputPoints.at( i );
3127  const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3128  segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3129 
3130  // left/right end points of the current segment polygon
3131  QgsPoint thisSegmentPolygonEndLeft;
3132  QgsPoint thisSegmentPolygonEndRight;
3133  // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3134  QgsPoint thisSegmentPolygonEndLeftForPainter;
3135  QgsPoint thisSegmentPolygonEndRightForPainter;
3136  if ( i == 1 )
3137  {
3138  // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3139  // (unless it's a closed line, that is. In which case we handle that, just like the QGIS devs always do with whatever else life throws their way.)
3140  if ( isClosedLine )
3141  {
3142  // project the current segment out by half the image thickness to either side of the line
3143  const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3144  const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3145  const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3146  const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3147 
3148  // angle of LAST line segment in the whole line (i.e. the one which will eventually connect back to the first point in the line). Used to determine
3149  // what angle the current segment polygon should START on.
3150  const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3151  segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3152 
3153  // project out the LAST segment in the line by half the image thickness to either side of the line
3154  const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3155  const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3156  const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3157  const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3158 
3159  // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3160  // of THIS segment would intersect with the project lines to the left/right of the VERY LAST segment in the line (i.e. simulate a miter style
3161  // join)
3162  QgsPoint intersectionPoint;
3163  bool isIntersection = false;
3164  QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3165  if ( !isIntersection )
3166  prevSegmentPolygonEndLeft = startPointLeft;
3167  isIntersection = false;
3168  QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3169  if ( !isIntersection )
3170  prevSegmentPolygonEndRight = startPointRight;
3171 
3172  startLinePolygonLeft = prevSegmentPolygonEndLeft;
3173  startLinePolygonRight = prevSegmentPolygonEndRight;
3174  }
3175  else
3176  {
3177  prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3178  if ( cap != Qt::PenCapStyle::FlatCap )
3179  prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3180  prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3181  if ( cap != Qt::PenCapStyle::FlatCap )
3182  prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3183  }
3184  }
3185 
3186  if ( i < inputPoints.size() - 1 )
3187  {
3188  // for all other segments except the last
3189 
3190  // project the current segment out by half the image thickness to either side of the line
3191  const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3192  const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3193  const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3194  const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3195 
3196  // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3197  // what angle the current segment polygon should end on
3198  const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3199  inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3200 
3201  // project out the next segment by half the image thickness to either side of the line
3202  const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3203  const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3204  const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3205  const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3206 
3207  // the polygon representing the current segment ends at the points where the projected lines to the left/right
3208  // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3209  // join)
3210  QgsPoint intersectionPoint;
3211  bool isIntersection = false;
3212  QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3213  if ( !isIntersection )
3214  thisSegmentPolygonEndLeft = endPointLeft;
3215  isIntersection = false;
3216  QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3217  if ( !isIntersection )
3218  thisSegmentPolygonEndRight = endPointRight;
3219 
3220  thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3221  thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3222  }
3223  else
3224  {
3225  // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3226  // unless it's a closed line
3227  if ( isClosedLine )
3228  {
3229  thisSegmentPolygonEndLeft = startLinePolygonLeft;
3230  thisSegmentPolygonEndRight = startLinePolygonRight;
3231 
3232  thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3233  thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3234  }
3235  else
3236  {
3237  thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3238  if ( cap != Qt::PenCapStyle::FlatCap )
3239  thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3240  thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3241  if ( cap != Qt::PenCapStyle::FlatCap )
3242  thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3243 
3244  thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3245  thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3246  }
3247  }
3248 
3249  // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3250  // where we got with the previous segment), at the correct angle
3251  QTransform brushTransform;
3252  brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3253  brushTransform.rotate( -segmentAngleDegrees );
3254  if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3255  {
3256  // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3257  // we need to also do the same for the brush transform
3258  brushTransform.translate( -( lineThickness / 2 ), 0 );
3259  }
3260  brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3261 
3262  brush.setTransform( brushTransform );
3263  imagePainter.setBrush( brush );
3264 
3265  // now draw the segment polygon
3266  imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3267  << thisSegmentPolygonEndLeftForPainter.toQPointF()
3268  << thisSegmentPolygonEndRightForPainter.toQPointF()
3269  << prevSegmentPolygonEndRight.toQPointF()
3270  << prevSegmentPolygonEndLeft.toQPointF() );
3271 
3272 #if 0 // for debugging, will draw the segment polygons
3273  imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3274  imagePainter.setBrush( Qt::NoBrush );
3275  imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3276  << thisSegmentPolygonEndLeftForPainter.toQPointF()
3277  << thisSegmentPolygonEndRightForPainter.toQPointF()
3278  << prevSegmentPolygonEndRight.toQPointF()
3279  << prevSegmentPolygonEndLeft.toQPointF() );
3280  imagePainter.setPen( Qt::NoPen );
3281 #endif
3282 
3283  // calculate the new progress horizontal through the source image to account for the length
3284  // of the segment we've just drawn
3285  progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3286  + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3287  + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3288  progressThroughImage = fmod( progressThroughImage, patternLength );
3289 
3290  // shuffle buffered variables for next loop
3291  segmentStartPoint = segmentEndPoint;
3292  prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3293  prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3294  }
3295  imagePainter.end();
3296 
3297  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3298  return;
3299 
3300  // lastly, draw the temporary image onto the destination painter at the correct place
3301  p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3302  minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3303 }
3304 
3305 
3306 //
3307 // QgsRasterLineSymbolLayer
3308 //
3309 
3311  : mPath( path )
3312 {
3313 }
3314 
3316 
3317 QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3318 {
3319  std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3320 
3321  if ( properties.contains( QStringLiteral( "line_width" ) ) )
3322  {
3323  res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3324  }
3325  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3326  {
3327  res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3328  }
3329  if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3330  {
3331  res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3332  }
3333 
3334  if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3335  res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3336 
3337  if ( properties.contains( QStringLiteral( "offset" ) ) )
3338  {
3339  res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3340  }
3341  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3342  {
3343  res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3344  }
3345  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3346  {
3347  res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3348  }
3349 
3350  if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3351  res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3352  if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3353  res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3354 
3355  if ( properties.contains( QStringLiteral( "alpha" ) ) )
3356  {
3357  res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3358  }
3359 
3360  return res.release();
3361 }
3362 
3363 
3365 {
3366  QVariantMap map;
3367  map[QStringLiteral( "imageFile" )] = mPath;
3368 
3369  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3370  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3371  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3372 
3373  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3374  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3375 
3376  map[QStringLiteral( "offset" )] = QString::number( mOffset );
3377  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3378  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3379 
3380  map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3381 
3382  return map;
3383 }
3384 
3386 {
3387  std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3388  res->setWidth( mWidth );
3389  res->setWidthUnit( mWidthUnit );
3390  res->setWidthMapUnitScale( mWidthMapUnitScale );
3391  res->setPenJoinStyle( mPenJoinStyle );
3392  res->setPenCapStyle( mPenCapStyle );
3393  res->setOffsetUnit( mOffsetUnit );
3394  res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3395  res->setOffset( mOffset );
3396  res->setOpacity( mOpacity );
3397  copyDataDefinedProperties( res.get() );
3398  copyPaintEffect( res.get() );
3399  return res.release();
3400 }
3401 
3402 void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3403 {
3404  const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3405  if ( it != properties.end() && it.value().type() == QVariant::String )
3406  {
3407  if ( saving )
3408  it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3409  else
3410  it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3411  }
3412 }
3413 
3414 void QgsRasterLineSymbolLayer::setPath( const QString &path )
3415 {
3416  mPath = path;
3417 }
3418 
3420 {
3421  return QStringLiteral( "RasterLine" );
3422 }
3423 
3425 {
3426  double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3427 
3429 
3430  double opacity = mOpacity * context.opacity();
3431  bool cached = false;
3433  QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3434  static_cast< int >( std::ceil( scaledHeight ) ) ),
3435  true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3436 }
3437 
3439 {
3440 }
3441 
3442 void QgsRasterLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
3443 {
3444  if ( !context.renderContext().painter() )
3445  return;
3446 
3447  QImage sourceImage = mLineImage;
3451  {
3452  QString path = mPath;
3454  {
3455  context.setOriginalValueVariable( path );
3457  }
3458 
3459  double strokeWidth = mWidth;
3461  {
3462  context.setOriginalValueVariable( strokeWidth );
3464  }
3465  const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3466 
3467  const QSize originalSize = QgsApplication::imageCache()->originalSize( path, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3468  double opacity = mOpacity;
3470  {
3473  }
3474  opacity *= context.opacity();
3475 
3476  bool cached = false;
3477  sourceImage = QgsApplication::imageCache()->pathAsImage( path,
3478  QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3479  static_cast< int >( std::ceil( scaledHeight ) ) ),
3480  true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3481  }
3482 
3483  if ( context.selected() )
3484  {
3485  QgsImageOperation::overlayColor( sourceImage, context.renderContext().selectionColor() );
3486  }
3487 
3488  const QBrush brush( sourceImage );
3489 
3490  renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3491 }
3492 
3494 {
3496  mWidthUnit = unit;
3497  mOffsetUnit = unit;
3498 }
3499 
3501 {
3503  if ( mWidthUnit != unit || mOffsetUnit != unit )
3504  {
3506  }
3507  return unit;
3508 }
3509 
3511 {
3514 }
3515 
3517 {
3519  mOffsetMapUnitScale = scale;
3520 }
3521 
3523 {
3526  {
3527  return mWidthMapUnitScale;
3528  }
3529  return QgsMapUnitScale();
3530 }
3531 
3533 {
3534  return ( mWidth / 2.0 ) + mOffset;
3535 }
3536 
3538 {
3539  return QColor();
3540 }
3541 
3542 
3543 //
3544 // QgsLineburstSymbolLayer
3545 //
3546 
3547 QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3549  , mColor2( color2 )
3550 {
3551  setColor( color );
3552 }
3553 
3555 
3556 QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3557 {
3558  std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3559 
3560  if ( properties.contains( QStringLiteral( "line_width" ) ) )
3561  {
3562  res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3563  }
3564  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3565  {
3566  res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3567  }
3568  if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3569  {
3570  res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3571  }
3572 
3573  if ( properties.contains( QStringLiteral( "offset" ) ) )
3574  {
3575  res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3576  }
3577  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3578  {
3579  res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3580  }
3581  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3582  {
3583  res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3584  }
3585 
3586  if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3587  res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3588  if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3589  res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3590 
3591  if ( properties.contains( QStringLiteral( "color_type" ) ) )
3592  res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3593 
3594  if ( properties.contains( QStringLiteral( "color" ) ) )
3595  {
3596  res->setColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
3597  }
3598  if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3599  {
3600  res->setColor2( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3601  }
3602 
3603  //attempt to create color ramp from props
3604  if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3605  {
3606  res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3607  }
3608  else
3609  {
3610  res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3611  }
3612 
3613  return res.release();
3614 }
3615 
3617 {
3618  QVariantMap map;
3619 
3620  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3621  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3622  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3623 
3624  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3625  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3626 
3627  map[QStringLiteral( "offset" )] = QString::number( mOffset );
3628  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3629  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3630 
3631  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3632  map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
3633  map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3634  if ( mGradientRamp )
3635  {
3636 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3637  map.unite( mGradientRamp->properties() );
3638 #else
3639  map.insert( mGradientRamp->properties() );
3640 #endif
3641  }
3642 
3643  return map;
3644 }
3645 
3647 {
3648  std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3649  res->setWidth( mWidth );
3650  res->setWidthUnit( mWidthUnit );
3651  res->setWidthMapUnitScale( mWidthMapUnitScale );
3652  res->setPenJoinStyle( mPenJoinStyle );
3653  res->setPenCapStyle( mPenCapStyle );
3654  res->setOffsetUnit( mOffsetUnit );
3655  res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3656  res->setOffset( mOffset );
3657  res->setColor( mColor );
3658  res->setColor2( mColor2 );
3659  res->setGradientColorType( mGradientColorType );
3660  if ( mGradientRamp )
3661  res->setColorRamp( mGradientRamp->clone() );
3662  copyDataDefinedProperties( res.get() );
3663  copyPaintEffect( res.get() );
3664  return res.release();
3665 }
3666 
3668 {
3669  return QStringLiteral( "Lineburst" );
3670 }
3671 
3673 {
3674 }
3675 
3677 {
3678 }
3679 
3680 void QgsLineburstSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
3681 {
3682  if ( !context.renderContext().painter() )
3683  return;
3684 
3685  double strokeWidth = mWidth;
3687  {
3688  context.setOriginalValueVariable( strokeWidth );
3690  }
3691  const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3692 
3693  //update alpha of gradient colors
3694  QColor color1 = mColor;
3696  {
3699  }
3700  if ( context.selected() )
3701  {
3702  color1 = context.renderContext().selectionColor();
3703  }
3704  color1.setAlphaF( context.opacity() * color1.alphaF() );
3705 
3706  //second gradient color
3707  QColor color2 = mColor2;
3709  {
3712  }
3713 
3714  //create a QGradient with the desired properties
3715  QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3716  //add stops to gradient
3719  {
3720  //color ramp gradient
3721  QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3722  gradRamp->addStopsToGradient( &gradient, context.opacity() );
3723  }
3724  else
3725  {
3726  //two color gradient
3727  gradient.setColorAt( 0.0, color1 );
3728  gradient.setColorAt( 1.0, color2 );
3729  }
3730  const QBrush brush( gradient );
3731 
3732  renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3733 }
3734 
3736 {
3738  mWidthUnit = unit;
3739  mOffsetUnit = unit;
3740 }
3741 
3743 {
3745  if ( mWidthUnit != unit || mOffsetUnit != unit )
3746  {
3748  }
3749  return unit;
3750 }
3751 
3753 {
3756 }
3757 
3759 {
3761  mOffsetMapUnitScale = scale;
3762 }
3763 
3765 {
3768  {
3769  return mWidthMapUnitScale;
3770  }
3771  return QgsMapUnitScale();
3772 }
3773 
3775 {
3776  return ( mWidth / 2.0 ) + mOffset;
3777 }
3778 
3780 {
3781  return QColor();
3782 }
3783 
3785 {
3786  return mGradientRamp.get();
3787 }
3788 
3790 {
3791  mGradientRamp.reset( ramp );
3792 }
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition: qgis.h:1430
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex) (since QGIS 3....
@ LastVertex
Place symbols on the last vertex in the line.
@ CentralPoint
Place symbols at the mid point of the line.
@ SegmentCenter
Place symbols at the center of every line segment.
@ Vertex
Place symbols on every vertex in the line.
@ Interval
Place symbols at regular intervals.
@ FirstVertex
Place symbols on the first vertex in the line.
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
GradientColorSource
Gradient color sources.
Definition: qgis.h:1452
@ ColorRamp
Gradient color ramp.
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ Marker
Marker symbol.
@ Line
Line symbol.
Base class for line symbol layer types which draws line sections using a QBrush.
void renderPolylineUsingBrush(const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength)
Renders a polyline of points using the specified brush.
static bool isGeneralizableByDeviceBoundingBox(const QgsRectangle &envelope, float mapToPixelTol=1.0f)
Returns whether the device-envelope can be replaced by its BBOX when is applied the specified toleran...
virtual double vertexAngle(QgsVertexId vertex) const =0
Returns approximate angle at a vertex.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
Class for doing transforms between two map coordinate systems.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
Curve polygon geometry type.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:65
static double mapUnitScaleFactor(double scale, QgsUnitTypes::RenderUnit symbolUnits, QgsUnitTypes::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
QgsUnitTypes::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
Definition: qgsdxfexport.h:229
void clipValueToMapUnitScale(double &value, const QgsMapUnitScale &scale, double pixelToMMFactor) const
Clips value to scale minimum/maximum.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
static const QString EXPR_GEOMETRY_RING_NUM
Inbuilt variable name for geometry ring number variable.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false) SIP_HOLDGIL
Compute the intersection between two segments.
static double lineAngle(double x1, double y1, double x2, double y2) SIP_HOLDGIL
Calculates the direction of line joining two points in radians, clockwise from the north direction.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
Line symbol layer type which draws repeating line sections along a line feature.
double hashAngle() const
Returns the angle to use when drawing the hashed lines sections, in degrees clockwise.
QgsHashedLineSymbolLayer(bool rotateSymbol=true, double interval=3)
Constructor for QgsHashedLineSymbolLayer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setWidth(double width) override
Sets the width of the line symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
double width() const override
Returns the estimated width for the line symbol layer.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsHashedLineSymbolLayer, using the settings serialized in the properties map (correspo...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsHashedLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
~QgsHashedLineSymbolLayer() override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QColor color() const override
Returns the "representative" color of the symbol layer.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QString layerType() const override
Returns a string that represents this layer type.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setHashAngle(double angle)
Sets the angle to use when drawing the hashed lines sections, in degrees clockwise.
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
RenderRingFilter
Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
@ ExteriorRingOnly
Render the exterior ring only.
@ InteriorRingsOnly
Render the interior rings only.
@ AllRings
Render both exterior and interior rings.
QgsMapUnitScale mWidthMapUnitScale
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mWidthUnit
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units for the line's offset.
void setWidthMapUnitScale(const QgsMapUnitScale &scale)
void setWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the line's width.
void setOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the line's offset.
void setOffset(double offset)
Sets the line's offset.
RenderRingFilter mRingFilter
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit widthUnit() const
Returns the units for the line's width.
virtual double width() const
Returns the estimated width for the line symbol layer.
QgsMapUnitScale mOffsetMapUnitScale
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the line's offset.
QgsMapUnitScale mapUnitScale() const override
void setRingFilter(QgsLineSymbolLayer::RenderRingFilter filter)
Sets the line symbol layer's ring filter, which controls which rings are rendered when the line symbo...
void setMapUnitScale(const QgsMapUnitScale &scale) override
double offset() const
Returns the line's offset.
QgsUnitTypes::RenderUnit mOffsetUnit
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
Line symbol layer type which draws a gradient pattern perpendicularly along a line.
std::unique_ptr< QgsColorRamp > mGradientRamp
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient line.
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
Qgis::GradientColorSource mGradientColorType
QString layerType() const override
Returns a string that represents this layer type.
~QgsLineburstSymbolLayer() override
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsLineburstSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLineburstSymbolLayer, using the settings serialized in the properties map (correspon...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QColor color() const override
Returns the "representative" color of the symbol layer.
QgsLineburstSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, const QColor &color2=Qt::white)
Constructor for QgsLineburstSymbolLayer, with the specified start and end gradient colors.
QgsColorRamp * colorRamp()
Returns the color ramp used for the gradient line.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
Line symbol layer type which draws repeating marker symbols along a line feature.
Q_DECL_DEPRECATED bool rotateMarker() const
Shall the marker be rotated.
std::unique_ptr< QgsMarkerSymbol > mMarker
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsMarkerLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
double width() const override
Returns the estimated width for the line symbol layer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsMarkerLineSymbolLayer, using the settings serialized in the properties map (correspo...
QgsMarkerLineSymbolLayer(bool rotateMarker=DEFAULT_MARKERLINE_ROTATE, double interval=DEFAULT_MARKERLINE_INTERVAL)
Constructor for QgsMarkerLineSymbolLayer.
void setWidth(double width) override
Sets the width of the line symbol layer.
QColor color() const override
Returns the "representative" color of the symbol layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
~QgsMarkerLineSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QString layerType() const override
Returns a string that represents this layer type.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM element.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
A class to represent a 2D point.
Definition: qgspointxy.h:59
QgsPointXY project(double distance, double bearing) const SIP_HOLDGIL
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
Definition: qgspointxy.cpp:87
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
QPointF toQPointF() const SIP_HOLDGIL
Returns the point as a QPointF.
Definition: qgspoint.h:331
QgsPoint project(double distance, double azimuth, double inclination=90.0) const SIP_HOLDGIL
Returns a new point which corresponds to this point projected by a specified distance with specified ...
Definition: qgspoint.cpp:735
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
A store for object properties.
Definition: qgsproperty.h:231
Line symbol layer type which draws line sections using a raster image file.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsRasterLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double opacity() const
Returns the line opacity.
QString path() const
Returns the raster image path.
void setPath(const QString &path)
Set the raster image path.
QString layerType() const override
Returns a string that represents this layer type.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QgsRasterLineSymbolLayer(const QString &path=QString())
Constructor for QgsRasterLineSymbolLayer, with the specified raster image path.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRasterLineSymbolLayer, using the settings serialized in the properties map (correspo...
QColor color() const override
Returns the "representative" color of the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsMapUnitScale mapUnitScale() const override
virtual ~QgsRasterLineSymbolLayer()
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
const QgsAbstractGeometry * geometry() const
Returns pointer to the unsegmentized geometry.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
QColor selectionColor() const
Returns the color to use when rendering selected features.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
Scoped object for saving and restoring a QPainter object's state.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setDrawInsidePolygon(bool drawInsidePolygon)
Sets whether the line should only be drawn inside polygons, and any portion of the line which falls o...
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsMapUnitScale mapUnitScale() const override
QVector< qreal > dxfCustomDashPattern(QgsUnitTypes::RenderUnit &unit) const override
Gets dash pattern.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleLineSymbolLayer, using the settings serialized in the properties map (correspo...
Qt::PenJoinStyle penJoinStyle() const
Returns the pen join style used to render the line (e.g.
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the outline of polygon, using the given render context.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setDashPatternOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the dash pattern offset.
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
void setCustomDashPatternMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for lengths used in the custom dash pattern.
void setTrimDistanceEndMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the end of the line.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QVector< qreal > customDashVector() const
Returns the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ...
void setTrimDistanceEnd(double distance)
Sets the trim distance for the end of the line, which dictates a length from the end of the line at w...
void setCustomDashPatternUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for lengths used in the custom dash pattern.
double dxfOffset(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets offset.
QgsSimpleLineSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, double width=DEFAULT_SIMPLELINE_WIDTH, Qt::PenStyle penStyle=DEFAULT_SIMPLELINE_PENSTYLE)
Constructor for QgsSimpleLineSymbolLayer.
~QgsSimpleLineSymbolLayer() override
void setUseCustomDashPattern(bool b)
Sets whether the line uses a custom dash pattern.
void setTweakDashPatternOnCorners(bool enabled)
Sets whether dash patterns tweaks should be applied on sharp corners, to ensure that a double-length ...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setCustomDashVector(const QVector< qreal > &vector)
Sets the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ren...
void setDashPatternOffset(double offset)
Sets the dash pattern offset, which dictates how far along the dash pattern the pattern should start ...
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
QString layerType() const override
Returns a string that represents this layer type.
void setDashPatternOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the dash pattern offset.
QgsSimpleLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
Qt::PenStyle penStyle() const
Returns the pen style used to render the line (e.g.
void setPenJoinStyle(Qt::PenJoinStyle style)
Sets the pen join style used to render the line (e.g.
void setAlignDashPattern(bool enabled)
Sets whether dash patterns should be aligned to the start and end of lines, by applying subtle tweaks...
void setTrimDistanceStartUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the trim distance for the start of the line.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsSimpleLineSymbolLayer from an SLD XML DOM element.
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
void setTrimDistanceStartMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the start of the line.
void setTrimDistanceEndUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the trim distance for the end of the line.
Qt::PenCapStyle penCapStyle() const
Returns the pen cap style used to render the line (e.g.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
void setMapUnitScale(const QgsMapUnitScale &scale) override
void setTrimDistanceStart(double distance)
Sets the trim distance for the start of the line, which dictates a length from the start of the line ...
QString ogrFeatureStyle(double mmScaleFactor, double mapUnitScaleFactor) const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool alignDashPattern() const
Returns true if dash patterns should be aligned to the start and end of lines, by applying subtle twe...
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static QColor decodeColor(const QString &str)
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static double rescaleUom(double size, QgsUnitTypes::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static bool isSharpCorner(QPointF p1, QPointF p2, QPointF p3)
Returns true if the angle formed by the line p1 - p2 - p3 forms a "sharp" corner.
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static Qt::PenCapStyle decodePenCapStyle(const QString &str)
static QgsUnitTypes::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static QVector< qreal > decodeRealVector(const QString &s)
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QString encodePenCapStyle(Qt::PenCapStyle style)
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static void appendPolyline(QPolygonF &target, const QPolygonF &line)
Appends a polyline line to an existing target polyline.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static double polylineLength(const QPolygonF &polyline)
Returns the total length of a polyline.
static void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static Qt::PenStyle decodePenStyle(const QString &str)
static QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QPolygonF polylineSubstring(const QPolygonF &polyline, double startOffset, double endOffset)
Returns the substring of a polyline which starts at startOffset from the beginning of the line and en...
static QString encodeRealVector(const QVector< qreal > &v)
Property
Data definable properties.
@ PropertyStrokeStyle
Stroke style (eg solid, dashed)
@ PropertyPlacement
Line marker placement.
@ PropertyFile
Filename, eg for svg files.
@ PropertyCapStyle
Line cap style.
@ PropertyLineDistance
Distance between lines, or length of lines for hash line symbols.
@ PropertyOffsetAlongLine
Offset along line.
@ PropertyCustomDash
Custom dash pattern.
@ PropertyJoinStyle
Line join style.
@ PropertyTrimEnd
Trim distance from end of line (since QGIS 3.20)
@ PropertyOpacity
Opacity.
@ PropertySecondaryColor
Secondary color (eg for gradient fills)
@ PropertyLineAngle
Line angle, or angle of hash lines for hash line symbols.
@ PropertyTrimStart
Trim distance from start of line (since QGIS 3.20)
@ PropertyOffset
Symbol offset.
@ PropertyStrokeWidth
Stroke width.
@ PropertyDashPatternOffset
Dash pattern offset,.
@ PropertyAverageAngleLength
Length to average symbol angles over.
@ PropertyInterval
Line marker interval.
@ PropertyStrokeColor
Stroke color.
@ PropertyWidth
Symbol width.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
virtual void setColor(const QColor &color)
Sets the "representative" color for the symbol layer.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QgsPropertyCollection mDataDefinedProperties
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsFields fields() const
Fields of the layer.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
QgsWkbTypes::GeometryType originalGeometryType() const
Returns the geometry type for the original feature geometry being rendered.
const QgsFeature * feature() const
Returns the current feature being rendered.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:495
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition: qgssymbol.h:502
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:152
Base class for templated line symbols, e.g.
bool rotateSymbols() const
Returns true if the repeating symbols be rotated to match their line segment orientation.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static void setCommonProperties(QgsTemplatedLineSymbolLayerBase *destLayer, const QVariantMap &properties)
Sets all common symbol properties in the destLayer, using the settings serialized in the properties m...
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QgsMapUnitScale mapUnitScale() const FINAL
void setMapUnitScale(const QgsMapUnitScale &scale) FINAL
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
double interval() const
Returns the interval between individual symbols.
void setAverageAngleMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the length over which the line's direction is averaged when calculating i...
double offsetAlongLine() const
Returns the offset along the line for the symbol placement.
void copyTemplateSymbolProperties(QgsTemplatedLineSymbolLayerBase *destLayer) const
Copies all common properties of this layer to another templated symbol layer.
Qgis::MarkerLinePlacements placements() const
Returns the placement of the symbols.
void setOffsetAlongLine(double offsetAlongLine)
Sets the the offset along the line for the symbol placement.
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) FINAL
Renders the line symbol layer along the outline of polygon, using the given render context.
QgsUnitTypes::RenderUnit outputUnit() const FINAL
Returns the units to use for sizes and widths within the symbol layer.
QgsTemplatedLineSymbolLayerBase(bool rotateSymbol=true, double interval=3)
Constructor for QgsTemplatedLineSymbolLayerBase.
virtual void setSymbolLineAngle(double angle)=0
Sets the line angle modification for the symbol's angle.
void setOffsetAlongLineUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit used for calculating the offset along line for symbols.
void setInterval(double interval)
Sets the interval between individual symbols.
const QgsMapUnitScale & offsetAlongLineMapUnitScale() const
Returns the map unit scale used for calculating the offset in map units along line for symbols.
QgsUnitTypes::RenderUnit averageAngleUnit() const
Returns the unit for the length over which the line's direction is averaged when calculating individu...
Q_DECL_DEPRECATED Qgis::MarkerLinePlacement placement() const
Returns the placement of the symbols.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setPlaceOnEveryPart(bool respect)
Sets whether the placement applies for every part of multi-part feature geometries.
QgsUnitTypes::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
Q_DECL_DEPRECATED void setPlacement(Qgis::MarkerLinePlacement placement)
Sets the placement of the symbols.
QgsUnitTypes::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setAverageAngleUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the length over which the line's direction is averaged when calculating individual ...
void setIntervalUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the interval between symbols.
virtual void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false)=0
Renders the templated symbol at the specified point, using the given render context.
void setIntervalMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the interval between symbols.
void setOffsetAlongLineMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for calculating the offset in map units along line for symbols.
void setAverageAngleLength(double length)
Sets the length of line over which the line's direction is averaged when calculating individual symbo...
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
@ RenderUnknownUnit
Mixed or unknown units.
Definition: qgsunittypes.h:175
@ RenderMetersInMapUnits
Meters value as Map units.
Definition: qgsunittypes.h:176
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
@ RenderInches
Inches.
Definition: qgsunittypes.h:174
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
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
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2199
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition: qgis.h:2493
T qgsFlagKeysToValue(const QString &keys, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given keys of a flag.
Definition: qgis.h:2515
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2260
QMap< QString, QString > QgsStringMap
Definition: qgis.h:2776
#define DEFAULT_MARKERLINE_INTERVAL
#define DEFAULT_SIMPLELINE_WIDTH
#define DEFAULT_MARKERLINE_ROTATE
#define DEFAULT_SIMPLELINE_PENSTYLE
#define DEFAULT_SIMPLELINE_COLOR
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QLineF segment(int index, QRectF rect, double radius)
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:27
QList< QPolygonF > offsetLine(QPolygonF polyline, double dist, QgsWkbTypes::GeometryType geometryType)
calculate geometry shifted by a specified distance
Single variable definition for use within a QgsExpressionContextScope.
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:31
Qgis::VertexType type
Vertex type.
Definition: qgsvertexid.h:98