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