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, ¤tRemainingDashLength, ¤tRemainingGapLength, ¤tBufferLineLength, &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