QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  p->setBrush( Qt::NoBrush );
423 
424  // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
425  std::unique_ptr< QgsScopedQPainterState > painterState;
426  if ( points.size() <= 2 &&
429  ( p->renderHints() & QPainter::Antialiasing ) )
430  {
431  painterState = std::make_unique< QgsScopedQPainterState >( p );
432  p->setRenderHint( QPainter::Antialiasing, false );
433  }
434 
435  const bool applyPatternTweaks = mAlignDashPattern
436  && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
437  && pen.dashOffset() == 0;
438 
439  if ( qgsDoubleNear( offset, 0 ) )
440  {
441  if ( applyPatternTweaks )
442  {
443  drawPathWithDashPatternTweaks( p, points, pen );
444  }
445  else
446  {
447  p->setPen( pen );
448  QPainterPath path;
449  path.addPolygon( points );
450  p->drawPath( path );
451  }
452  }
453  else
454  {
455  double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
457  {
458  // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
459  // and clamp it to a reasonable range. It's the best we can do in this situation!
460  scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
461  }
462 
463  QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
464  for ( const QPolygonF &part : mline )
465  {
466  if ( applyPatternTweaks )
467  {
468  drawPathWithDashPatternTweaks( p, part, pen );
469  }
470  else
471  {
472  p->setPen( pen );
473  QPainterPath path;
474  path.addPolygon( part );
475  p->drawPath( path );
476  }
477  }
478  }
479 }
480 
482 {
483  QVariantMap map;
484  map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
485  map[QStringLiteral( "line_width" )] = QString::number( mWidth );
486  map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
487  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
488  map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
489  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
490  map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
491  map[QStringLiteral( "offset" )] = QString::number( mOffset );
492  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
493  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
494  map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
495  map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
496  map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
497  map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
498  map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
499  map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
500  map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
501  map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
502  map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
503  map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
504  map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
505  map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
506  map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
507  map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
508  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
509  map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
510  map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
511  return map;
512 }
513 
515 {
517  l->setWidthUnit( mWidthUnit );
521  l->setCustomDashPatternUnit( mCustomDashPatternUnit );
522  l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
523  l->setOffset( mOffset );
524  l->setPenJoinStyle( mPenJoinStyle );
525  l->setPenCapStyle( mPenCapStyle );
526  l->setUseCustomDashPattern( mUseCustomDashPattern );
527  l->setCustomDashVector( mCustomDashVector );
528  l->setDrawInsidePolygon( mDrawInsidePolygon );
530  l->setDashPatternOffset( mDashPatternOffset );
531  l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
532  l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
533  l->setTrimDistanceStart( mTrimDistanceStart );
534  l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
535  l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
536  l->setTrimDistanceEnd( mTrimDistanceEnd );
537  l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
538  l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
539  l->setAlignDashPattern( mAlignDashPattern );
540  l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
541 
543  copyPaintEffect( l );
544  return l;
545 }
546 
547 void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
548 {
549  if ( mPenStyle == Qt::NoPen )
550  return;
551 
552  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
553  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
554  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
555  element.appendChild( symbolizerElem );
556 
557  // <Geometry>
558  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
559 
560  // <Stroke>
561  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
562  symbolizerElem.appendChild( strokeElem );
563 
564  Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
566  QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
568  &mPenJoinStyle, &mPenCapStyle, &customDashVector );
569 
570  // <se:PerpendicularOffset>
571  if ( !qgsDoubleNear( mOffset, 0.0 ) )
572  {
573  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
575  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
576  symbolizerElem.appendChild( perpOffsetElem );
577  }
578 }
579 
580 QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
581 {
582  if ( mUseCustomDashPattern )
583  {
584  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
585  mPen.color(), mPenJoinStyle,
586  mPenCapStyle, mOffset, &mCustomDashVector );
587  }
588  else
589  {
590  return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
591  mPenCapStyle, mOffset );
592  }
593 }
594 
596 {
597  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
598 
599  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
600  if ( strokeElem.isNull() )
601  return nullptr;
602 
603  Qt::PenStyle penStyle;
604  QColor color;
605  double width;
606  Qt::PenJoinStyle penJoinStyle;
607  Qt::PenCapStyle penCapStyle;
608  QVector<qreal> customDashVector;
609 
610  if ( !QgsSymbolLayerUtils::lineFromSld( strokeElem, penStyle,
611  color, width,
613  &customDashVector ) )
614  return nullptr;
615 
616  double offset = 0.0;
617  QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
618  if ( !perpOffsetElem.isNull() )
619  {
620  bool ok;
621  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
622  if ( ok )
623  offset = d;
624  }
625 
626  QString uom = element.attribute( QStringLiteral( "uom" ) );
629 
631  l->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
632  l->setOffset( offset );
635  l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
637  return l;
638 }
639 
640 void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
641 {
642  if ( !dataDefinedProperties().hasActiveProperties() )
643  return; // shortcut
644 
645  //data defined properties
646  bool hasStrokeWidthExpression = false;
648  {
649  context.setOriginalValueVariable( mWidth );
650  double scaledWidth = context.renderContext().convertToPainterUnits(
653  pen.setWidthF( scaledWidth );
654  selPen.setWidthF( scaledWidth );
655  hasStrokeWidthExpression = true;
656  }
657 
658  //color
660  {
662 
664  penColor.setAlphaF( context.opacity() * penColor.alphaF() );
665  pen.setColor( penColor );
666  }
667 
668  //offset
670  {
673  }
674 
675  //dash dot vector
676 
677  //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
678  //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
679  const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
680 
682  {
683  QVector<qreal> dashVector;
685  if ( !exprVal.isNull() )
686  {
687  QStringList dashList = exprVal.toString().split( ';' );
688  QStringList::const_iterator dashIt = dashList.constBegin();
689  for ( ; dashIt != dashList.constEnd(); ++dashIt )
690  {
691  dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
692  }
693  pen.setDashPattern( dashVector );
694  }
695  }
696  else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
697  {
698  //re-scale pattern vector after data defined pen width was applied
699 
700  QVector<qreal> scaledVector;
701  for ( double v : std::as_const( mCustomDashVector ) )
702  {
703  //the dash is specified in terms of pen widths, therefore the division
704  scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
705  }
706  mPen.setDashPattern( scaledVector );
707  }
708 
709  // dash pattern offset
710  double patternOffset = mDashPatternOffset;
711  if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
712  {
713  context.setOriginalValueVariable( patternOffset );
715  pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
716  }
717 
718  //line style
720  {
723  if ( !exprVal.isNull() )
724  pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
725  }
726 
727  //join style
729  {
732  if ( !exprVal.isNull() )
733  pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
734  }
735 
736  //cap style
738  {
741  if ( !exprVal.isNull() )
742  pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
743  }
744 }
745 
746 void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
747 {
748  if ( pen.dashPattern().empty() || points.size() < 2 )
749  return;
750 
751  QVector< qreal > sourcePattern = pen.dashPattern();
752  const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
753  // back to painter units
754  for ( int i = 0; i < sourcePattern.size(); ++ i )
755  sourcePattern[i] *= pen.widthF();
756 
757  if ( pen.widthF() <= 1.0 )
758  pen.setWidthF( 1.0001 );
759 
760  QVector< qreal > buffer;
761  QPolygonF bufferedPoints;
762  QPolygonF previousSegmentBuffer;
763  // we iterate through the line points, building a custom dash pattern and adding it to the buffer
764  // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
765  // and then append the buffer to the output pattern.
766 
767  auto ptIt = points.constBegin();
768  double totalBufferLength = 0;
769  int patternIndex = 0;
770  double currentRemainingDashLength = 0;
771  double currentRemainingGapLength = 0;
772 
773  auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
774  {
775  QVector< qreal > result;
776  result.reserve( buffer.size() );
777  for ( auto it = buffer.begin(); it != buffer.end(); )
778  {
779  qreal dash = *it++;
780  qreal gap = *it++;
781  while ( dash == 0 && !result.empty() )
782  {
783  result.last() += gap;
784 
785  if ( it == buffer.end() )
786  return result;
787  dash = *it++;
788  gap = *it++;
789  }
790  while ( gap == 0 && it != buffer.end() )
791  {
792  dash += *it++;
793  gap = *it++;
794  }
795  result << dash << gap;
796  }
797  return result;
798  };
799 
800  double currentBufferLineLength = 0;
801  auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
802  dashWidthDiv, &compressPattern]( QPointF * nextPoint )
803  {
804  if ( buffer.empty() || bufferedPoints.size() < 2 )
805  {
806  return;
807  }
808 
809  if ( currentRemainingDashLength )
810  {
811  // ended midway through a dash -- we want to finish this off
812  buffer << currentRemainingDashLength << 0.0;
813  totalBufferLength += currentRemainingDashLength;
814  }
815  QVector< qreal > compressed = compressPattern( buffer );
816  if ( !currentRemainingDashLength )
817  {
818  // ended midway through a gap -- we don't want this, we want to end at previous dash
819  totalBufferLength -= compressed.last();
820  compressed.last() = 0;
821  }
822 
823  // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
824  const double scaleFactor = currentBufferLineLength / totalBufferLength;
825 
826  bool shouldFlushPreviousSegmentBuffer = false;
827 
828  if ( !previousSegmentBuffer.empty() )
829  {
830  // add first dash from current buffer
831  QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
832  if ( !firstDashSubstring.empty() )
833  QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
834 
835  // then we skip over the first dash and gap for this segment
836  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
837 
838  compressed = compressed.mid( 2 );
839  shouldFlushPreviousSegmentBuffer = !compressed.empty();
840  }
841 
842  if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
843  {
844  QPen adjustedPen = pen;
845  adjustedPen.setStyle( Qt::SolidLine );
846  painter->setPen( adjustedPen );
847  QPainterPath path;
848  path.addPolygon( previousSegmentBuffer );
849  painter->drawPath( path );
850  previousSegmentBuffer.clear();
851  }
852 
853  double finalDash = 0;
854  if ( nextPoint )
855  {
856  // sharp bend:
857  // 1. rewind buffered points line by final dash and gap length
858  // (later) 2. draw the bend with a solid line of length 2 * final dash size
859 
860  if ( !compressed.empty() )
861  {
862  finalDash = compressed.at( compressed.size() - 2 );
863  const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
864 
865  const QPolygonF thisPoints = bufferedPoints;
866  bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
867  previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
868  }
869  else
870  {
871  previousSegmentBuffer << bufferedPoints;
872  }
873  }
874 
875  currentBufferLineLength = 0;
876  currentRemainingDashLength = 0;
877  currentRemainingGapLength = 0;
878  totalBufferLength = 0;
879  buffer.clear();
880 
881  if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
882  {
883  QPen adjustedPen = pen;
884  if ( !compressed.empty() )
885  {
886  // maximum size of dash pattern is 32 elements
887  compressed = compressed.mid( 0, 32 );
888  std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
889  adjustedPen.setDashPattern( compressed );
890  }
891  else
892  {
893  adjustedPen.setStyle( Qt::SolidLine );
894  }
895 
896  painter->setPen( adjustedPen );
897  QPainterPath path;
898  path.addPolygon( bufferedPoints );
899  painter->drawPath( path );
900  }
901 
902  bufferedPoints.clear();
903  };
904 
905  QPointF p1;
906  QPointF p2 = *ptIt;
907  ptIt++;
908  bufferedPoints << p2;
909  for ( ; ptIt != points.constEnd(); ++ptIt )
910  {
911  p1 = *ptIt;
912  if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
913  {
914  continue;
915  }
916 
917  double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
918  currentBufferLineLength += remainingSegmentDistance;
919  while ( true )
920  {
921  // handle currentRemainingDashLength/currentRemainingGapLength
922  if ( currentRemainingDashLength > 0 )
923  {
924  // bit more of dash to insert
925  if ( remainingSegmentDistance >= currentRemainingDashLength )
926  {
927  // all of dash fits in
928  buffer << currentRemainingDashLength << 0.0;
929  totalBufferLength += currentRemainingDashLength;
930  remainingSegmentDistance -= currentRemainingDashLength;
931  patternIndex++;
932  currentRemainingDashLength = 0.0;
933  currentRemainingGapLength = sourcePattern.at( patternIndex );
934  }
935  else
936  {
937  // only part of remaining dash fits in
938  buffer << remainingSegmentDistance << 0.0;
939  totalBufferLength += remainingSegmentDistance;
940  currentRemainingDashLength -= remainingSegmentDistance;
941  break;
942  }
943  }
944  if ( currentRemainingGapLength > 0 )
945  {
946  // bit more of gap to insert
947  if ( remainingSegmentDistance >= currentRemainingGapLength )
948  {
949  // all of gap fits in
950  buffer << 0.0 << currentRemainingGapLength;
951  totalBufferLength += currentRemainingGapLength;
952  remainingSegmentDistance -= currentRemainingGapLength;
953  currentRemainingGapLength = 0.0;
954  patternIndex++;
955  }
956  else
957  {
958  // only part of remaining gap fits in
959  buffer << 0.0 << remainingSegmentDistance;
960  totalBufferLength += remainingSegmentDistance;
961  currentRemainingGapLength -= remainingSegmentDistance;
962  break;
963  }
964  }
965 
966  if ( patternIndex >= sourcePattern.size() )
967  patternIndex = 0;
968 
969  const double nextPatternDashLength = sourcePattern.at( patternIndex );
970  const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
971  if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
972  {
973  buffer << nextPatternDashLength << nextPatternGapLength;
974  remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
975  totalBufferLength += nextPatternDashLength + nextPatternGapLength;
976  patternIndex += 2;
977  }
978  else if ( nextPatternDashLength <= remainingSegmentDistance )
979  {
980  // can fit in "dash", but not "gap"
981  buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
982  totalBufferLength += remainingSegmentDistance;
983  currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
984  currentRemainingDashLength = 0;
985  patternIndex++;
986  break;
987  }
988  else
989  {
990  // can't fit in "dash"
991  buffer << remainingSegmentDistance << 0.0;
992  totalBufferLength += remainingSegmentDistance;
993  currentRemainingGapLength = 0;
994  currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
995  break;
996  }
997  }
998 
999  bufferedPoints << p1;
1000  if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1001  {
1002  QPointF nextPoint = *( ptIt + 1 );
1003 
1004  // extreme angles form more than 45 degree angle at a node
1005  if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1006  {
1007  // extreme angle. Rescale buffer and flush
1008  flushBuffer( &nextPoint );
1009  bufferedPoints << p1;
1010  // restart the line with the full length of the most recent dash element -- see
1011  // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1012  if ( patternIndex % 2 == 1 )
1013  {
1014  patternIndex--;
1015  }
1016  currentRemainingDashLength = sourcePattern.at( patternIndex );
1017  }
1018  }
1019 
1020  p2 = p1;
1021  }
1022 
1023  flushBuffer( nullptr );
1024  if ( !previousSegmentBuffer.empty() )
1025  {
1026  QPen adjustedPen = pen;
1027  adjustedPen.setStyle( Qt::SolidLine );
1028  painter->setPen( adjustedPen );
1029  QPainterPath path;
1030  path.addPolygon( previousSegmentBuffer );
1031  painter->drawPath( path );
1032  previousSegmentBuffer.clear();
1033  }
1034 }
1035 
1037 {
1038  if ( mDrawInsidePolygon )
1039  {
1040  //set to clip line to the interior of polygon, so we expect no bleed
1041  return 0;
1042  }
1043  else
1044  {
1045  return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1047  }
1048 }
1049 
1051 {
1052  unit = mCustomDashPatternUnit;
1053  return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1054 }
1055 
1057 {
1058  return mPenStyle;
1059 }
1060 
1062 {
1063  double width = mWidth;
1065  {
1066  context.setOriginalValueVariable( mWidth );
1068  }
1069 
1072  {
1074  }
1075  return width;
1076 }
1077 
1079 {
1081  {
1084  }
1085  return mColor;
1086 }
1087 
1089 {
1090  return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1091 }
1092 
1094 {
1095  return mAlignDashPattern;
1096 }
1097 
1099 {
1100  mAlignDashPattern = enabled;
1101 }
1102 
1104 {
1105  return mPatternCartographicTweakOnSharpCorners;
1106 }
1107 
1109 {
1110  mPatternCartographicTweakOnSharpCorners = enabled;
1111 }
1112 
1114 {
1115  Q_UNUSED( e )
1116  double offset = mOffset;
1117 
1119  {
1120  context.setOriginalValueVariable( mOffset );
1122  }
1123 
1126  {
1128  }
1129  return -offset; //direction seems to be inverse to symbology offset
1130 }
1131 
1133 
1135 
1136 class MyLine
1137 {
1138  public:
1139  MyLine( QPointF p1, QPointF p2 )
1140  : mVertical( false )
1141  , mIncreasing( false )
1142  , mT( 0.0 )
1143  , mLength( 0.0 )
1144  {
1145  if ( p1 == p2 )
1146  return; // invalid
1147 
1148  // tangent and direction
1149  if ( qgsDoubleNear( p1.x(), p2.x() ) )
1150  {
1151  // vertical line - tangent undefined
1152  mVertical = true;
1153  mIncreasing = ( p2.y() > p1.y() );
1154  }
1155  else
1156  {
1157  mVertical = false;
1158  mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1159  mIncreasing = ( p2.x() > p1.x() );
1160  }
1161 
1162  // length
1163  double x = ( p2.x() - p1.x() );
1164  double y = ( p2.y() - p1.y() );
1165  mLength = std::sqrt( x * x + y * y );
1166  }
1167 
1168  // return angle in radians
1169  double angle()
1170  {
1171  double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1172 
1173  if ( !mIncreasing )
1174  a += M_PI;
1175  return a;
1176  }
1177 
1178  // return difference for x,y when going along the line with specified interval
1179  QPointF diffForInterval( double interval )
1180  {
1181  if ( mVertical )
1182  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1183 
1184  double alpha = std::atan( mT );
1185  double dx = std::cos( alpha ) * interval;
1186  double dy = std::sin( alpha ) * interval;
1187  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1188  }
1189 
1190  double length() { return mLength; }
1191 
1192  protected:
1193  bool mVertical;
1194  bool mIncreasing;
1195  double mT;
1196  double mLength;
1197 };
1198 
1200 
1201 //
1202 // QgsTemplatedLineSymbolLayerBase
1203 //
1205  : mRotateSymbols( rotateSymbol )
1206  , mInterval( interval )
1207 {
1208 
1209 }
1210 
1212 
1214 {
1215  double offset = mOffset;
1216 
1218  {
1219  context.setOriginalValueVariable( mOffset );
1221  }
1222 
1224 
1226  {
1228  if ( !exprVal.isNull() )
1229  {
1230  QString placementString = exprVal.toString();
1231  if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1232  {
1234  }
1235  else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1236  {
1238  }
1239  else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1240  {
1242  }
1243  else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1244  {
1246  }
1247  else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1248  {
1250  }
1251  else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1252  {
1254  }
1255  else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1256  {
1258  }
1259  else
1260  {
1262  }
1263  }
1264  }
1265 
1266  QgsScopedQPainterState painterState( context.renderContext().painter() );
1267 
1268  double averageOver = mAverageAngleLength;
1270  {
1271  context.setOriginalValueVariable( mAverageAngleLength );
1273  }
1274  averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1275 
1276  if ( qgsDoubleNear( offset, 0.0 ) )
1277  {
1278  switch ( placement )
1279  {
1280  case Interval:
1281  renderPolylineInterval( points, context, averageOver );
1282  break;
1283 
1284  case CentralPoint:
1285  renderPolylineCentral( points, context, averageOver );
1286  break;
1287 
1288  case Vertex:
1289  case LastVertex:
1290  case FirstVertex:
1291  case CurvePoint:
1292  case SegmentCenter:
1293  renderPolylineVertex( points, context, placement );
1294  break;
1295  }
1296  }
1297  else
1298  {
1299  context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1301 
1302  for ( int part = 0; part < mline.count(); ++part )
1303  {
1304  const QPolygonF &points2 = mline[ part ];
1305 
1306  switch ( placement )
1307  {
1308  case Interval:
1309  renderPolylineInterval( points2, context, averageOver );
1310  break;
1311 
1312  case CentralPoint:
1313  renderPolylineCentral( points2, context, averageOver );
1314  break;
1315 
1316  case Vertex:
1317  case LastVertex:
1318  case FirstVertex:
1319  case CurvePoint:
1320  case SegmentCenter:
1321  renderPolylineVertex( points2, context, placement );
1322  break;
1323  }
1324  }
1325  }
1326 }
1327 
1328 void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1329 {
1330  const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1331 
1332  if ( curvePolygon )
1333  {
1334  context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1335  }
1336 
1337  QgsExpressionContextScope *scope = nullptr;
1338  std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1339  if ( hasDataDefinedProperties() )
1340  {
1341  scope = new QgsExpressionContextScope();
1342  scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1343  }
1344 
1345  switch ( mRingFilter )
1346  {
1347  case AllRings:
1348  case ExteriorRingOnly:
1349  {
1350  if ( scope )
1352 
1353  renderPolyline( points, context );
1354  break;
1355  }
1356  case InteriorRingsOnly:
1357  break;
1358  }
1359 
1360  if ( rings )
1361  {
1362  switch ( mRingFilter )
1363  {
1364  case AllRings:
1365  case InteriorRingsOnly:
1366  {
1367  mOffset = -mOffset; // invert the offset for rings!
1368  for ( int i = 0; i < rings->size(); ++i )
1369  {
1370  if ( curvePolygon )
1371  {
1372  context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1373  }
1374  if ( scope )
1376 
1377  renderPolyline( rings->at( i ), context );
1378  }
1379  mOffset = -mOffset;
1380  }
1381  break;
1382  case ExteriorRingOnly:
1383  break;
1384  }
1385  }
1386 }
1387 
1389 {
1391  if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1392  {
1394  }
1395  return unit;
1396 }
1397 
1399 {
1401  setIntervalMapUnitScale( scale );
1402  mOffsetMapUnitScale = scale;
1404 }
1405 
1407 {
1411  {
1412  return mOffsetMapUnitScale;
1413  }
1414  return QgsMapUnitScale();
1415 }
1416 
1418 {
1419  QVariantMap map;
1420  map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1421  map[QStringLiteral( "interval" )] = QString::number( interval() );
1422  map[QStringLiteral( "offset" )] = QString::number( mOffset );
1423  map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1424  map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1425  map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1426  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1427  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1428  map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1429  map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1430  map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1431  map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1432  map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1433 
1434  switch ( mPlacement )
1435  {
1436  case Vertex:
1437  map[QStringLiteral( "placement" )] = QStringLiteral( "vertex" );
1438  break;
1439  case LastVertex:
1440  map[QStringLiteral( "placement" )] = QStringLiteral( "lastvertex" );
1441  break;
1442  case FirstVertex:
1443  map[QStringLiteral( "placement" )] = QStringLiteral( "firstvertex" );
1444  break;
1445  case CentralPoint:
1446  map[QStringLiteral( "placement" )] = QStringLiteral( "centralpoint" );
1447  break;
1448  case CurvePoint:
1449  map[QStringLiteral( "placement" )] = QStringLiteral( "curvepoint" );
1450  break;
1451  case Interval:
1452  map[QStringLiteral( "placement" )] = QStringLiteral( "interval" );
1453  break;
1454  case SegmentCenter:
1455  map[QStringLiteral( "placement" )] = QStringLiteral( "segmentcenter" );
1456  break;
1457  }
1458 
1459  map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1460  return map;
1461 }
1462 
1464 {
1465  switch ( mPlacement )
1466  {
1470  return true;
1471 
1476  return false;
1477  }
1478  return false;
1479 }
1480 
1482 {
1483  destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1484  destLayer->setOffset( mOffset );
1485  destLayer->setPlacement( placement() );
1486  destLayer->setOffsetUnit( mOffsetUnit );
1488  destLayer->setIntervalUnit( intervalUnit() );
1490  destLayer->setOffsetAlongLine( offsetAlongLine() );
1493  destLayer->setAverageAngleLength( mAverageAngleLength );
1494  destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1495  destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1496  destLayer->setRingFilter( mRingFilter );
1497  copyDataDefinedProperties( destLayer );
1498  copyPaintEffect( destLayer );
1499 }
1500 
1502 {
1503  if ( properties.contains( QStringLiteral( "offset" ) ) )
1504  {
1505  destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1506  }
1507  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1508  {
1509  destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1510  }
1511  if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1512  {
1513  destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1514  }
1515  if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1516  {
1517  destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1518  }
1519  if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1520  {
1521  destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1522  }
1523  if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1524  {
1525  destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1526  }
1527 
1528  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1529  {
1530  destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1531  }
1532  if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1533  {
1534  destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1535  }
1536 
1537  if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1538  {
1539  destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1540  }
1541  if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1542  {
1543  destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1544  }
1545  if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1546  {
1547  destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1548  }
1549 
1550  if ( properties.contains( QStringLiteral( "placement" ) ) )
1551  {
1552  if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1554  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1556  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1558  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1560  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1562  else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1564  else
1566  }
1567 
1568  if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1569  {
1570  destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1571  }
1572 
1574 }
1575 
1576 void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1577 {
1578  if ( points.isEmpty() )
1579  return;
1580 
1581  double lengthLeft = 0; // how much is left until next marker
1582 
1583  QgsRenderContext &rc = context.renderContext();
1584  double interval = mInterval;
1585 
1587  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1588 
1590  {
1591  context.setOriginalValueVariable( mInterval );
1593  }
1594  if ( interval <= 0 )
1595  {
1596  interval = 0.1;
1597  }
1598  double offsetAlongLine = mOffsetAlongLine;
1600  {
1601  context.setOriginalValueVariable( mOffsetAlongLine );
1603  }
1604 
1605  double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1607  {
1608  // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1609  // and clamp it to a reasonable range. It's the best we can do in this situation!
1610  painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, QgsUnitTypes::RenderMillimeters ), 10.0 ), 100.0 );
1611  }
1612 
1613  if ( painterUnitInterval < 0 )
1614  return;
1615 
1616  double painterUnitOffsetAlongLine = rc.convertToPainterUnits( offsetAlongLine, offsetAlongLineUnit(), offsetAlongLineMapUnitScale() );
1618  {
1619  // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1620  // and clamp it to a reasonable range. It's the best we can do in this situation!
1621  painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
1622  }
1623 
1624  lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1625 
1626  if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1627  {
1628  QVector< QPointF > angleStartPoints;
1629  QVector< QPointF > symbolPoints;
1630  QVector< QPointF > angleEndPoints;
1631 
1632  // we collect 3 arrays of points. These correspond to
1633  // 1. the actual point at which to render the symbol
1634  // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1635  // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1636  // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1637  // (or trace past the final point)
1638  collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1639 
1640  if ( symbolPoints.empty() )
1641  {
1642  // no symbols to draw, shortcut out early
1643  return;
1644  }
1645 
1646  if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1647  {
1648  // avoid duplicate points at start and end of closed rings
1649  symbolPoints.pop_back();
1650  }
1651 
1652  angleEndPoints.reserve( symbolPoints.size() );
1653  angleStartPoints.reserve( symbolPoints.size() );
1654  if ( averageOver <= painterUnitOffsetAlongLine )
1655  {
1656  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1657  }
1658  else
1659  {
1660  collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1661  }
1662  collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1663 
1664  int pointNum = 0;
1665  for ( int i = 0; i < symbolPoints.size(); ++ i )
1666  {
1667  if ( context.renderContext().renderingStopped() )
1668  break;
1669 
1670  const QPointF pt = symbolPoints[i];
1671  const QPointF startPt = angleStartPoints[i];
1672  const QPointF endPt = angleEndPoints[i];
1673 
1674  MyLine l( startPt, endPt );
1675  // rotate marker (if desired)
1676  if ( rotateSymbols() )
1677  {
1678  setSymbolLineAngle( l.angle() * 180 / M_PI );
1679  }
1680 
1682  renderSymbol( pt, context.feature(), rc, -1, context.selected() );
1683  }
1684  }
1685  else
1686  {
1687  // not averaging line angle -- always use exact section angle
1688  int pointNum = 0;
1689  QPointF lastPt = points[0];
1690  for ( int i = 1; i < points.count(); ++i )
1691  {
1692  if ( context.renderContext().renderingStopped() )
1693  break;
1694 
1695  const QPointF &pt = points[i];
1696 
1697  if ( lastPt == pt ) // must not be equal!
1698  continue;
1699 
1700  // for each line, find out dx and dy, and length
1701  MyLine l( lastPt, pt );
1702  QPointF diff = l.diffForInterval( painterUnitInterval );
1703 
1704  // if there's some length left from previous line
1705  // use only the rest for the first point in new line segment
1706  double c = 1 - lengthLeft / painterUnitInterval;
1707 
1708  lengthLeft += l.length();
1709 
1710  // rotate marker (if desired)
1711  if ( rotateSymbols() )
1712  {
1713  setSymbolLineAngle( l.angle() * 180 / M_PI );
1714  }
1715 
1716  // while we're not at the end of line segment, draw!
1717  while ( lengthLeft > painterUnitInterval )
1718  {
1719  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1720  lastPt += c * diff;
1721  lengthLeft -= painterUnitInterval;
1723  renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
1724  c = 1; // reset c (if wasn't 1 already)
1725  }
1726 
1727  lastPt = pt;
1728  }
1729 
1730  }
1731 }
1732 
1733 static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1734 {
1735  // calc average angle between the previous and next point
1736  double a1 = MyLine( prevPt, pt ).angle();
1737  double a2 = MyLine( pt, nextPt ).angle();
1738  double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1739 
1740  return std::atan2( unitY, unitX );
1741 }
1742 
1743 void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, QgsTemplatedLineSymbolLayerBase::Placement placement )
1744 {
1745  if ( points.isEmpty() )
1746  return;
1747 
1748  QgsRenderContext &rc = context.renderContext();
1749 
1750  int i = -1, maxCount = 0;
1751  bool isRing = false;
1752 
1754  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1756 
1757  double offsetAlongLine = mOffsetAlongLine;
1759  {
1760  context.setOriginalValueVariable( mOffsetAlongLine );
1762  }
1763  if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1764  {
1765  //scale offset along line
1767  }
1768 
1769  if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1771  {
1773  const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1774 
1775  QgsVertexId vId;
1776  QgsPoint vPoint;
1777  double x, y, z;
1778  QPointF mapPoint;
1779  int pointNum = 0;
1780  while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1781  {
1782  if ( context.renderContext().renderingStopped() )
1783  break;
1784 
1786 
1789  {
1790  //transform
1791  x = vPoint.x();
1792  y = vPoint.y();
1793  z = 0.0;
1794  if ( ct.isValid() )
1795  {
1796  ct.transformInPlace( x, y, z );
1797  }
1798  mapPoint.setX( x );
1799  mapPoint.setY( y );
1800  mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1801  if ( rotateSymbols() )
1802  {
1803  double angle = context.renderContext().geometry()->vertexAngle( vId );
1804  setSymbolLineAngle( angle * 180 / M_PI );
1805  }
1806  renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() );
1807  }
1808  }
1809 
1810  return;
1811  }
1812 
1813  switch ( placement )
1814  {
1815  case FirstVertex:
1816  {
1817  i = 0;
1818  maxCount = 1;
1819  break;
1820  }
1821 
1822  case LastVertex:
1823  {
1824  i = points.count() - 1;
1825  maxCount = points.count();
1826  break;
1827  }
1828 
1829  case Vertex:
1830  case SegmentCenter:
1831  {
1832  i = placement == Vertex ? 0 : 1;
1833  maxCount = points.count();
1834  if ( points.first() == points.last() )
1835  isRing = true;
1836  break;
1837  }
1838 
1839  case Interval:
1840  case CentralPoint:
1841  case CurvePoint:
1842  {
1843  return;
1844  }
1845  }
1846 
1848  {
1849  double distance;
1851  renderOffsetVertexAlongLine( points, i, distance, context );
1852 
1853  return;
1854  }
1855 
1856  int pointNum = 0;
1857  QPointF prevPoint;
1858  if ( placement == SegmentCenter && !points.empty() )
1859  prevPoint = points.at( 0 );
1860 
1861  QPointF symbolPoint;
1862  for ( ; i < maxCount; ++i )
1863  {
1865 
1866  if ( isRing && placement == QgsTemplatedLineSymbolLayerBase::Vertex && i == points.count() - 1 )
1867  {
1868  continue; // don't draw the last marker - it has been drawn already
1869  }
1870 
1871  if ( placement == SegmentCenter )
1872  {
1873  QPointF currentPoint = points.at( i );
1874  symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
1875  0.5 * ( currentPoint.y() + prevPoint.y() ) );
1876  if ( rotateSymbols() )
1877  {
1878  double angle = std::atan2( currentPoint.y() - prevPoint.y(),
1879  currentPoint.x() - prevPoint.x() );
1880  setSymbolLineAngle( angle * 180 / M_PI );
1881  }
1882  prevPoint = currentPoint;
1883  }
1884  else
1885  {
1886  symbolPoint = points.at( i );
1887  // rotate marker (if desired)
1888  if ( rotateSymbols() )
1889  {
1890  double angle = markerAngle( points, isRing, i );
1891  setSymbolLineAngle( angle * 180 / M_PI );
1892  }
1893  }
1894 
1895  renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
1896  }
1897 }
1898 
1899 double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
1900 {
1901  double angle = 0;
1902  const QPointF &pt = points[vertex];
1903 
1904  if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
1905  {
1906  int prevIndex = vertex - 1;
1907  int nextIndex = vertex + 1;
1908 
1909  if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
1910  {
1911  prevIndex = points.count() - 2;
1912  nextIndex = 1;
1913  }
1914 
1915  QPointF prevPoint, nextPoint;
1916  while ( prevIndex >= 0 )
1917  {
1918  prevPoint = points[ prevIndex ];
1919  if ( prevPoint != pt )
1920  {
1921  break;
1922  }
1923  --prevIndex;
1924  }
1925 
1926  while ( nextIndex < points.count() )
1927  {
1928  nextPoint = points[ nextIndex ];
1929  if ( nextPoint != pt )
1930  {
1931  break;
1932  }
1933  ++nextIndex;
1934  }
1935 
1936  if ( prevIndex >= 0 && nextIndex < points.count() )
1937  {
1938  angle = _averageAngle( prevPoint, pt, nextPoint );
1939  }
1940  }
1941  else //no ring and vertex is at start / at end
1942  {
1943  if ( vertex == 0 )
1944  {
1945  while ( vertex < points.size() - 1 )
1946  {
1947  const QPointF &nextPt = points[vertex + 1];
1948  if ( pt != nextPt )
1949  {
1950  angle = MyLine( pt, nextPt ).angle();
1951  return angle;
1952  }
1953  ++vertex;
1954  }
1955  }
1956  else
1957  {
1958  // use last segment's angle
1959  while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
1960  {
1961  const QPointF &prevPt = points[vertex - 1];
1962  if ( pt != prevPt )
1963  {
1964  angle = MyLine( prevPt, pt ).angle();
1965  return angle;
1966  }
1967  --vertex;
1968  }
1969  }
1970  }
1971  return angle;
1972 }
1973 
1974 void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context )
1975 {
1976  if ( points.isEmpty() )
1977  return;
1978 
1979  QgsRenderContext &rc = context.renderContext();
1980  if ( qgsDoubleNear( distance, 0.0 ) )
1981  {
1982  // rotate marker (if desired)
1983  if ( rotateSymbols() )
1984  {
1985  bool isRing = false;
1986  if ( points.first() == points.last() )
1987  isRing = true;
1988  double angle = markerAngle( points, isRing, vertex );
1989  setSymbolLineAngle( angle * 180 / M_PI );
1990  }
1991  renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
1992  return;
1993  }
1994 
1995  int pointIncrement = distance > 0 ? 1 : -1;
1996  QPointF previousPoint = points[vertex];
1997  int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
1998  int endPoint = distance > 0 ? points.count() - 1 : 0;
1999  double distanceLeft = std::fabs( distance );
2000 
2001  for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2002  {
2003  const QPointF &pt = points[i];
2004 
2005  if ( previousPoint == pt ) // must not be equal!
2006  continue;
2007 
2008  // create line segment
2009  MyLine l( previousPoint, pt );
2010 
2011  if ( distanceLeft < l.length() )
2012  {
2013  //destination point is in current segment
2014  QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2015  // rotate marker (if desired)
2016  if ( rotateSymbols() )
2017  {
2018  setSymbolLineAngle( l.angle() * 180 / M_PI );
2019  }
2020  renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
2021  return;
2022  }
2023 
2024  distanceLeft -= l.length();
2025  previousPoint = pt;
2026  }
2027 
2028  //didn't find point
2029 }
2030 
2031 void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2032 {
2033  if ( p.empty() )
2034  return;
2035 
2036  QVector< QPointF > points = p;
2037  const bool closedRing = points.first() == points.last();
2038 
2039  double lengthLeft = initialOffset;
2040 
2041  double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2042  if ( initialLagLeft < 0 && closedRing )
2043  {
2044  // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2045  QPointF lastPt = points.constLast();
2046  QVector< QPointF > pseudoPoints;
2047  for ( int i = points.count() - 2; i > 0; --i )
2048  {
2049  if ( initialLagLeft >= 0 )
2050  {
2051  break;
2052  }
2053 
2054  const QPointF &pt = points[i];
2055 
2056  if ( lastPt == pt ) // must not be equal!
2057  continue;
2058 
2059  MyLine l( lastPt, pt );
2060  initialLagLeft += l.length();
2061  lastPt = pt;
2062 
2063  pseudoPoints << pt;
2064  }
2065  std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2066 
2067  points = pseudoPoints;
2068  points.append( p );
2069  }
2070  else
2071  {
2072  while ( initialLagLeft < 0 )
2073  {
2074  dest << points.constFirst();
2075  initialLagLeft += intervalPainterUnits;
2076  }
2077  }
2078  if ( initialLag > 0 )
2079  {
2080  lengthLeft += intervalPainterUnits - initialLagLeft;
2081  }
2082 
2083  QPointF lastPt = points[0];
2084  for ( int i = 1; i < points.count(); ++i )
2085  {
2086  const QPointF &pt = points[i];
2087 
2088  if ( lastPt == pt ) // must not be equal!
2089  {
2090  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2091  {
2092  lastPt = points[0];
2093  i = 0;
2094  }
2095  continue;
2096  }
2097 
2098  // for each line, find out dx and dy, and length
2099  MyLine l( lastPt, pt );
2100  QPointF diff = l.diffForInterval( intervalPainterUnits );
2101 
2102  // if there's some length left from previous line
2103  // use only the rest for the first point in new line segment
2104  double c = 1 - lengthLeft / intervalPainterUnits;
2105 
2106  lengthLeft += l.length();
2107 
2108 
2109  while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2110  {
2111  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2112  lastPt += c * diff;
2113  lengthLeft -= intervalPainterUnits;
2114  dest << lastPt;
2115  c = 1; // reset c (if wasn't 1 already)
2116  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2117  break;
2118  }
2119  lastPt = pt;
2120 
2121  if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2122  break;
2123 
2124  // if a closed ring, we keep looping around the ring until we hit the required number of points
2125  if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2126  {
2127  lastPt = points[0];
2128  i = 0;
2129  }
2130  }
2131 
2132  if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2133  {
2134  // pad with repeating last point to match desired size
2135  while ( dest.size() < numberPointsRequired )
2136  dest << points.constLast();
2137  }
2138 }
2139 
2140 void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2141 {
2142  if ( !points.isEmpty() )
2143  {
2144  // calc length
2145  qreal length = 0;
2146  QPolygonF::const_iterator it = points.constBegin();
2147  QPointF last = *it;
2148  for ( ++it; it != points.constEnd(); ++it )
2149  {
2150  length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2151  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2152  last = *it;
2153  }
2154  if ( qgsDoubleNear( length, 0.0 ) )
2155  return;
2156 
2157  const double midPoint = length / 2;
2158 
2159  QPointF pt;
2160  double thisSymbolAngle = 0;
2161 
2162  if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2163  {
2164  QVector< QPointF > angleStartPoints;
2165  QVector< QPointF > symbolPoints;
2166  QVector< QPointF > angleEndPoints;
2167  // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2168  collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2169  collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2170  collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2171 
2172  pt = symbolPoints.at( 1 );
2173  MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2174  thisSymbolAngle = l.angle();
2175  }
2176  else
2177  {
2178  // find the segment where the central point lies
2179  it = points.constBegin();
2180  last = *it;
2181  qreal last_at = 0, next_at = 0;
2182  QPointF next;
2183  int segment = 0;
2184  for ( ++it; it != points.constEnd(); ++it )
2185  {
2186  next = *it;
2187  next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2188  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2189  if ( next_at >= midPoint )
2190  break; // we have reached the center
2191  last = *it;
2192  last_at = next_at;
2193  segment++;
2194  }
2195 
2196  // find out the central point on segment
2197  MyLine l( last, next ); // for line angle
2198  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2199  pt = last + ( next - last ) * k;
2200  thisSymbolAngle = l.angle();
2201  }
2202 
2203  // draw the marker
2204  // rotate marker (if desired)
2205  if ( rotateSymbols() )
2206  {
2207  setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2208  }
2209 
2210  renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2211 
2212  }
2213 }
2214 
2216 {
2217  return mMarker.get();
2218 }
2219 
2221 {
2222  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2223  {
2224  delete symbol;
2225  return false;
2226  }
2227 
2228  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2229  mColor = mMarker->color();
2230  return true;
2231 }
2232 
2233 
2234 
2235 //
2236 // QgsMarkerLineSymbolLayer
2237 //
2238 
2239 QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2240  : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2241 {
2242  setSubSymbol( new QgsMarkerSymbol() );
2243 }
2244 
2246 
2248 {
2249  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2251 
2252  if ( props.contains( QStringLiteral( "interval" ) ) )
2253  interval = props[QStringLiteral( "interval" )].toDouble();
2254  if ( props.contains( QStringLiteral( "rotate" ) ) )
2255  rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2256 
2257  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2258  setCommonProperties( x.get(), props );
2259  return x.release();
2260 }
2261 
2263 {
2264  return QStringLiteral( "MarkerLine" );
2265 }
2266 
2267 void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2268 {
2269  mMarker->setColor( color );
2270  mColor = color;
2271 }
2272 
2274 {
2275  return mMarker ? mMarker->color() : mColor;
2276 }
2277 
2279 {
2280  // if being rotated, it gets initialized with every line segment
2281  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2282  if ( rotateSymbols() )
2284  mMarker->setRenderHints( hints );
2285 
2286  mMarker->startRender( context.renderContext(), context.fields() );
2287 }
2288 
2290 {
2291  mMarker->stopRender( context.renderContext() );
2292 }
2293 
2294 
2296 {
2297  std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2298  copyTemplateSymbolProperties( x.get() );
2299  return x.release();
2300 }
2301 
2302 void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2303 {
2304  for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2305  {
2306  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2307  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2308  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2309  element.appendChild( symbolizerElem );
2310 
2311  // <Geometry>
2312  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2313 
2314  QString gap;
2315  switch ( placement() )
2316  {
2318  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2319  break;
2321  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2322  break;
2324  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2325  break;
2327  // no way to get line/polygon's vertices, use a VendorOption
2328  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2329  break;
2330  default:
2332  gap = qgsDoubleToString( interval );
2333  break;
2334  }
2335 
2336  if ( !rotateSymbols() )
2337  {
2338  // markers in LineSymbolizer must be drawn following the line orientation,
2339  // use a VendorOption when no marker rotation
2340  symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2341  }
2342 
2343  // <Stroke>
2344  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2345  symbolizerElem.appendChild( strokeElem );
2346 
2347  // <GraphicStroke>
2348  QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2349  strokeElem.appendChild( graphicStrokeElem );
2350 
2351  QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2352  if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2353  {
2354  markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2355  }
2356  else if ( layer )
2357  {
2358  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2359  }
2360  else
2361  {
2362  graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2363  }
2364 
2365  if ( !gap.isEmpty() )
2366  {
2367  QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2368  QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap );
2369  graphicStrokeElem.appendChild( gapElem );
2370  }
2371 
2372  if ( !qgsDoubleNear( mOffset, 0.0 ) )
2373  {
2374  QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2376  perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2377  symbolizerElem.appendChild( perpOffsetElem );
2378  }
2379  }
2380 }
2381 
2383 {
2384  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2385 
2386  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2387  if ( strokeElem.isNull() )
2388  return nullptr;
2389 
2390  QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2391  if ( graphicStrokeElem.isNull() )
2392  return nullptr;
2393 
2394  // retrieve vendor options
2395  bool rotateMarker = true;
2397 
2398  QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2399  for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2400  {
2401  if ( it.key() == QLatin1String( "placement" ) )
2402  {
2403  if ( it.value() == QLatin1String( "points" ) )
2405  else if ( it.value() == QLatin1String( "firstPoint" ) )
2407  else if ( it.value() == QLatin1String( "lastPoint" ) )
2409  else if ( it.value() == QLatin1String( "centralPoint" ) )
2411  }
2412  else if ( it.value() == QLatin1String( "rotateMarker" ) )
2413  {
2414  rotateMarker = it.value() == QLatin1String( "0" );
2415  }
2416  }
2417 
2418  std::unique_ptr< QgsMarkerSymbol > marker;
2419 
2421  if ( l )
2422  {
2423  QgsSymbolLayerList layers;
2424  layers.append( l );
2425  marker.reset( new QgsMarkerSymbol( layers ) );
2426  }
2427 
2428  if ( !marker )
2429  return nullptr;
2430 
2431  double interval = 0.0;
2432  QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2433  if ( !gapElem.isNull() )
2434  {
2435  bool ok;
2436  double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2437  if ( ok )
2438  interval = d;
2439  }
2440 
2441  double offset = 0.0;
2442  QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2443  if ( !perpOffsetElem.isNull() )
2444  {
2445  bool ok;
2446  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2447  if ( ok )
2448  offset = d;
2449  }
2450 
2451  QString uom = element.attribute( QStringLiteral( "uom" ) );
2454 
2456  x->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
2457  x->setPlacement( placement );
2458  x->setInterval( interval );
2459  x->setSubSymbol( marker.release() );
2460  x->setOffset( offset );
2461  return x;
2462 }
2463 
2465 {
2466  mMarker->setSize( width );
2467 }
2468 
2470 {
2471  if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2472  {
2473  mMarker->setDataDefinedSize( property );
2474  }
2476 }
2477 
2478 void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2479 {
2480  const double prevOpacity = mMarker->opacity();
2481  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2483  mMarker->setOpacity( prevOpacity );
2484 }
2485 
2487 {
2488  mMarker->setLineAngle( angle );
2489 }
2490 
2492 {
2493  return mMarker->angle();
2494 }
2495 
2497 {
2498  mMarker->setAngle( angle );
2499 }
2500 
2501 void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2502 {
2503  mMarker->renderPoint( point, feature, context, layer, selected );
2504 }
2505 
2507 {
2508  return mMarker->size();
2509 }
2510 
2512 {
2513  return mMarker->size( context );
2514 }
2515 
2517 {
2519  mMarker->setOutputUnit( unit );
2520  setIntervalUnit( unit );
2521  mOffsetUnit = unit;
2522  setOffsetAlongLineUnit( unit );
2523 }
2524 
2526 {
2532  || ( mMarker && mMarker->usesMapUnits() );
2533 }
2534 
2535 QSet<QString> QgsMarkerLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2536 {
2537  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2538  if ( mMarker )
2539  attr.unite( mMarker->usedAttributes( context ) );
2540  return attr;
2541 }
2542 
2544 {
2546  return true;
2547  if ( mMarker && mMarker->hasDataDefinedProperties() )
2548  return true;
2549  return false;
2550 }
2551 
2553 {
2554  return ( mMarker->size( context ) / 2.0 ) +
2556 }
2557 
2558 
2559 //
2560 // QgsHashedLineSymbolLayer
2561 //
2562 
2563 QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2564  : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2565 {
2566  setSubSymbol( new QgsLineSymbol() );
2567 }
2568 
2570 
2572 {
2573  bool rotate = DEFAULT_MARKERLINE_ROTATE;
2575 
2576  if ( props.contains( QStringLiteral( "interval" ) ) )
2577  interval = props[QStringLiteral( "interval" )].toDouble();
2578  if ( props.contains( QStringLiteral( "rotate" ) ) )
2579  rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2580 
2581  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2582  setCommonProperties( x.get(), props );
2583  if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2584  {
2585  x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2586  }
2587 
2588  if ( props.contains( QStringLiteral( "hash_length" ) ) )
2589  x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2590 
2591  if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2592  x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2593 
2594  if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2595  x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2596 
2597  return x.release();
2598 }
2599 
2601 {
2602  return QStringLiteral( "HashLine" );
2603 }
2604 
2606 {
2607  // if being rotated, it gets initialized with every line segment
2608  Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2609  if ( rotateSymbols() )
2611  mHashSymbol->setRenderHints( hints );
2612 
2613  mHashSymbol->startRender( context.renderContext(), context.fields() );
2614 }
2615 
2617 {
2618  mHashSymbol->stopRender( context.renderContext() );
2619 }
2620 
2622 {
2623  QVariantMap map = QgsTemplatedLineSymbolLayerBase::properties();
2624  map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2625 
2626  map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2627  map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2628  map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2629 
2630  return map;
2631 }
2632 
2634 {
2635  std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2636  copyTemplateSymbolProperties( x.get() );
2637  x->setHashAngle( mHashAngle );
2638  x->setHashLength( mHashLength );
2639  x->setHashLengthUnit( mHashLengthUnit );
2640  x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2641  return x.release();
2642 }
2643 
2644 void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2645 {
2646  mHashSymbol->setColor( color );
2647  mColor = color;
2648 }
2649 
2651 {
2652  return mHashSymbol ? mHashSymbol->color() : mColor;
2653 }
2654 
2656 {
2657  return mHashSymbol.get();
2658 }
2659 
2661 {
2662  if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2663  {
2664  delete symbol;
2665  return false;
2666  }
2667 
2668  mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2669  mColor = mHashSymbol->color();
2670  return true;
2671 }
2672 
2673 void QgsHashedLineSymbolLayer::setWidth( const double width )
2674 {
2675  mHashLength = width;
2676 }
2677 
2679 {
2680  return mHashLength;
2681 }
2682 
2684 {
2685  return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2686 }
2687 
2689 {
2690  return ( mHashSymbol->width( context ) / 2.0 )
2691  + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2692  + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
2693 }
2694 
2696 {
2698  mHashSymbol->setOutputUnit( unit );
2699  setIntervalUnit( unit );
2700  mOffsetUnit = unit;
2701  setOffsetAlongLineUnit( unit );
2702 }
2703 
2704 QSet<QString> QgsHashedLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2705 {
2706  QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2707  if ( mHashSymbol )
2708  attr.unite( mHashSymbol->usedAttributes( context ) );
2709  return attr;
2710 }
2711 
2713 {
2715  return true;
2716  if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2717  return true;
2718  return false;
2719 }
2720 
2722 {
2723  if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2724  {
2725  mHashSymbol->setDataDefinedWidth( property );
2726  }
2728 }
2729 
2731 {
2732  return mHashLengthUnit == QgsUnitTypes::RenderMapUnits || mHashLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
2738  || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2739 }
2740 
2742 {
2743  mSymbolLineAngle = angle;
2744 }
2745 
2747 {
2748  return mSymbolAngle;
2749 }
2750 
2752 {
2753  mSymbolAngle = angle;
2754 }
2755 
2756 void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2757 {
2758  double lineLength = mHashLength;
2760  {
2761  context.expressionContext().setOriginalValueVariable( mHashLength );
2763  }
2764  const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2765 
2766  double hashAngle = mHashAngle;
2768  {
2769  context.expressionContext().setOriginalValueVariable( mHashAngle );
2771  }
2772 
2773  QgsPointXY center( point );
2774  QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2775  QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2776 
2777  QPolygonF points;
2778  points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2779 
2780 
2781  mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2782 }
2783 
2785 {
2786  return mHashAngle;
2787 }
2788 
2790 {
2791  mHashAngle = angle;
2792 }
2793 
2794 void QgsHashedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
2795 {
2796  const double prevOpacity = mHashSymbol->opacity();
2797  mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2799  mHashSymbol->setOpacity( prevOpacity );
2800 }
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
@ 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.
void transformInPlace(double &x, double &y, double &z, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
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:64
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:228
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 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...
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
QColor selectionColor() const
Returns the color to use when rendering selected features.
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Flags flags() const
Returns combination of flags used for rendering.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
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:550
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
QMap< QString, QString > QgsStringMap
Definition: qgis.h:1041
#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.
VertexType type
Vertex type.
@ SegmentVertex
The actual start or end point of a segment.
@ CurveVertex
An intermediate point on a segment defining the curvature of the segment.