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