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