QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 
32 #include <algorithm>
33 #include <QPainter>
34 #include <QDomDocument>
35 #include <QDomElement>
36 
37 #include <cmath>
38 
39 QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
40  : mPenStyle( penStyle )
41 {
42  mColor = color;
43  mWidth = width;
44  mCustomDashVector << 5 << 2;
45 }
46 
48 
50 {
52  mWidthUnit = unit;
53  mOffsetUnit = unit;
54  mCustomDashPatternUnit = unit;
55 }
56 
58 {
60  if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
61  {
63  }
64  return unit;
65 }
66 
68 {
71 }
72 
74 {
76  mWidthMapUnitScale = scale;
77  mOffsetMapUnitScale = scale;
78  mCustomDashPatternMapUnitScale = scale;
79 }
80 
82 {
85  mOffsetMapUnitScale == mCustomDashPatternMapUnitScale )
86  {
87  return mWidthMapUnitScale;
88  }
89  return QgsMapUnitScale();
90 }
91 
93 {
97 
98  if ( props.contains( QStringLiteral( "line_color" ) ) )
99  {
100  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
101  }
102  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
103  {
104  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
105  }
106  else if ( props.contains( QStringLiteral( "color" ) ) )
107  {
108  //pre 2.5 projects used "color"
109  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
110  }
111  if ( props.contains( QStringLiteral( "line_width" ) ) )
112  {
113  width = props[QStringLiteral( "line_width" )].toDouble();
114  }
115  else if ( props.contains( QStringLiteral( "outline_width" ) ) )
116  {
117  width = props[QStringLiteral( "outline_width" )].toDouble();
118  }
119  else if ( props.contains( QStringLiteral( "width" ) ) )
120  {
121  //pre 2.5 projects used "width"
122  width = props[QStringLiteral( "width" )].toDouble();
123  }
124  if ( props.contains( QStringLiteral( "line_style" ) ) )
125  {
126  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
127  }
128  else if ( props.contains( QStringLiteral( "outline_style" ) ) )
129  {
130  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
131  }
132  else if ( props.contains( QStringLiteral( "penstyle" ) ) )
133  {
134  penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
135  }
136 
138  if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
139  {
140  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
141  }
142  else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
143  {
144  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
145  }
146  else if ( props.contains( QStringLiteral( "width_unit" ) ) )
147  {
148  //pre 2.5 projects used "width_unit"
149  l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
150  }
151  if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
152  l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
153  if ( props.contains( QStringLiteral( "offset" ) ) )
154  l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
155  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
156  l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
157  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
158  l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
159  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
160  l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
161  if ( props.contains( QStringLiteral( "capstyle" ) ) )
162  l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
163 
164  if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
165  {
166  l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
167  }
168  if ( props.contains( QStringLiteral( "customdash" ) ) )
169  {
170  l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
171  }
172  if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
173  {
174  l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
175  }
176  if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
177  {
178  l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
179  }
180 
181  if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
182  {
183  l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
184  }
185 
186  if ( props.contains( QStringLiteral( "ring_filter" ) ) )
187  {
188  l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
189  }
190 
191  if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
192  l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
193  if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
194  l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
195  if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
196  l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
197 
198  if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
199  l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
200  if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
201  l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
202  if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
203  l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
204  if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
205  l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
206  if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
207  l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
208  if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
209  l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
210 
211  if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
212  l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
213 
214  if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
215  l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
216 
218 
219  return l;
220 }
221 
223 {
224  return QStringLiteral( "SimpleLine" );
225 }
226 
228 {
229  QColor penColor = mColor;
230  penColor.setAlphaF( mColor.alphaF() * context.opacity() );
231  mPen.setColor( penColor );
232  double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
233  mPen.setWidthF( scaledWidth );
234 
235  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
236  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
237  const double dashWidthDiv = std::max( 1.0, scaledWidth );
238  if ( mUseCustomDashPattern )
239  {
240  mPen.setStyle( Qt::CustomDashLine );
241 
242  //scale pattern vector
243 
244  QVector<qreal> scaledVector;
245  QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
246  for ( ; it != mCustomDashVector.constEnd(); ++it )
247  {
248  //the dash is specified in terms of pen widths, therefore the division
249  scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
250  }
251  mPen.setDashPattern( scaledVector );
252  }
253  else
254  {
255  mPen.setStyle( mPenStyle );
256  }
257 
258  if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
259  {
260  mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
261  }
262 
263  mPen.setJoinStyle( mPenJoinStyle );
264  mPen.setCapStyle( mPenCapStyle );
265 
266  mSelPen = mPen;
267  QColor selColor = context.renderContext().selectionColor();
268  if ( ! SELECTION_IS_OPAQUE )
269  selColor.setAlphaF( context.opacity() );
270  mSelPen.setColor( selColor );
271 }
272 
274 {
275  Q_UNUSED( context )
276 }
277 
278 void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
279 {
280  QPainter *p = context.renderContext().painter();
281  if ( !p )
282  {
283  return;
284  }
285 
286  QgsExpressionContextScope *scope = nullptr;
287  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
288  if ( hasDataDefinedProperties() )
289  {
290  scope = new QgsExpressionContextScope();
291  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
292  }
293 
294  if ( mDrawInsidePolygon )
295  p->save();
296 
297  switch ( mRingFilter )
298  {
299  case AllRings:
300  case ExteriorRingOnly:
301  {
302  if ( mDrawInsidePolygon )
303  {
304  //only drawing the line on the interior of the polygon, so set clip path for painter
305  QPainterPath clipPath;
306  clipPath.addPolygon( points );
307 
308  if ( rings )
309  {
310  //add polygon rings
311  for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
312  {
313  QPolygonF ring = *it;
314  clipPath.addPolygon( ring );
315  }
316  }
317 
318  //use intersect mode, as a clip path may already exist (e.g., for composer maps)
319  p->setClipPath( clipPath, Qt::IntersectClip );
320  }
321 
322  if ( scope )
324 
325  renderPolyline( points, context );
326  }
327  break;
328 
329  case InteriorRingsOnly:
330  break;
331  }
332 
333  if ( rings )
334  {
335  switch ( mRingFilter )
336  {
337  case AllRings:
338  case InteriorRingsOnly:
339  {
340  mOffset = -mOffset; // invert the offset for rings!
341  int ringIndex = 1;
342  for ( const QPolygonF &ring : std::as_const( *rings ) )
343  {
344  if ( scope )
346 
347  renderPolyline( ring, context );
348  ringIndex++;
349  }
350  mOffset = -mOffset;
351  }
352  break;
353  case ExteriorRingOnly:
354  break;
355  }
356  }
357 
358  if ( mDrawInsidePolygon )
359  {
360  //restore painter to reset clip path
361  p->restore();
362  }
363 
364 }
365 
367 {
368  QPainter *p = context.renderContext().painter();
369  if ( !p )
370  {
371  return;
372  }
373 
374  QPolygonF points = pts;
375 
376  double startTrim = mTrimDistanceStart;
378  {
379  context.setOriginalValueVariable( startTrim );
381  }
382  double endTrim = mTrimDistanceEnd;
384  {
385  context.setOriginalValueVariable( endTrim );
387  }
388 
389  double totalLength = -1;
390  if ( mTrimDistanceStartUnit == QgsUnitTypes::RenderPercentage )
391  {
392  totalLength = QgsSymbolLayerUtils::polylineLength( points );
393  startTrim = startTrim * 0.01 * totalLength;
394  }
395  else
396  {
397  startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
398  }
399  if ( mTrimDistanceEndUnit == QgsUnitTypes::RenderPercentage )
400  {
401  if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
402  totalLength = QgsSymbolLayerUtils::polylineLength( points );
403  endTrim = endTrim * 0.01 * totalLength;
404  }
405  else
406  {
407  endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
408  }
409  if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
410  {
411  points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
412  }
413 
414  QColor penColor = mColor;
415  penColor.setAlphaF( mColor.alphaF() * context.opacity() );
416  mPen.setColor( penColor );
417 
418  double offset = mOffset;
419  applyDataDefinedSymbology( context, mPen, mSelPen, offset );
420 
421  const QPen pen = context.selected() ? mSelPen : mPen;
422 
423  if ( !pen.dashPattern().isEmpty() )
424  {
425  // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
426  const QVector<double> pattern = pen.dashPattern();
427  bool foundNonNull = false;
428  for ( int i = 0; i < pattern.size(); ++i )
429  {
430  if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
431  {
432  foundNonNull = true;
433  break;
434  }
435  }
436  if ( !foundNonNull )
437  return;
438  }
439 
440  p->setBrush( Qt::NoBrush );
441 
442  // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
443  std::unique_ptr< QgsScopedQPainterState > painterState;
444  if ( points.size() <= 2 &&
447  ( p->renderHints() & QPainter::Antialiasing ) )
448  {
449  painterState = std::make_unique< QgsScopedQPainterState >( p );
450  p->setRenderHint( QPainter::Antialiasing, false );
451  }
452 
453  const bool applyPatternTweaks = mAlignDashPattern
454  && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
455  && pen.dashOffset() == 0;
456 
457  if ( qgsDoubleNear( offset, 0 ) )
458  {
459  if ( applyPatternTweaks )
460  {
461  drawPathWithDashPatternTweaks( p, points, pen );
462  }
463  else
464  {
465  p->setPen( pen );
466  QPainterPath path;
467  path.addPolygon( points );
468  p->drawPath( path );
469  }
470  }
471  else
472  {
473  double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
475  {
476  // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
477  // and clamp it to a reasonable range. It's the best we can do in this situation!
478  scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
479  }
480 
481  QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
482  for ( const QPolygonF &part : mline )
483  {
484  if ( applyPatternTweaks )
485  {
486  drawPathWithDashPatternTweaks( p, part, pen );
487  }
488  else
489  {
490  p->setPen( pen );
491  QPainterPath path;
492  path.addPolygon( part );
493  p->drawPath( path );
494  }
495  }
496  }
497 }
498 
500 {
501  QVariantMap map;
502  map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
503  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
504  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
505  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
506  map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
507  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
508  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
509  map[QStringLiteral( "offset" )] = QString::number( mOffset );
510  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
511  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
512  map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
513  map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
514  map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
515  map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
516  map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
517  map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
518  map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
519  map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
520  map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
521  map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
522  map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
523  map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
524  map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
525  map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
526  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
527  map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
528  map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
529  return map;
530 }
531 
533 {
535  l->setWidthUnit( mWidthUnit );
539  l->setCustomDashPatternUnit( mCustomDashPatternUnit );
540  l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
541  l->setOffset( mOffset );
542  l->setPenJoinStyle( mPenJoinStyle );
543  l->setPenCapStyle( mPenCapStyle );
544  l->setUseCustomDashPattern( mUseCustomDashPattern );
545  l->setCustomDashVector( mCustomDashVector );
546  l->setDrawInsidePolygon( mDrawInsidePolygon );
548  l->setDashPatternOffset( mDashPatternOffset );
549  l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
550  l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
551  l->setTrimDistanceStart( mTrimDistanceStart );
552  l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
553  l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
554  l->setTrimDistanceEnd( mTrimDistanceEnd );
555  l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
556  l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
557  l->setAlignDashPattern( mAlignDashPattern );
558  l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
559 
561  copyPaintEffect( l );
562  return l;
563 }
564 
565 void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
566 {
567  if ( mPenStyle == Qt::NoPen )
568  return;
569 
570  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
571  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
572  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
573  element.appendChild( symbolizerElem );
574 
575  // <Geometry>
576  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
577 
578  // <Stroke>
579  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
580  symbolizerElem.appendChild( strokeElem );
581 
582  Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
584  QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
586  &mPenJoinStyle, &mPenCapStyle, &customDashVector );
587 
588  // <se:PerpendicularOffset>
589  if ( !qgsDoubleNear( mOffset, 0.0 ) )
590  {
591  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
593  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
594  symbolizerElem.appendChild( perpOffsetElem );
595  }
596 }
597 
598 QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
599 {
600  if ( mUseCustomDashPattern )
601  {
602  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
603  mPen.color(), mPenJoinStyle,
604  mPenCapStyle, mOffset, &mCustomDashVector );
605  }
606  else
607  {
608  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
609  mPenCapStyle, mOffset );
610  }
611 }
612 
614 {
615  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
616 
617  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
618  if ( strokeElem.isNull() )
619  return nullptr;
620 
621  Qt::PenStyle penStyle;
622  QColor color;
623  double width;
624  Qt::PenJoinStyle penJoinStyle;
625  Qt::PenCapStyle penCapStyle;
626  QVector<qreal> customDashVector;
627 
628  if ( !QgsSymbolLayerUtils::lineFromSld( strokeElem, penStyle,
629  color, width,
631  &customDashVector ) )
632  return nullptr;
633 
634  double offset = 0.0;
635  QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
636  if ( !perpOffsetElem.isNull() )
637  {
638  bool ok;
639  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
640  if ( ok )
641  offset = d;
642  }
643 
644  QString uom = element.attribute( QStringLiteral( "uom" ) );
647 
649  l->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
650  l->setOffset( offset );
653  l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
655  return l;
656 }
657 
658 void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
659 {
660  if ( !dataDefinedProperties().hasActiveProperties() )
661  return; // shortcut
662 
663  //data defined properties
664  bool hasStrokeWidthExpression = false;
666  {
667  context.setOriginalValueVariable( mWidth );
668  double scaledWidth = context.renderContext().convertToPainterUnits(
671  pen.setWidthF( scaledWidth );
672  selPen.setWidthF( scaledWidth );
673  hasStrokeWidthExpression = true;
674  }
675 
676  //color
678  {
680 
682  penColor.setAlphaF( context.opacity() * penColor.alphaF() );
683  pen.setColor( penColor );
684  }
685 
686  //offset
688  {
691  }
692 
693  //dash dot vector
694 
695  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
696  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
697  const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
698 
700  {
701  QVector<qreal> dashVector;
703  if ( !exprVal.isNull() )
704  {
705  QStringList dashList = exprVal.toString().split( ';' );
706  QStringList::const_iterator dashIt = dashList.constBegin();
707  for ( ; dashIt != dashList.constEnd(); ++dashIt )
708  {
709  dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
710  }
711  pen.setDashPattern( dashVector );
712  }
713  }
714  else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
715  {
716  //re-scale pattern vector after data defined pen width was applied
717 
718  QVector<qreal> scaledVector;
719  for ( double v : std::as_const( mCustomDashVector ) )
720  {
721  //the dash is specified in terms of pen widths, therefore the division
722  scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
723  }
724  mPen.setDashPattern( scaledVector );
725  }
726 
727  // dash pattern offset
728  double patternOffset = mDashPatternOffset;
729  if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
730  {
731  context.setOriginalValueVariable( patternOffset );
733  pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
734  }
735 
736  //line style
738  {
741  if ( !exprVal.isNull() )
742  pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
743  }
744 
745  //join style
747  {
750  if ( !exprVal.isNull() )
751  pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
752  }
753 
754  //cap style
756  {
759  if ( !exprVal.isNull() )
760  pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
761  }
762 }
763 
764 void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
765 {
766  if ( pen.dashPattern().empty() || points.size() < 2 )
767  return;
768 
769  QVector< qreal > sourcePattern = pen.dashPattern();
770  const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
771  // back to painter units
772  for ( int i = 0; i < sourcePattern.size(); ++ i )
773  sourcePattern[i] *= pen.widthF();
774 
775  if ( pen.widthF() <= 1.0 )
776  pen.setWidthF( 1.0001 );
777 
778  QVector< qreal > buffer;
779  QPolygonF bufferedPoints;
780  QPolygonF previousSegmentBuffer;
781  // we iterate through the line points, building a custom dash pattern and adding it to the buffer
782  // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
783  // and then append the buffer to the output pattern.
784 
785  auto ptIt = points.constBegin();
786  double totalBufferLength = 0;
787  int patternIndex = 0;
788  double currentRemainingDashLength = 0;
789  double currentRemainingGapLength = 0;
790 
791  auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
792  {
793  QVector< qreal > result;
794  result.reserve( buffer.size() );
795  for ( auto it = buffer.begin(); it != buffer.end(); )
796  {
797  qreal dash = *it++;
798  qreal gap = *it++;
799  while ( dash == 0 && !result.empty() )
800  {
801  result.last() += gap;
802 
803  if ( it == buffer.end() )
804  return result;
805  dash = *it++;
806  gap = *it++;
807  }
808  while ( gap == 0 && it != buffer.end() )
809  {
810  dash += *it++;
811  gap = *it++;
812  }
813  result << dash << gap;
814  }
815  return result;
816  };
817 
818  double currentBufferLineLength = 0;
819  auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
820  dashWidthDiv, &compressPattern]( QPointF * nextPoint )
821  {
822  if ( buffer.empty() || bufferedPoints.size() < 2 )
823  {
824  return;
825  }
826 
827  if ( currentRemainingDashLength )
828  {
829  // ended midway through a dash -- we want to finish this off
830  buffer << currentRemainingDashLength << 0.0;
831  totalBufferLength += currentRemainingDashLength;
832  }
833  QVector< qreal > compressed = compressPattern( buffer );
834  if ( !currentRemainingDashLength )
835  {
836  // ended midway through a gap -- we don't want this, we want to end at previous dash
837  totalBufferLength -= compressed.last();
838  compressed.last() = 0;
839  }
840 
841  // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
842  const double scaleFactor = currentBufferLineLength / totalBufferLength;
843 
844  bool shouldFlushPreviousSegmentBuffer = false;
845 
846  if ( !previousSegmentBuffer.empty() )
847  {
848  // add first dash from current buffer
849  QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
850  if ( !firstDashSubstring.empty() )
851  QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
852 
853  // then we skip over the first dash and gap for this segment
854  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
855 
856  compressed = compressed.mid( 2 );
857  shouldFlushPreviousSegmentBuffer = !compressed.empty();
858  }
859 
860  if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
861  {
862  QPen adjustedPen = pen;
863  adjustedPen.setStyle( Qt::SolidLine );
864  painter->setPen( adjustedPen );
865  QPainterPath path;
866  path.addPolygon( previousSegmentBuffer );
867  painter->drawPath( path );
868  previousSegmentBuffer.clear();
869  }
870 
871  double finalDash = 0;
872  if ( nextPoint )
873  {
874  // sharp bend:
875  // 1. rewind buffered points line by final dash and gap length
876  // (later) 2. draw the bend with a solid line of length 2 * final dash size
877 
878  if ( !compressed.empty() )
879  {
880  finalDash = compressed.at( compressed.size() - 2 );
881  const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
882 
883  const QPolygonF thisPoints = bufferedPoints;
884  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
885  previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
886  }
887  else
888  {
889  previousSegmentBuffer << bufferedPoints;
890  }
891  }
892 
893  currentBufferLineLength = 0;
894  currentRemainingDashLength = 0;
895  currentRemainingGapLength = 0;
896  totalBufferLength = 0;
897  buffer.clear();
898 
899  if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
900  {
901  QPen adjustedPen = pen;
902  if ( !compressed.empty() )
903  {
904  // maximum size of dash pattern is 32 elements
905  compressed = compressed.mid( 0, 32 );
906  std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
907  adjustedPen.setDashPattern( compressed );
908  }
909  else
910  {
911  adjustedPen.setStyle( Qt::SolidLine );
912  }
913 
914  painter->setPen( adjustedPen );
915  QPainterPath path;
916  path.addPolygon( bufferedPoints );
917  painter->drawPath( path );
918  }
919 
920  bufferedPoints.clear();
921  };
922 
923  QPointF p1;
924  QPointF p2 = *ptIt;
925  ptIt++;
926  bufferedPoints << p2;
927  for ( ; ptIt != points.constEnd(); ++ptIt )
928  {
929  p1 = *ptIt;
930  if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
931  {
932  continue;
933  }
934 
935  double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
936  currentBufferLineLength += remainingSegmentDistance;
937  while ( true )
938  {
939  // handle currentRemainingDashLength/currentRemainingGapLength
940  if ( currentRemainingDashLength > 0 )
941  {
942  // bit more of dash to insert
943  if ( remainingSegmentDistance >= currentRemainingDashLength )
944  {
945  // all of dash fits in
946  buffer << currentRemainingDashLength << 0.0;
947  totalBufferLength += currentRemainingDashLength;
948  remainingSegmentDistance -= currentRemainingDashLength;
949  patternIndex++;
950  currentRemainingDashLength = 0.0;
951  currentRemainingGapLength = sourcePattern.at( patternIndex );
952  }
953  else
954  {
955  // only part of remaining dash fits in
956  buffer << remainingSegmentDistance << 0.0;
957  totalBufferLength += remainingSegmentDistance;
958  currentRemainingDashLength -= remainingSegmentDistance;
959  break;
960  }
961  }
962  if ( currentRemainingGapLength > 0 )
963  {
964  // bit more of gap to insert
965  if ( remainingSegmentDistance >= currentRemainingGapLength )
966  {
967  // all of gap fits in
968  buffer << 0.0 << currentRemainingGapLength;
969  totalBufferLength += currentRemainingGapLength;
970  remainingSegmentDistance -= currentRemainingGapLength;
971  currentRemainingGapLength = 0.0;
972  patternIndex++;
973  }
974  else
975  {
976  // only part of remaining gap fits in
977  buffer << 0.0 << remainingSegmentDistance;
978  totalBufferLength += remainingSegmentDistance;
979  currentRemainingGapLength -= remainingSegmentDistance;
980  break;
981  }
982  }
983 
984  if ( patternIndex >= sourcePattern.size() )
985  patternIndex = 0;
986 
987  const double nextPatternDashLength = sourcePattern.at( patternIndex );
988  const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
989  if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
990  {
991  buffer << nextPatternDashLength << nextPatternGapLength;
992  remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
993  totalBufferLength += nextPatternDashLength + nextPatternGapLength;
994  patternIndex += 2;
995  }
996  else if ( nextPatternDashLength <= remainingSegmentDistance )
997  {
998  // can fit in "dash", but not "gap"
999  buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1000  totalBufferLength += remainingSegmentDistance;
1001  currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1002  currentRemainingDashLength = 0;
1003  patternIndex++;
1004  break;
1005  }
1006  else
1007  {
1008  // can't fit in "dash"
1009  buffer << remainingSegmentDistance << 0.0;
1010  totalBufferLength += remainingSegmentDistance;
1011  currentRemainingGapLength = 0;
1012  currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1013  break;
1014  }
1015  }
1016 
1017  bufferedPoints << p1;
1018  if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1019  {
1020  QPointF nextPoint = *( ptIt + 1 );
1021 
1022  // extreme angles form more than 45 degree angle at a node
1023  if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1024  {
1025  // extreme angle. Rescale buffer and flush
1026  flushBuffer( &nextPoint );
1027  bufferedPoints << p1;
1028  // restart the line with the full length of the most recent dash element -- see
1029  // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1030  if ( patternIndex % 2 == 1 )
1031  {
1032  patternIndex--;
1033  }
1034  currentRemainingDashLength = sourcePattern.at( patternIndex );
1035  }
1036  }
1037 
1038  p2 = p1;
1039  }
1040 
1041  flushBuffer( nullptr );
1042  if ( !previousSegmentBuffer.empty() )
1043  {
1044  QPen adjustedPen = pen;
1045  adjustedPen.setStyle( Qt::SolidLine );
1046  painter->setPen( adjustedPen );
1047  QPainterPath path;
1048  path.addPolygon( previousSegmentBuffer );
1049  painter->drawPath( path );
1050  previousSegmentBuffer.clear();
1051  }
1052 }
1053 
1055 {
1056  if ( mDrawInsidePolygon )
1057  {
1058  //set to clip line to the interior of polygon, so we expect no bleed
1059  return 0;
1060  }
1061  else
1062  {
1063  return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1065  }
1066 }
1067 
1069 {
1070  unit = mCustomDashPatternUnit;
1071  return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1072 }
1073 
1075 {
1076  return mPenStyle;
1077 }
1078 
1080 {
1081  double width = mWidth;
1083  {
1084  context.setOriginalValueVariable( mWidth );
1086  }
1087 
1090  {
1092  }
1093  return width;
1094 }
1095 
1097 {
1099  {
1102  }
1103  return mColor;
1104 }
1105 
1107 {
1108  return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1109 }
1110 
1112 {
1113  return mAlignDashPattern;
1114 }
1115 
1117 {
1118  mAlignDashPattern = enabled;
1119 }
1120 
1122 {
1123  return mPatternCartographicTweakOnSharpCorners;
1124 }
1125 
1127 {
1128  mPatternCartographicTweakOnSharpCorners = enabled;
1129 }
1130 
1132 {
1133  Q_UNUSED( e )
1134  double offset = mOffset;
1135 
1137  {
1138  context.setOriginalValueVariable( mOffset );
1140  }
1141 
1144  {
1146  }
1147  return -offset; //direction seems to be inverse to symbology offset
1148 }
1149 
1151 
1153 
1154 class MyLine
1155 {
1156  public:
1157  MyLine( QPointF p1, QPointF p2 )
1158  : mVertical( false )
1159  , mIncreasing( false )
1160  , mT( 0.0 )
1161  , mLength( 0.0 )
1162  {
1163  if ( p1 == p2 )
1164  return; // invalid
1165 
1166  // tangent and direction
1167  if ( qgsDoubleNear( p1.x(), p2.x() ) )
1168  {
1169  // vertical line - tangent undefined
1170  mVertical = true;
1171  mIncreasing = ( p2.y() > p1.y() );
1172  }
1173  else
1174  {
1175  mVertical = false;
1176  mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1177  mIncreasing = ( p2.x() > p1.x() );
1178  }
1179 
1180  // length
1181  double x = ( p2.x() - p1.x() );
1182  double y = ( p2.y() - p1.y() );
1183  mLength = std::sqrt( x * x + y * y );
1184  }
1185 
1186  // return angle in radians
1187  double angle()
1188  {
1189  double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1190 
1191  if ( !mIncreasing )
1192  a += M_PI;
1193  return a;
1194  }
1195 
1196  // return difference for x,y when going along the line with specified interval
1197  QPointF diffForInterval( double interval )
1198  {
1199  if ( mVertical )
1200  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1201 
1202  double alpha = std::atan( mT );
1203  double dx = std::cos( alpha ) * interval;
1204  double dy = std::sin( alpha ) * interval;
1205  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1206  }
1207 
1208  double length() { return mLength; }
1209 
1210  protected:
1211  bool mVertical;
1212  bool mIncreasing;
1213  double mT;
1214  double mLength;
1215 };
1216 
1218 
1219 //
1220 // QgsTemplatedLineSymbolLayerBase
1221 //
1223  : mRotateSymbols( rotateSymbol )
1224  , mInterval( interval )
1225 {
1226 
1227 }
1228 
1230 
1232 {
1233  double offset = mOffset;
1234 
1236  {
1237  context.setOriginalValueVariable( mOffset );
1239  }
1240 
1242 
1244  {
1246  if ( !exprVal.isNull() )
1247  {
1248  QString placementString = exprVal.toString();
1249  if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1250  {
1252  }
1253  else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1254  {
1256  }
1257  else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1258  {
1260  }
1261  else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1262  {
1264  }
1265  else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1266  {
1268  }
1269  else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1270  {
1272  }
1273  else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1274  {
1276  }
1277  else
1278  {
1280  }
1281  }
1282  }
1283 
1284  QgsScopedQPainterState painterState( context.renderContext().painter() );
1285 
1286  double averageOver = mAverageAngleLength;
1288  {
1289  context.setOriginalValueVariable( mAverageAngleLength );
1291  }
1292  averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1293 
1294  if ( qgsDoubleNear( offset, 0.0 ) )
1295  {
1296  switch ( placement )
1297  {
1298  case Interval:
1299  renderPolylineInterval( points, context, averageOver );
1300  break;
1301 
1302  case CentralPoint:
1303  renderPolylineCentral( points, context, averageOver );
1304  break;
1305 
1306  case Vertex:
1307  case LastVertex:
1308  case FirstVertex:
1309  case CurvePoint:
1310  case SegmentCenter:
1311  renderPolylineVertex( points, context, placement );
1312  break;
1313  }
1314  }
1315  else
1316  {
1317  context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1319 
1320  for ( int part = 0; part < mline.count(); ++part )
1321  {
1322  const QPolygonF &points2 = mline[ part ];
1323 
1324  switch ( placement )
1325  {
1326  case Interval:
1327  renderPolylineInterval( points2, context, averageOver );
1328  break;
1329 
1330  case CentralPoint:
1331  renderPolylineCentral( points2, context, averageOver );
1332  break;
1333 
1334  case Vertex:
1335  case LastVertex:
1336  case FirstVertex:
1337  case CurvePoint:
1338  case SegmentCenter:
1339  renderPolylineVertex( points2, context, placement );
1340  break;
1341  }
1342  }
1343  }
1344 }
1345 
1346 void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1347 {
1348  const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1349 
1350  if ( curvePolygon )
1351  {
1352  context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1353  }
1354 
1355  QgsExpressionContextScope *scope = nullptr;
1356  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1357  if ( hasDataDefinedProperties() )
1358  {
1359  scope = new QgsExpressionContextScope();
1360  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1361  }
1362 
1363  switch ( mRingFilter )
1364  {
1365  case AllRings:
1366  case ExteriorRingOnly:
1367  {
1368  if ( scope )
1370 
1371  renderPolyline( points, context );
1372  break;
1373  }
1374  case InteriorRingsOnly:
1375  break;
1376  }
1377 
1378  if ( rings )
1379  {
1380  switch ( mRingFilter )
1381  {
1382  case AllRings:
1383  case InteriorRingsOnly:
1384  {
1385  mOffset = -mOffset; // invert the offset for rings!
1386  for ( int i = 0; i < rings->size(); ++i )
1387  {
1388  if ( curvePolygon )
1389  {
1390  context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1391  }
1392  if ( scope )
1394 
1395  renderPolyline( rings->at( i ), context );
1396  }
1397  mOffset = -mOffset;
1398  }
1399  break;
1400  case ExteriorRingOnly:
1401  break;
1402  }
1403  }
1404 }
1405 
1407 {
1409  if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1410  {
1412  }
1413  return unit;
1414 }
1415 
1417 {
1419  setIntervalMapUnitScale( scale );
1420  mOffsetMapUnitScale = scale;
1422 }
1423 
1425 {
1429  {
1430  return mOffsetMapUnitScale;
1431  }
1432  return QgsMapUnitScale();
1433 }
1434 
1436 {
1437  QVariantMap map;
1438  map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1439  map[QStringLiteral( "interval" )] = QString::number( interval() );
1440  map[QStringLiteral( "offset" )] = QString::number( mOffset );
1441  map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1442  map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1443  map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1444  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1445  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1446  map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1447  map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1448  map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1449  map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1450  map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1451 
1452  switch ( mPlacement )
1453  {
1454  case Vertex:
1455  map[QStringLiteral( "placement" )] = QStringLiteral( "vertex" );
1456  break;
1457  case LastVertex:
1458  map[QStringLiteral( "placement" )] = QStringLiteral( "lastvertex" );
1459  break;
1460  case FirstVertex:
1461  map[QStringLiteral( "placement" )] = QStringLiteral( "firstvertex" );
1462  break;
1463  case CentralPoint:
1464  map[QStringLiteral( "placement" )] = QStringLiteral( "centralpoint" );
1465  break;
1466  case CurvePoint:
1467  map[QStringLiteral( "placement" )] = QStringLiteral( "curvepoint" );
1468  break;
1469  case Interval:
1470  map[QStringLiteral( "placement" )] = QStringLiteral( "interval" );
1471  break;
1472  case SegmentCenter:
1473  map[QStringLiteral( "placement" )] = QStringLiteral( "segmentcenter" );
1474  break;
1475  }
1476 
1477  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1478  return map;
1479 }
1480 
1482 {
1483  switch ( mPlacement )
1484  {
1488  return true;
1489 
1494  return false;
1495  }
1496  return false;
1497 }
1498 
1500 {
1501  destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1502  destLayer->setOffset( mOffset );
1503  destLayer->setPlacement( placement() );
1504  destLayer->setOffsetUnit( mOffsetUnit );
1506  destLayer->setIntervalUnit( intervalUnit() );
1508  destLayer->setOffsetAlongLine( offsetAlongLine() );
1511  destLayer->setAverageAngleLength( mAverageAngleLength );
1512  destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1513  destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1514  destLayer->setRingFilter( mRingFilter );
1515  copyDataDefinedProperties( destLayer );
1516  copyPaintEffect( destLayer );
1517 }
1518 
1520 {
1521  if ( properties.contains( QStringLiteral( "offset" ) ) )
1522  {
1523  destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1524  }
1525  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1526  {
1527  destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1528  }
1529  if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1530  {
1531  destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1532  }
1533  if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1534  {
1535  destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1536  }
1537  if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1538  {
1539  destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1540  }
1541  if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1542  {
1543  destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1544  }
1545 
1546  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1547  {
1548  destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1549  }
1550  if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1551  {
1552  destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1553  }
1554 
1555  if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1556  {
1557  destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1558  }
1559  if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1560  {
1561  destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1562  }
1563  if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1564  {
1565  destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1566  }
1567 
1568  if ( properties.contains( QStringLiteral( "placement" ) ) )
1569  {
1570  if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1572  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1574  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1576  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1578  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1580  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1582  else
1584  }
1585 
1586  if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1587  {
1588  destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1589  }
1590 
1592 }
1593 
1594 void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1595 {
1596  if ( points.isEmpty() )
1597  return;
1598 
1599  double lengthLeft = 0; // how much is left until next marker
1600 
1601  QgsRenderContext &rc = context.renderContext();
1602  double interval = mInterval;
1603 
1605  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1606 
1608  {
1609  context.setOriginalValueVariable( mInterval );
1611  }
1612  if ( interval <= 0 )
1613  {
1614  interval = 0.1;
1615  }
1616  double offsetAlongLine = mOffsetAlongLine;
1618  {
1619  context.setOriginalValueVariable( mOffsetAlongLine );
1621  }
1622 
1623  double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1625  {
1626  // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1627  // and clamp it to a reasonable range. It's the best we can do in this situation!
1628  painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, QgsUnitTypes::RenderMillimeters ), 10.0 ), 100.0 );
1629  }
1630 
1631  if ( painterUnitInterval < 0 )
1632  return;
1633 
1634  double painterUnitOffsetAlongLine = rc.convertToPainterUnits( offsetAlongLine, offsetAlongLineUnit(), offsetAlongLineMapUnitScale() );
1636  {
1637  // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1638  // and clamp it to a reasonable range. It's the best we can do in this situation!
1639  painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
1640  }
1641 
1642  lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1643 
1644  if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1645  {
1646  QVector< QPointF > angleStartPoints;
1647  QVector< QPointF > symbolPoints;
1648  QVector< QPointF > angleEndPoints;
1649 
1650  // we collect 3 arrays of points. These correspond to
1651  // 1. the actual point at which to render the symbol
1652  // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1653  // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1654  // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1655  // (or trace past the final point)
1656  collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1657 
1658  if ( symbolPoints.empty() )
1659  {
1660  // no symbols to draw, shortcut out early
1661  return;
1662  }
1663 
1664  if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1665  {
1666  // avoid duplicate points at start and end of closed rings
1667  symbolPoints.pop_back();
1668  }
1669 
1670  angleEndPoints.reserve( symbolPoints.size() );
1671  angleStartPoints.reserve( symbolPoints.size() );
1672  if ( averageOver <= painterUnitOffsetAlongLine )
1673  {
1674  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1675  }
1676  else
1677  {
1678  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1679  }
1680  collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1681 
1682  int pointNum = 0;
1683  for ( int i = 0; i < symbolPoints.size(); ++ i )
1684  {
1685  if ( context.renderContext().renderingStopped() )
1686  break;
1687 
1688  const QPointF pt = symbolPoints[i];
1689  const QPointF startPt = angleStartPoints[i];
1690  const QPointF endPt = angleEndPoints[i];
1691 
1692  MyLine l( startPt, endPt );
1693  // rotate marker (if desired)
1694  if ( rotateSymbols() )
1695  {
1696  setSymbolLineAngle( l.angle() * 180 / M_PI );
1697  }
1698 
1700  renderSymbol( pt, context.feature(), rc, -1, context.selected() );
1701  }
1702  }
1703  else
1704  {
1705  // not averaging line angle -- always use exact section angle
1706  int pointNum = 0;
1707  QPointF lastPt = points[0];
1708  for ( int i = 1; i < points.count(); ++i )
1709  {
1710  if ( context.renderContext().renderingStopped() )
1711  break;
1712 
1713  const QPointF &pt = points[i];
1714 
1715  if ( lastPt == pt ) // must not be equal!
1716  continue;
1717 
1718  // for each line, find out dx and dy, and length
1719  MyLine l( lastPt, pt );
1720  QPointF diff = l.diffForInterval( painterUnitInterval );
1721 
1722  // if there's some length left from previous line
1723  // use only the rest for the first point in new line segment
1724  double c = 1 - lengthLeft / painterUnitInterval;
1725 
1726  lengthLeft += l.length();
1727 
1728  // rotate marker (if desired)
1729  if ( rotateSymbols() )
1730  {
1731  setSymbolLineAngle( l.angle() * 180 / M_PI );
1732  }
1733 
1734  // while we're not at the end of line segment, draw!
1735  while ( lengthLeft > painterUnitInterval )
1736  {
1737  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1738  lastPt += c * diff;
1739  lengthLeft -= painterUnitInterval;
1741  renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
1742  c = 1; // reset c (if wasn't 1 already)
1743  }
1744 
1745  lastPt = pt;
1746  }
1747 
1748  }
1749 }
1750 
1751 static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1752 {
1753  // calc average angle between the previous and next point
1754  double a1 = MyLine( prevPt, pt ).angle();
1755  double a2 = MyLine( pt, nextPt ).angle();
1756  double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1757 
1758  return std::atan2( unitY, unitX );
1759 }
1760 
1761 void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, QgsTemplatedLineSymbolLayerBase::Placement placement )
1762 {
1763  if ( points.isEmpty() )
1764  return;
1765 
1766  QgsRenderContext &rc = context.renderContext();
1767 
1768  int i = -1, maxCount = 0;
1769  bool isRing = false;
1770 
1772  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1774 
1775  double offsetAlongLine = mOffsetAlongLine;
1777  {
1778  context.setOriginalValueVariable( mOffsetAlongLine );
1780  }
1781  if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1782  {
1783  //scale offset along line
1785  }
1786 
1787  if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1789  {
1791  const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1792 
1793  QgsVertexId vId;
1794  QgsPoint vPoint;
1795  double x, y, z;
1796  QPointF mapPoint;
1797  int pointNum = 0;
1798  while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1799  {
1800  if ( context.renderContext().renderingStopped() )
1801  break;
1802 
1804 
1805  if ( ( placement == QgsTemplatedLineSymbolLayerBase::Vertex && vId.type == Qgis::VertexType::Segment )
1806  || ( placement == QgsTemplatedLineSymbolLayerBase::CurvePoint && vId.type == Qgis::VertexType::Curve ) )
1807  {
1808  //transform
1809  x = vPoint.x();
1810  y = vPoint.y();
1811  z = 0.0;
1812  if ( ct.isValid() )
1813  {
1814  ct.transformInPlace( x, y, z );
1815  }
1816  mapPoint.setX( x );
1817  mapPoint.setY( y );
1818  mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1819  if ( rotateSymbols() )
1820  {
1821  double angle = context.renderContext().geometry()->vertexAngle( vId );
1822  setSymbolLineAngle( angle * 180 / M_PI );
1823  }
1824  renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() );
1825  }
1826  }
1827 
1828  return;
1829  }
1830 
1831  switch ( placement )
1832  {
1833  case FirstVertex:
1834  {
1835  i = 0;
1836  maxCount = 1;
1837  break;
1838  }
1839 
1840  case LastVertex:
1841  {
1842  i = points.count() - 1;
1843  maxCount = points.count();
1844  break;
1845  }
1846 
1847  case Vertex:
1848  case SegmentCenter:
1849  {
1850  i = placement == Vertex ? 0 : 1;
1851  maxCount = points.count();
1852  if ( points.first() == points.last() )
1853  isRing = true;
1854  break;
1855  }
1856 
1857  case Interval:
1858  case CentralPoint:
1859  case CurvePoint:
1860  {
1861  return;
1862  }
1863  }
1864 
1866  {
1867  double distance;
1869  renderOffsetVertexAlongLine( points, i, distance, context );
1870 
1871  return;
1872  }
1873 
1874  int pointNum = 0;
1875  QPointF prevPoint;
1876  if ( placement == SegmentCenter && !points.empty() )
1877  prevPoint = points.at( 0 );
1878 
1879  QPointF symbolPoint;
1880  for ( ; i < maxCount; ++i )
1881  {
1883 
1884  if ( isRing && placement == QgsTemplatedLineSymbolLayerBase::Vertex && i == points.count() - 1 )
1885  {
1886  continue; // don't draw the last marker - it has been drawn already
1887  }
1888 
1889  if ( placement == SegmentCenter )
1890  {
1891  QPointF currentPoint = points.at( i );
1892  symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
1893  0.5 * ( currentPoint.y() + prevPoint.y() ) );
1894  if ( rotateSymbols() )
1895  {
1896  double angle = std::atan2( currentPoint.y() - prevPoint.y(),
1897  currentPoint.x() - prevPoint.x() );
1898  setSymbolLineAngle( angle * 180 / M_PI );
1899  }
1900  prevPoint = currentPoint;
1901  }
1902  else
1903  {
1904  symbolPoint = points.at( i );
1905  // rotate marker (if desired)
1906  if ( rotateSymbols() )
1907  {
1908  double angle = markerAngle( points, isRing, i );
1909  setSymbolLineAngle( angle * 180 / M_PI );
1910  }
1911  }
1912 
1913  renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
1914  }
1915 }
1916 
1917 double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
1918 {
1919  double angle = 0;
1920  const QPointF &pt = points[vertex];
1921 
1922  if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
1923  {
1924  int prevIndex = vertex - 1;
1925  int nextIndex = vertex + 1;
1926 
1927  if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
1928  {
1929  prevIndex = points.count() - 2;
1930  nextIndex = 1;
1931  }
1932 
1933  QPointF prevPoint, nextPoint;
1934  while ( prevIndex >= 0 )
1935  {
1936  prevPoint = points[ prevIndex ];
1937  if ( prevPoint != pt )
1938  {
1939  break;
1940  }
1941  --prevIndex;
1942  }
1943 
1944  while ( nextIndex < points.count() )
1945  {
1946  nextPoint = points[ nextIndex ];
1947  if ( nextPoint != pt )
1948  {
1949  break;
1950  }
1951  ++nextIndex;
1952  }
1953 
1954  if ( prevIndex >= 0 && nextIndex < points.count() )
1955  {
1956  angle = _averageAngle( prevPoint, pt, nextPoint );
1957  }
1958  }
1959  else //no ring and vertex is at start / at end
1960  {
1961  if ( vertex == 0 )
1962  {
1963  while ( vertex < points.size() - 1 )
1964  {
1965  const QPointF &nextPt = points[vertex + 1];
1966  if ( pt != nextPt )
1967  {
1968  angle = MyLine( pt, nextPt ).angle();
1969  return angle;
1970  }
1971  ++vertex;
1972  }
1973  }
1974  else
1975  {
1976  // use last segment's angle
1977  while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
1978  {
1979  const QPointF &prevPt = points[vertex - 1];
1980  if ( pt != prevPt )
1981  {
1982  angle = MyLine( prevPt, pt ).angle();
1983  return angle;
1984  }
1985  --vertex;
1986  }
1987  }
1988  }
1989  return angle;
1990 }
1991 
1992 void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context )
1993 {
1994  if ( points.isEmpty() )
1995  return;
1996 
1997  QgsRenderContext &rc = context.renderContext();
1998  if ( qgsDoubleNear( distance, 0.0 ) )
1999  {
2000  // rotate marker (if desired)
2001  if ( rotateSymbols() )
2002  {
2003  bool isRing = false;
2004  if ( points.first() == points.last() )
2005  isRing = true;
2006  double angle = markerAngle( points, isRing, vertex );
2007  setSymbolLineAngle( angle * 180 / M_PI );
2008  }
2009  renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
2010  return;
2011  }
2012 
2013  int pointIncrement = distance > 0 ? 1 : -1;
2014  QPointF previousPoint = points[vertex];
2015  int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2016  int endPoint = distance > 0 ? points.count() - 1 : 0;
2017  double distanceLeft = std::fabs( distance );
2018 
2019  for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2020  {
2021  const QPointF &pt = points[i];
2022 
2023  if ( previousPoint == pt ) // must not be equal!
2024  continue;
2025 
2026  // create line segment
2027  MyLine l( previousPoint, pt );
2028 
2029  if ( distanceLeft < l.length() )
2030  {
2031  //destination point is in current segment
2032  QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2033  // rotate marker (if desired)
2034  if ( rotateSymbols() )
2035  {
2036  setSymbolLineAngle( l.angle() * 180 / M_PI );
2037  }
2038  renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
2039  return;
2040  }
2041 
2042  distanceLeft -= l.length();
2043  previousPoint = pt;
2044  }
2045 
2046  //didn't find point
2047 }
2048 
2049 void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2050 {
2051  if ( p.empty() )
2052  return;
2053 
2054  QVector< QPointF > points = p;
2055  const bool closedRing = points.first() == points.last();
2056 
2057  double lengthLeft = initialOffset;
2058 
2059  double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2060  if ( initialLagLeft < 0 && closedRing )
2061  {
2062  // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2063  QPointF lastPt = points.constLast();
2064  QVector< QPointF > pseudoPoints;
2065  for ( int i = points.count() - 2; i > 0; --i )
2066  {
2067  if ( initialLagLeft >= 0 )
2068  {
2069  break;
2070  }
2071 
2072  const QPointF &pt = points[i];
2073 
2074  if ( lastPt == pt ) // must not be equal!
2075  continue;
2076 
2077  MyLine l( lastPt, pt );
2078  initialLagLeft += l.length();
2079  lastPt = pt;
2080 
2081  pseudoPoints << pt;
2082  }
2083  std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2084 
2085  points = pseudoPoints;
2086  points.append( p );
2087  }
2088  else
2089  {
2090  while ( initialLagLeft < 0 )
2091  {
2092  dest << points.constFirst();
2093  initialLagLeft += intervalPainterUnits;
2094  }
2095  }
2096  if ( initialLag > 0 )
2097  {
2098  lengthLeft += intervalPainterUnits - initialLagLeft;
2099  }
2100 
2101  QPointF lastPt = points[0];
2102  for ( int i = 1; i < points.count(); ++i )
2103  {
2104  const QPointF &pt = points[i];
2105 
2106  if ( lastPt == pt ) // must not be equal!
2107  {
2108  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2109  {
2110  lastPt = points[0];
2111  i = 0;
2112  }
2113  continue;
2114  }
2115 
2116  // for each line, find out dx and dy, and length
2117  MyLine l( lastPt, pt );
2118  QPointF diff = l.diffForInterval( intervalPainterUnits );
2119 
2120  // if there's some length left from previous line
2121  // use only the rest for the first point in new line segment
2122  double c = 1 - lengthLeft / intervalPainterUnits;
2123 
2124  lengthLeft += l.length();
2125 
2126 
2127  while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2128  {
2129  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2130  lastPt += c * diff;
2131  lengthLeft -= intervalPainterUnits;
2132  dest << lastPt;
2133  c = 1; // reset c (if wasn't 1 already)
2134  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2135  break;
2136  }
2137  lastPt = pt;
2138 
2139  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2140  break;
2141 
2142  // if a closed ring, we keep looping around the ring until we hit the required number of points
2143  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2144  {
2145  lastPt = points[0];
2146  i = 0;
2147  }
2148  }
2149 
2150  if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2151  {
2152  // pad with repeating last point to match desired size
2153  while ( dest.size() < numberPointsRequired )
2154  dest << points.constLast();
2155  }
2156 }
2157 
2158 void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2159 {
2160  if ( !points.isEmpty() )
2161  {
2162  // calc length
2163  qreal length = 0;
2164  QPolygonF::const_iterator it = points.constBegin();
2165  QPointF last = *it;
2166  for ( ++it; it != points.constEnd(); ++it )
2167  {
2168  length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2169  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2170  last = *it;
2171  }
2172  if ( qgsDoubleNear( length, 0.0 ) )
2173  return;
2174 
2175  const double midPoint = length / 2;
2176 
2177  QPointF pt;
2178  double thisSymbolAngle = 0;
2179 
2180  if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2181  {
2182  QVector< QPointF > angleStartPoints;
2183  QVector< QPointF > symbolPoints;
2184  QVector< QPointF > angleEndPoints;
2185  // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2186  collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2187  collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2188  collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2189 
2190  pt = symbolPoints.at( 1 );
2191  MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2192  thisSymbolAngle = l.angle();
2193  }
2194  else
2195  {
2196  // find the segment where the central point lies
2197  it = points.constBegin();
2198  last = *it;
2199  qreal last_at = 0, next_at = 0;
2200  QPointF next;
2201  int segment = 0;
2202  for ( ++it; it != points.constEnd(); ++it )
2203  {
2204  next = *it;
2205  next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2206  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2207  if ( next_at >= midPoint )
2208  break; // we have reached the center
2209  last = *it;
2210  last_at = next_at;
2211  segment++;
2212  }
2213 
2214  // find out the central point on segment
2215  MyLine l( last, next ); // for line angle
2216  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2217  pt = last + ( next - last ) * k;
2218  thisSymbolAngle = l.angle();
2219  }
2220 
2221  // draw the marker
2222  // rotate marker (if desired)
2223  if ( rotateSymbols() )
2224  {
2225  setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2226  }
2227 
2228  renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2229 
2230  }
2231 }
2232 
2234 {
2235  return mMarker.get();
2236 }
2237 
2239 {
2240  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2241  {
2242  delete symbol;
2243  return false;
2244  }
2245 
2246  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2247  mColor = mMarker->color();
2248  return true;
2249 }
2250 
2251 
2252 
2253 //
2254 // QgsMarkerLineSymbolLayer
2255 //
2256 
2257 QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2258  : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2259 {
2260  setSubSymbol( new QgsMarkerSymbol() );
2261 }
2262 
2264 
2266 {
2267  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2269 
2270  if ( props.contains( QStringLiteral( "interval" ) ) )
2271  interval = props[QStringLiteral( "interval" )].toDouble();
2272  if ( props.contains( QStringLiteral( "rotate" ) ) )
2273  rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2274 
2275  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2276  setCommonProperties( x.get(), props );
2277  return x.release();
2278 }
2279 
2281 {
2282  return QStringLiteral( "MarkerLine" );
2283 }
2284 
2285 void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2286 {
2287  mMarker->setColor( color );
2288  mColor = color;
2289 }
2290 
2292 {
2293  return mMarker ? mMarker->color() : mColor;
2294 }
2295 
2297 {
2298  // if being rotated, it gets initialized with every line segment
2299  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2300  if ( rotateSymbols() )
2302  mMarker->setRenderHints( hints );
2303 
2304  mMarker->startRender( context.renderContext(), context.fields() );
2305 }
2306 
2308 {
2309  mMarker->stopRender( context.renderContext() );
2310 }
2311 
2312 
2314 {
2315  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2316  copyTemplateSymbolProperties( x.get() );
2317  return x.release();
2318 }
2319 
2320 void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2321 {
2322  for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2323  {
2324  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2325  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2326  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2327  element.appendChild( symbolizerElem );
2328 
2329  // <Geometry>
2330  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2331 
2332  QString gap;
2333  switch ( placement() )
2334  {
2336  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2337  break;
2339  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2340  break;
2342  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2343  break;
2345  // no way to get line/polygon's vertices, use a VendorOption
2346  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2347  break;
2348  default:
2350  gap = qgsDoubleToString( interval );
2351  break;
2352  }
2353 
2354  if ( !rotateSymbols() )
2355  {
2356  // markers in LineSymbolizer must be drawn following the line orientation,
2357  // use a VendorOption when no marker rotation
2358  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2359  }
2360 
2361  // <Stroke>
2362  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2363  symbolizerElem.appendChild( strokeElem );
2364 
2365  // <GraphicStroke>
2366  QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2367  strokeElem.appendChild( graphicStrokeElem );
2368 
2369  QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2370  if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2371  {
2372  markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2373  }
2374  else if ( layer )
2375  {
2376  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2377  }
2378  else
2379  {
2380  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2381  }
2382 
2383  if ( !gap.isEmpty() )
2384  {
2385  QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2386  QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap );
2387  graphicStrokeElem.appendChild( gapElem );
2388  }
2389 
2390  if ( !qgsDoubleNear( mOffset, 0.0 ) )
2391  {
2392  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2394  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2395  symbolizerElem.appendChild( perpOffsetElem );
2396  }
2397  }
2398 }
2399 
2401 {
2402  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2403 
2404  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2405  if ( strokeElem.isNull() )
2406  return nullptr;
2407 
2408  QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2409  if ( graphicStrokeElem.isNull() )
2410  return nullptr;
2411 
2412  // retrieve vendor options
2413  bool rotateMarker = true;
2415 
2416  QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2417  for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2418  {
2419  if ( it.key() == QLatin1String( "placement" ) )
2420  {
2421  if ( it.value() == QLatin1String( "points" ) )
2423  else if ( it.value() == QLatin1String( "firstPoint" ) )
2425  else if ( it.value() == QLatin1String( "lastPoint" ) )
2427  else if ( it.value() == QLatin1String( "centralPoint" ) )
2429  }
2430  else if ( it.value() == QLatin1String( "rotateMarker" ) )
2431  {
2432  rotateMarker = it.value() == QLatin1String( "0" );
2433  }
2434  }
2435 
2436  std::unique_ptr< QgsMarkerSymbol > marker;
2437 
2439  if ( l )
2440  {
2441  QgsSymbolLayerList layers;
2442  layers.append( l );
2443  marker.reset( new QgsMarkerSymbol( layers ) );
2444  }
2445 
2446  if ( !marker )
2447  return nullptr;
2448 
2449  double interval = 0.0;
2450  QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2451  if ( !gapElem.isNull() )
2452  {
2453  bool ok;
2454  double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2455  if ( ok )
2456  interval = d;
2457  }
2458 
2459  double offset = 0.0;
2460  QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2461  if ( !perpOffsetElem.isNull() )
2462  {
2463  bool ok;
2464  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2465  if ( ok )
2466  offset = d;
2467  }
2468 
2469  QString uom = element.attribute( QStringLiteral( "uom" ) );
2472 
2474  x->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
2475  x->setPlacement( placement );
2476  x->setInterval( interval );
2477  x->setSubSymbol( marker.release() );
2478  x->setOffset( offset );
2479  return x;
2480 }
2481 
2483 {
2484  mMarker->setSize( width );
2485 }
2486 
2488 {
2489  if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2490  {
2491  mMarker->setDataDefinedSize( property );
2492  }
2494 }
2495 
2496 void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2497 {
2498  const double prevOpacity = mMarker->opacity();
2499  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2501  mMarker->setOpacity( prevOpacity );
2502 }
2503 
2505 {
2506  mMarker->setLineAngle( angle );
2507 }
2508 
2510 {
2511  return mMarker->angle();
2512 }
2513 
2515 {
2516  mMarker->setAngle( angle );
2517 }
2518 
2519 void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2520 {
2521  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2523 
2524  mMarker->renderPoint( point, feature, context, layer, selected );
2525 
2526  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2527 }
2528 
2530 {
2531  return mMarker->size();
2532 }
2533 
2535 {
2536  return mMarker->size( context );
2537 }
2538 
2540 {
2542  mMarker->setOutputUnit( unit );
2543  setIntervalUnit( unit );
2544  mOffsetUnit = unit;
2545  setOffsetAlongLineUnit( unit );
2546 }
2547 
2549 {
2555  || ( mMarker && mMarker->usesMapUnits() );
2556 }
2557 
2558 QSet<QString> QgsMarkerLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2559 {
2560  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2561  if ( mMarker )
2562  attr.unite( mMarker->usedAttributes( context ) );
2563  return attr;
2564 }
2565 
2567 {
2569  return true;
2570  if ( mMarker && mMarker->hasDataDefinedProperties() )
2571  return true;
2572  return false;
2573 }
2574 
2576 {
2577  return ( mMarker->size( context ) / 2.0 ) +
2579 }
2580 
2581 
2582 //
2583 // QgsHashedLineSymbolLayer
2584 //
2585 
2586 QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2587  : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2588 {
2589  setSubSymbol( new QgsLineSymbol() );
2590 }
2591 
2593 
2595 {
2596  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2598 
2599  if ( props.contains( QStringLiteral( "interval" ) ) )
2600  interval = props[QStringLiteral( "interval" )].toDouble();
2601  if ( props.contains( QStringLiteral( "rotate" ) ) )
2602  rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2603 
2604  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2605  setCommonProperties( x.get(), props );
2606  if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2607  {
2608  x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2609  }
2610 
2611  if ( props.contains( QStringLiteral( "hash_length" ) ) )
2612  x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2613 
2614  if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2615  x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2616 
2617  if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2618  x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2619 
2620  return x.release();
2621 }
2622 
2624 {
2625  return QStringLiteral( "HashLine" );
2626 }
2627 
2629 {
2630  // if being rotated, it gets initialized with every line segment
2631  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2632  if ( rotateSymbols() )
2634  mHashSymbol->setRenderHints( hints );
2635 
2636  mHashSymbol->startRender( context.renderContext(), context.fields() );
2637 }
2638 
2640 {
2641  mHashSymbol->stopRender( context.renderContext() );
2642 }
2643 
2645 {
2646  QVariantMap map = QgsTemplatedLineSymbolLayerBase::properties();
2647  map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2648 
2649  map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2650  map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2651  map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2652 
2653  return map;
2654 }
2655 
2657 {
2658  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2659  copyTemplateSymbolProperties( x.get() );
2660  x->setHashAngle( mHashAngle );
2661  x->setHashLength( mHashLength );
2662  x->setHashLengthUnit( mHashLengthUnit );
2663  x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2664  return x.release();
2665 }
2666 
2667 void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2668 {
2669  mHashSymbol->setColor( color );
2670  mColor = color;
2671 }
2672 
2674 {
2675  return mHashSymbol ? mHashSymbol->color() : mColor;
2676 }
2677 
2679 {
2680  return mHashSymbol.get();
2681 }
2682 
2684 {
2685  if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2686  {
2687  delete symbol;
2688  return false;
2689  }
2690 
2691  mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2692  mColor = mHashSymbol->color();
2693  return true;
2694 }
2695 
2696 void QgsHashedLineSymbolLayer::setWidth( const double width )
2697 {
2698  mHashLength = width;
2699 }
2700 
2702 {
2703  return mHashLength;
2704 }
2705 
2707 {
2708  return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2709 }
2710 
2712 {
2713  return ( mHashSymbol->width( context ) / 2.0 )
2714  + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2715  + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
2716 }
2717 
2719 {
2721  mHashSymbol->setOutputUnit( unit );
2722  setIntervalUnit( unit );
2723  mOffsetUnit = unit;
2724  setOffsetAlongLineUnit( unit );
2725 }
2726 
2727 QSet<QString> QgsHashedLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2728 {
2729  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2730  if ( mHashSymbol )
2731  attr.unite( mHashSymbol->usedAttributes( context ) );
2732  return attr;
2733 }
2734 
2736 {
2738  return true;
2739  if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2740  return true;
2741  return false;
2742 }
2743 
2745 {
2746  if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2747  {
2748  mHashSymbol->setDataDefinedWidth( property );
2749  }
2751 }
2752 
2754 {
2755  return mHashLengthUnit == QgsUnitTypes::RenderMapUnits || mHashLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
2761  || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2762 }
2763 
2765 {
2766  mSymbolLineAngle = angle;
2767 }
2768 
2770 {
2771  return mSymbolAngle;
2772 }
2773 
2775 {
2776  mSymbolAngle = angle;
2777 }
2778 
2779 void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2780 {
2781  double lineLength = mHashLength;
2783  {
2784  context.expressionContext().setOriginalValueVariable( mHashLength );
2786  }
2787  const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2788 
2789  double hashAngle = mHashAngle;
2791  {
2792  context.expressionContext().setOriginalValueVariable( mHashAngle );
2794  }
2795 
2796  QgsPointXY center( point );
2797  QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2798  QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2799 
2800  QPolygonF points;
2801  points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2802 
2803  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2805 
2806  mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2807 
2808  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2809 }
2810 
2812 {
2813  return mHashAngle;
2814 }
2815 
2817 {
2818  mHashAngle = angle;
2819 }
2820 
2821 void QgsHashedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2822 {
2823  const double prevOpacity = mHashSymbol->opacity();
2824  mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2826  mHashSymbol->setOpacity( prevOpacity );
2827 }
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
@ 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,...
@ Marker
Marker symbol.
@ Line
Line symbol.
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 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.
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...
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
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
The fill color.
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
The fill color.
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.
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
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
The fill color.
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
The fill color.
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.
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
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:232
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...
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.
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 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 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.
@ 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)
@ 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 QColor color() const
The fill color.
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 QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
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:38
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:97
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.
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...
Placement placement() const
Returns the placement of the symbols.
Placement
Defines how/where the templated symbol should be placed on the line.
@ Vertex
Place symbols on every vertex in the line.
@ LastVertex
Place symbols on the last vertex in the line.
@ CentralPoint
Place symbols at the mid point of the line.
@ FirstVertex
Place symbols on the first vertex in the line.
@ SegmentCenter
Place symbols at the center of every line segment.
@ Interval
Place symbols at regular intervals.
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
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.
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 setPlacement(Placement placement)
Sets the placement of the symbols.
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...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
QgsUnitTypes::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
QgsUnitTypes::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
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
@ 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:1198
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1246
QMap< QString, QString > QgsStringMap
Definition: qgis.h:1703
#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