QGIS API Documentation 3.39.0-Master (67e056379ed)
Loading...
Searching...
No Matches
qgslinesymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinesymbollayer.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslinesymbollayer.h"
17#include "qgscurvepolygon.h"
18#include "qgsdxfexport.h"
19#include "qgssymbollayerutils.h"
20#include "qgsrendercontext.h"
21#include "qgslogger.h"
23#include "qgsunittypes.h"
24#include "qgsproperty.h"
26#include "qgsmarkersymbol.h"
27#include "qgslinesymbol.h"
28#include "qgsapplication.h"
29#include "qgsimagecache.h"
30#include "qgsfeedback.h"
31#include "qgsimageoperation.h"
32#include "qgscolorrampimpl.h"
33#include "qgsfillsymbol.h"
34#include "qgscolorutils.h"
35
36#include <algorithm>
37#include <QPainter>
38#include <QDomDocument>
39#include <QDomElement>
40
41#include <cmath>
42
43QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
44 : mPenStyle( penStyle )
45{
46 mColor = color;
47 mWidth = width;
48 mCustomDashVector << 5 << 2;
49}
50
52
54{
56 mWidthUnit = unit;
57 mOffsetUnit = unit;
58 mCustomDashPatternUnit = unit;
59 mDashPatternOffsetUnit = unit;
60 mTrimDistanceStartUnit = unit;
61 mTrimDistanceEndUnit = unit;
62}
63
65{
67 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
68 {
70 }
71 return unit;
72}
73
79
81{
83 mWidthMapUnitScale = scale;
84 mOffsetMapUnitScale = scale;
85 mCustomDashPatternMapUnitScale = scale;
86}
87
98
100{
104
105 if ( props.contains( QStringLiteral( "line_color" ) ) )
106 {
107 color = QgsColorUtils::colorFromString( props[QStringLiteral( "line_color" )].toString() );
108 }
109 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
110 {
111 color = QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() );
112 }
113 else if ( props.contains( QStringLiteral( "color" ) ) )
114 {
115 //pre 2.5 projects used "color"
116 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
117 }
118 if ( props.contains( QStringLiteral( "line_width" ) ) )
119 {
120 width = props[QStringLiteral( "line_width" )].toDouble();
121 }
122 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
123 {
124 width = props[QStringLiteral( "outline_width" )].toDouble();
125 }
126 else if ( props.contains( QStringLiteral( "width" ) ) )
127 {
128 //pre 2.5 projects used "width"
129 width = props[QStringLiteral( "width" )].toDouble();
130 }
131 if ( props.contains( QStringLiteral( "line_style" ) ) )
132 {
133 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
134 }
135 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
136 {
137 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
138 }
139 else if ( props.contains( QStringLiteral( "penstyle" ) ) )
140 {
141 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
142 }
143
145 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
146 {
147 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
148 }
149 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
150 {
151 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
152 }
153 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
154 {
155 //pre 2.5 projects used "width_unit"
156 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
157 }
158 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
159 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
160 if ( props.contains( QStringLiteral( "offset" ) ) )
161 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
162 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
163 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
164 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
165 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
166 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
167 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
168 if ( props.contains( QStringLiteral( "capstyle" ) ) )
169 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
170
171 if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
172 {
173 l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
174 }
175 if ( props.contains( QStringLiteral( "customdash" ) ) )
176 {
177 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
178 }
179 if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
180 {
181 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
182 }
183 if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
184 {
185 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
186 }
187
188 if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
189 {
190 l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
191 }
192
193 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
194 {
195 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
196 }
197
198 if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
199 l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
200 if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
201 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
202 if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
203 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
204
205 if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
206 l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
207 if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
208 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
209 if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
210 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
211 if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
212 l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
213 if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
214 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
215 if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
216 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
217
218 if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
219 l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
220
221 if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
222 l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
223
225
226 return l;
227}
228
230{
231 return QStringLiteral( "SimpleLine" );
232}
233
238
240{
241 QColor penColor = mColor;
242 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
243 mPen.setColor( penColor );
244 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
245 mPen.setWidthF( scaledWidth );
246
247 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
248 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
249 const double dashWidthDiv = std::max( 1.0, scaledWidth );
250 if ( mUseCustomDashPattern )
251 {
252 mPen.setStyle( Qt::CustomDashLine );
253
254 //scale pattern vector
255
256 QVector<qreal> scaledVector;
257 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
258 for ( ; it != mCustomDashVector.constEnd(); ++it )
259 {
260 //the dash is specified in terms of pen widths, therefore the division
261 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
262 }
263 mPen.setDashPattern( scaledVector );
264 }
265 else
266 {
267 mPen.setStyle( mPenStyle );
268 }
269
270 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
271 {
272 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
273 }
274
275 mPen.setJoinStyle( mPenJoinStyle );
276 mPen.setCapStyle( mPenCapStyle );
277
278 mSelPen = mPen;
279 QColor selColor = context.renderContext().selectionColor();
280 if ( ! SELECTION_IS_OPAQUE )
281 selColor.setAlphaF( context.opacity() );
282 mSelPen.setColor( selColor );
283}
284
286{
287 Q_UNUSED( context )
288}
289
290void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
291{
292 QPainter *p = context.renderContext().painter();
293 if ( !p )
294 {
295 return;
296 }
297
298 QgsExpressionContextScope *scope = nullptr;
299 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
301 {
302 scope = new QgsExpressionContextScope();
303 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
304 }
305
306 if ( mDrawInsidePolygon )
307 p->save();
308
309 switch ( mRingFilter )
310 {
311 case AllRings:
312 case ExteriorRingOnly:
313 {
314 if ( mDrawInsidePolygon )
315 {
316 //only drawing the line on the interior of the polygon, so set clip path for painter
317 QPainterPath clipPath;
318 clipPath.addPolygon( points );
319
320 if ( rings )
321 {
322 //add polygon rings
323 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
324 {
325 QPolygonF ring = *it;
326 clipPath.addPolygon( ring );
327 }
328 }
329
330 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
331 p->setClipPath( clipPath, Qt::IntersectClip );
332 }
333
334 if ( scope )
336
337 renderPolyline( points, context );
338 }
339 break;
340
342 break;
343 }
344
345 if ( rings )
346 {
347 switch ( mRingFilter )
348 {
349 case AllRings:
351 {
352 mOffset = -mOffset; // invert the offset for rings!
353 int ringIndex = 1;
354 for ( const QPolygonF &ring : std::as_const( *rings ) )
355 {
356 if ( scope )
358
359 renderPolyline( ring, context );
360 ringIndex++;
361 }
362 mOffset = -mOffset;
363 }
364 break;
365 case ExteriorRingOnly:
366 break;
367 }
368 }
369
370 if ( mDrawInsidePolygon )
371 {
372 //restore painter to reset clip path
373 p->restore();
374 }
375
376}
377
379{
380 QPainter *p = context.renderContext().painter();
381 if ( !p )
382 {
383 return;
384 }
385
386 QPolygonF points = pts;
387
388 double startTrim = mTrimDistanceStart;
390 {
391 context.setOriginalValueVariable( startTrim );
393 }
394 double endTrim = mTrimDistanceEnd;
396 {
397 context.setOriginalValueVariable( endTrim );
399 }
400
401 double totalLength = -1;
402 if ( mTrimDistanceStartUnit == Qgis::RenderUnit::Percentage )
403 {
404 totalLength = QgsSymbolLayerUtils::polylineLength( points );
405 startTrim = startTrim * 0.01 * totalLength;
406 }
407 else
408 {
409 startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
410 }
411 if ( mTrimDistanceEndUnit == Qgis::RenderUnit::Percentage )
412 {
413 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
414 totalLength = QgsSymbolLayerUtils::polylineLength( points );
415 endTrim = endTrim * 0.01 * totalLength;
416 }
417 else
418 {
419 endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
420 }
421 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
422 {
423 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
424 }
425
426 QColor penColor = mColor;
427 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
428 mPen.setColor( penColor );
429
430 double offset = mOffset;
431 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
432
433 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
434 const QPen pen = useSelectedColor ? mSelPen : mPen;
435
436 if ( !pen.dashPattern().isEmpty() )
437 {
438 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
439 const QVector<double> pattern = pen.dashPattern();
440 bool foundNonNull = false;
441 for ( int i = 0; i < pattern.size(); ++i )
442 {
443 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
444 {
445 foundNonNull = true;
446 break;
447 }
448 }
449 if ( !foundNonNull )
450 return;
451 }
452
453 p->setBrush( Qt::NoBrush );
454
455 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
456 std::unique_ptr< QgsScopedQPainterState > painterState;
457 if ( points.size() <= 2 &&
460 ( p->renderHints() & QPainter::Antialiasing ) )
461 {
462 painterState = std::make_unique< QgsScopedQPainterState >( p );
463 p->setRenderHint( QPainter::Antialiasing, false );
464 }
465
466 const bool applyPatternTweaks = mAlignDashPattern
467 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
468 && pen.dashOffset() == 0;
469
470 if ( qgsDoubleNear( offset, 0 ) )
471 {
472 if ( applyPatternTweaks )
473 {
474 drawPathWithDashPatternTweaks( p, points, pen );
475 }
476 else
477 {
478 p->setPen( pen );
479 QPainterPath path;
480 path.addPolygon( points );
481 p->drawPath( path );
482 }
483 }
484 else
485 {
486 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
488 {
489 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
490 // and clamp it to a reasonable range. It's the best we can do in this situation!
491 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
492 }
493
494 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
495 for ( const QPolygonF &part : mline )
496 {
497 if ( applyPatternTweaks )
498 {
499 drawPathWithDashPatternTweaks( p, part, pen );
500 }
501 else
502 {
503 p->setPen( pen );
504 QPainterPath path;
505 path.addPolygon( part );
506 p->drawPath( path );
507 }
508 }
509 }
510}
511
513{
514 QVariantMap map;
515 map[QStringLiteral( "line_color" )] = QgsColorUtils::colorToString( mColor );
516 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
517 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
518 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
519 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
520 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
521 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
522 map[QStringLiteral( "offset" )] = QString::number( mOffset );
523 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
524 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
525 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
526 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
527 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
528 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
529 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
530 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
531 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
532 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
533 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
534 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
535 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
536 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
537 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
538 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
539 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
540 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
541 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
542 return map;
543}
544
546{
552 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
553 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
554 l->setOffset( mOffset );
555 l->setPenJoinStyle( mPenJoinStyle );
556 l->setPenCapStyle( mPenCapStyle );
557 l->setUseCustomDashPattern( mUseCustomDashPattern );
558 l->setCustomDashVector( mCustomDashVector );
559 l->setDrawInsidePolygon( mDrawInsidePolygon );
561 l->setDashPatternOffset( mDashPatternOffset );
562 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
563 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
564 l->setTrimDistanceStart( mTrimDistanceStart );
565 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
566 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
567 l->setTrimDistanceEnd( mTrimDistanceEnd );
568 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
569 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
570 l->setAlignDashPattern( mAlignDashPattern );
571 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
572
574 copyPaintEffect( l );
575 return l;
576}
577
578void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
579{
580 if ( mPenStyle == Qt::NoPen )
581 return;
582
583 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
584 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
585 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
586 element.appendChild( symbolizerElem );
587
588 // <Geometry>
589 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
590
591 // <Stroke>
592 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
593 symbolizerElem.appendChild( strokeElem );
594
595 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
597 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
599 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
600
601 // <se:PerpendicularOffset>
602 if ( !qgsDoubleNear( mOffset, 0.0 ) )
603 {
604 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
606 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
607 symbolizerElem.appendChild( perpOffsetElem );
608 }
609}
610
611QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
612{
613 if ( mUseCustomDashPattern )
614 {
615 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
616 mPen.color(), mPenJoinStyle,
617 mPenCapStyle, mOffset, &mCustomDashVector );
618 }
619 else
620 {
621 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
622 mPenCapStyle, mOffset );
623 }
624}
625
627{
628 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
629
630 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
631 if ( strokeElem.isNull() )
632 return nullptr;
633
634 Qt::PenStyle penStyle;
635 QColor color;
636 double width;
637 Qt::PenJoinStyle penJoinStyle;
638 Qt::PenCapStyle penCapStyle;
639 QVector<qreal> customDashVector;
640
642 color, width,
645 return nullptr;
646
647 double offset = 0.0;
648 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
649 if ( !perpOffsetElem.isNull() )
650 {
651 bool ok;
652 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
653 if ( ok )
654 offset = d;
655 }
656
657 double scaleFactor = 1.0;
658 const QString uom = element.attribute( QStringLiteral( "uom" ) );
659 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
660 width = width * scaleFactor;
661 offset = offset * scaleFactor;
662
664 l->setOutputUnit( sldUnitSize );
665 l->setOffset( offset );
668 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
670 return l;
671}
672
673void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
674{
675 if ( !dataDefinedProperties().hasActiveProperties() )
676 return; // shortcut
677
678 //data defined properties
679 bool hasStrokeWidthExpression = false;
681 {
683 double scaledWidth = context.renderContext().convertToPainterUnits(
686 pen.setWidthF( scaledWidth );
687 selPen.setWidthF( scaledWidth );
688 hasStrokeWidthExpression = true;
689 }
690
691 //color
693 {
695
697 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
698 pen.setColor( penColor );
699 }
700
701 //offset
703 {
706 }
707
708 //dash dot vector
709
710 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
711 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
712 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
713
715 {
716 QVector<qreal> dashVector;
718 if ( !QgsVariantUtils::isNull( exprVal ) )
719 {
720 QStringList dashList = exprVal.toString().split( ';' );
721 QStringList::const_iterator dashIt = dashList.constBegin();
722 for ( ; dashIt != dashList.constEnd(); ++dashIt )
723 {
724 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
725 }
726 pen.setDashPattern( dashVector );
727 }
728 }
729 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
730 {
731 //re-scale pattern vector after data defined pen width was applied
732
733 QVector<qreal> scaledVector;
734 for ( double v : std::as_const( mCustomDashVector ) )
735 {
736 //the dash is specified in terms of pen widths, therefore the division
737 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
738 }
739 mPen.setDashPattern( scaledVector );
740 }
741
742 // dash pattern offset
743 double patternOffset = mDashPatternOffset;
744 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
745 {
746 context.setOriginalValueVariable( patternOffset );
748 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
749 }
750
751 //line style
753 {
756 if ( !QgsVariantUtils::isNull( exprVal ) )
757 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
758 }
759
760 //join style
762 {
765 if ( !QgsVariantUtils::isNull( exprVal ) )
766 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
767 }
768
769 //cap style
771 {
774 if ( !QgsVariantUtils::isNull( exprVal ) )
775 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
776 }
777}
778
779void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
780{
781 if ( pen.dashPattern().empty() || points.size() < 2 )
782 return;
783
784 if ( pen.widthF() <= 1.0 )
785 {
786 pen.setWidthF( 1.0001 );
787 }
788
789 QVector< qreal > sourcePattern = pen.dashPattern();
790 const double dashWidthDiv = pen.widthF();
791 // back to painter units
792 for ( int i = 0; i < sourcePattern.size(); ++ i )
793 sourcePattern[i] *= pen.widthF();
794
795 QVector< qreal > buffer;
796 QPolygonF bufferedPoints;
797 QPolygonF previousSegmentBuffer;
798 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
799 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
800 // and then append the buffer to the output pattern.
801
802 auto ptIt = points.constBegin();
803 double totalBufferLength = 0;
804 int patternIndex = 0;
805 double currentRemainingDashLength = 0;
806 double currentRemainingGapLength = 0;
807
808 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
809 {
810 QVector< qreal > result;
811 result.reserve( buffer.size() );
812 for ( auto it = buffer.begin(); it != buffer.end(); )
813 {
814 qreal dash = *it++;
815 qreal gap = *it++;
816 while ( dash == 0 && !result.empty() )
817 {
818 result.last() += gap;
819
820 if ( it == buffer.end() )
821 return result;
822 dash = *it++;
823 gap = *it++;
824 }
825 while ( gap == 0 && it != buffer.end() )
826 {
827 dash += *it++;
828 gap = *it++;
829 }
830 result << dash << gap;
831 }
832 return result;
833 };
834
835 double currentBufferLineLength = 0;
836 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
837 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
838 {
839 if ( buffer.empty() || bufferedPoints.size() < 2 )
840 {
841 return;
842 }
843
844 if ( currentRemainingDashLength )
845 {
846 // ended midway through a dash -- we want to finish this off
847 buffer << currentRemainingDashLength << 0.0;
848 totalBufferLength += currentRemainingDashLength;
849 }
850 QVector< qreal > compressed = compressPattern( buffer );
851 if ( !currentRemainingDashLength )
852 {
853 // ended midway through a gap -- we don't want this, we want to end at previous dash
854 totalBufferLength -= compressed.last();
855 compressed.last() = 0;
856 }
857
858 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
859 const double scaleFactor = currentBufferLineLength / totalBufferLength;
860
861 bool shouldFlushPreviousSegmentBuffer = false;
862
863 if ( !previousSegmentBuffer.empty() )
864 {
865 // add first dash from current buffer
866 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
867 if ( !firstDashSubstring.empty() )
868 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
869
870 // then we skip over the first dash and gap for this segment
871 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
872
873 compressed = compressed.mid( 2 );
874 shouldFlushPreviousSegmentBuffer = !compressed.empty();
875 }
876
877 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
878 {
879 QPen adjustedPen = pen;
880 adjustedPen.setStyle( Qt::SolidLine );
881 painter->setPen( adjustedPen );
882 QPainterPath path;
883 path.addPolygon( previousSegmentBuffer );
884 painter->drawPath( path );
885 previousSegmentBuffer.clear();
886 }
887
888 double finalDash = 0;
889 if ( nextPoint )
890 {
891 // sharp bend:
892 // 1. rewind buffered points line by final dash and gap length
893 // (later) 2. draw the bend with a solid line of length 2 * final dash size
894
895 if ( !compressed.empty() )
896 {
897 finalDash = compressed.at( compressed.size() - 2 );
898 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
899
900 const QPolygonF thisPoints = bufferedPoints;
901 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
902 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
903 }
904 else
905 {
906 previousSegmentBuffer << bufferedPoints;
907 }
908 }
909
910 currentBufferLineLength = 0;
911 currentRemainingDashLength = 0;
912 currentRemainingGapLength = 0;
913 totalBufferLength = 0;
914 buffer.clear();
915
916 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
917 {
918 QPen adjustedPen = pen;
919 if ( !compressed.empty() )
920 {
921 // maximum size of dash pattern is 32 elements
922 compressed = compressed.mid( 0, 32 );
923 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
924 adjustedPen.setDashPattern( compressed );
925 }
926 else
927 {
928 adjustedPen.setStyle( Qt::SolidLine );
929 }
930
931 painter->setPen( adjustedPen );
932 QPainterPath path;
933 path.addPolygon( bufferedPoints );
934 painter->drawPath( path );
935 }
936
937 bufferedPoints.clear();
938 };
939
940 QPointF p1;
941 QPointF p2 = *ptIt;
942 ptIt++;
943 bufferedPoints << p2;
944 for ( ; ptIt != points.constEnd(); ++ptIt )
945 {
946 p1 = *ptIt;
947 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
948 {
949 continue;
950 }
951
952 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
953 currentBufferLineLength += remainingSegmentDistance;
954 while ( true )
955 {
956 // handle currentRemainingDashLength/currentRemainingGapLength
957 if ( currentRemainingDashLength > 0 )
958 {
959 // bit more of dash to insert
960 if ( remainingSegmentDistance >= currentRemainingDashLength )
961 {
962 // all of dash fits in
963 buffer << currentRemainingDashLength << 0.0;
964 totalBufferLength += currentRemainingDashLength;
965 remainingSegmentDistance -= currentRemainingDashLength;
966 patternIndex++;
967 currentRemainingDashLength = 0.0;
968 currentRemainingGapLength = sourcePattern.at( patternIndex );
969 if ( currentRemainingGapLength == 0.0 )
970 {
971 patternIndex++;
972 }
973 }
974 else
975 {
976 // only part of remaining dash fits in
977 buffer << remainingSegmentDistance << 0.0;
978 totalBufferLength += remainingSegmentDistance;
979 currentRemainingDashLength -= remainingSegmentDistance;
980 break;
981 }
982 }
983 if ( currentRemainingGapLength > 0 )
984 {
985 // bit more of gap to insert
986 if ( remainingSegmentDistance >= currentRemainingGapLength )
987 {
988 // all of gap fits in
989 buffer << 0.0 << currentRemainingGapLength;
990 totalBufferLength += currentRemainingGapLength;
991 remainingSegmentDistance -= currentRemainingGapLength;
992 currentRemainingGapLength = 0.0;
993 patternIndex++;
994 }
995 else
996 {
997 // only part of remaining gap fits in
998 buffer << 0.0 << remainingSegmentDistance;
999 totalBufferLength += remainingSegmentDistance;
1000 currentRemainingGapLength -= remainingSegmentDistance;
1001 break;
1002 }
1003 }
1004
1005 if ( patternIndex + 1 >= sourcePattern.size() )
1006 {
1007 patternIndex = 0;
1008 }
1009
1010 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1011 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1012 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1013 {
1014 buffer << nextPatternDashLength << nextPatternGapLength;
1015 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1016 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1017 patternIndex += 2;
1018 }
1019 else if ( nextPatternDashLength <= remainingSegmentDistance )
1020 {
1021 // can fit in "dash", but not "gap"
1022 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1023 totalBufferLength += remainingSegmentDistance;
1024 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1025 currentRemainingDashLength = 0;
1026 patternIndex++;
1027 break;
1028 }
1029 else
1030 {
1031 // can't fit in "dash"
1032 buffer << remainingSegmentDistance << 0.0;
1033 totalBufferLength += remainingSegmentDistance;
1034 currentRemainingGapLength = 0;
1035 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1036 break;
1037 }
1038 }
1039
1040 bufferedPoints << p1;
1041 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1042 {
1043 QPointF nextPoint = *( ptIt + 1 );
1044
1045 // extreme angles form more than 45 degree angle at a node
1046 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1047 {
1048 // extreme angle. Rescale buffer and flush
1049 flushBuffer( &nextPoint );
1050 bufferedPoints << p1;
1051 // restart the line with the full length of the most recent dash element -- see
1052 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1053 if ( patternIndex % 2 == 1 )
1054 {
1055 patternIndex--;
1056 }
1057 currentRemainingDashLength = sourcePattern.at( patternIndex );
1058 }
1059 }
1060
1061 p2 = p1;
1062 }
1063
1064 flushBuffer( nullptr );
1065 if ( !previousSegmentBuffer.empty() )
1066 {
1067 QPen adjustedPen = pen;
1068 adjustedPen.setStyle( Qt::SolidLine );
1069 painter->setPen( adjustedPen );
1070 QPainterPath path;
1071 path.addPolygon( previousSegmentBuffer );
1072 painter->drawPath( path );
1073 previousSegmentBuffer.clear();
1074 }
1075}
1076
1078{
1079 if ( mDrawInsidePolygon )
1080 {
1081 //set to clip line to the interior of polygon, so we expect no bleed
1082 return 0;
1083 }
1084 else
1085 {
1086 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1088 }
1089}
1090
1092{
1093 unit = mCustomDashPatternUnit;
1094 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1095}
1096
1098{
1099 return mPenStyle;
1100}
1101
1118
1128
1130{
1131 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1132}
1133
1135{
1136 return mAlignDashPattern;
1137}
1138
1140{
1141 mAlignDashPattern = enabled;
1142}
1143
1145{
1146 return mPatternCartographicTweakOnSharpCorners;
1147}
1148
1150{
1151 mPatternCartographicTweakOnSharpCorners = enabled;
1152}
1153
1171
1173
1175
1176class MyLine
1177{
1178 public:
1179 MyLine( QPointF p1, QPointF p2 )
1180 : mVertical( false )
1181 , mIncreasing( false )
1182 , mT( 0.0 )
1183 , mLength( 0.0 )
1184 {
1185 if ( p1 == p2 )
1186 return; // invalid
1187
1188 // tangent and direction
1189 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1190 {
1191 // vertical line - tangent undefined
1192 mVertical = true;
1193 mIncreasing = ( p2.y() > p1.y() );
1194 }
1195 else
1196 {
1197 mVertical = false;
1198 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1199 mIncreasing = ( p2.x() > p1.x() );
1200 }
1201
1202 // length
1203 double x = ( p2.x() - p1.x() );
1204 double y = ( p2.y() - p1.y() );
1205 mLength = std::sqrt( x * x + y * y );
1206 }
1207
1208 // return angle in radians
1209 double angle()
1210 {
1211 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1212
1213 if ( !mIncreasing )
1214 a += M_PI;
1215 return a;
1216 }
1217
1218 // return difference for x,y when going along the line with specified interval
1219 QPointF diffForInterval( double interval )
1220 {
1221 if ( mVertical )
1222 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1223
1224 double alpha = std::atan( mT );
1225 double dx = std::cos( alpha ) * interval;
1226 double dy = std::sin( alpha ) * interval;
1227 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1228 }
1229
1230 double length() const { return mLength; }
1231
1232 protected:
1233 bool mVertical;
1234 bool mIncreasing;
1235 double mT;
1236 double mLength;
1237};
1238
1240
1241//
1242// QgsTemplatedLineSymbolLayerBase
1243//
1245 : mRotateSymbols( rotateSymbol )
1246 , mInterval( interval )
1247{
1248
1249}
1250
1274
1279
1281
1283{
1284 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1285 if ( mRenderingFeature )
1286 {
1287 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1288 // until after we've received the final part
1289 mFeatureSymbolOpacity = context.opacity();
1290 mCurrentFeatureIsSelected = useSelectedColor;
1291 }
1292
1293 double offset = mOffset;
1294
1296 {
1299 }
1300
1302
1304 {
1306 if ( !QgsVariantUtils::isNull( exprVal ) )
1307 {
1308 QString placementString = exprVal.toString();
1309 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1310 {
1312 }
1313 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1314 {
1316 }
1317 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1318 {
1320 }
1321 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1322 {
1324 }
1325 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1326 {
1328 }
1329 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1330 {
1332 }
1333 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1334 {
1336 }
1337 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1338 {
1340 }
1341 else
1342 {
1344 }
1345 }
1346 }
1347
1348 QgsScopedQPainterState painterState( context.renderContext().painter() );
1349
1350 double averageOver = mAverageAngleLength;
1352 {
1353 context.setOriginalValueVariable( mAverageAngleLength );
1355 }
1356 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1357
1358 if ( qgsDoubleNear( offset, 0.0 ) )
1359 {
1361 renderPolylineInterval( points, context, averageOver );
1363 renderPolylineCentral( points, context, averageOver );
1365 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1367 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1368 {
1369 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1370 mHasRenderedFirstPart = mRenderingFeature;
1371 }
1373 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1375 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1377 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1379 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1380 }
1381 else
1382 {
1383 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1385
1386 for ( int part = 0; part < mline.count(); ++part )
1387 {
1388 const QPolygonF &points2 = mline[ part ];
1389
1391 renderPolylineInterval( points2, context, averageOver );
1393 renderPolylineCentral( points2, context, averageOver );
1395 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1397 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1399 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1401 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1402 {
1403 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1404 mHasRenderedFirstPart = mRenderingFeature;
1405 }
1407 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1409 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1410 }
1411 }
1412}
1413
1414void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1415{
1416 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1417
1418 if ( curvePolygon )
1419 {
1420 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1421 }
1422
1423 QgsExpressionContextScope *scope = nullptr;
1424 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1426 {
1427 scope = new QgsExpressionContextScope();
1428 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1429 }
1430
1431 switch ( mRingFilter )
1432 {
1433 case AllRings:
1434 case ExteriorRingOnly:
1435 {
1436 if ( scope )
1438
1439 renderPolyline( points, context );
1440 break;
1441 }
1442 case InteriorRingsOnly:
1443 break;
1444 }
1445
1446 if ( rings )
1447 {
1448 switch ( mRingFilter )
1449 {
1450 case AllRings:
1451 case InteriorRingsOnly:
1452 {
1453 mOffset = -mOffset; // invert the offset for rings!
1454 for ( int i = 0; i < rings->size(); ++i )
1455 {
1456 if ( curvePolygon )
1457 {
1458 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1459 }
1460 if ( scope )
1462
1463 renderPolyline( rings->at( i ), context );
1464 }
1465 mOffset = -mOffset;
1466 }
1467 break;
1468 case ExteriorRingOnly:
1469 break;
1470 }
1471 }
1472}
1473
1475{
1477 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1478 {
1480 }
1481 return unit;
1482}
1483
1485{
1487 mIntervalUnit = unit;
1488 mOffsetAlongLineUnit = unit;
1489 mAverageAngleLengthUnit = unit;
1490}
1491
1499
1510
1512{
1513 QVariantMap map;
1514 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1515 map[QStringLiteral( "interval" )] = QString::number( interval() );
1516 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1517 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1518 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1519 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1520 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1521 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1522 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1523 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1524 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1525 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1526 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1527
1528 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1529
1530 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1531 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1532 return map;
1533}
1534
1536{
1537 return mPlaceOnEveryPart
1538 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1539 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1540 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1541}
1542
1544{
1545 installMasks( context, true );
1546
1547 mRenderingFeature = true;
1548 mHasRenderedFirstPart = false;
1549}
1550
1552{
1553 mRenderingFeature = false;
1554 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1555 {
1556 removeMasks( context, true );
1557 return;
1558 }
1559
1560 const double prevOpacity = subSymbol()->opacity();
1561 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1562
1563 // render final point
1564 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1565 mFeatureSymbolOpacity = 1;
1566 subSymbol()->setOpacity( prevOpacity );
1567
1568 removeMasks( context, true );
1569}
1570
1572{
1573 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1574 destLayer->setOffset( mOffset );
1575 destLayer->setPlacements( placements() );
1576 destLayer->setOffsetUnit( mOffsetUnit );
1578 destLayer->setIntervalUnit( intervalUnit() );
1580 destLayer->setOffsetAlongLine( offsetAlongLine() );
1583 destLayer->setAverageAngleLength( mAverageAngleLength );
1584 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1585 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1586 destLayer->setRingFilter( mRingFilter );
1587 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1588 copyDataDefinedProperties( destLayer );
1589 copyPaintEffect( destLayer );
1590}
1591
1593{
1594 if ( properties.contains( QStringLiteral( "offset" ) ) )
1595 {
1596 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1597 }
1598 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1599 {
1600 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1601 }
1602 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1603 {
1604 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1605 }
1606 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1607 {
1608 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1609 }
1610 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1611 {
1612 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1613 }
1614 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1615 {
1616 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1617 }
1618
1619 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1620 {
1621 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1622 }
1623 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1624 {
1625 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1626 }
1627
1628 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1629 {
1630 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1631 }
1632 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1633 {
1634 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1635 }
1636 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1637 {
1638 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1639 }
1640
1641 if ( properties.contains( QStringLiteral( "placement" ) ) )
1642 {
1643 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1645 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1647 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1649 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1651 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1653 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1655 else
1657 }
1658 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1659 {
1660 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1661 destLayer->setPlacements( placements );
1662 }
1663
1664 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1665 {
1666 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1667 }
1668
1669 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1670
1672}
1673
1674void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1675{
1676 if ( points.isEmpty() )
1677 return;
1678
1679 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1680 double lengthLeft = 0; // how much is left until next marker
1681
1682 QgsRenderContext &rc = context.renderContext();
1683 double interval = mInterval;
1684
1686 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1687
1689 {
1690 context.setOriginalValueVariable( mInterval );
1692 }
1693 if ( interval <= 0 )
1694 {
1695 interval = 0.1;
1696 }
1697 double offsetAlongLine = mOffsetAlongLine;
1699 {
1700 context.setOriginalValueVariable( mOffsetAlongLine );
1702 }
1703
1704 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1706 {
1707 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1708 // and clamp it to a reasonable range. It's the best we can do in this situation!
1709 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1710 }
1711
1712 if ( painterUnitInterval < 0 )
1713 return;
1714
1715 double painterUnitOffsetAlongLine = 0;
1716
1717 // only calculated if we need it!
1718 double totalLength = -1;
1719
1720 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1721 {
1722 switch ( offsetAlongLineUnit() )
1723 {
1732 break;
1734 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1735 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1736 break;
1737 }
1738
1739 if ( points.isClosed() )
1740 {
1741 if ( painterUnitOffsetAlongLine > 0 )
1742 {
1743 if ( totalLength < 0 )
1744 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1745 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1746 }
1747 else if ( painterUnitOffsetAlongLine < 0 )
1748 {
1749 if ( totalLength < 0 )
1750 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1751 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1752 }
1753 }
1754 }
1755
1757 {
1758 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1759 // and clamp it to a reasonable range. It's the best we can do in this situation!
1760 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1761 }
1762
1763 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1764
1765 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1766 {
1767 QVector< QPointF > angleStartPoints;
1768 QVector< QPointF > symbolPoints;
1769 QVector< QPointF > angleEndPoints;
1770
1771 // we collect 3 arrays of points. These correspond to
1772 // 1. the actual point at which to render the symbol
1773 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1774 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1775 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1776 // (or trace past the final point)
1777 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1778
1779 if ( symbolPoints.empty() )
1780 {
1781 // no symbols to draw, shortcut out early
1782 return;
1783 }
1784
1785 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1786 {
1787 // avoid duplicate points at start and end of closed rings
1788 symbolPoints.pop_back();
1789 }
1790
1791 angleEndPoints.reserve( symbolPoints.size() );
1792 angleStartPoints.reserve( symbolPoints.size() );
1793 if ( averageOver <= painterUnitOffsetAlongLine )
1794 {
1795 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1796 }
1797 else
1798 {
1799 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1800 }
1801 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1802
1803 int pointNum = 0;
1804 for ( int i = 0; i < symbolPoints.size(); ++ i )
1805 {
1806 if ( context.renderContext().renderingStopped() )
1807 break;
1808
1809 const QPointF pt = symbolPoints[i];
1810 const QPointF startPt = angleStartPoints[i];
1811 const QPointF endPt = angleEndPoints[i];
1812
1813 MyLine l( startPt, endPt );
1814 // rotate marker (if desired)
1815 if ( rotateSymbols() )
1816 {
1817 setSymbolLineAngle( l.angle() * 180 / M_PI );
1818 }
1819
1821 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1822 }
1823 }
1824 else
1825 {
1826 // not averaging line angle -- always use exact section angle
1827 int pointNum = 0;
1828 QPointF lastPt = points[0];
1829 for ( int i = 1; i < points.count(); ++i )
1830 {
1831 if ( context.renderContext().renderingStopped() )
1832 break;
1833
1834 const QPointF &pt = points[i];
1835
1836 if ( lastPt == pt ) // must not be equal!
1837 continue;
1838
1839 // for each line, find out dx and dy, and length
1840 MyLine l( lastPt, pt );
1841 QPointF diff = l.diffForInterval( painterUnitInterval );
1842
1843 // if there's some length left from previous line
1844 // use only the rest for the first point in new line segment
1845 double c = 1 - lengthLeft / painterUnitInterval;
1846
1847 lengthLeft += l.length();
1848
1849 // rotate marker (if desired)
1850 if ( rotateSymbols() )
1851 {
1852 setSymbolLineAngle( l.angle() * 180 / M_PI );
1853 }
1854
1855 // while we're not at the end of line segment, draw!
1856 while ( lengthLeft > painterUnitInterval )
1857 {
1858 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1859 lastPt += c * diff;
1860 lengthLeft -= painterUnitInterval;
1862 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1863 c = 1; // reset c (if wasn't 1 already)
1864 }
1865
1866 lastPt = pt;
1867 }
1868
1869 }
1870}
1871
1872static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1873{
1874 // calc average angle between the previous and next point
1875 double a1 = MyLine( prevPt, pt ).angle();
1876 double a2 = MyLine( pt, nextPt ).angle();
1877 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1878
1879 return std::atan2( unitY, unitX );
1880}
1881
1882void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1883{
1884 if ( points.isEmpty() )
1885 return;
1886
1887 QgsRenderContext &rc = context.renderContext();
1888
1889 int i = -1, maxCount = 0;
1890 bool isRing = false;
1891
1893 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1895
1896 double offsetAlongLine = mOffsetAlongLine;
1898 {
1899 context.setOriginalValueVariable( mOffsetAlongLine );
1901 }
1902
1903 // only calculated if we need it!!
1904 double totalLength = -1;
1905 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1906 {
1907 //scale offset along line
1908 switch ( offsetAlongLineUnit() )
1909 {
1918 break;
1920 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1921 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1922 break;
1923 }
1924 if ( points.isClosed() )
1925 {
1926 if ( offsetAlongLine > 0 )
1927 {
1928 if ( totalLength < 0 )
1929 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1930 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1931 }
1932 else if ( offsetAlongLine < 0 )
1933 {
1934 if ( totalLength < 0 )
1935 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1936 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1937 }
1938 }
1939 }
1940
1941 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1942 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1946 {
1948 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1949
1950 QgsVertexId vId;
1951 QgsPoint vPoint;
1952 double x, y, z;
1953 QPointF mapPoint;
1954 int pointNum = 0;
1955 const int numPoints = context.renderContext().geometry()->nCoordinates();
1956 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1957 {
1958 if ( context.renderContext().renderingStopped() )
1959 break;
1960
1962
1963 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1964 continue;
1965
1966 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1967 continue;
1968
1971 {
1972 //transform
1973 x = vPoint.x();
1974 y = vPoint.y();
1975 z = 0.0;
1976 if ( ct.isValid() )
1977 {
1978 ct.transformInPlace( x, y, z );
1979 }
1980 mapPoint.setX( x );
1981 mapPoint.setY( y );
1982 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1983 if ( rotateSymbols() )
1984 {
1985 double angle = context.renderContext().geometry()->vertexAngle( vId );
1986 setSymbolLineAngle( angle * 180 / M_PI );
1987 }
1988 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
1989 }
1990 }
1991
1992 return;
1993 }
1994
1995 int pointNum = 0;
1996
1997 switch ( placement )
1998 {
2000 {
2001 i = 0;
2002 maxCount = 1;
2003 break;
2004 }
2005
2007 {
2008 i = points.count() - 1;
2009 pointNum = i;
2010 maxCount = points.count();
2011 break;
2012 }
2013
2015 {
2016 i = 1;
2017 pointNum = 1;
2018 maxCount = points.count() - 1;
2019 break;
2020 }
2021
2024 {
2026 maxCount = points.count();
2027 if ( points.first() == points.last() )
2028 isRing = true;
2029 break;
2030 }
2031
2035 {
2036 return;
2037 }
2038 }
2039
2041 {
2042 double distance;
2044 renderOffsetVertexAlongLine( points, i, distance, context, placement );
2045
2046 return;
2047 }
2048
2049 QPointF prevPoint;
2050 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2051 prevPoint = points.at( 0 );
2052
2053 QPointF symbolPoint;
2054 for ( ; i < maxCount; ++i )
2055 {
2057
2058 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2059 {
2060 continue; // don't draw the last marker - it has been drawn already
2061 }
2062
2064 {
2065 QPointF currentPoint = points.at( i );
2066 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2067 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2068 if ( rotateSymbols() )
2069 {
2070 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2071 currentPoint.x() - prevPoint.x() );
2072 setSymbolLineAngle( angle * 180 / M_PI );
2073 }
2074 prevPoint = currentPoint;
2075 }
2076 else
2077 {
2078 symbolPoint = points.at( i );
2079 // rotate marker (if desired)
2080 if ( rotateSymbols() )
2081 {
2082 double angle = markerAngle( points, isRing, i );
2083 setSymbolLineAngle( angle * 180 / M_PI );
2084 }
2085 }
2086
2087 mFinalVertex = symbolPoint;
2088 if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2089 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2090 }
2091}
2092
2093double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2094{
2095 double angle = 0;
2096 const QPointF &pt = points[vertex];
2097
2098 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2099 {
2100 int prevIndex = vertex - 1;
2101 int nextIndex = vertex + 1;
2102
2103 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2104 {
2105 prevIndex = points.count() - 2;
2106 nextIndex = 1;
2107 }
2108
2109 QPointF prevPoint, nextPoint;
2110 while ( prevIndex >= 0 )
2111 {
2112 prevPoint = points[ prevIndex ];
2113 if ( prevPoint != pt )
2114 {
2115 break;
2116 }
2117 --prevIndex;
2118 }
2119
2120 while ( nextIndex < points.count() )
2121 {
2122 nextPoint = points[ nextIndex ];
2123 if ( nextPoint != pt )
2124 {
2125 break;
2126 }
2127 ++nextIndex;
2128 }
2129
2130 if ( prevIndex >= 0 && nextIndex < points.count() )
2131 {
2132 angle = _averageAngle( prevPoint, pt, nextPoint );
2133 }
2134 }
2135 else //no ring and vertex is at start / at end
2136 {
2137 if ( vertex == 0 )
2138 {
2139 while ( vertex < points.size() - 1 )
2140 {
2141 const QPointF &nextPt = points[vertex + 1];
2142 if ( pt != nextPt )
2143 {
2144 angle = MyLine( pt, nextPt ).angle();
2145 return angle;
2146 }
2147 ++vertex;
2148 }
2149 }
2150 else
2151 {
2152 // use last segment's angle
2153 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2154 {
2155 const QPointF &prevPt = points[vertex - 1];
2156 if ( pt != prevPt )
2157 {
2158 angle = MyLine( prevPt, pt ).angle();
2159 return angle;
2160 }
2161 --vertex;
2162 }
2163 }
2164 }
2165 return angle;
2166}
2167
2168void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2169{
2170 if ( points.isEmpty() )
2171 return;
2172
2173 QgsRenderContext &rc = context.renderContext();
2174 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2175 if ( qgsDoubleNear( distance, 0.0 ) )
2176 {
2177 // rotate marker (if desired)
2178 if ( rotateSymbols() )
2179 {
2180 bool isRing = false;
2181 if ( points.first() == points.last() )
2182 isRing = true;
2183 double angle = markerAngle( points, isRing, vertex );
2184 setSymbolLineAngle( angle * 180 / M_PI );
2185 }
2186 mFinalVertex = points[vertex];
2187 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2188 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2189 return;
2190 }
2191
2192 int pointIncrement = distance > 0 ? 1 : -1;
2193 QPointF previousPoint = points[vertex];
2194 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2195 int endPoint = distance > 0 ? points.count() - 1 : 0;
2196 double distanceLeft = std::fabs( distance );
2197
2198 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2199 {
2200 const QPointF &pt = points[i];
2201
2202 if ( previousPoint == pt ) // must not be equal!
2203 continue;
2204
2205 // create line segment
2206 MyLine l( previousPoint, pt );
2207
2208 if ( distanceLeft < l.length() )
2209 {
2210 //destination point is in current segment
2211 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2212 // rotate marker (if desired)
2213 if ( rotateSymbols() )
2214 {
2215 setSymbolLineAngle( l.angle() * 180 / M_PI );
2216 }
2217 mFinalVertex = markerPoint;
2218 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2219 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2220 return;
2221 }
2222
2223 distanceLeft -= l.length();
2224 previousPoint = pt;
2225 }
2226
2227 //didn't find point
2228}
2229
2230void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2231{
2232 if ( p.empty() )
2233 return;
2234
2235 QVector< QPointF > points = p;
2236 const bool closedRing = points.first() == points.last();
2237
2238 double lengthLeft = initialOffset;
2239
2240 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2241 if ( initialLagLeft < 0 && closedRing )
2242 {
2243 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2244 QPointF lastPt = points.constLast();
2245 QVector< QPointF > pseudoPoints;
2246 for ( int i = points.count() - 2; i > 0; --i )
2247 {
2248 if ( initialLagLeft >= 0 )
2249 {
2250 break;
2251 }
2252
2253 const QPointF &pt = points[i];
2254
2255 if ( lastPt == pt ) // must not be equal!
2256 continue;
2257
2258 MyLine l( lastPt, pt );
2259 initialLagLeft += l.length();
2260 lastPt = pt;
2261
2262 pseudoPoints << pt;
2263 }
2264 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2265
2266 points = pseudoPoints;
2267 points.append( p );
2268 }
2269 else
2270 {
2271 while ( initialLagLeft < 0 )
2272 {
2273 dest << points.constFirst();
2274 initialLagLeft += intervalPainterUnits;
2275 }
2276 }
2277 if ( initialLag > 0 )
2278 {
2279 lengthLeft += intervalPainterUnits - initialLagLeft;
2280 }
2281
2282 QPointF lastPt = points[0];
2283 for ( int i = 1; i < points.count(); ++i )
2284 {
2285 const QPointF &pt = points[i];
2286
2287 if ( lastPt == pt ) // must not be equal!
2288 {
2289 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2290 {
2291 lastPt = points[0];
2292 i = 0;
2293 }
2294 continue;
2295 }
2296
2297 // for each line, find out dx and dy, and length
2298 MyLine l( lastPt, pt );
2299 QPointF diff = l.diffForInterval( intervalPainterUnits );
2300
2301 // if there's some length left from previous line
2302 // use only the rest for the first point in new line segment
2303 double c = 1 - lengthLeft / intervalPainterUnits;
2304
2305 lengthLeft += l.length();
2306
2307
2308 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2309 {
2310 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2311 lastPt += c * diff;
2312 lengthLeft -= intervalPainterUnits;
2313 dest << lastPt;
2314 c = 1; // reset c (if wasn't 1 already)
2315 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2316 break;
2317 }
2318 lastPt = pt;
2319
2320 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2321 break;
2322
2323 // if a closed ring, we keep looping around the ring until we hit the required number of points
2324 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2325 {
2326 lastPt = points[0];
2327 i = 0;
2328 }
2329 }
2330
2331 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2332 {
2333 // pad with repeating last point to match desired size
2334 while ( dest.size() < numberPointsRequired )
2335 dest << points.constLast();
2336 }
2337}
2338
2339void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2340{
2341 if ( !points.isEmpty() )
2342 {
2343 // calc length
2344 qreal length = 0;
2345 QPolygonF::const_iterator it = points.constBegin();
2346 QPointF last = *it;
2347 for ( ++it; it != points.constEnd(); ++it )
2348 {
2349 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2350 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2351 last = *it;
2352 }
2353 if ( qgsDoubleNear( length, 0.0 ) )
2354 return;
2355
2356 const double midPoint = length / 2;
2357
2358 QPointF pt;
2359 double thisSymbolAngle = 0;
2360
2361 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2362 {
2363 QVector< QPointF > angleStartPoints;
2364 QVector< QPointF > symbolPoints;
2365 QVector< QPointF > angleEndPoints;
2366 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2367 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2368 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2369 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2370
2371 pt = symbolPoints.at( 1 );
2372 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2373 thisSymbolAngle = l.angle();
2374 }
2375 else
2376 {
2377 // find the segment where the central point lies
2378 it = points.constBegin();
2379 last = *it;
2380 qreal last_at = 0, next_at = 0;
2381 QPointF next;
2382 for ( ++it; it != points.constEnd(); ++it )
2383 {
2384 next = *it;
2385 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2386 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2387 if ( next_at >= midPoint )
2388 break; // we have reached the center
2389 last = *it;
2390 last_at = next_at;
2391 }
2392
2393 // find out the central point on segment
2394 MyLine l( last, next ); // for line angle
2395 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2396 pt = last + ( next - last ) * k;
2397 thisSymbolAngle = l.angle();
2398 }
2399
2400 // draw the marker
2401 // rotate marker (if desired)
2402 if ( rotateSymbols() )
2403 {
2404 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2405 }
2406
2407 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2408 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2409 }
2410}
2411
2416
2418{
2419 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2420 {
2421 delete symbol;
2422 return false;
2423 }
2424
2425 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2426 mColor = mMarker->color();
2427 return true;
2428}
2429
2430
2431
2432//
2433// QgsMarkerLineSymbolLayer
2434//
2435
2436QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2437 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2438{
2440}
2441
2443
2445{
2446 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2448
2449 if ( props.contains( QStringLiteral( "interval" ) ) )
2450 interval = props[QStringLiteral( "interval" )].toDouble();
2451 if ( props.contains( QStringLiteral( "rotate" ) ) )
2452 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2453
2454 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2455 setCommonProperties( x.get(), props );
2456 return x.release();
2457}
2458
2460{
2461 return QStringLiteral( "MarkerLine" );
2462}
2463
2464void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2465{
2466 mMarker->setColor( color );
2467 mColor = color;
2468}
2469
2471{
2472 return mMarker ? mMarker->color() : mColor;
2473}
2474
2476{
2477 // if being rotated, it gets initialized with every line segment
2479 if ( rotateSymbols() )
2481 mMarker->setRenderHints( hints );
2482
2483 mMarker->startRender( context.renderContext(), context.fields() );
2484}
2485
2487{
2488 mMarker->stopRender( context.renderContext() );
2489}
2490
2491
2493{
2494 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2496 return x.release();
2497}
2498
2499void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2500{
2501 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2502 {
2503 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2504 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2505 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2506 element.appendChild( symbolizerElem );
2507
2508 // <Geometry>
2509 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2510
2511 QString gap;
2513 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2515 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2517 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2519 // no way to get line/polygon's vertices, use a VendorOption
2520 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2521
2523 {
2525 gap = qgsDoubleToString( interval );
2526 }
2527
2528 if ( !rotateSymbols() )
2529 {
2530 // markers in LineSymbolizer must be drawn following the line orientation,
2531 // use a VendorOption when no marker rotation
2532 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2533 }
2534
2535 // <Stroke>
2536 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2537 symbolizerElem.appendChild( strokeElem );
2538
2539 // <GraphicStroke>
2540 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2541 strokeElem.appendChild( graphicStrokeElem );
2542
2543 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2544 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2545 {
2546 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2547 }
2548 else if ( layer )
2549 {
2550 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2551 }
2552 else
2553 {
2554 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2555 }
2556
2557 if ( !gap.isEmpty() )
2558 {
2559 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2561 graphicStrokeElem.appendChild( gapElem );
2562 }
2563
2564 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2565 {
2566 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2568 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2569 symbolizerElem.appendChild( perpOffsetElem );
2570 }
2571 }
2572}
2573
2575{
2576 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2577
2578 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2579 if ( strokeElem.isNull() )
2580 return nullptr;
2581
2582 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2583 if ( graphicStrokeElem.isNull() )
2584 return nullptr;
2585
2586 // retrieve vendor options
2587 bool rotateMarker = true;
2589
2590 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2591 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2592 {
2593 if ( it.key() == QLatin1String( "placement" ) )
2594 {
2595 if ( it.value() == QLatin1String( "points" ) )
2597 else if ( it.value() == QLatin1String( "firstPoint" ) )
2599 else if ( it.value() == QLatin1String( "lastPoint" ) )
2601 else if ( it.value() == QLatin1String( "centralPoint" ) )
2603 }
2604 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2605 {
2606 rotateMarker = it.value() == QLatin1String( "0" );
2607 }
2608 }
2609
2610 std::unique_ptr< QgsMarkerSymbol > marker;
2611
2613 if ( l )
2614 {
2615 QgsSymbolLayerList layers;
2616 layers.append( l );
2617 marker.reset( new QgsMarkerSymbol( layers ) );
2618 }
2619
2620 if ( !marker )
2621 return nullptr;
2622
2623 double interval = 0.0;
2624 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2625 if ( !gapElem.isNull() )
2626 {
2627 bool ok;
2628 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2629 if ( ok )
2630 interval = d;
2631 }
2632
2633 double offset = 0.0;
2634 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2635 if ( !perpOffsetElem.isNull() )
2636 {
2637 bool ok;
2638 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2639 if ( ok )
2640 offset = d;
2641 }
2642
2643 double scaleFactor = 1.0;
2644 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2645 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2646 interval = interval * scaleFactor;
2647 offset = offset * scaleFactor;
2648
2650 x->setOutputUnit( sldUnitSize );
2652 x->setInterval( interval );
2653 x->setSubSymbol( marker.release() );
2654 x->setOffset( offset );
2655 return x;
2656}
2657
2659{
2660 mMarker->setSize( width );
2661}
2662
2664{
2665 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2666 {
2667 mMarker->setDataDefinedSize( property );
2668 }
2670}
2671
2673{
2674 const double prevOpacity = mMarker->opacity();
2675 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2677 mMarker->setOpacity( prevOpacity );
2678}
2679
2681{
2682 mMarker->setLineAngle( angle );
2683}
2684
2686{
2687 return mMarker->angle();
2688}
2689
2691{
2692 mMarker->setAngle( angle );
2693}
2694
2695void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2696{
2697 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2699
2700 mMarker->renderPoint( point, feature, context, layer, selected );
2701
2702 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2703}
2704
2706{
2707 return mMarker->size();
2708}
2709
2711{
2712 return mMarker->size( context );
2713}
2714
2720
2730
2732{
2733 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2734 if ( mMarker )
2735 attr.unite( mMarker->usedAttributes( context ) );
2736 return attr;
2737}
2738
2740{
2742 return true;
2743 if ( mMarker && mMarker->hasDataDefinedProperties() )
2744 return true;
2745 return false;
2746}
2747
2749{
2750 return ( mMarker->size( context ) / 2.0 ) +
2752}
2753
2754
2755//
2756// QgsHashedLineSymbolLayer
2757//
2758
2759QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2760 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2761{
2762 setSubSymbol( new QgsLineSymbol() );
2763}
2764
2766
2768{
2769 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2771
2772 if ( props.contains( QStringLiteral( "interval" ) ) )
2773 interval = props[QStringLiteral( "interval" )].toDouble();
2774 if ( props.contains( QStringLiteral( "rotate" ) ) )
2775 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2776
2777 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2778 setCommonProperties( x.get(), props );
2779 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2780 {
2781 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2782 }
2783
2784 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2785 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2786
2787 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2788 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2789
2790 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2791 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2792
2793 return x.release();
2794}
2795
2797{
2798 return QStringLiteral( "HashLine" );
2799}
2800
2802{
2803 // if being rotated, it gets initialized with every line segment
2805 if ( rotateSymbols() )
2807 mHashSymbol->setRenderHints( hints );
2808
2809 mHashSymbol->startRender( context.renderContext(), context.fields() );
2810}
2811
2813{
2814 mHashSymbol->stopRender( context.renderContext() );
2815}
2816
2818{
2820 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2821
2822 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2823 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2824 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2825
2826 return map;
2827}
2828
2830{
2831 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2833 x->setHashAngle( mHashAngle );
2834 x->setHashLength( mHashLength );
2835 x->setHashLengthUnit( mHashLengthUnit );
2836 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2837 return x.release();
2838}
2839
2840void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2841{
2842 mHashSymbol->setColor( color );
2843 mColor = color;
2844}
2845
2847{
2848 return mHashSymbol ? mHashSymbol->color() : mColor;
2849}
2850
2852{
2853 return mHashSymbol.get();
2854}
2855
2857{
2858 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2859 {
2860 delete symbol;
2861 return false;
2862 }
2863
2864 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2865 mColor = mHashSymbol->color();
2866 return true;
2867}
2868
2869void QgsHashedLineSymbolLayer::setWidth( const double width )
2870{
2871 mHashLength = width;
2872}
2873
2875{
2876 return mHashLength;
2877}
2878
2880{
2881 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2882}
2883
2885{
2886 return ( mHashSymbol->width( context ) / 2.0 )
2887 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2889}
2890
2892{
2894 mHashSymbol->setOutputUnit( unit );
2895}
2896
2898{
2899 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2900 if ( mHashSymbol )
2901 attr.unite( mHashSymbol->usedAttributes( context ) );
2902 return attr;
2903}
2904
2906{
2908 return true;
2909 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2910 return true;
2911 return false;
2912}
2913
2915{
2916 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
2917 {
2918 mHashSymbol->setDataDefinedWidth( property );
2919 }
2921}
2922
2933
2935{
2936 mSymbolLineAngle = angle;
2937}
2938
2940{
2941 return mSymbolAngle;
2942}
2943
2945{
2946 mSymbolAngle = angle;
2947}
2948
2949void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2950{
2951 double lineLength = mHashLength;
2953 {
2954 context.expressionContext().setOriginalValueVariable( mHashLength );
2956 }
2957 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2958
2959 double hashAngle = mHashAngle;
2961 {
2962 context.expressionContext().setOriginalValueVariable( mHashAngle );
2964 }
2965
2966 QgsPointXY center( point );
2967 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2968 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2969
2970 QPolygonF points;
2971 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2972
2973 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2975
2976 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2977
2978 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2979}
2980
2982{
2983 return mHashAngle;
2984}
2985
2987{
2988 mHashAngle = angle;
2989}
2990
2992{
2993 const double prevOpacity = mHashSymbol->opacity();
2994 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2996 mHashSymbol->setOpacity( prevOpacity );
2997}
2998
2999//
3000// QgsAbstractBrushedLineSymbolLayer
3001//
3002
3003void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
3004{
3005 if ( !context.renderContext().painter() )
3006 return;
3007
3008 double offset = mOffset;
3010 {
3013 }
3014
3015 QPolygonF offsetPoints;
3016 if ( qgsDoubleNear( offset, 0 ) )
3017 {
3018 renderLine( points, context, patternThickness, patternLength, brush );
3019 }
3020 else
3021 {
3022 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3023
3024 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3025 for ( const QPolygonF &part : offsetLine )
3026 {
3027 renderLine( part, context, patternThickness, patternLength, brush );
3028 }
3029 }
3030}
3031
3032void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3033 const double patternLength, const QBrush &sourceBrush )
3034{
3035 QPainter *p = context.renderContext().painter();
3036 if ( !p )
3037 return;
3038
3039 QBrush brush = sourceBrush;
3040
3041 // duplicate points mess up the calculations, we need to remove them first
3042 // we'll calculate the min/max coordinate at the same time, since we're already looping
3043 // through the points
3044 QPolygonF inputPoints;
3045 inputPoints.reserve( points.size() );
3046 QPointF prev;
3047 double minX = std::numeric_limits< double >::max();
3048 double minY = std::numeric_limits< double >::max();
3049 double maxX = std::numeric_limits< double >::lowest();
3050 double maxY = std::numeric_limits< double >::lowest();
3051
3052 for ( const QPointF &pt : std::as_const( points ) )
3053 {
3054 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3055 continue;
3056
3057 inputPoints << pt;
3058 prev = pt;
3059 minX = std::min( minX, pt.x() );
3060 minY = std::min( minY, pt.y() );
3061 maxX = std::max( maxX, pt.x() );
3062 maxY = std::max( maxY, pt.y() );
3063 }
3064
3065 if ( inputPoints.size() < 2 ) // nothing to render
3066 return;
3067
3068 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3069 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3070 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3071 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3072
3073 // our temporary image needs to extend out by the line thickness on each side, and we'll also add an allowance for antialiasing effects on each side
3074 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3075 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3076
3077 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3078 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3079
3080 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3081 if ( temporaryImage.isNull() )
3082 {
3083 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3084 return;
3085 }
3086
3087 // clear temporary image contents
3088 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3089 return;
3090 temporaryImage.fill( Qt::transparent );
3091 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3092 return;
3093
3094 Qt::PenJoinStyle join = mPenJoinStyle;
3096 {
3099 if ( !QgsVariantUtils::isNull( exprVal ) )
3100 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3101 }
3102
3103 Qt::PenCapStyle cap = mPenCapStyle;
3105 {
3108 if ( !QgsVariantUtils::isNull( exprVal ) )
3109 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3110 }
3111
3112 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3113 QPainterPathStroker stroker;
3114 stroker.setWidth( lineThickness );
3115 stroker.setCapStyle( cap );
3116 stroker.setJoinStyle( join );
3117
3118 QPainterPath path;
3119 path.addPolygon( inputPoints );
3120 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3121
3122 // prepare temporary image
3123 QPainter imagePainter;
3124 imagePainter.begin( &temporaryImage );
3125 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3126 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3127
3128 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3129 imagePainter.setPen( Qt::NoPen );
3130
3131 QPointF segmentStartPoint = inputPoints.at( 0 );
3132
3133 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3134 double progressThroughImage = 0;
3135
3136 QgsPoint prevSegmentPolygonEndLeft;
3137 QgsPoint prevSegmentPolygonEndRight;
3138
3139 // for closed rings this will store the left/right polygon points of the start/end of the line
3140 QgsPoint startLinePolygonLeft;
3141 QgsPoint startLinePolygonRight;
3142
3143 for ( int i = 1; i < inputPoints.size(); ++i )
3144 {
3145 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3146 break;
3147
3148 const QPointF segmentEndPoint = inputPoints.at( i );
3149 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3150 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3151
3152 // left/right end points of the current segment polygon
3153 QgsPoint thisSegmentPolygonEndLeft;
3154 QgsPoint thisSegmentPolygonEndRight;
3155 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3156 QgsPoint thisSegmentPolygonEndLeftForPainter;
3157 QgsPoint thisSegmentPolygonEndRightForPainter;
3158 if ( i == 1 )
3159 {
3160 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3161 // (unless it's a closed line, that is. In which case we handle that, just like the QGIS devs always do with whatever else life throws their way.)
3162 if ( isClosedLine )
3163 {
3164 // project the current segment out by half the image thickness to either side of the line
3165 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3166 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3167 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3168 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3169
3170 // angle of LAST line segment in the whole line (i.e. the one which will eventually connect back to the first point in the line). Used to determine
3171 // what angle the current segment polygon should START on.
3172 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3173 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3174
3175 // project out the LAST segment in the line by half the image thickness to either side of the line
3176 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3177 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3178 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3179 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3180
3181 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3182 // of THIS segment would intersect with the project lines to the left/right of the VERY LAST segment in the line (i.e. simulate a miter style
3183 // join)
3184 QgsPoint intersectionPoint;
3185 bool isIntersection = false;
3186 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3187 if ( !isIntersection )
3188 prevSegmentPolygonEndLeft = startPointLeft;
3189 isIntersection = false;
3190 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3191 if ( !isIntersection )
3192 prevSegmentPolygonEndRight = startPointRight;
3193
3194 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3195 startLinePolygonRight = prevSegmentPolygonEndRight;
3196 }
3197 else
3198 {
3199 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3200 if ( cap != Qt::PenCapStyle::FlatCap )
3201 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3202 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3203 if ( cap != Qt::PenCapStyle::FlatCap )
3204 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3205 }
3206 }
3207
3208 if ( i < inputPoints.size() - 1 )
3209 {
3210 // for all other segments except the last
3211
3212 // project the current segment out by half the image thickness to either side of the line
3213 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3214 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3215 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3216 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3217
3218 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3219 // what angle the current segment polygon should end on
3220 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3221 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3222
3223 // project out the next segment by half the image thickness to either side of the line
3224 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3225 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3226 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3227 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3228
3229 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3230 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3231 // join)
3232 QgsPoint intersectionPoint;
3233 bool isIntersection = false;
3234 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3235 if ( !isIntersection )
3236 thisSegmentPolygonEndLeft = endPointLeft;
3237 isIntersection = false;
3238 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3239 if ( !isIntersection )
3240 thisSegmentPolygonEndRight = endPointRight;
3241
3242 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3243 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3244 }
3245 else
3246 {
3247 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3248 // unless it's a closed line
3249 if ( isClosedLine )
3250 {
3251 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3252 thisSegmentPolygonEndRight = startLinePolygonRight;
3253
3254 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3255 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3256 }
3257 else
3258 {
3259 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3260 if ( cap != Qt::PenCapStyle::FlatCap )
3261 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3262 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3263 if ( cap != Qt::PenCapStyle::FlatCap )
3264 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3265
3266 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3267 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3268 }
3269 }
3270
3271 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3272 // where we got with the previous segment), at the correct angle
3273 QTransform brushTransform;
3274 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3275 brushTransform.rotate( -segmentAngleDegrees );
3276 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3277 {
3278 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3279 // we need to also do the same for the brush transform
3280 brushTransform.translate( -( lineThickness / 2 ), 0 );
3281 }
3282 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3283
3284 brush.setTransform( brushTransform );
3285 imagePainter.setBrush( brush );
3286
3287 // now draw the segment polygon
3288 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3289 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3290 << thisSegmentPolygonEndRightForPainter.toQPointF()
3291 << prevSegmentPolygonEndRight.toQPointF()
3292 << prevSegmentPolygonEndLeft.toQPointF() );
3293
3294#if 0 // for debugging, will draw the segment polygons
3295 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3296 imagePainter.setBrush( Qt::NoBrush );
3297 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3298 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3299 << thisSegmentPolygonEndRightForPainter.toQPointF()
3300 << prevSegmentPolygonEndRight.toQPointF()
3301 << prevSegmentPolygonEndLeft.toQPointF() );
3302 imagePainter.setPen( Qt::NoPen );
3303#endif
3304
3305 // calculate the new progress horizontal through the source image to account for the length
3306 // of the segment we've just drawn
3307 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3308 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3309 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3310 progressThroughImage = fmod( progressThroughImage, patternLength );
3311
3312 // shuffle buffered variables for next loop
3313 segmentStartPoint = segmentEndPoint;
3314 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3315 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3316 }
3317 imagePainter.end();
3318
3319 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3320 return;
3321
3322 // lastly, draw the temporary image onto the destination painter at the correct place
3323 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3324 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3325}
3326
3327
3328//
3329// QgsRasterLineSymbolLayer
3330//
3331
3333 : mPath( path )
3334{
3335}
3336
3338
3339QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3340{
3341 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3342
3343 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3344 {
3345 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3346 }
3347 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3348 {
3349 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3350 }
3351 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3352 {
3353 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3354 }
3355
3356 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3357 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3358
3359 if ( properties.contains( QStringLiteral( "offset" ) ) )
3360 {
3361 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3362 }
3363 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3364 {
3365 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3366 }
3367 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3368 {
3369 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3370 }
3371
3372 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3373 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3374 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3375 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3376
3377 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3378 {
3379 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3380 }
3381
3382 return res.release();
3383}
3384
3385
3387{
3388 QVariantMap map;
3389 map[QStringLiteral( "imageFile" )] = mPath;
3390
3391 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3392 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3393 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3394
3395 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3396 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3397
3398 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3399 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3400 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3401
3402 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3403
3404 return map;
3405}
3406
3408{
3409 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3410 res->setWidth( mWidth );
3411 res->setWidthUnit( mWidthUnit );
3412 res->setWidthMapUnitScale( mWidthMapUnitScale );
3413 res->setPenJoinStyle( mPenJoinStyle );
3414 res->setPenCapStyle( mPenCapStyle );
3415 res->setOffsetUnit( mOffsetUnit );
3416 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3417 res->setOffset( mOffset );
3418 res->setOpacity( mOpacity );
3419 copyDataDefinedProperties( res.get() );
3420 copyPaintEffect( res.get() );
3421 return res.release();
3422}
3423
3424void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3425{
3426 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3427 if ( it != properties.end() && it.value().userType() == QMetaType::Type::QString )
3428 {
3429 if ( saving )
3430 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3431 else
3432 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3433 }
3434}
3435
3436void QgsRasterLineSymbolLayer::setPath( const QString &path )
3437{
3438 mPath = path;
3439}
3440
3442{
3443 return QStringLiteral( "RasterLine" );
3444}
3445
3450
3452{
3453 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3454
3456
3457 double opacity = mOpacity * context.opacity();
3458 bool cached = false;
3460 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3461 static_cast< int >( std::ceil( scaledHeight ) ) ),
3462 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3463}
3464
3468
3470{
3471 if ( !context.renderContext().painter() )
3472 return;
3473
3474 QImage sourceImage = mLineImage;
3478 {
3479 QString path = mPath;
3481 {
3482 context.setOriginalValueVariable( path );
3484 }
3485
3486 double strokeWidth = mWidth;
3488 {
3489 context.setOriginalValueVariable( strokeWidth );
3491 }
3492 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3493
3495 double opacity = mOpacity;
3497 {
3500 }
3501 opacity *= context.opacity();
3502
3503 bool cached = false;
3505 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3506 static_cast< int >( std::ceil( scaledHeight ) ) ),
3507 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3508 }
3509
3510 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3511 if ( useSelectedColor )
3512 {
3514 }
3515
3516 const QBrush brush( sourceImage );
3517
3518 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3519}
3520
3527
3529{
3531 if ( mWidthUnit != unit || mOffsetUnit != unit )
3532 {
3534 }
3535 return unit;
3536}
3537
3543
3549
3559
3561{
3562 return ( mWidth / 2.0 ) + mOffset;
3563}
3564
3566{
3567 return QColor();
3568}
3569
3570
3571//
3572// QgsLineburstSymbolLayer
3573//
3574
3575QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3577 , mColor2( color2 )
3578{
3579 setColor( color );
3580}
3581
3583
3584QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3585{
3586 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3587
3588 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3589 {
3590 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3591 }
3592 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3593 {
3594 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3595 }
3596 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3597 {
3598 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3599 }
3600
3601 if ( properties.contains( QStringLiteral( "offset" ) ) )
3602 {
3603 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3604 }
3605 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3606 {
3607 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3608 }
3609 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3610 {
3611 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3612 }
3613
3614 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3615 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3616 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3617 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3618
3619 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3620 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3621
3622 if ( properties.contains( QStringLiteral( "color" ) ) )
3623 {
3624 res->setColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() ) );
3625 }
3626 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3627 {
3628 res->setColor2( QgsColorUtils::colorFromString( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3629 }
3630
3631 //attempt to create color ramp from props
3632 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3633 {
3634 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3635 }
3636 else
3637 {
3638 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3639 }
3640
3641 return res.release();
3642}
3643
3645{
3646 QVariantMap map;
3647
3648 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3649 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3650 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3651
3652 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3653 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3654
3655 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3656 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3657 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3658
3659 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
3660 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
3661 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3662 if ( mGradientRamp )
3663 {
3664 map.insert( mGradientRamp->properties() );
3665 }
3666
3667 return map;
3668}
3669
3671{
3672 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3673 res->setWidth( mWidth );
3674 res->setWidthUnit( mWidthUnit );
3675 res->setWidthMapUnitScale( mWidthMapUnitScale );
3676 res->setPenJoinStyle( mPenJoinStyle );
3677 res->setPenCapStyle( mPenCapStyle );
3678 res->setOffsetUnit( mOffsetUnit );
3679 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3680 res->setOffset( mOffset );
3681 res->setColor( mColor );
3682 res->setColor2( mColor2 );
3683 res->setGradientColorType( mGradientColorType );
3684 if ( mGradientRamp )
3685 res->setColorRamp( mGradientRamp->clone() );
3686 copyDataDefinedProperties( res.get() );
3687 copyPaintEffect( res.get() );
3688 return res.release();
3689}
3690
3692{
3693 return QStringLiteral( "Lineburst" );
3694}
3695
3700
3704
3708
3710{
3711 if ( !context.renderContext().painter() )
3712 return;
3713
3714 double strokeWidth = mWidth;
3716 {
3717 context.setOriginalValueVariable( strokeWidth );
3719 }
3720 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3721
3722 //update alpha of gradient colors
3723 QColor color1 = mColor;
3725 {
3728 }
3729 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3730 if ( useSelectedColor )
3731 {
3732 color1 = context.renderContext().selectionColor();
3733 }
3734 color1.setAlphaF( context.opacity() * color1.alphaF() );
3735
3736 //second gradient color
3737 QColor color2 = mColor2;
3739 {
3742 }
3743
3744 //create a QGradient with the desired properties
3745 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3746 //add stops to gradient
3749 {
3750 //color ramp gradient
3751 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3752 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3753 }
3754 else
3755 {
3756 //two color gradient
3757 gradient.setColorAt( 0.0, color1 );
3758 gradient.setColorAt( 1.0, color2 );
3759 }
3760 const QBrush brush( gradient );
3761
3762 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3763}
3764
3771
3773{
3775 if ( mWidthUnit != unit || mOffsetUnit != unit )
3776 {
3778 }
3779 return unit;
3780}
3781
3787
3793
3803
3805{
3806 return ( mWidth / 2.0 ) + mOffset;
3807}
3808
3813
3815{
3816 mGradientRamp.reset( ramp );
3817}
3818
3819//
3820// QgsFilledLineSymbolLayer
3821//
3822
3825{
3826 mWidth = width;
3827 mFill.reset( fillSymbol ? fillSymbol : static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
3828}
3829
3831
3833{
3835
3836 // throughout the history of QGIS and different layer types, we've used
3837 // a huge range of different strings for the same property. The logic here
3838 // is designed to be forgiving to this and accept a range of string keys:
3839 if ( props.contains( QStringLiteral( "line_width" ) ) )
3840 {
3841 width = props[QStringLiteral( "line_width" )].toDouble();
3842 }
3843 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
3844 {
3845 width = props[QStringLiteral( "outline_width" )].toDouble();
3846 }
3847 else if ( props.contains( QStringLiteral( "width" ) ) )
3848 {
3849 width = props[QStringLiteral( "width" )].toDouble();
3850 }
3851
3852 std::unique_ptr<QgsFilledLineSymbolLayer > l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ) );
3853
3854 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
3855 {
3856 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
3857 }
3858 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
3859 {
3860 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
3861 }
3862 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
3863 {
3864 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
3865 }
3866
3867 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3868 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3869 if ( props.contains( QStringLiteral( "offset" ) ) )
3870 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
3871 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
3872 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
3873 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3874 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3875 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
3876 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
3877 if ( props.contains( QStringLiteral( "capstyle" ) ) )
3878 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
3879
3880 l->restoreOldDataDefinedProperties( props );
3881
3882 return l.release();
3883}
3884
3886{
3887 return QStringLiteral( "FilledLine" );
3888}
3889
3891{
3892 if ( mFill )
3893 {
3894 mFill->setRenderHints( mFill->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3895 mFill->startRender( context.renderContext(), context.fields() );
3896 }
3897}
3898
3900{
3901 if ( mFill )
3902 {
3903 mFill->stopRender( context.renderContext() );
3904 }
3905}
3906
3908{
3909 QPainter *p = context.renderContext().painter();
3910 if ( !p || !mFill )
3911 return;
3912
3913 double width = mWidth;
3915 {
3918 }
3919
3920 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
3921
3922 Qt::PenJoinStyle join = mPenJoinStyle;
3924 {
3927 if ( !QgsVariantUtils::isNull( exprVal ) )
3928 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3929 }
3930
3931 Qt::PenCapStyle cap = mPenCapStyle;
3933 {
3936 if ( !QgsVariantUtils::isNull( exprVal ) )
3937 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3938 }
3939
3940 double offset = mOffset;
3942 {
3945 }
3946
3947 const double prevOpacity = mFill->opacity();
3948 mFill->setOpacity( mFill->opacity() * context.opacity() );
3949
3950 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3952
3953 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3954
3955 // stroke out the path using the correct line cap/join style. We'll then use this as the fill polygon
3956 QPainterPathStroker stroker;
3957 stroker.setWidth( scaledWidth );
3958 stroker.setCapStyle( cap );
3959 stroker.setJoinStyle( join );
3960
3961 QPolygonF polygon;
3962 if ( qgsDoubleNear( offset, 0 ) )
3963 {
3964 QPainterPath path;
3965 path.addPolygon( points );
3966 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3967 const QPolygonF polygon = stroke.toFillPolygon();
3968 if ( !polygon.isEmpty() )
3969 {
3970 mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
3971 }
3972 }
3973 else
3974 {
3975 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3977 {
3978 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
3979 // and clamp it to a reasonable range. It's the best we can do in this situation!
3980 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
3981 }
3982
3983 const QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3984 for ( const QPolygonF &part : mline )
3985 {
3986 QPainterPath path;
3987 path.addPolygon( part );
3988 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3989 const QPolygonF polygon = stroke.toFillPolygon();
3990 if ( !polygon.isEmpty() )
3991 {
3992 mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
3993 }
3994 }
3995 }
3996
3998
3999 mFill->setOpacity( prevOpacity );
4000}
4001
4003{
4004 QVariantMap map;
4005
4006 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
4007 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
4008 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4009 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
4010 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
4011 map[QStringLiteral( "offset" )] = QString::number( mOffset );
4012 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4013 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4014 if ( mFill )
4015 {
4016 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mFill->color() );
4017 }
4018 return map;
4019}
4020
4022{
4023 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4024 copyPaintEffect( res.get() );
4025 copyDataDefinedProperties( res.get() );
4026 res->setSubSymbol( mFill->clone() );
4027 return res.release();
4028}
4029
4031{
4032 return mFill.get();
4033}
4034
4036{
4037 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4038 {
4039 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4040 return true;
4041 }
4042 else
4043 {
4044 delete symbol;
4045 return false;
4046 }
4047}
4048
4050{
4051 if ( mFill )
4052 {
4053 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4054 }
4055 return 0;
4056}
4057
4059{
4060 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4061 if ( mFill )
4062 attr.unite( mFill->usedAttributes( context ) );
4063 return attr;
4064}
4065
4067{
4069 return true;
4070 if ( mFill && mFill->hasDataDefinedProperties() )
4071 return true;
4072 return false;
4073}
4074
4076{
4077 mColor = c;
4078 if ( mFill )
4079 mFill->setColor( c );
4080}
4081
4083{
4084 return mFill ? mFill->color() : mColor;
4085}
4086
4093
4095{
4097 if ( mFill )
4098 mFill->setMapUnitScale( scale );
4099}
4100
4102{
4103 if ( mFill )
4104 {
4105 return mFill->mapUnitScale();
4106 }
4107 return QgsMapUnitScale();
4108}
4109
4111{
4113 if ( mFill )
4114 mFill->setOutputUnit( unit );
4115}
4116
4118{
4119 if ( mFill )
4120 {
4121 return mFill->outputUnit();
4122 }
4124}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:2682
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex) (since QGIS 3....
@ LastVertex
Place symbols on the last vertex in the line.
@ CentralPoint
Place symbols at the mid point of the line.
@ SegmentCenter
Place symbols at the center of every line segment.
@ Vertex
Place symbols on every vertex in the line.
@ Interval
Place symbols at regular intervals.
@ FirstVertex
Place symbols on the first vertex in the line.
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer (since QGIS 3.38)
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
GradientColorSource
Gradient color sources.
Definition qgis.h:2704
@ ColorRamp
Gradient color ramp.
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
@ Curve
An intermediate point on a segment defining the curvature of the segment.
@ Segment
The actual start or end point of a segment.
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:644
@ Unknown
Unknown types.
RenderUnit
Rendering size units.
Definition qgis.h:4494
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:585
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
QFlags< MarkerLinePlacement > MarkerLinePlacements
Definition qgis.h:2693
Base class for line symbol layer types which draws line sections using a QBrush.
void renderPolylineUsingBrush(const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength)
Renders a polyline of points using the specified brush.
static bool isGeneralizableByDeviceBoundingBox(const QgsRectangle &envelope, float mapToPixelTol=1.0f)
Returns whether the device-envelope can be replaced by its BBOX when is applied the specified toleran...
virtual double vertexAngle(QgsVertexId vertex) const =0
Returns approximate angle at a vertex.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
Abstract base class for color ramps.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Class for doing transforms between two map coordinate systems.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
Curve polygon geometry type.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Exports QGIS layers to the DXF format.
static double mapUnitScaleFactor(double scale, Qgis::RenderUnit symbolUnits, Qgis::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
Qgis::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
void clipValueToMapUnitScale(double &value, const QgsMapUnitScale &scale, double pixelToMMFactor) const
Clips value to scale minimum/maximum.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
static const QString EXPR_GEOMETRY_RING_NUM
Inbuilt variable name for geometry ring number variable.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
A line symbol layer type which fills a stroked line with a QgsFillSymbol.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsFilledLineSymbolLayer, using the settings serialized in the properties map (correspo...
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
~QgsFilledLineSymbolLayer() override
QgsFilledLineSymbolLayer(double width=DEFAULT_SIMPLELINE_WIDTH, QgsFillSymbol *fillSymbol=nullptr)
Constructor for QgsFilledLineSymbolLayer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
QColor color() const override
Returns the "representative" color of the symbol layer.
QgsFilledLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsMapUnitScale mapUnitScale() const override
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
static double lineAngle(double x1, double y1, double x2, double y2)
Calculates the direction of line joining two points in radians, clockwise from the north direction.
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false)
Compute the intersection between two segments.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
Line symbol layer type which draws repeating line sections along a line feature.
double hashAngle() const
Returns the angle to use when drawing the hashed lines sections, in degrees clockwise.
QgsHashedLineSymbolLayer(bool rotateSymbol=true, double interval=3)
Constructor for QgsHashedLineSymbolLayer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setWidth(double width) override
Sets the width of the line symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
double width() const override
Returns the estimated width for the line symbol layer.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsHashedLineSymbolLayer, using the settings serialized in the properties map (correspo...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsHashedLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
~QgsHashedLineSymbolLayer() override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QColor color() const override
Returns the "representative" color of the symbol layer.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setHashAngle(double angle)
Sets the angle to use when drawing the hashed lines sections, in degrees clockwise.
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Qgis::RenderUnit mOffsetUnit
RenderRingFilter
Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
@ ExteriorRingOnly
Render the exterior ring only.
@ InteriorRingsOnly
Render the interior rings only.
@ AllRings
Render both exterior and interior rings.
QgsMapUnitScale mWidthMapUnitScale
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setWidthMapUnitScale(const QgsMapUnitScale &scale)
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
Qgis::RenderUnit mWidthUnit
void setOffset(double offset)
Sets the line's offset.
RenderRingFilter mRingFilter
void setOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the line's offset.
void setWidthUnit(Qgis::RenderUnit unit)
Sets the units for the line's width.
virtual double width() const
Returns the estimated width for the line symbol layer.
QgsMapUnitScale mOffsetMapUnitScale
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the line's offset.
QgsMapUnitScale mapUnitScale() const override
void setRingFilter(QgsLineSymbolLayer::RenderRingFilter filter)
Sets the line symbol layer's ring filter, which controls which rings are rendered when the line symbo...
void setMapUnitScale(const QgsMapUnitScale &scale) override
double offset() const
Returns the line's offset.
Qgis::RenderUnit offsetUnit() const
Returns the units for the line's offset.
Qgis::RenderUnit widthUnit() const
Returns the units for the line's width.
A line symbol type, for rendering LineString and MultiLineString geometries.
Line symbol layer type which draws a gradient pattern perpendicularly along a line.
std::unique_ptr< QgsColorRamp > mGradientRamp
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient line.
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
Qgis::GradientColorSource mGradientColorType
QString layerType() const override
Returns a string that represents this layer type.
~QgsLineburstSymbolLayer() override
QgsLineburstSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLineburstSymbolLayer, using the settings serialized in the properties map (correspon...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QgsLineburstSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, const QColor &color2=Qt::white)
Constructor for QgsLineburstSymbolLayer, with the specified start and end gradient colors.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsColorRamp * colorRamp()
Returns the color ramp used for the gradient line.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
Line symbol layer type which draws repeating marker symbols along a line feature.
Q_DECL_DEPRECATED bool rotateMarker() const
Shall the marker be rotated.
std::unique_ptr< QgsMarkerSymbol > mMarker
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsMarkerLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
double width() const override
Returns the estimated width for the line symbol layer.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsMarkerLineSymbolLayer, using the settings serialized in the properties map (correspo...
QgsMarkerLineSymbolLayer(bool rotateMarker=DEFAULT_MARKERLINE_ROTATE, double interval=DEFAULT_MARKERLINE_INTERVAL)
Constructor for QgsMarkerLineSymbolLayer.
void setWidth(double width) override
Sets the width of the line symbol layer.
QColor color() const override
Returns the "representative" color of the symbol layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
~QgsMarkerLineSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QString layerType() const override
Returns a string that represents this layer type.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM element.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
A class to represent a 2D point.
Definition qgspointxy.h:60
QgsPointXY project(double distance, double bearing) const
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
QPointF toQPointF() const
Returns the point as a QPointF.
Definition qgspoint.h:382
double x
Definition qgspoint.h:52
QgsPoint project(double distance, double azimuth, double inclination=90.0) const
Returns a new point which corresponds to this point projected by a specified distance with specified ...
Definition qgspoint.cpp:705
double y
Definition qgspoint.h:53
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
A store for object properties.
Line symbol layer type which draws line sections using a raster image file.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsRasterLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double opacity() const
Returns the line opacity.
QString path() const
Returns the raster image path.
void setPath(const QString &path)
Set the raster image path.
QString layerType() const override
Returns a string that represents this layer type.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QgsRasterLineSymbolLayer(const QString &path=QString())
Constructor for QgsRasterLineSymbolLayer, with the specified raster image path.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRasterLineSymbolLayer, using the settings serialized in the properties map (correspo...
QColor color() const override
Returns the "representative" color of the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsMapUnitScale mapUnitScale() const override
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
virtual ~QgsRasterLineSymbolLayer()
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QColor selectionColor() const
Returns the color to use when rendering selected features.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsAbstractGeometry * geometry() const
Returns pointer to the unsegmentized geometry.
Scoped object for saving and restoring a QPainter object's state.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setDrawInsidePolygon(bool drawInsidePolygon)
Sets whether the line should only be drawn inside polygons, and any portion of the line which falls o...
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsMapUnitScale mapUnitScale() const override
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleLineSymbolLayer, using the settings serialized in the properties map (correspo...
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QVector< qreal > customDashVector() const
Returns the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ...
Qt::PenJoinStyle penJoinStyle() const
Returns the pen join style used to render the line (e.g.
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the outline of polygon, using the given render context.
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
void setCustomDashPatternMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for lengths used in the custom dash pattern.
void setTrimDistanceEndMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the end of the line.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void setTrimDistanceEnd(double distance)
Sets the trim distance for the end of the line, which dictates a length from the end of the line at w...
double dxfOffset(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets offset.
QgsSimpleLineSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, double width=DEFAULT_SIMPLELINE_WIDTH, Qt::PenStyle penStyle=DEFAULT_SIMPLELINE_PENSTYLE)
Constructor for QgsSimpleLineSymbolLayer.
~QgsSimpleLineSymbolLayer() override
void setUseCustomDashPattern(bool b)
Sets whether the line uses a custom dash pattern.
void setTweakDashPatternOnCorners(bool enabled)
Sets whether dash patterns tweaks should be applied on sharp corners, to ensure that a double-length ...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setCustomDashVector(const QVector< qreal > &vector)
Sets the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ren...
void setDashPatternOffset(double offset)
Sets the dash pattern offset, which dictates how far along the dash pattern the pattern should start ...
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
QString layerType() const override
Returns a string that represents this layer type.
void setDashPatternOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the dash pattern offset.
QgsSimpleLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qt::PenStyle penStyle() const
Returns the pen style used to render the line (e.g.
void setPenJoinStyle(Qt::PenJoinStyle style)
Sets the pen join style used to render the line (e.g.
void setAlignDashPattern(bool enabled)
Sets whether dash patterns should be aligned to the start and end of lines, by applying subtle tweaks...
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsSimpleLineSymbolLayer from an SLD XML DOM element.
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
void setTrimDistanceStartMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the start of the line.
QVector< qreal > dxfCustomDashPattern(Qgis::RenderUnit &unit) const override
Gets dash pattern.
Qt::PenCapStyle penCapStyle() const
Returns the pen cap style used to render the line (e.g.
void setTrimDistanceEndUnit(Qgis::RenderUnit unit)
Sets the unit for the trim distance for the end of the line.
void setDashPatternOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the dash pattern offset.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
void setMapUnitScale(const QgsMapUnitScale &scale) override
void setTrimDistanceStart(double distance)
Sets the trim distance for the start of the line, which dictates a length from the start of the line ...
QString ogrFeatureStyle(double mmScaleFactor, double mapUnitScaleFactor) const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setTrimDistanceStartUnit(Qgis::RenderUnit unit)
Sets the unit for the trim distance for the start of the line.
bool alignDashPattern() const
Returns true if dash patterns should be aligned to the start and end of lines, by applying subtle twe...
void setCustomDashPatternUnit(Qgis::RenderUnit unit)
Sets the unit for lengths used in the custom dash pattern.
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)