QGIS API Documentation  3.25.0-Master (dec16ba68b)
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 }
61 
63 {
65  if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
66  {
68  }
69  return unit;
70 }
71 
73 {
76 }
77 
79 {
81  mWidthMapUnitScale = scale;
82  mOffsetMapUnitScale = scale;
83  mCustomDashPatternMapUnitScale = scale;
84 }
85 
87 {
90  mOffsetMapUnitScale == mCustomDashPatternMapUnitScale )
91  {
92  return mWidthMapUnitScale;
93  }
94  return QgsMapUnitScale();
95 }
96 
98 {
101  Qt::PenStyle penStyle = DEFAULT_SIMPLELINE_PENSTYLE;
102 
103  if ( props.contains( QStringLiteral( "line_color" ) ) )
104  {
105  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
106  }
107  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
108  {
109  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
110  }
111  else if ( props.contains( QStringLiteral( "color" ) ) )
112  {
113  //pre 2.5 projects used "color"
114  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
115  }
116  if ( props.contains( QStringLiteral( "line_width" ) ) )
117  {
118  width = props[QStringLiteral( "line_width" )].toDouble();
119  }
120  else if ( props.contains( QStringLiteral( "outline_width" ) ) )
121  {
122  width = props[QStringLiteral( "outline_width" )].toDouble();
123  }
124  else if ( props.contains( QStringLiteral( "width" ) ) )
125  {
126  //pre 2.5 projects used "width"
127  width = props[QStringLiteral( "width" )].toDouble();
128  }
129  if ( props.contains( QStringLiteral( "line_style" ) ) )
130  {
131  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
132  }
133  else if ( props.contains( QStringLiteral( "outline_style" ) ) )
134  {
135  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
136  }
137  else if ( props.contains( QStringLiteral( "penstyle" ) ) )
138  {
139  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
140  }
141 
143  if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
144  {
145  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
146  }
147  else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
148  {
149  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
150  }
151  else if ( props.contains( QStringLiteral( "width_unit" ) ) )
152  {
153  //pre 2.5 projects used "width_unit"
154  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
155  }
156  if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
157  l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
158  if ( props.contains( QStringLiteral( "offset" ) ) )
159  l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
160  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
161  l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
162  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
163  l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
164  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
165  l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
166  if ( props.contains( QStringLiteral( "capstyle" ) ) )
167  l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
168 
169  if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
170  {
171  l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
172  }
173  if ( props.contains( QStringLiteral( "customdash" ) ) )
174  {
175  l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
176  }
177  if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
178  {
179  l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
180  }
181  if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
182  {
183  l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
184  }
185 
186  if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
187  {
188  l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
189  }
190 
191  if ( props.contains( QStringLiteral( "ring_filter" ) ) )
192  {
193  l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
194  }
195 
196  if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
197  l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
198  if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
199  l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
200  if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
201  l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
202 
203  if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
204  l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
205  if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
206  l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
207  if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
208  l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
209  if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
210  l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
211  if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
212  l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
213  if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
214  l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
215 
216  if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
217  l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
218 
219  if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
220  l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
221 
223 
224  return l;
225 }
226 
228 {
229  return QStringLiteral( "SimpleLine" );
230 }
231 
233 {
234  QColor penColor = mColor;
235  penColor.setAlphaF( mColor.alphaF() * context.opacity() );
236  mPen.setColor( penColor );
237  double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
238  mPen.setWidthF( scaledWidth );
239 
240  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
241  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
242  const double dashWidthDiv = std::max( 1.0, scaledWidth );
243  if ( mUseCustomDashPattern )
244  {
245  mPen.setStyle( Qt::CustomDashLine );
246 
247  //scale pattern vector
248 
249  QVector<qreal> scaledVector;
250  QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
251  for ( ; it != mCustomDashVector.constEnd(); ++it )
252  {
253  //the dash is specified in terms of pen widths, therefore the division
254  scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
255  }
256  mPen.setDashPattern( scaledVector );
257  }
258  else
259  {
260  mPen.setStyle( mPenStyle );
261  }
262 
263  if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
264  {
265  mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
266  }
267 
268  mPen.setJoinStyle( mPenJoinStyle );
269  mPen.setCapStyle( mPenCapStyle );
270 
271  mSelPen = mPen;
272  QColor selColor = context.renderContext().selectionColor();
273  if ( ! SELECTION_IS_OPAQUE )
274  selColor.setAlphaF( context.opacity() );
275  mSelPen.setColor( selColor );
276 }
277 
279 {
280  Q_UNUSED( context )
281 }
282 
283 void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
284 {
285  QPainter *p = context.renderContext().painter();
286  if ( !p )
287  {
288  return;
289  }
290 
291  QgsExpressionContextScope *scope = nullptr;
292  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
293  if ( hasDataDefinedProperties() )
294  {
295  scope = new QgsExpressionContextScope();
296  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
297  }
298 
299  if ( mDrawInsidePolygon )
300  p->save();
301 
302  switch ( mRingFilter )
303  {
304  case AllRings:
305  case ExteriorRingOnly:
306  {
307  if ( mDrawInsidePolygon )
308  {
309  //only drawing the line on the interior of the polygon, so set clip path for painter
310  QPainterPath clipPath;
311  clipPath.addPolygon( points );
312 
313  if ( rings )
314  {
315  //add polygon rings
316  for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
317  {
318  QPolygonF ring = *it;
319  clipPath.addPolygon( ring );
320  }
321  }
322 
323  //use intersect mode, as a clip path may already exist (e.g., for composer maps)
324  p->setClipPath( clipPath, Qt::IntersectClip );
325  }
326 
327  if ( scope )
329 
330  renderPolyline( points, context );
331  }
332  break;
333 
334  case InteriorRingsOnly:
335  break;
336  }
337 
338  if ( rings )
339  {
340  switch ( mRingFilter )
341  {
342  case AllRings:
343  case InteriorRingsOnly:
344  {
345  mOffset = -mOffset; // invert the offset for rings!
346  int ringIndex = 1;
347  for ( const QPolygonF &ring : std::as_const( *rings ) )
348  {
349  if ( scope )
351 
352  renderPolyline( ring, context );
353  ringIndex++;
354  }
355  mOffset = -mOffset;
356  }
357  break;
358  case ExteriorRingOnly:
359  break;
360  }
361  }
362 
363  if ( mDrawInsidePolygon )
364  {
365  //restore painter to reset clip path
366  p->restore();
367  }
368 
369 }
370 
372 {
373  QPainter *p = context.renderContext().painter();
374  if ( !p )
375  {
376  return;
377  }
378 
379  QPolygonF points = pts;
380 
381  double startTrim = mTrimDistanceStart;
383  {
384  context.setOriginalValueVariable( startTrim );
386  }
387  double endTrim = mTrimDistanceEnd;
389  {
390  context.setOriginalValueVariable( endTrim );
392  }
393 
394  double totalLength = -1;
395  if ( mTrimDistanceStartUnit == QgsUnitTypes::RenderPercentage )
396  {
397  totalLength = QgsSymbolLayerUtils::polylineLength( points );
398  startTrim = startTrim * 0.01 * totalLength;
399  }
400  else
401  {
402  startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
403  }
404  if ( mTrimDistanceEndUnit == QgsUnitTypes::RenderPercentage )
405  {
406  if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
407  totalLength = QgsSymbolLayerUtils::polylineLength( points );
408  endTrim = endTrim * 0.01 * totalLength;
409  }
410  else
411  {
412  endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
413  }
414  if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
415  {
416  points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
417  }
418 
419  QColor penColor = mColor;
420  penColor.setAlphaF( mColor.alphaF() * context.opacity() );
421  mPen.setColor( penColor );
422 
423  double offset = mOffset;
424  applyDataDefinedSymbology( context, mPen, mSelPen, offset );
425 
426  const QPen pen = context.selected() ? mSelPen : mPen;
427 
428  if ( !pen.dashPattern().isEmpty() )
429  {
430  // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
431  const QVector<double> pattern = pen.dashPattern();
432  bool foundNonNull = false;
433  for ( int i = 0; i < pattern.size(); ++i )
434  {
435  if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
436  {
437  foundNonNull = true;
438  break;
439  }
440  }
441  if ( !foundNonNull )
442  return;
443  }
444 
445  p->setBrush( Qt::NoBrush );
446 
447  // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
448  std::unique_ptr< QgsScopedQPainterState > painterState;
449  if ( points.size() <= 2 &&
452  ( p->renderHints() & QPainter::Antialiasing ) )
453  {
454  painterState = std::make_unique< QgsScopedQPainterState >( p );
455  p->setRenderHint( QPainter::Antialiasing, false );
456  }
457 
458  const bool applyPatternTweaks = mAlignDashPattern
459  && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
460  && pen.dashOffset() == 0;
461 
462  if ( qgsDoubleNear( offset, 0 ) )
463  {
464  if ( applyPatternTweaks )
465  {
466  drawPathWithDashPatternTweaks( p, points, pen );
467  }
468  else
469  {
470  p->setPen( pen );
471  QPainterPath path;
472  path.addPolygon( points );
473  p->drawPath( path );
474  }
475  }
476  else
477  {
478  double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
480  {
481  // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
482  // and clamp it to a reasonable range. It's the best we can do in this situation!
483  scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
484  }
485 
486  QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
487  for ( const QPolygonF &part : mline )
488  {
489  if ( applyPatternTweaks )
490  {
491  drawPathWithDashPatternTweaks( p, part, pen );
492  }
493  else
494  {
495  p->setPen( pen );
496  QPainterPath path;
497  path.addPolygon( part );
498  p->drawPath( path );
499  }
500  }
501  }
502 }
503 
505 {
506  QVariantMap map;
507  map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
508  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
509  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
510  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
511  map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
512  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
513  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
514  map[QStringLiteral( "offset" )] = QString::number( mOffset );
515  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
516  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
517  map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
518  map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
519  map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
520  map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
521  map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
522  map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
523  map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
524  map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
525  map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
526  map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
527  map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
528  map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
529  map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
530  map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
531  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
532  map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
533  map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
534  return map;
535 }
536 
538 {
540  l->setWidthUnit( mWidthUnit );
544  l->setCustomDashPatternUnit( mCustomDashPatternUnit );
545  l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
546  l->setOffset( mOffset );
547  l->setPenJoinStyle( mPenJoinStyle );
548  l->setPenCapStyle( mPenCapStyle );
549  l->setUseCustomDashPattern( mUseCustomDashPattern );
550  l->setCustomDashVector( mCustomDashVector );
551  l->setDrawInsidePolygon( mDrawInsidePolygon );
553  l->setDashPatternOffset( mDashPatternOffset );
554  l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
555  l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
556  l->setTrimDistanceStart( mTrimDistanceStart );
557  l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
558  l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
559  l->setTrimDistanceEnd( mTrimDistanceEnd );
560  l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
561  l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
562  l->setAlignDashPattern( mAlignDashPattern );
563  l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
564 
566  copyPaintEffect( l );
567  return l;
568 }
569 
570 void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
571 {
572  if ( mPenStyle == Qt::NoPen )
573  return;
574 
575  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
576  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
577  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
578  element.appendChild( symbolizerElem );
579 
580  // <Geometry>
581  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
582 
583  // <Stroke>
584  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
585  symbolizerElem.appendChild( strokeElem );
586 
587  Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
589  QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
591  &mPenJoinStyle, &mPenCapStyle, &customDashVector );
592 
593  // <se:PerpendicularOffset>
594  if ( !qgsDoubleNear( mOffset, 0.0 ) )
595  {
596  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
598  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
599  symbolizerElem.appendChild( perpOffsetElem );
600  }
601 }
602 
603 QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
604 {
605  if ( mUseCustomDashPattern )
606  {
607  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
608  mPen.color(), mPenJoinStyle,
609  mPenCapStyle, mOffset, &mCustomDashVector );
610  }
611  else
612  {
613  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
614  mPenCapStyle, mOffset );
615  }
616 }
617 
619 {
620  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
621 
622  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
623  if ( strokeElem.isNull() )
624  return nullptr;
625 
626  Qt::PenStyle penStyle;
627  QColor color;
628  double width;
629  Qt::PenJoinStyle penJoinStyle;
630  Qt::PenCapStyle penCapStyle;
631  QVector<qreal> customDashVector;
632 
633  if ( !QgsSymbolLayerUtils::lineFromSld( strokeElem, penStyle,
634  color, width,
636  &customDashVector ) )
637  return nullptr;
638 
639  double offset = 0.0;
640  QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
641  if ( !perpOffsetElem.isNull() )
642  {
643  bool ok;
644  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
645  if ( ok )
646  offset = d;
647  }
648 
649  QString uom = element.attribute( QStringLiteral( "uom" ) );
652 
654  l->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
655  l->setOffset( offset );
658  l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
660  return l;
661 }
662 
663 void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
664 {
665  if ( !dataDefinedProperties().hasActiveProperties() )
666  return; // shortcut
667 
668  //data defined properties
669  bool hasStrokeWidthExpression = false;
671  {
672  context.setOriginalValueVariable( mWidth );
673  double scaledWidth = context.renderContext().convertToPainterUnits(
676  pen.setWidthF( scaledWidth );
677  selPen.setWidthF( scaledWidth );
678  hasStrokeWidthExpression = true;
679  }
680 
681  //color
683  {
685 
687  penColor.setAlphaF( context.opacity() * penColor.alphaF() );
688  pen.setColor( penColor );
689  }
690 
691  //offset
693  {
696  }
697 
698  //dash dot vector
699 
700  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
701  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
702  const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
703 
705  {
706  QVector<qreal> dashVector;
708  if ( !exprVal.isNull() )
709  {
710  QStringList dashList = exprVal.toString().split( ';' );
711  QStringList::const_iterator dashIt = dashList.constBegin();
712  for ( ; dashIt != dashList.constEnd(); ++dashIt )
713  {
714  dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
715  }
716  pen.setDashPattern( dashVector );
717  }
718  }
719  else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
720  {
721  //re-scale pattern vector after data defined pen width was applied
722 
723  QVector<qreal> scaledVector;
724  for ( double v : std::as_const( mCustomDashVector ) )
725  {
726  //the dash is specified in terms of pen widths, therefore the division
727  scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
728  }
729  mPen.setDashPattern( scaledVector );
730  }
731 
732  // dash pattern offset
733  double patternOffset = mDashPatternOffset;
734  if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
735  {
736  context.setOriginalValueVariable( patternOffset );
738  pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
739  }
740 
741  //line style
743  {
746  if ( !exprVal.isNull() )
747  pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
748  }
749 
750  //join style
752  {
755  if ( !exprVal.isNull() )
756  pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
757  }
758 
759  //cap style
761  {
764  if ( !exprVal.isNull() )
765  pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
766  }
767 }
768 
769 void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
770 {
771  if ( pen.dashPattern().empty() || points.size() < 2 )
772  return;
773 
774  QVector< qreal > sourcePattern = pen.dashPattern();
775  const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
776  // back to painter units
777  for ( int i = 0; i < sourcePattern.size(); ++ i )
778  sourcePattern[i] *= pen.widthF();
779 
780  if ( pen.widthF() <= 1.0 )
781  pen.setWidthF( 1.0001 );
782 
783  QVector< qreal > buffer;
784  QPolygonF bufferedPoints;
785  QPolygonF previousSegmentBuffer;
786  // we iterate through the line points, building a custom dash pattern and adding it to the buffer
787  // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
788  // and then append the buffer to the output pattern.
789 
790  auto ptIt = points.constBegin();
791  double totalBufferLength = 0;
792  int patternIndex = 0;
793  double currentRemainingDashLength = 0;
794  double currentRemainingGapLength = 0;
795 
796  auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
797  {
798  QVector< qreal > result;
799  result.reserve( buffer.size() );
800  for ( auto it = buffer.begin(); it != buffer.end(); )
801  {
802  qreal dash = *it++;
803  qreal gap = *it++;
804  while ( dash == 0 && !result.empty() )
805  {
806  result.last() += gap;
807 
808  if ( it == buffer.end() )
809  return result;
810  dash = *it++;
811  gap = *it++;
812  }
813  while ( gap == 0 && it != buffer.end() )
814  {
815  dash += *it++;
816  gap = *it++;
817  }
818  result << dash << gap;
819  }
820  return result;
821  };
822 
823  double currentBufferLineLength = 0;
824  auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
825  dashWidthDiv, &compressPattern]( QPointF * nextPoint )
826  {
827  if ( buffer.empty() || bufferedPoints.size() < 2 )
828  {
829  return;
830  }
831 
832  if ( currentRemainingDashLength )
833  {
834  // ended midway through a dash -- we want to finish this off
835  buffer << currentRemainingDashLength << 0.0;
836  totalBufferLength += currentRemainingDashLength;
837  }
838  QVector< qreal > compressed = compressPattern( buffer );
839  if ( !currentRemainingDashLength )
840  {
841  // ended midway through a gap -- we don't want this, we want to end at previous dash
842  totalBufferLength -= compressed.last();
843  compressed.last() = 0;
844  }
845 
846  // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
847  const double scaleFactor = currentBufferLineLength / totalBufferLength;
848 
849  bool shouldFlushPreviousSegmentBuffer = false;
850 
851  if ( !previousSegmentBuffer.empty() )
852  {
853  // add first dash from current buffer
854  QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
855  if ( !firstDashSubstring.empty() )
856  QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
857 
858  // then we skip over the first dash and gap for this segment
859  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
860 
861  compressed = compressed.mid( 2 );
862  shouldFlushPreviousSegmentBuffer = !compressed.empty();
863  }
864 
865  if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
866  {
867  QPen adjustedPen = pen;
868  adjustedPen.setStyle( Qt::SolidLine );
869  painter->setPen( adjustedPen );
870  QPainterPath path;
871  path.addPolygon( previousSegmentBuffer );
872  painter->drawPath( path );
873  previousSegmentBuffer.clear();
874  }
875 
876  double finalDash = 0;
877  if ( nextPoint )
878  {
879  // sharp bend:
880  // 1. rewind buffered points line by final dash and gap length
881  // (later) 2. draw the bend with a solid line of length 2 * final dash size
882 
883  if ( !compressed.empty() )
884  {
885  finalDash = compressed.at( compressed.size() - 2 );
886  const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
887 
888  const QPolygonF thisPoints = bufferedPoints;
889  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
890  previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
891  }
892  else
893  {
894  previousSegmentBuffer << bufferedPoints;
895  }
896  }
897 
898  currentBufferLineLength = 0;
899  currentRemainingDashLength = 0;
900  currentRemainingGapLength = 0;
901  totalBufferLength = 0;
902  buffer.clear();
903 
904  if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
905  {
906  QPen adjustedPen = pen;
907  if ( !compressed.empty() )
908  {
909  // maximum size of dash pattern is 32 elements
910  compressed = compressed.mid( 0, 32 );
911  std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
912  adjustedPen.setDashPattern( compressed );
913  }
914  else
915  {
916  adjustedPen.setStyle( Qt::SolidLine );
917  }
918 
919  painter->setPen( adjustedPen );
920  QPainterPath path;
921  path.addPolygon( bufferedPoints );
922  painter->drawPath( path );
923  }
924 
925  bufferedPoints.clear();
926  };
927 
928  QPointF p1;
929  QPointF p2 = *ptIt;
930  ptIt++;
931  bufferedPoints << p2;
932  for ( ; ptIt != points.constEnd(); ++ptIt )
933  {
934  p1 = *ptIt;
935  if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
936  {
937  continue;
938  }
939 
940  double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
941  currentBufferLineLength += remainingSegmentDistance;
942  while ( true )
943  {
944  // handle currentRemainingDashLength/currentRemainingGapLength
945  if ( currentRemainingDashLength > 0 )
946  {
947  // bit more of dash to insert
948  if ( remainingSegmentDistance >= currentRemainingDashLength )
949  {
950  // all of dash fits in
951  buffer << currentRemainingDashLength << 0.0;
952  totalBufferLength += currentRemainingDashLength;
953  remainingSegmentDistance -= currentRemainingDashLength;
954  patternIndex++;
955  currentRemainingDashLength = 0.0;
956  currentRemainingGapLength = sourcePattern.at( patternIndex );
957  }
958  else
959  {
960  // only part of remaining dash fits in
961  buffer << remainingSegmentDistance << 0.0;
962  totalBufferLength += remainingSegmentDistance;
963  currentRemainingDashLength -= remainingSegmentDistance;
964  break;
965  }
966  }
967  if ( currentRemainingGapLength > 0 )
968  {
969  // bit more of gap to insert
970  if ( remainingSegmentDistance >= currentRemainingGapLength )
971  {
972  // all of gap fits in
973  buffer << 0.0 << currentRemainingGapLength;
974  totalBufferLength += currentRemainingGapLength;
975  remainingSegmentDistance -= currentRemainingGapLength;
976  currentRemainingGapLength = 0.0;
977  patternIndex++;
978  }
979  else
980  {
981  // only part of remaining gap fits in
982  buffer << 0.0 << remainingSegmentDistance;
983  totalBufferLength += remainingSegmentDistance;
984  currentRemainingGapLength -= remainingSegmentDistance;
985  break;
986  }
987  }
988 
989  if ( patternIndex >= sourcePattern.size() )
990  patternIndex = 0;
991 
992  const double nextPatternDashLength = sourcePattern.at( patternIndex );
993  const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
994  if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
995  {
996  buffer << nextPatternDashLength << nextPatternGapLength;
997  remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
998  totalBufferLength += nextPatternDashLength + nextPatternGapLength;
999  patternIndex += 2;
1000  }
1001  else if ( nextPatternDashLength <= remainingSegmentDistance )
1002  {
1003  // can fit in "dash", but not "gap"
1004  buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1005  totalBufferLength += remainingSegmentDistance;
1006  currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1007  currentRemainingDashLength = 0;
1008  patternIndex++;
1009  break;
1010  }
1011  else
1012  {
1013  // can't fit in "dash"
1014  buffer << remainingSegmentDistance << 0.0;
1015  totalBufferLength += remainingSegmentDistance;
1016  currentRemainingGapLength = 0;
1017  currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1018  break;
1019  }
1020  }
1021 
1022  bufferedPoints << p1;
1023  if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1024  {
1025  QPointF nextPoint = *( ptIt + 1 );
1026 
1027  // extreme angles form more than 45 degree angle at a node
1028  if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1029  {
1030  // extreme angle. Rescale buffer and flush
1031  flushBuffer( &nextPoint );
1032  bufferedPoints << p1;
1033  // restart the line with the full length of the most recent dash element -- see
1034  // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1035  if ( patternIndex % 2 == 1 )
1036  {
1037  patternIndex--;
1038  }
1039  currentRemainingDashLength = sourcePattern.at( patternIndex );
1040  }
1041  }
1042 
1043  p2 = p1;
1044  }
1045 
1046  flushBuffer( nullptr );
1047  if ( !previousSegmentBuffer.empty() )
1048  {
1049  QPen adjustedPen = pen;
1050  adjustedPen.setStyle( Qt::SolidLine );
1051  painter->setPen( adjustedPen );
1052  QPainterPath path;
1053  path.addPolygon( previousSegmentBuffer );
1054  painter->drawPath( path );
1055  previousSegmentBuffer.clear();
1056  }
1057 }
1058 
1060 {
1061  if ( mDrawInsidePolygon )
1062  {
1063  //set to clip line to the interior of polygon, so we expect no bleed
1064  return 0;
1065  }
1066  else
1067  {
1068  return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1070  }
1071 }
1072 
1074 {
1075  unit = mCustomDashPatternUnit;
1076  return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1077 }
1078 
1080 {
1081  return mPenStyle;
1082 }
1083 
1085 {
1086  double width = mWidth;
1088  {
1089  context.setOriginalValueVariable( mWidth );
1091  }
1092 
1095  {
1097  }
1098  return width;
1099 }
1100 
1102 {
1104  {
1107  }
1108  return mColor;
1109 }
1110 
1112 {
1113  return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1114 }
1115 
1117 {
1118  return mAlignDashPattern;
1119 }
1120 
1122 {
1123  mAlignDashPattern = enabled;
1124 }
1125 
1127 {
1128  return mPatternCartographicTweakOnSharpCorners;
1129 }
1130 
1132 {
1133  mPatternCartographicTweakOnSharpCorners = enabled;
1134 }
1135 
1137 {
1138  double offset = mOffset;
1139 
1141  {
1142  context.setOriginalValueVariable( mOffset );
1144  }
1145 
1148  {
1150  }
1151  return -offset; //direction seems to be inverse to symbology offset
1152 }
1153 
1155 
1157 
1158 class MyLine
1159 {
1160  public:
1161  MyLine( QPointF p1, QPointF p2 )
1162  : mVertical( false )
1163  , mIncreasing( false )
1164  , mT( 0.0 )
1165  , mLength( 0.0 )
1166  {
1167  if ( p1 == p2 )
1168  return; // invalid
1169 
1170  // tangent and direction
1171  if ( qgsDoubleNear( p1.x(), p2.x() ) )
1172  {
1173  // vertical line - tangent undefined
1174  mVertical = true;
1175  mIncreasing = ( p2.y() > p1.y() );
1176  }
1177  else
1178  {
1179  mVertical = false;
1180  mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1181  mIncreasing = ( p2.x() > p1.x() );
1182  }
1183 
1184  // length
1185  double x = ( p2.x() - p1.x() );
1186  double y = ( p2.y() - p1.y() );
1187  mLength = std::sqrt( x * x + y * y );
1188  }
1189 
1190  // return angle in radians
1191  double angle()
1192  {
1193  double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1194 
1195  if ( !mIncreasing )
1196  a += M_PI;
1197  return a;
1198  }
1199 
1200  // return difference for x,y when going along the line with specified interval
1201  QPointF diffForInterval( double interval )
1202  {
1203  if ( mVertical )
1204  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1205 
1206  double alpha = std::atan( mT );
1207  double dx = std::cos( alpha ) * interval;
1208  double dy = std::sin( alpha ) * interval;
1209  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1210  }
1211 
1212  double length() const { return mLength; }
1213 
1214  protected:
1215  bool mVertical;
1216  bool mIncreasing;
1217  double mT;
1218  double mLength;
1219 };
1220 
1222 
1223 //
1224 // QgsTemplatedLineSymbolLayerBase
1225 //
1227  : mRotateSymbols( rotateSymbol )
1228  , mInterval( interval )
1229 {
1230 
1231 }
1232 
1234 {
1235  if ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1237  else if ( mPlacements & Qgis::MarkerLinePlacement::Vertex )
1239  else if ( ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1240  && ( mPlacements & Qgis::MarkerLinePlacement::InnerVertices )
1241  && ( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1242  return Qgis::MarkerLinePlacement::Vertex; // retain round trip for deprecated old API
1243  else if ( mPlacements & Qgis::MarkerLinePlacement::LastVertex )
1245  else if ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1247  else if ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1249  else if ( mPlacements & Qgis::MarkerLinePlacement::CurvePoint )
1251  else if ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter )
1253  else
1255 }
1256 
1258 {
1259  mPlacements = placement;
1260 }
1261 
1263 
1265 {
1266  if ( mRenderingFeature )
1267  {
1268  // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1269  // until after we've received the final part
1270  mFeatureSymbolOpacity = context.opacity();
1271  mCurrentFeatureIsSelected = context.selected();
1272  }
1273 
1274  double offset = mOffset;
1275 
1277  {
1278  context.setOriginalValueVariable( mOffset );
1280  }
1281 
1282  Qgis::MarkerLinePlacements placements = QgsTemplatedLineSymbolLayerBase::placements();
1283 
1285  {
1287  if ( !exprVal.isNull() )
1288  {
1289  QString placementString = exprVal.toString();
1290  if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1291  {
1293  }
1294  else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1295  {
1297  }
1298  else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1299  {
1301  }
1302  else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1303  {
1305  }
1306  else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1307  {
1309  }
1310  else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1311  {
1313  }
1314  else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1315  {
1317  }
1318  else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1319  {
1321  }
1322  else
1323  {
1325  }
1326  }
1327  }
1328 
1329  QgsScopedQPainterState painterState( context.renderContext().painter() );
1330 
1331  double averageOver = mAverageAngleLength;
1333  {
1334  context.setOriginalValueVariable( mAverageAngleLength );
1336  }
1337  averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1338 
1339  if ( qgsDoubleNear( offset, 0.0 ) )
1340  {
1342  renderPolylineInterval( points, context, averageOver );
1344  renderPolylineCentral( points, context, averageOver );
1346  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1348  && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1349  {
1350  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1351  mHasRenderedFirstPart = mRenderingFeature;
1352  }
1354  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1356  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1358  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1360  renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1361  }
1362  else
1363  {
1364  context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1366 
1367  for ( int part = 0; part < mline.count(); ++part )
1368  {
1369  const QPolygonF &points2 = mline[ part ];
1370 
1372  renderPolylineInterval( points2, context, averageOver );
1374  renderPolylineCentral( points2, context, averageOver );
1376  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1378  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1380  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1382  && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1383  {
1384  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1385  mHasRenderedFirstPart = mRenderingFeature;
1386  }
1388  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1390  renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1391  }
1392  }
1393 }
1394 
1395 void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1396 {
1397  const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1398 
1399  if ( curvePolygon )
1400  {
1401  context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1402  }
1403 
1404  QgsExpressionContextScope *scope = nullptr;
1405  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1406  if ( hasDataDefinedProperties() )
1407  {
1408  scope = new QgsExpressionContextScope();
1409  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1410  }
1411 
1412  switch ( mRingFilter )
1413  {
1414  case AllRings:
1415  case ExteriorRingOnly:
1416  {
1417  if ( scope )
1419 
1420  renderPolyline( points, context );
1421  break;
1422  }
1423  case InteriorRingsOnly:
1424  break;
1425  }
1426 
1427  if ( rings )
1428  {
1429  switch ( mRingFilter )
1430  {
1431  case AllRings:
1432  case InteriorRingsOnly:
1433  {
1434  mOffset = -mOffset; // invert the offset for rings!
1435  for ( int i = 0; i < rings->size(); ++i )
1436  {
1437  if ( curvePolygon )
1438  {
1439  context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1440  }
1441  if ( scope )
1443 
1444  renderPolyline( rings->at( i ), context );
1445  }
1446  mOffset = -mOffset;
1447  }
1448  break;
1449  case ExteriorRingOnly:
1450  break;
1451  }
1452  }
1453 }
1454 
1456 {
1458  if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1459  {
1461  }
1462  return unit;
1463 }
1464 
1466 {
1468  setIntervalMapUnitScale( scale );
1469  mOffsetMapUnitScale = scale;
1471 }
1472 
1474 {
1478  {
1479  return mOffsetMapUnitScale;
1480  }
1481  return QgsMapUnitScale();
1482 }
1483 
1485 {
1486  QVariantMap map;
1487  map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1488  map[QStringLiteral( "interval" )] = QString::number( interval() );
1489  map[QStringLiteral( "offset" )] = QString::number( mOffset );
1490  map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1491  map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1492  map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1493  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1494  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1495  map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1496  map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1497  map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1498  map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1499  map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1500 
1501  map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1502 
1503  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1504  map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1505  return map;
1506 }
1507 
1509 {
1510  return mPlaceOnEveryPart
1511  || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1512  || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1513  || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1514 }
1515 
1517 {
1518  mRenderingFeature = true;
1519  mHasRenderedFirstPart = false;
1520 }
1521 
1523 {
1524  mRenderingFeature = false;
1525  if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1526  return;
1527 
1528  const double prevOpacity = subSymbol()->opacity();
1529  subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1530 
1531  // render final point
1532  renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1533  mFeatureSymbolOpacity = 1;
1534  subSymbol()->setOpacity( prevOpacity );
1535 }
1536 
1538 {
1539  destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1540  destLayer->setOffset( mOffset );
1541  destLayer->setPlacements( placements() );
1542  destLayer->setOffsetUnit( mOffsetUnit );
1544  destLayer->setIntervalUnit( intervalUnit() );
1546  destLayer->setOffsetAlongLine( offsetAlongLine() );
1549  destLayer->setAverageAngleLength( mAverageAngleLength );
1550  destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1551  destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1552  destLayer->setRingFilter( mRingFilter );
1553  destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1554  copyDataDefinedProperties( destLayer );
1555  copyPaintEffect( destLayer );
1556 }
1557 
1559 {
1560  if ( properties.contains( QStringLiteral( "offset" ) ) )
1561  {
1562  destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1563  }
1564  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1565  {
1566  destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1567  }
1568  if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1569  {
1570  destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1571  }
1572  if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1573  {
1574  destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1575  }
1576  if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1577  {
1578  destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1579  }
1580  if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1581  {
1582  destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1583  }
1584 
1585  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1586  {
1587  destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1588  }
1589  if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1590  {
1591  destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1592  }
1593 
1594  if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1595  {
1596  destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1597  }
1598  if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1599  {
1600  destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1601  }
1602  if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1603  {
1604  destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1605  }
1606 
1607  if ( properties.contains( QStringLiteral( "placement" ) ) )
1608  {
1609  if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1611  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1613  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1615  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1617  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1619  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1621  else
1623  }
1624  else if ( properties.contains( QStringLiteral( "placements" ) ) )
1625  {
1626  Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1627  destLayer->setPlacements( placements );
1628  }
1629 
1630  if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1631  {
1632  destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1633  }
1634 
1635  destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1636 
1638 }
1639 
1640 void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1641 {
1642  if ( points.isEmpty() )
1643  return;
1644 
1645  double lengthLeft = 0; // how much is left until next marker
1646 
1647  QgsRenderContext &rc = context.renderContext();
1648  double interval = mInterval;
1649 
1651  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1652 
1654  {
1655  context.setOriginalValueVariable( mInterval );
1657  }
1658  if ( interval <= 0 )
1659  {
1660  interval = 0.1;
1661  }
1662  double offsetAlongLine = mOffsetAlongLine;
1664  {
1665  context.setOriginalValueVariable( mOffsetAlongLine );
1667  }
1668 
1669  double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1671  {
1672  // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1673  // and clamp it to a reasonable range. It's the best we can do in this situation!
1674  painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, QgsUnitTypes::RenderMillimeters ), 10.0 ), 100.0 );
1675  }
1676 
1677  if ( painterUnitInterval < 0 )
1678  return;
1679 
1680  double painterUnitOffsetAlongLine = 0;
1681 
1682  // only calculated if we need it!
1683  double totalLength = -1;
1684 
1685  if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1686  {
1687  switch ( offsetAlongLineUnit() )
1688  {
1697  break;
1699  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1700  painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1701  break;
1702  }
1703 
1704  if ( points.isClosed() )
1705  {
1706  if ( painterUnitOffsetAlongLine > 0 )
1707  {
1708  if ( totalLength < 0 )
1709  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1710  painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1711  }
1712  else if ( painterUnitOffsetAlongLine < 0 )
1713  {
1714  if ( totalLength < 0 )
1715  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1716  painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1717  }
1718  }
1719  }
1720 
1722  {
1723  // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1724  // and clamp it to a reasonable range. It's the best we can do in this situation!
1725  painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
1726  }
1727 
1728  lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1729 
1730  if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1731  {
1732  QVector< QPointF > angleStartPoints;
1733  QVector< QPointF > symbolPoints;
1734  QVector< QPointF > angleEndPoints;
1735 
1736  // we collect 3 arrays of points. These correspond to
1737  // 1. the actual point at which to render the symbol
1738  // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1739  // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1740  // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1741  // (or trace past the final point)
1742  collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1743 
1744  if ( symbolPoints.empty() )
1745  {
1746  // no symbols to draw, shortcut out early
1747  return;
1748  }
1749 
1750  if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1751  {
1752  // avoid duplicate points at start and end of closed rings
1753  symbolPoints.pop_back();
1754  }
1755 
1756  angleEndPoints.reserve( symbolPoints.size() );
1757  angleStartPoints.reserve( symbolPoints.size() );
1758  if ( averageOver <= painterUnitOffsetAlongLine )
1759  {
1760  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1761  }
1762  else
1763  {
1764  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1765  }
1766  collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1767 
1768  int pointNum = 0;
1769  for ( int i = 0; i < symbolPoints.size(); ++ i )
1770  {
1771  if ( context.renderContext().renderingStopped() )
1772  break;
1773 
1774  const QPointF pt = symbolPoints[i];
1775  const QPointF startPt = angleStartPoints[i];
1776  const QPointF endPt = angleEndPoints[i];
1777 
1778  MyLine l( startPt, endPt );
1779  // rotate marker (if desired)
1780  if ( rotateSymbols() )
1781  {
1782  setSymbolLineAngle( l.angle() * 180 / M_PI );
1783  }
1784 
1786  renderSymbol( pt, context.feature(), rc, -1, context.selected() );
1787  }
1788  }
1789  else
1790  {
1791  // not averaging line angle -- always use exact section angle
1792  int pointNum = 0;
1793  QPointF lastPt = points[0];
1794  for ( int i = 1; i < points.count(); ++i )
1795  {
1796  if ( context.renderContext().renderingStopped() )
1797  break;
1798 
1799  const QPointF &pt = points[i];
1800 
1801  if ( lastPt == pt ) // must not be equal!
1802  continue;
1803 
1804  // for each line, find out dx and dy, and length
1805  MyLine l( lastPt, pt );
1806  QPointF diff = l.diffForInterval( painterUnitInterval );
1807 
1808  // if there's some length left from previous line
1809  // use only the rest for the first point in new line segment
1810  double c = 1 - lengthLeft / painterUnitInterval;
1811 
1812  lengthLeft += l.length();
1813 
1814  // rotate marker (if desired)
1815  if ( rotateSymbols() )
1816  {
1817  setSymbolLineAngle( l.angle() * 180 / M_PI );
1818  }
1819 
1820  // while we're not at the end of line segment, draw!
1821  while ( lengthLeft > painterUnitInterval )
1822  {
1823  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1824  lastPt += c * diff;
1825  lengthLeft -= painterUnitInterval;
1827  renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
1828  c = 1; // reset c (if wasn't 1 already)
1829  }
1830 
1831  lastPt = pt;
1832  }
1833 
1834  }
1835 }
1836 
1837 static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1838 {
1839  // calc average angle between the previous and next point
1840  double a1 = MyLine( prevPt, pt ).angle();
1841  double a2 = MyLine( pt, nextPt ).angle();
1842  double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1843 
1844  return std::atan2( unitY, unitX );
1845 }
1846 
1847 void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1848 {
1849  if ( points.isEmpty() )
1850  return;
1851 
1852  QgsRenderContext &rc = context.renderContext();
1853 
1854  int i = -1, maxCount = 0;
1855  bool isRing = false;
1856 
1858  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1860 
1861  double offsetAlongLine = mOffsetAlongLine;
1863  {
1864  context.setOriginalValueVariable( mOffsetAlongLine );
1866  }
1867 
1868  // only calculated if we need it!!
1869  double totalLength = -1;
1870  if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1871  {
1872  //scale offset along line
1873  switch ( offsetAlongLineUnit() )
1874  {
1883  break;
1885  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1886  offsetAlongLine = offsetAlongLine / 100 * totalLength;
1887  break;
1888  }
1889  if ( points.isClosed() )
1890  {
1891  if ( offsetAlongLine > 0 )
1892  {
1893  if ( totalLength < 0 )
1894  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1895  offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1896  }
1897  else if ( offsetAlongLine < 0 )
1898  {
1899  if ( totalLength < 0 )
1900  totalLength = QgsSymbolLayerUtils::polylineLength( points );
1901  offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1902  }
1903  }
1904  }
1905 
1906  if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1910  {
1912  const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1913 
1914  QgsVertexId vId;
1915  QgsPoint vPoint;
1916  double x, y, z;
1917  QPointF mapPoint;
1918  int pointNum = 0;
1919  const int numPoints = context.renderContext().geometry()->nCoordinates();
1920  while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1921  {
1922  if ( context.renderContext().renderingStopped() )
1923  break;
1924 
1926 
1927  if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1928  continue;
1929 
1930  if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1931  continue;
1932 
1933  if ( ( ( placement == Qgis::MarkerLinePlacement::Vertex || placement == Qgis::MarkerLinePlacement::InnerVertices ) && vId.type == Qgis::VertexType::Segment )
1934  || ( placement == Qgis::MarkerLinePlacement::CurvePoint && vId.type == Qgis::VertexType::Curve ) )
1935  {
1936  //transform
1937  x = vPoint.x();
1938  y = vPoint.y();
1939  z = 0.0;
1940  if ( ct.isValid() )
1941  {
1942  ct.transformInPlace( x, y, z );
1943  }
1944  mapPoint.setX( x );
1945  mapPoint.setY( y );
1946  mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1947  if ( rotateSymbols() )
1948  {
1949  double angle = context.renderContext().geometry()->vertexAngle( vId );
1950  setSymbolLineAngle( angle * 180 / M_PI );
1951  }
1952  renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() );
1953  }
1954  }
1955 
1956  return;
1957  }
1958 
1959  int pointNum = 0;
1960 
1961  switch ( placement )
1962  {
1964  {
1965  i = 0;
1966  maxCount = 1;
1967  break;
1968  }
1969 
1971  {
1972  i = points.count() - 1;
1973  pointNum = i;
1974  maxCount = points.count();
1975  break;
1976  }
1977 
1979  {
1980  i = 1;
1981  pointNum = 1;
1982  maxCount = points.count() - 1;
1983  break;
1984  }
1985 
1988  {
1990  maxCount = points.count();
1991  if ( points.first() == points.last() )
1992  isRing = true;
1993  break;
1994  }
1995 
1999  {
2000  return;
2001  }
2002  }
2003 
2005  {
2006  double distance;
2008  renderOffsetVertexAlongLine( points, i, distance, context, placement );
2009 
2010  return;
2011  }
2012 
2013  QPointF prevPoint;
2014  if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2015  prevPoint = points.at( 0 );
2016 
2017  QPointF symbolPoint;
2018  for ( ; i < maxCount; ++i )
2019  {
2021 
2022  if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2023  {
2024  continue; // don't draw the last marker - it has been drawn already
2025  }
2026 
2028  {
2029  QPointF currentPoint = points.at( i );
2030  symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2031  0.5 * ( currentPoint.y() + prevPoint.y() ) );
2032  if ( rotateSymbols() )
2033  {
2034  double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2035  currentPoint.x() - prevPoint.x() );
2036  setSymbolLineAngle( angle * 180 / M_PI );
2037  }
2038  prevPoint = currentPoint;
2039  }
2040  else
2041  {
2042  symbolPoint = points.at( i );
2043  // rotate marker (if desired)
2044  if ( rotateSymbols() )
2045  {
2046  double angle = markerAngle( points, isRing, i );
2047  setSymbolLineAngle( angle * 180 / M_PI );
2048  }
2049  }
2050 
2051  mFinalVertex = symbolPoint;
2052  if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2053  renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
2054  }
2055 }
2056 
2057 double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2058 {
2059  double angle = 0;
2060  const QPointF &pt = points[vertex];
2061 
2062  if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2063  {
2064  int prevIndex = vertex - 1;
2065  int nextIndex = vertex + 1;
2066 
2067  if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2068  {
2069  prevIndex = points.count() - 2;
2070  nextIndex = 1;
2071  }
2072 
2073  QPointF prevPoint, nextPoint;
2074  while ( prevIndex >= 0 )
2075  {
2076  prevPoint = points[ prevIndex ];
2077  if ( prevPoint != pt )
2078  {
2079  break;
2080  }
2081  --prevIndex;
2082  }
2083 
2084  while ( nextIndex < points.count() )
2085  {
2086  nextPoint = points[ nextIndex ];
2087  if ( nextPoint != pt )
2088  {
2089  break;
2090  }
2091  ++nextIndex;
2092  }
2093 
2094  if ( prevIndex >= 0 && nextIndex < points.count() )
2095  {
2096  angle = _averageAngle( prevPoint, pt, nextPoint );
2097  }
2098  }
2099  else //no ring and vertex is at start / at end
2100  {
2101  if ( vertex == 0 )
2102  {
2103  while ( vertex < points.size() - 1 )
2104  {
2105  const QPointF &nextPt = points[vertex + 1];
2106  if ( pt != nextPt )
2107  {
2108  angle = MyLine( pt, nextPt ).angle();
2109  return angle;
2110  }
2111  ++vertex;
2112  }
2113  }
2114  else
2115  {
2116  // use last segment's angle
2117  while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2118  {
2119  const QPointF &prevPt = points[vertex - 1];
2120  if ( pt != prevPt )
2121  {
2122  angle = MyLine( prevPt, pt ).angle();
2123  return angle;
2124  }
2125  --vertex;
2126  }
2127  }
2128  }
2129  return angle;
2130 }
2131 
2132 void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2133 {
2134  if ( points.isEmpty() )
2135  return;
2136 
2137  QgsRenderContext &rc = context.renderContext();
2138  if ( qgsDoubleNear( distance, 0.0 ) )
2139  {
2140  // rotate marker (if desired)
2141  if ( rotateSymbols() )
2142  {
2143  bool isRing = false;
2144  if ( points.first() == points.last() )
2145  isRing = true;
2146  double angle = markerAngle( points, isRing, vertex );
2147  setSymbolLineAngle( angle * 180 / M_PI );
2148  }
2149  mFinalVertex = points[vertex];
2150  if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2151  renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
2152  return;
2153  }
2154 
2155  int pointIncrement = distance > 0 ? 1 : -1;
2156  QPointF previousPoint = points[vertex];
2157  int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2158  int endPoint = distance > 0 ? points.count() - 1 : 0;
2159  double distanceLeft = std::fabs( distance );
2160 
2161  for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2162  {
2163  const QPointF &pt = points[i];
2164 
2165  if ( previousPoint == pt ) // must not be equal!
2166  continue;
2167 
2168  // create line segment
2169  MyLine l( previousPoint, pt );
2170 
2171  if ( distanceLeft < l.length() )
2172  {
2173  //destination point is in current segment
2174  QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2175  // rotate marker (if desired)
2176  if ( rotateSymbols() )
2177  {
2178  setSymbolLineAngle( l.angle() * 180 / M_PI );
2179  }
2180  mFinalVertex = markerPoint;
2181  if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2182  renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
2183  return;
2184  }
2185 
2186  distanceLeft -= l.length();
2187  previousPoint = pt;
2188  }
2189 
2190  //didn't find point
2191 }
2192 
2193 void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2194 {
2195  if ( p.empty() )
2196  return;
2197 
2198  QVector< QPointF > points = p;
2199  const bool closedRing = points.first() == points.last();
2200 
2201  double lengthLeft = initialOffset;
2202 
2203  double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2204  if ( initialLagLeft < 0 && closedRing )
2205  {
2206  // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2207  QPointF lastPt = points.constLast();
2208  QVector< QPointF > pseudoPoints;
2209  for ( int i = points.count() - 2; i > 0; --i )
2210  {
2211  if ( initialLagLeft >= 0 )
2212  {
2213  break;
2214  }
2215 
2216  const QPointF &pt = points[i];
2217 
2218  if ( lastPt == pt ) // must not be equal!
2219  continue;
2220 
2221  MyLine l( lastPt, pt );
2222  initialLagLeft += l.length();
2223  lastPt = pt;
2224 
2225  pseudoPoints << pt;
2226  }
2227  std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2228 
2229  points = pseudoPoints;
2230  points.append( p );
2231  }
2232  else
2233  {
2234  while ( initialLagLeft < 0 )
2235  {
2236  dest << points.constFirst();
2237  initialLagLeft += intervalPainterUnits;
2238  }
2239  }
2240  if ( initialLag > 0 )
2241  {
2242  lengthLeft += intervalPainterUnits - initialLagLeft;
2243  }
2244 
2245  QPointF lastPt = points[0];
2246  for ( int i = 1; i < points.count(); ++i )
2247  {
2248  const QPointF &pt = points[i];
2249 
2250  if ( lastPt == pt ) // must not be equal!
2251  {
2252  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2253  {
2254  lastPt = points[0];
2255  i = 0;
2256  }
2257  continue;
2258  }
2259 
2260  // for each line, find out dx and dy, and length
2261  MyLine l( lastPt, pt );
2262  QPointF diff = l.diffForInterval( intervalPainterUnits );
2263 
2264  // if there's some length left from previous line
2265  // use only the rest for the first point in new line segment
2266  double c = 1 - lengthLeft / intervalPainterUnits;
2267 
2268  lengthLeft += l.length();
2269 
2270 
2271  while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2272  {
2273  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2274  lastPt += c * diff;
2275  lengthLeft -= intervalPainterUnits;
2276  dest << lastPt;
2277  c = 1; // reset c (if wasn't 1 already)
2278  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2279  break;
2280  }
2281  lastPt = pt;
2282 
2283  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2284  break;
2285 
2286  // if a closed ring, we keep looping around the ring until we hit the required number of points
2287  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2288  {
2289  lastPt = points[0];
2290  i = 0;
2291  }
2292  }
2293 
2294  if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2295  {
2296  // pad with repeating last point to match desired size
2297  while ( dest.size() < numberPointsRequired )
2298  dest << points.constLast();
2299  }
2300 }
2301 
2302 void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2303 {
2304  if ( !points.isEmpty() )
2305  {
2306  // calc length
2307  qreal length = 0;
2308  QPolygonF::const_iterator it = points.constBegin();
2309  QPointF last = *it;
2310  for ( ++it; it != points.constEnd(); ++it )
2311  {
2312  length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2313  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2314  last = *it;
2315  }
2316  if ( qgsDoubleNear( length, 0.0 ) )
2317  return;
2318 
2319  const double midPoint = length / 2;
2320 
2321  QPointF pt;
2322  double thisSymbolAngle = 0;
2323 
2324  if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2325  {
2326  QVector< QPointF > angleStartPoints;
2327  QVector< QPointF > symbolPoints;
2328  QVector< QPointF > angleEndPoints;
2329  // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2330  collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2331  collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2332  collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2333 
2334  pt = symbolPoints.at( 1 );
2335  MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2336  thisSymbolAngle = l.angle();
2337  }
2338  else
2339  {
2340  // find the segment where the central point lies
2341  it = points.constBegin();
2342  last = *it;
2343  qreal last_at = 0, next_at = 0;
2344  QPointF next;
2345  int segment = 0;
2346  for ( ++it; it != points.constEnd(); ++it )
2347  {
2348  next = *it;
2349  next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2350  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2351  if ( next_at >= midPoint )
2352  break; // we have reached the center
2353  last = *it;
2354  last_at = next_at;
2355  segment++;
2356  }
2357 
2358  // find out the central point on segment
2359  MyLine l( last, next ); // for line angle
2360  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2361  pt = last + ( next - last ) * k;
2362  thisSymbolAngle = l.angle();
2363  }
2364 
2365  // draw the marker
2366  // rotate marker (if desired)
2367  if ( rotateSymbols() )
2368  {
2369  setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2370  }
2371 
2372  renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2373 
2374  }
2375 }
2376 
2378 {
2379  return mMarker.get();
2380 }
2381 
2383 {
2384  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2385  {
2386  delete symbol;
2387  return false;
2388  }
2389 
2390  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2391  mColor = mMarker->color();
2392  return true;
2393 }
2394 
2395 
2396 
2397 //
2398 // QgsMarkerLineSymbolLayer
2399 //
2400 
2401 QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2402  : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2403 {
2404  setSubSymbol( new QgsMarkerSymbol() );
2405 }
2406 
2408 
2410 {
2411  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2413 
2414  if ( props.contains( QStringLiteral( "interval" ) ) )
2415  interval = props[QStringLiteral( "interval" )].toDouble();
2416  if ( props.contains( QStringLiteral( "rotate" ) ) )
2417  rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2418 
2419  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2420  setCommonProperties( x.get(), props );
2421  return x.release();
2422 }
2423 
2425 {
2426  return QStringLiteral( "MarkerLine" );
2427 }
2428 
2429 void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2430 {
2431  mMarker->setColor( color );
2432  mColor = color;
2433 }
2434 
2436 {
2437  return mMarker ? mMarker->color() : mColor;
2438 }
2439 
2441 {
2442  // if being rotated, it gets initialized with every line segment
2443  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2444  if ( rotateSymbols() )
2446  mMarker->setRenderHints( hints );
2447 
2448  mMarker->startRender( context.renderContext(), context.fields() );
2449 }
2450 
2452 {
2453  mMarker->stopRender( context.renderContext() );
2454 }
2455 
2456 
2458 {
2459  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2460  copyTemplateSymbolProperties( x.get() );
2461  return x.release();
2462 }
2463 
2464 void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2465 {
2466  for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2467  {
2468  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2469  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2470  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2471  element.appendChild( symbolizerElem );
2472 
2473  // <Geometry>
2474  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2475 
2476  QString gap;
2478  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2480  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2482  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2484  // no way to get line/polygon's vertices, use a VendorOption
2485  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2486 
2488  {
2490  gap = qgsDoubleToString( interval );
2491  }
2492 
2493  if ( !rotateSymbols() )
2494  {
2495  // markers in LineSymbolizer must be drawn following the line orientation,
2496  // use a VendorOption when no marker rotation
2497  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2498  }
2499 
2500  // <Stroke>
2501  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2502  symbolizerElem.appendChild( strokeElem );
2503 
2504  // <GraphicStroke>
2505  QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2506  strokeElem.appendChild( graphicStrokeElem );
2507 
2508  QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2509  if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2510  {
2511  markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2512  }
2513  else if ( layer )
2514  {
2515  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2516  }
2517  else
2518  {
2519  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2520  }
2521 
2522  if ( !gap.isEmpty() )
2523  {
2524  QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2525  QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap );
2526  graphicStrokeElem.appendChild( gapElem );
2527  }
2528 
2529  if ( !qgsDoubleNear( mOffset, 0.0 ) )
2530  {
2531  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2533  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2534  symbolizerElem.appendChild( perpOffsetElem );
2535  }
2536  }
2537 }
2538 
2540 {
2541  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2542 
2543  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2544  if ( strokeElem.isNull() )
2545  return nullptr;
2546 
2547  QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2548  if ( graphicStrokeElem.isNull() )
2549  return nullptr;
2550 
2551  // retrieve vendor options
2552  bool rotateMarker = true;
2554 
2555  QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2556  for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2557  {
2558  if ( it.key() == QLatin1String( "placement" ) )
2559  {
2560  if ( it.value() == QLatin1String( "points" ) )
2562  else if ( it.value() == QLatin1String( "firstPoint" ) )
2564  else if ( it.value() == QLatin1String( "lastPoint" ) )
2566  else if ( it.value() == QLatin1String( "centralPoint" ) )
2568  }
2569  else if ( it.value() == QLatin1String( "rotateMarker" ) )
2570  {
2571  rotateMarker = it.value() == QLatin1String( "0" );
2572  }
2573  }
2574 
2575  std::unique_ptr< QgsMarkerSymbol > marker;
2576 
2578  if ( l )
2579  {
2580  QgsSymbolLayerList layers;
2581  layers.append( l );
2582  marker.reset( new QgsMarkerSymbol( layers ) );
2583  }
2584 
2585  if ( !marker )
2586  return nullptr;
2587 
2588  double interval = 0.0;
2589  QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2590  if ( !gapElem.isNull() )
2591  {
2592  bool ok;
2593  double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2594  if ( ok )
2595  interval = d;
2596  }
2597 
2598  double offset = 0.0;
2599  QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2600  if ( !perpOffsetElem.isNull() )
2601  {
2602  bool ok;
2603  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2604  if ( ok )
2605  offset = d;
2606  }
2607 
2608  QString uom = element.attribute( QStringLiteral( "uom" ) );
2611 
2613  x->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
2614  x->setPlacements( placement );
2615  x->setInterval( interval );
2616  x->setSubSymbol( marker.release() );
2617  x->setOffset( offset );
2618  return x;
2619 }
2620 
2622 {
2623  mMarker->setSize( width );
2624 }
2625 
2627 {
2628  if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2629  {
2630  mMarker->setDataDefinedSize( property );
2631  }
2633 }
2634 
2635 void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2636 {
2637  const double prevOpacity = mMarker->opacity();
2638  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2640  mMarker->setOpacity( prevOpacity );
2641 }
2642 
2644 {
2645  mMarker->setLineAngle( angle );
2646 }
2647 
2649 {
2650  return mMarker->angle();
2651 }
2652 
2654 {
2655  mMarker->setAngle( angle );
2656 }
2657 
2658 void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2659 {
2660  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2662 
2663  mMarker->renderPoint( point, feature, context, layer, selected );
2664 
2665  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2666 }
2667 
2669 {
2670  return mMarker->size();
2671 }
2672 
2674 {
2675  return mMarker->size( context );
2676 }
2677 
2679 {
2681  mMarker->setOutputUnit( unit );
2682  setIntervalUnit( unit );
2683  mOffsetUnit = unit;
2684  setOffsetAlongLineUnit( unit );
2685 }
2686 
2688 {
2694  || ( mMarker && mMarker->usesMapUnits() );
2695 }
2696 
2697 QSet<QString> QgsMarkerLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2698 {
2699  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2700  if ( mMarker )
2701  attr.unite( mMarker->usedAttributes( context ) );
2702  return attr;
2703 }
2704 
2706 {
2708  return true;
2709  if ( mMarker && mMarker->hasDataDefinedProperties() )
2710  return true;
2711  return false;
2712 }
2713 
2715 {
2716  return ( mMarker->size( context ) / 2.0 ) +
2718 }
2719 
2720 
2721 //
2722 // QgsHashedLineSymbolLayer
2723 //
2724 
2725 QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2726  : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2727 {
2728  setSubSymbol( new QgsLineSymbol() );
2729 }
2730 
2732 
2734 {
2735  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2737 
2738  if ( props.contains( QStringLiteral( "interval" ) ) )
2739  interval = props[QStringLiteral( "interval" )].toDouble();
2740  if ( props.contains( QStringLiteral( "rotate" ) ) )
2741  rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2742 
2743  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2744  setCommonProperties( x.get(), props );
2745  if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2746  {
2747  x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2748  }
2749 
2750  if ( props.contains( QStringLiteral( "hash_length" ) ) )
2751  x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2752 
2753  if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2754  x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2755 
2756  if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2757  x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2758 
2759  return x.release();
2760 }
2761 
2763 {
2764  return QStringLiteral( "HashLine" );
2765 }
2766 
2768 {
2769  // if being rotated, it gets initialized with every line segment
2770  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2771  if ( rotateSymbols() )
2773  mHashSymbol->setRenderHints( hints );
2774 
2775  mHashSymbol->startRender( context.renderContext(), context.fields() );
2776 }
2777 
2779 {
2780  mHashSymbol->stopRender( context.renderContext() );
2781 }
2782 
2784 {
2785  QVariantMap map = QgsTemplatedLineSymbolLayerBase::properties();
2786  map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2787 
2788  map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2789  map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2790  map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2791 
2792  return map;
2793 }
2794 
2796 {
2797  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2798  copyTemplateSymbolProperties( x.get() );
2799  x->setHashAngle( mHashAngle );
2800  x->setHashLength( mHashLength );
2801  x->setHashLengthUnit( mHashLengthUnit );
2802  x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2803  return x.release();
2804 }
2805 
2806 void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2807 {
2808  mHashSymbol->setColor( color );
2809  mColor = color;
2810 }
2811 
2813 {
2814  return mHashSymbol ? mHashSymbol->color() : mColor;
2815 }
2816 
2818 {
2819  return mHashSymbol.get();
2820 }
2821 
2823 {
2824  if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2825  {
2826  delete symbol;
2827  return false;
2828  }
2829 
2830  mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2831  mColor = mHashSymbol->color();
2832  return true;
2833 }
2834 
2835 void QgsHashedLineSymbolLayer::setWidth( const double width )
2836 {
2837  mHashLength = width;
2838 }
2839 
2841 {
2842  return mHashLength;
2843 }
2844 
2846 {
2847  return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2848 }
2849 
2851 {
2852  return ( mHashSymbol->width( context ) / 2.0 )
2853  + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2854  + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
2855 }
2856 
2858 {
2860  mHashSymbol->setOutputUnit( unit );
2861  setIntervalUnit( unit );
2862  mOffsetUnit = unit;
2863  setOffsetAlongLineUnit( unit );
2864 }
2865 
2866 QSet<QString> QgsHashedLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2867 {
2868  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2869  if ( mHashSymbol )
2870  attr.unite( mHashSymbol->usedAttributes( context ) );
2871  return attr;
2872 }
2873 
2875 {
2877  return true;
2878  if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2879  return true;
2880  return false;
2881 }
2882 
2884 {
2885  if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2886  {
2887  mHashSymbol->setDataDefinedWidth( property );
2888  }
2890 }
2891 
2893 {
2894  return mHashLengthUnit == QgsUnitTypes::RenderMapUnits || mHashLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
2900  || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2901 }
2902 
2904 {
2905  mSymbolLineAngle = angle;
2906 }
2907 
2909 {
2910  return mSymbolAngle;
2911 }
2912 
2914 {
2915  mSymbolAngle = angle;
2916 }
2917 
2918 void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2919 {
2920  double lineLength = mHashLength;
2922  {
2923  context.expressionContext().setOriginalValueVariable( mHashLength );
2925  }
2926  const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2927 
2928  double hashAngle = mHashAngle;
2930  {
2931  context.expressionContext().setOriginalValueVariable( mHashAngle );
2933  }
2934 
2935  QgsPointXY center( point );
2936  QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2937  QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2938 
2939  QPolygonF points;
2940  points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2941 
2942  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2944 
2945  mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2946 
2947  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2948 }
2949 
2951 {
2952  return mHashAngle;
2953 }
2954 
2956 {
2957  mHashAngle = angle;
2958 }
2959 
2960 void QgsHashedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2961 {
2962  const double prevOpacity = mHashSymbol->opacity();
2963  mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2965  mHashSymbol->setOpacity( prevOpacity );
2966 }
2967 
2968 //
2969 // QgsAbstractBrushedLineSymbolLayer
2970 //
2971 
2972 void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2973 {
2974  if ( !context.renderContext().painter() )
2975  return;
2976 
2977  double offset = mOffset;
2979  {
2980  context.setOriginalValueVariable( offset );
2982  }
2983 
2984  QPolygonF offsetPoints;
2985  if ( qgsDoubleNear( offset, 0 ) )
2986  {
2987  renderLine( points, context, patternThickness, patternLength, brush );
2988  }
2989  else
2990  {
2991  const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
2992 
2993  const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
2994  for ( const QPolygonF &part : offsetLine )
2995  {
2996  renderLine( part, context, patternThickness, patternLength, brush );
2997  }
2998  }
2999 }
3000 
3001 void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3002  const double patternLength, const QBrush &sourceBrush )
3003 {
3004  QPainter *p = context.renderContext().painter();
3005  if ( !p )
3006  return;
3007 
3008  QBrush brush = sourceBrush;
3009 
3010  // duplicate points mess up the calculations, we need to remove them first
3011  // we'll calculate the min/max coordinate at the same time, since we're already looping
3012  // through the points
3013  QPolygonF inputPoints;
3014  inputPoints.reserve( points.size() );
3015  QPointF prev;
3016  double minX = std::numeric_limits< double >::max();
3017  double minY = std::numeric_limits< double >::max();
3018  double maxX = std::numeric_limits< double >::lowest();
3019  double maxY = std::numeric_limits< double >::lowest();
3020 
3021  for ( const QPointF &pt : std::as_const( points ) )
3022  {
3023  if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3024  continue;
3025 
3026  inputPoints << pt;
3027  prev = pt;
3028  minX = std::min( minX, pt.x() );
3029  minY = std::min( minY, pt.y() );
3030  maxX = std::max( maxX, pt.x() );
3031  maxY = std::max( maxY, pt.y() );
3032  }
3033 
3034  if ( inputPoints.size() < 2 ) // nothing to render
3035  return;
3036 
3037  // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3038  constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3039  // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3040  constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3041 
3042  // 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
3043  const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3044  const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3045 
3046  const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3047  && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3048 
3049  QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3050  if ( temporaryImage.isNull() )
3051  {
3052  QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3053  return;
3054  }
3055 
3056  // clear temporary image contents
3057  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3058  return;
3059  temporaryImage.fill( Qt::transparent );
3060  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3061  return;
3062 
3063  Qt::PenJoinStyle join = mPenJoinStyle;
3065  {
3068  if ( !exprVal.isNull() )
3069  join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3070  }
3071 
3072  Qt::PenCapStyle cap = mPenCapStyle;
3074  {
3077  if ( !exprVal.isNull() )
3078  cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3079  }
3080 
3081  // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3082  QPainterPathStroker stroker;
3083  stroker.setWidth( lineThickness );
3084  stroker.setCapStyle( cap );
3085  stroker.setJoinStyle( join );
3086 
3087  QPainterPath path;
3088  path.addPolygon( inputPoints );
3089  const QPainterPath stroke = stroker.createStroke( path ).simplified();
3090 
3091  // prepare temporary image
3092  QPainter imagePainter;
3093  imagePainter.begin( &temporaryImage );
3094  context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3095  imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3096 
3097  imagePainter.setClipPath( stroke, Qt::IntersectClip );
3098  imagePainter.setPen( Qt::NoPen );
3099 
3100  QPointF segmentStartPoint = inputPoints.at( 0 );
3101 
3102  // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3103  double progressThroughImage = 0;
3104 
3105  QgsPoint prevSegmentPolygonEndLeft;
3106  QgsPoint prevSegmentPolygonEndRight;
3107 
3108  // for closed rings this will store the left/right polygon points of the start/end of the line
3109  QgsPoint startLinePolygonLeft;
3110  QgsPoint startLinePolygonRight;
3111 
3112  for ( int i = 1; i < inputPoints.size(); ++i )
3113  {
3114  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3115  break;
3116 
3117  const QPointF segmentEndPoint = inputPoints.at( i );
3118  const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3119  segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3120 
3121  // left/right end points of the current segment polygon
3122  QgsPoint thisSegmentPolygonEndLeft;
3123  QgsPoint thisSegmentPolygonEndRight;
3124  // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3125  QgsPoint thisSegmentPolygonEndLeftForPainter;
3126  QgsPoint thisSegmentPolygonEndRightForPainter;
3127  if ( i == 1 )
3128  {
3129  // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3130  // (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.)
3131  if ( isClosedLine )
3132  {
3133  // project the current segment out by half the image thickness to either side of the line
3134  const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3135  const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3136  const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3137  const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3138 
3139  // 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
3140  // what angle the current segment polygon should START on.
3141  const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3142  segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3143 
3144  // project out the LAST segment in the line by half the image thickness to either side of the line
3145  const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3146  const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3147  const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3148  const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3149 
3150  // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3151  // 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
3152  // join)
3153  QgsPoint intersectionPoint;
3154  bool isIntersection = false;
3155  QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3156  if ( !isIntersection )
3157  prevSegmentPolygonEndLeft = startPointLeft;
3158  isIntersection = false;
3159  QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3160  if ( !isIntersection )
3161  prevSegmentPolygonEndRight = startPointRight;
3162 
3163  startLinePolygonLeft = prevSegmentPolygonEndLeft;
3164  startLinePolygonRight = prevSegmentPolygonEndRight;
3165  }
3166  else
3167  {
3168  prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3169  if ( cap != Qt::PenCapStyle::FlatCap )
3170  prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3171  prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3172  if ( cap != Qt::PenCapStyle::FlatCap )
3173  prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3174  }
3175  }
3176 
3177  if ( i < inputPoints.size() - 1 )
3178  {
3179  // for all other segments except the last
3180 
3181  // project the current segment out by half the image thickness to either side of the line
3182  const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3183  const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3184  const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3185  const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3186 
3187  // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3188  // what angle the current segment polygon should end on
3189  const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3190  inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3191 
3192  // project out the next segment by half the image thickness to either side of the line
3193  const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3194  const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3195  const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3196  const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3197 
3198  // the polygon representing the current segment ends at the points where the projected lines to the left/right
3199  // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3200  // join)
3201  QgsPoint intersectionPoint;
3202  bool isIntersection = false;
3203  QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3204  if ( !isIntersection )
3205  thisSegmentPolygonEndLeft = endPointLeft;
3206  isIntersection = false;
3207  QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3208  if ( !isIntersection )
3209  thisSegmentPolygonEndRight = endPointRight;
3210 
3211  thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3212  thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3213  }
3214  else
3215  {
3216  // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3217  // unless it's a closed line
3218  if ( isClosedLine )
3219  {
3220  thisSegmentPolygonEndLeft = startLinePolygonLeft;
3221  thisSegmentPolygonEndRight = startLinePolygonRight;
3222 
3223  thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3224  thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3225  }
3226  else
3227  {
3228  thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3229  if ( cap != Qt::PenCapStyle::FlatCap )
3230  thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3231  thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3232  if ( cap != Qt::PenCapStyle::FlatCap )
3233  thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3234 
3235  thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3236  thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3237  }
3238  }
3239 
3240  // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3241  // where we got with the previous segment), at the correct angle
3242  QTransform brushTransform;
3243  brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3244  brushTransform.rotate( -segmentAngleDegrees );
3245  if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3246  {
3247  // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3248  // we need to also do the same for the brush transform
3249  brushTransform.translate( -( lineThickness / 2 ), 0 );
3250  }
3251  brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3252 
3253  brush.setTransform( brushTransform );
3254  imagePainter.setBrush( brush );
3255 
3256  // now draw the segment polygon
3257  imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3258  << thisSegmentPolygonEndLeftForPainter.toQPointF()
3259  << thisSegmentPolygonEndRightForPainter.toQPointF()
3260  << prevSegmentPolygonEndRight.toQPointF()
3261  << prevSegmentPolygonEndLeft.toQPointF() );
3262 
3263 #if 0 // for debugging, will draw the segment polygons
3264  imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3265  imagePainter.setBrush( Qt::NoBrush );
3266  imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3267  << thisSegmentPolygonEndLeftForPainter.toQPointF()
3268  << thisSegmentPolygonEndRightForPainter.toQPointF()
3269  << prevSegmentPolygonEndRight.toQPointF()
3270  << prevSegmentPolygonEndLeft.toQPointF() );
3271  imagePainter.setPen( Qt::NoPen );
3272 #endif
3273 
3274  // calculate the new progress horizontal through the source image to account for the length
3275  // of the segment we've just drawn
3276  progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3277  + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3278  + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3279  progressThroughImage = fmod( progressThroughImage, patternLength );
3280 
3281  // shuffle buffered variables for next loop
3282  segmentStartPoint = segmentEndPoint;
3283  prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3284  prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3285  }
3286  imagePainter.end();
3287 
3288  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3289  return;
3290 
3291  // lastly, draw the temporary image onto the destination painter at the correct place
3292  p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3293  minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3294 }
3295 
3296 
3297 //
3298 // QgsRasterLineSymbolLayer
3299 //
3300 
3302  : mPath( path )
3303 {
3304 }
3305 
3307 
3308 QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3309 {
3310  std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3311 
3312  if ( properties.contains( QStringLiteral( "line_width" ) ) )
3313  {
3314  res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3315  }
3316  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3317  {
3318  res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3319  }
3320  if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3321  {
3322  res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3323  }
3324 
3325  if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3326  res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3327 
3328  if ( properties.contains( QStringLiteral( "offset" ) ) )
3329  {
3330  res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3331  }
3332  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3333  {
3334  res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3335  }
3336  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3337  {
3338  res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3339  }
3340 
3341  if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3342  res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3343  if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3344  res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3345 
3346  if ( properties.contains( QStringLiteral( "alpha" ) ) )
3347  {
3348  res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3349  }
3350 
3351  return res.release();
3352 }
3353 
3354 
3356 {
3357  QVariantMap map;
3358  map[QStringLiteral( "imageFile" )] = mPath;
3359 
3360  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3361  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3362  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3363 
3364  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3365  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3366 
3367  map[QStringLiteral( "offset" )] = QString::number( mOffset );
3368  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3369  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3370 
3371  map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3372 
3373  return map;
3374 }
3375 
3377 {
3378  std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3379  res->setWidth( mWidth );
3380  res->setWidthUnit( mWidthUnit );
3381  res->setWidthMapUnitScale( mWidthMapUnitScale );
3382  res->setPenJoinStyle( mPenJoinStyle );
3383  res->setPenCapStyle( mPenCapStyle );
3384  res->setOffsetUnit( mOffsetUnit );
3385  res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3386  res->setOffset( mOffset );
3387  res->setOpacity( mOpacity );
3388  copyDataDefinedProperties( res.get() );
3389  copyPaintEffect( res.get() );
3390  return res.release();
3391 }
3392 
3393 void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3394 {
3395  const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3396  if ( it != properties.end() && it.value().type() == QVariant::String )
3397  {
3398  if ( saving )
3399  it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3400  else
3401  it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3402  }
3403 }
3404 
3405 void QgsRasterLineSymbolLayer::setPath( const QString &path )
3406 {
3407  mPath = path;
3408 }
3409 
3411 {
3412  return QStringLiteral( "RasterLine" );
3413 }
3414 
3416 {
3417  double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3418 
3420 
3421  double opacity = mOpacity * context.opacity();
3422  bool cached = false;
3424  QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3425  static_cast< int >( std::ceil( scaledHeight ) ) ),
3426  true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3427 }
3428 
3430 {
3431 }
3432 
3433 void QgsRasterLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
3434 {
3435  if ( !context.renderContext().painter() )
3436  return;
3437 
3438  QImage sourceImage = mLineImage;
3442  {
3443  QString path = mPath;
3445  {
3446  context.setOriginalValueVariable( path );
3448  }
3449 
3450  double strokeWidth = mWidth;
3452  {
3453  context.setOriginalValueVariable( strokeWidth );
3455  }
3456  const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3457 
3458  const QSize originalSize = QgsApplication::imageCache()->originalSize( path, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3459  double opacity = mOpacity;
3461  {
3464  }
3465  opacity *= context.opacity();
3466 
3467  bool cached = false;
3468  sourceImage = QgsApplication::imageCache()->pathAsImage( path,
3469  QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3470  static_cast< int >( std::ceil( scaledHeight ) ) ),
3471  true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3472  }
3473 
3474  if ( context.selected() )
3475  {
3476  QgsImageOperation::adjustHueSaturation( sourceImage, 1.0, context.renderContext().selectionColor(), 1.0, context.renderContext().feedback() );
3477  }
3478 
3479  const QBrush brush( sourceImage );
3480 
3481  renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3482 }
3483 
3485 {
3487  mWidthUnit = unit;
3488  mOffsetUnit = unit;
3489 }
3490 
3492 {
3494  if ( mWidthUnit != unit || mOffsetUnit != unit )
3495  {
3497  }
3498  return unit;
3499 }
3500 
3502 {
3505 }
3506 
3508 {
3510  mOffsetMapUnitScale = scale;
3511 }
3512 
3514 {
3517  {
3518  return mWidthMapUnitScale;
3519  }
3520  return QgsMapUnitScale();
3521 }
3522 
3524 {
3525  return ( mWidth / 2.0 ) + mOffset;
3526 }
3527 
3529 {
3530  return QColor();
3531 }
3532 
3533 
3534 //
3535 // QgsLineburstSymbolLayer
3536 //
3537 
3538 QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3540  , mColor2( color2 )
3541 {
3542  setColor( color );
3543 }
3544 
3546 
3547 QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3548 {
3549  std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3550 
3551  if ( properties.contains( QStringLiteral( "line_width" ) ) )
3552  {
3553  res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3554  }
3555  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3556  {
3557  res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3558  }
3559  if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3560  {
3561  res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3562  }
3563 
3564  if ( properties.contains( QStringLiteral( "offset" ) ) )
3565  {
3566  res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3567  }
3568  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3569  {
3570  res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3571  }
3572  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3573  {
3574  res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3575  }
3576 
3577  if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3578  res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3579  if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3580  res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3581 
3582  if ( properties.contains( QStringLiteral( "color_type" ) ) )
3583  res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3584 
3585  if ( properties.contains( QStringLiteral( "color" ) ) )
3586  {
3587  res->setColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
3588  }
3589  if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3590  {
3591  res->setColor2( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3592  }
3593 
3594  //attempt to create color ramp from props
3595  if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3596  {
3597  res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3598  }
3599  else
3600  {
3601  res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3602  }
3603 
3604  return res.release();
3605 }
3606 
3608 {
3609  QVariantMap map;
3610 
3611  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3612  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3613  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3614 
3615  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3616  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3617 
3618  map[QStringLiteral( "offset" )] = QString::number( mOffset );
3619  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3620  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3621 
3622  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3623  map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
3624  map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3625  if ( mGradientRamp )
3626  {
3627 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3628  map.unite( mGradientRamp->properties() );
3629 #else
3630  map.insert( mGradientRamp->properties() );
3631 #endif
3632  }
3633 
3634  return map;
3635 }
3636 
3638 {
3639  std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3640  res->setWidth( mWidth );
3641  res->setWidthUnit( mWidthUnit );
3642  res->setWidthMapUnitScale( mWidthMapUnitScale );
3643  res->setPenJoinStyle( mPenJoinStyle );
3644  res->setPenCapStyle( mPenCapStyle );
3645  res->setOffsetUnit( mOffsetUnit );
3646  res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3647  res->setOffset( mOffset );
3648  res->setColor( mColor );
3649  res->setColor2( mColor2 );
3650  res->setGradientColorType( mGradientColorType );
3651  if ( mGradientRamp )
3652  res->setColorRamp( mGradientRamp->clone() );
3653  copyDataDefinedProperties( res.get() );
3654  copyPaintEffect( res.get() );
3655  return res.release();
3656 }
3657 
3659 {
3660  return QStringLiteral( "Lineburst" );
3661 }
3662 
3664 {
3665 }
3666 
3668 {
3669 }
3670 
3671 void QgsLineburstSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
3672 {
3673  if ( !context.renderContext().painter() )
3674  return;
3675 
3676  double strokeWidth = mWidth;
3678  {
3679  context.setOriginalValueVariable( strokeWidth );
3681  }
3682  const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3683 
3684  //update alpha of gradient colors
3685  QColor color1 = mColor;
3687  {
3690  }
3691  if ( context.selected() )
3692  {
3693  color1 = context.renderContext().selectionColor();
3694  }
3695  color1.setAlphaF( context.opacity() * color1.alphaF() );
3696 
3697  //second gradient color
3698  QColor color2 = mColor2;
3700  {
3703  }
3704 
3705  //create a QGradient with the desired properties
3706  QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3707  //add stops to gradient
3710  {
3711  //color ramp gradient
3712  QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3713  gradRamp->addStopsToGradient( &gradient, context.opacity() );
3714  }
3715  else
3716  {
3717  //two color gradient
3718  gradient.setColorAt( 0.0, color1 );
3719  gradient.setColorAt( 1.0, color2 );
3720  }
3721  const QBrush brush( gradient );
3722 
3723  renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3724 }
3725 
3727 {
3729  mWidthUnit = unit;
3730  mOffsetUnit = unit;
3731 }
3732 
3734 {
3736  if ( mWidthUnit != unit || mOffsetUnit != unit )
3737  {
3739  }
3740  return unit;
3741 }
3742 
3744 {
3747 }
3748 
3750 {
3752  mOffsetMapUnitScale = scale;
3753 }
3754 
3756 {
3759  {
3760  return mWidthMapUnitScale;
3761  }
3762  return QgsMapUnitScale();
3763 }
3764 
3766 {
3767  return ( mWidth / 2.0 ) + mOffset;
3768 }
3769 
3771 {
3772  return QColor();
3773 }
3774 
3776 {
3777  return mGradientRamp.get();
3778 }
3779 
3781 {
3782  mGradientRamp.reset( ramp );
3783 }
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition: qgis.h:1247
@ 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:1269
@ 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 adjustHueSaturation(QImage &image, double saturation, const QColor &colorizeColor=QColor(), double colorizeStrength=1.0, QgsFeedback *feedback=nullptr)
Alter the hue or saturation of a QImage.
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 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 double sizeInPixelsFromSldUom(const QString &uom, double size)
Returns the size scaled in pixels according to the uom attribute.
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 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:1942
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition: qgis.h:2223
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:2245
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1990
QMap< QString, QString > QgsStringMap
Definition: qgis.h:2506
#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