QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 "qgscurve.h"
18#include "qgscurvepolygon.h"
19#include "qgsdxfexport.h"
20#include "qgssymbollayerutils.h"
21#include "qgsrendercontext.h"
22#include "qgslogger.h"
24#include "qgsunittypes.h"
25#include "qgsproperty.h"
27#include "qgsmarkersymbol.h"
28#include "qgslinesymbol.h"
29#include "qgsapplication.h"
30#include "qgsimagecache.h"
31#include "qgsfeedback.h"
32#include "qgsimageoperation.h"
33#include "qgscolorrampimpl.h"
34#include "qgsfillsymbol.h"
35#include "qgscolorutils.h"
36
37#include <algorithm>
38#include <QPainter>
39#include <QDomDocument>
40#include <QDomElement>
41
42#include <cmath>
43
44QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
45 : mPenStyle( penStyle )
46{
47 mColor = color;
48 mWidth = width;
49 mCustomDashVector << 5 << 2;
50}
51
53
55{
57 mWidthUnit = unit;
58 mOffsetUnit = unit;
59 mCustomDashPatternUnit = unit;
60 mDashPatternOffsetUnit = unit;
61 mTrimDistanceStartUnit = unit;
62 mTrimDistanceEndUnit = unit;
63}
64
66{
68 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
69 {
71 }
72 return unit;
73}
74
76{
79}
80
82{
84 mWidthMapUnitScale = scale;
85 mOffsetMapUnitScale = scale;
86 mCustomDashPatternMapUnitScale = scale;
87}
88
90{
93 mOffsetMapUnitScale == mCustomDashPatternMapUnitScale )
94 {
95 return mWidthMapUnitScale;
96 }
97 return QgsMapUnitScale();
98}
99
101{
105
106 if ( props.contains( QStringLiteral( "line_color" ) ) )
107 {
108 color = QgsColorUtils::colorFromString( props[QStringLiteral( "line_color" )].toString() );
109 }
110 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
111 {
112 color = QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() );
113 }
114 else if ( props.contains( QStringLiteral( "color" ) ) )
115 {
116 //pre 2.5 projects used "color"
117 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
118 }
119 if ( props.contains( QStringLiteral( "line_width" ) ) )
120 {
121 width = props[QStringLiteral( "line_width" )].toDouble();
122 }
123 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
124 {
125 width = props[QStringLiteral( "outline_width" )].toDouble();
126 }
127 else if ( props.contains( QStringLiteral( "width" ) ) )
128 {
129 //pre 2.5 projects used "width"
130 width = props[QStringLiteral( "width" )].toDouble();
131 }
132 if ( props.contains( QStringLiteral( "line_style" ) ) )
133 {
134 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
135 }
136 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
137 {
138 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
139 }
140 else if ( props.contains( QStringLiteral( "penstyle" ) ) )
141 {
142 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
143 }
144
146 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
147 {
148 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
149 }
150 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
151 {
152 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
153 }
154 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
155 {
156 //pre 2.5 projects used "width_unit"
157 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
158 }
159 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
160 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
161 if ( props.contains( QStringLiteral( "offset" ) ) )
162 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
163 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
164 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
165 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
166 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
167 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
168 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
169 if ( props.contains( QStringLiteral( "capstyle" ) ) )
170 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
171
172 if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
173 {
174 l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
175 }
176 if ( props.contains( QStringLiteral( "customdash" ) ) )
177 {
178 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
179 }
180 if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
181 {
182 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
183 }
184 if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
185 {
186 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
187 }
188
189 if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
190 {
191 l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
192 }
193
194 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
195 {
196 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
197 }
198
199 if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
200 l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
201 if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
202 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
203 if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
204 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
205
206 if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
207 l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
208 if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
209 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
210 if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
211 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
212 if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
213 l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
214 if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
215 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
216 if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
217 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
218
219 if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
220 l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
221
222 if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
223 l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
224
226
227 return l;
228}
229
231{
232 return QStringLiteral( "SimpleLine" );
233}
234
236{
237 QColor penColor = mColor;
238 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
239 mPen.setColor( penColor );
240 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
241 mPen.setWidthF( scaledWidth );
242
243 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
244 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
245 const double dashWidthDiv = std::max( 1.0, scaledWidth );
246 if ( mUseCustomDashPattern )
247 {
248 mPen.setStyle( Qt::CustomDashLine );
249
250 //scale pattern vector
251
252 QVector<qreal> scaledVector;
253 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
254 for ( ; it != mCustomDashVector.constEnd(); ++it )
255 {
256 //the dash is specified in terms of pen widths, therefore the division
257 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
258 }
259 mPen.setDashPattern( scaledVector );
260 }
261 else
262 {
263 mPen.setStyle( mPenStyle );
264 }
265
266 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
267 {
268 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
269 }
270
271 mPen.setJoinStyle( mPenJoinStyle );
272 mPen.setCapStyle( mPenCapStyle );
273
274 mSelPen = mPen;
275 QColor selColor = context.renderContext().selectionColor();
276 if ( ! SELECTION_IS_OPAQUE )
277 selColor.setAlphaF( context.opacity() );
278 mSelPen.setColor( selColor );
279}
280
282{
283 Q_UNUSED( context )
284}
285
286void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
287{
288 QPainter *p = context.renderContext().painter();
289 if ( !p )
290 {
291 return;
292 }
293
294 QgsExpressionContextScope *scope = nullptr;
295 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
297 {
298 scope = new QgsExpressionContextScope();
299 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
300 }
301
302 if ( mDrawInsidePolygon )
303 p->save();
304
305 switch ( mRingFilter )
306 {
307 case AllRings:
308 case ExteriorRingOnly:
309 {
310 if ( mDrawInsidePolygon )
311 {
312 //only drawing the line on the interior of the polygon, so set clip path for painter
313 QPainterPath clipPath;
314 clipPath.addPolygon( points );
315
316 if ( rings )
317 {
318 //add polygon rings
319 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
320 {
321 QPolygonF ring = *it;
322 clipPath.addPolygon( ring );
323 }
324 }
325
326 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
327 p->setClipPath( clipPath, Qt::IntersectClip );
328 }
329
330 if ( scope )
332
333 renderPolyline( points, context );
334 }
335 break;
336
338 break;
339 }
340
341 if ( rings )
342 {
343 switch ( mRingFilter )
344 {
345 case AllRings:
347 {
348 mOffset = -mOffset; // invert the offset for rings!
349 int ringIndex = 1;
350 for ( const QPolygonF &ring : std::as_const( *rings ) )
351 {
352 if ( scope )
354
355 renderPolyline( ring, context );
356 ringIndex++;
357 }
358 mOffset = -mOffset;
359 }
360 break;
361 case ExteriorRingOnly:
362 break;
363 }
364 }
365
366 if ( mDrawInsidePolygon )
367 {
368 //restore painter to reset clip path
369 p->restore();
370 }
371
372}
373
375{
376 QPainter *p = context.renderContext().painter();
377 if ( !p )
378 {
379 return;
380 }
381
382 QPolygonF points = pts;
383
384 double startTrim = mTrimDistanceStart;
386 {
387 context.setOriginalValueVariable( startTrim );
389 }
390 double endTrim = mTrimDistanceEnd;
392 {
393 context.setOriginalValueVariable( endTrim );
395 }
396
397 double totalLength = -1;
398 if ( mTrimDistanceStartUnit == Qgis::RenderUnit::Percentage )
399 {
400 totalLength = QgsSymbolLayerUtils::polylineLength( points );
401 startTrim = startTrim * 0.01 * totalLength;
402 }
403 else
404 {
405 startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
406 }
407 if ( mTrimDistanceEndUnit == Qgis::RenderUnit::Percentage )
408 {
409 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
410 totalLength = QgsSymbolLayerUtils::polylineLength( points );
411 endTrim = endTrim * 0.01 * totalLength;
412 }
413 else
414 {
415 endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
416 }
417 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
418 {
419 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
420 }
421
422 QColor penColor = mColor;
423 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
424 mPen.setColor( penColor );
425
426 double offset = mOffset;
427 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
428
429 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
430 const QPen pen = useSelectedColor ? mSelPen : mPen;
431
432 if ( !pen.dashPattern().isEmpty() )
433 {
434 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
435 const QVector<double> pattern = pen.dashPattern();
436 bool foundNonNull = false;
437 for ( int i = 0; i < pattern.size(); ++i )
438 {
439 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
440 {
441 foundNonNull = true;
442 break;
443 }
444 }
445 if ( !foundNonNull )
446 return;
447 }
448
449 p->setBrush( Qt::NoBrush );
450
451 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
452 std::unique_ptr< QgsScopedQPainterState > painterState;
453 if ( points.size() <= 2 &&
456 ( p->renderHints() & QPainter::Antialiasing ) )
457 {
458 painterState = std::make_unique< QgsScopedQPainterState >( p );
459 p->setRenderHint( QPainter::Antialiasing, false );
460 }
461
462 const bool applyPatternTweaks = mAlignDashPattern
463 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
464 && pen.dashOffset() == 0;
465
466 if ( qgsDoubleNear( offset, 0 ) )
467 {
468 if ( applyPatternTweaks )
469 {
470 drawPathWithDashPatternTweaks( p, points, pen );
471 }
472 else
473 {
474 p->setPen( pen );
475 QPainterPath path;
476 path.addPolygon( points );
477 p->drawPath( path );
478 }
479 }
480 else
481 {
482 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
484 {
485 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
486 // and clamp it to a reasonable range. It's the best we can do in this situation!
487 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
488 }
489
490 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
491 for ( const QPolygonF &part : mline )
492 {
493 if ( applyPatternTweaks )
494 {
495 drawPathWithDashPatternTweaks( p, part, pen );
496 }
497 else
498 {
499 p->setPen( pen );
500 QPainterPath path;
501 path.addPolygon( part );
502 p->drawPath( path );
503 }
504 }
505 }
506}
507
509{
510 QVariantMap map;
511 map[QStringLiteral( "line_color" )] = QgsColorUtils::colorToString( mColor );
512 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
513 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
514 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
515 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
516 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
517 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
518 map[QStringLiteral( "offset" )] = QString::number( mOffset );
519 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
520 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
521 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
522 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
523 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
524 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
525 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
526 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
527 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
528 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
529 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
530 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
531 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
532 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
533 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
534 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
535 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
536 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
537 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
538 return map;
539}
540
542{
548 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
549 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
550 l->setOffset( mOffset );
551 l->setPenJoinStyle( mPenJoinStyle );
552 l->setPenCapStyle( mPenCapStyle );
553 l->setUseCustomDashPattern( mUseCustomDashPattern );
554 l->setCustomDashVector( mCustomDashVector );
555 l->setDrawInsidePolygon( mDrawInsidePolygon );
557 l->setDashPatternOffset( mDashPatternOffset );
558 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
559 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
560 l->setTrimDistanceStart( mTrimDistanceStart );
561 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
562 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
563 l->setTrimDistanceEnd( mTrimDistanceEnd );
564 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
565 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
566 l->setAlignDashPattern( mAlignDashPattern );
567 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
568
570 copyPaintEffect( l );
571 return l;
572}
573
574void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
575{
576 if ( mPenStyle == Qt::NoPen )
577 return;
578
579 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
580 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
581 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
582 element.appendChild( symbolizerElem );
583
584 // <Geometry>
585 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
586
587 // <Stroke>
588 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
589 symbolizerElem.appendChild( strokeElem );
590
591 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
593 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
595 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
596
597 // <se:PerpendicularOffset>
598 if ( !qgsDoubleNear( mOffset, 0.0 ) )
599 {
600 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
602 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
603 symbolizerElem.appendChild( perpOffsetElem );
604 }
605}
606
607QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
608{
609 if ( mUseCustomDashPattern )
610 {
611 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
612 mPen.color(), mPenJoinStyle,
613 mPenCapStyle, mOffset, &mCustomDashVector );
614 }
615 else
616 {
617 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
618 mPenCapStyle, mOffset );
619 }
620}
621
623{
624 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
625
626 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
627 if ( strokeElem.isNull() )
628 return nullptr;
629
630 Qt::PenStyle penStyle;
631 QColor color;
632 double width;
633 Qt::PenJoinStyle penJoinStyle;
634 Qt::PenCapStyle penCapStyle;
635 QVector<qreal> customDashVector;
636
638 color, width,
641 return nullptr;
642
643 double offset = 0.0;
644 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
645 if ( !perpOffsetElem.isNull() )
646 {
647 bool ok;
648 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
649 if ( ok )
650 offset = d;
651 }
652
653 double scaleFactor = 1.0;
654 const QString uom = element.attribute( QStringLiteral( "uom" ) );
655 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
656 width = width * scaleFactor;
657 offset = offset * scaleFactor;
658
660 l->setOutputUnit( sldUnitSize );
661 l->setOffset( offset );
664 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
666 return l;
667}
668
669void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
670{
671 if ( !dataDefinedProperties().hasActiveProperties() )
672 return; // shortcut
673
674 //data defined properties
675 bool hasStrokeWidthExpression = false;
677 {
679 double scaledWidth = context.renderContext().convertToPainterUnits(
682 pen.setWidthF( scaledWidth );
683 selPen.setWidthF( scaledWidth );
684 hasStrokeWidthExpression = true;
685 }
686
687 //color
689 {
691
693 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
694 pen.setColor( penColor );
695 }
696
697 //offset
699 {
702 }
703
704 //dash dot vector
705
706 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
707 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
708 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
709
711 {
712 QVector<qreal> dashVector;
714 if ( !QgsVariantUtils::isNull( exprVal ) )
715 {
716 QStringList dashList = exprVal.toString().split( ';' );
717 QStringList::const_iterator dashIt = dashList.constBegin();
718 for ( ; dashIt != dashList.constEnd(); ++dashIt )
719 {
720 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
721 }
722 pen.setDashPattern( dashVector );
723 }
724 }
725 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
726 {
727 //re-scale pattern vector after data defined pen width was applied
728
729 QVector<qreal> scaledVector;
730 for ( double v : std::as_const( mCustomDashVector ) )
731 {
732 //the dash is specified in terms of pen widths, therefore the division
733 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
734 }
735 mPen.setDashPattern( scaledVector );
736 }
737
738 // dash pattern offset
739 double patternOffset = mDashPatternOffset;
740 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
741 {
742 context.setOriginalValueVariable( patternOffset );
744 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
745 }
746
747 //line style
749 {
752 if ( !QgsVariantUtils::isNull( exprVal ) )
753 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
754 }
755
756 //join style
758 {
761 if ( !QgsVariantUtils::isNull( exprVal ) )
762 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
763 }
764
765 //cap style
767 {
770 if ( !QgsVariantUtils::isNull( exprVal ) )
771 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
772 }
773}
774
775void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
776{
777 if ( pen.dashPattern().empty() || points.size() < 2 )
778 return;
779
780 if ( pen.widthF() <= 1.0 )
781 {
782 pen.setWidthF( 1.0001 );
783 }
784
785 QVector< qreal > sourcePattern = pen.dashPattern();
786 const double dashWidthDiv = pen.widthF();
787 // back to painter units
788 for ( int i = 0; i < sourcePattern.size(); ++ i )
789 sourcePattern[i] *= pen.widthF();
790
791 QVector< qreal > buffer;
792 QPolygonF bufferedPoints;
793 QPolygonF previousSegmentBuffer;
794 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
795 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
796 // and then append the buffer to the output pattern.
797
798 auto ptIt = points.constBegin();
799 double totalBufferLength = 0;
800 int patternIndex = 0;
801 double currentRemainingDashLength = 0;
802 double currentRemainingGapLength = 0;
803
804 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
805 {
806 QVector< qreal > result;
807 result.reserve( buffer.size() );
808 for ( auto it = buffer.begin(); it != buffer.end(); )
809 {
810 qreal dash = *it++;
811 qreal gap = *it++;
812 while ( dash == 0 && !result.empty() )
813 {
814 result.last() += gap;
815
816 if ( it == buffer.end() )
817 return result;
818 dash = *it++;
819 gap = *it++;
820 }
821 while ( gap == 0 && it != buffer.end() )
822 {
823 dash += *it++;
824 gap = *it++;
825 }
826 result << dash << gap;
827 }
828 return result;
829 };
830
831 double currentBufferLineLength = 0;
832 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
833 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
834 {
835 if ( buffer.empty() || bufferedPoints.size() < 2 )
836 {
837 return;
838 }
839
840 if ( currentRemainingDashLength )
841 {
842 // ended midway through a dash -- we want to finish this off
843 buffer << currentRemainingDashLength << 0.0;
844 totalBufferLength += currentRemainingDashLength;
845 }
846 QVector< qreal > compressed = compressPattern( buffer );
847 if ( !currentRemainingDashLength )
848 {
849 // ended midway through a gap -- we don't want this, we want to end at previous dash
850 totalBufferLength -= compressed.last();
851 compressed.last() = 0;
852 }
853
854 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
855 const double scaleFactor = currentBufferLineLength / totalBufferLength;
856
857 bool shouldFlushPreviousSegmentBuffer = false;
858
859 if ( !previousSegmentBuffer.empty() )
860 {
861 // add first dash from current buffer
862 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
863 if ( !firstDashSubstring.empty() )
864 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
865
866 // then we skip over the first dash and gap for this segment
867 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
868
869 compressed = compressed.mid( 2 );
870 shouldFlushPreviousSegmentBuffer = !compressed.empty();
871 }
872
873 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
874 {
875 QPen adjustedPen = pen;
876 adjustedPen.setStyle( Qt::SolidLine );
877 painter->setPen( adjustedPen );
878 QPainterPath path;
879 path.addPolygon( previousSegmentBuffer );
880 painter->drawPath( path );
881 previousSegmentBuffer.clear();
882 }
883
884 double finalDash = 0;
885 if ( nextPoint )
886 {
887 // sharp bend:
888 // 1. rewind buffered points line by final dash and gap length
889 // (later) 2. draw the bend with a solid line of length 2 * final dash size
890
891 if ( !compressed.empty() )
892 {
893 finalDash = compressed.at( compressed.size() - 2 );
894 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
895
896 const QPolygonF thisPoints = bufferedPoints;
897 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
898 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
899 }
900 else
901 {
902 previousSegmentBuffer << bufferedPoints;
903 }
904 }
905
906 currentBufferLineLength = 0;
907 currentRemainingDashLength = 0;
908 currentRemainingGapLength = 0;
909 totalBufferLength = 0;
910 buffer.clear();
911
912 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
913 {
914 QPen adjustedPen = pen;
915 if ( !compressed.empty() )
916 {
917 // maximum size of dash pattern is 32 elements
918 compressed = compressed.mid( 0, 32 );
919 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
920 adjustedPen.setDashPattern( compressed );
921 }
922 else
923 {
924 adjustedPen.setStyle( Qt::SolidLine );
925 }
926
927 painter->setPen( adjustedPen );
928 QPainterPath path;
929 path.addPolygon( bufferedPoints );
930 painter->drawPath( path );
931 }
932
933 bufferedPoints.clear();
934 };
935
936 QPointF p1;
937 QPointF p2 = *ptIt;
938 ptIt++;
939 bufferedPoints << p2;
940 for ( ; ptIt != points.constEnd(); ++ptIt )
941 {
942 p1 = *ptIt;
943 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
944 {
945 continue;
946 }
947
948 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
949 currentBufferLineLength += remainingSegmentDistance;
950 while ( true )
951 {
952 // handle currentRemainingDashLength/currentRemainingGapLength
953 if ( currentRemainingDashLength > 0 )
954 {
955 // bit more of dash to insert
956 if ( remainingSegmentDistance >= currentRemainingDashLength )
957 {
958 // all of dash fits in
959 buffer << currentRemainingDashLength << 0.0;
960 totalBufferLength += currentRemainingDashLength;
961 remainingSegmentDistance -= currentRemainingDashLength;
962 patternIndex++;
963 currentRemainingDashLength = 0.0;
964 currentRemainingGapLength = sourcePattern.at( patternIndex );
965 if ( currentRemainingGapLength == 0.0 )
966 {
967 patternIndex++;
968 }
969 }
970 else
971 {
972 // only part of remaining dash fits in
973 buffer << remainingSegmentDistance << 0.0;
974 totalBufferLength += remainingSegmentDistance;
975 currentRemainingDashLength -= remainingSegmentDistance;
976 break;
977 }
978 }
979 if ( currentRemainingGapLength > 0 )
980 {
981 // bit more of gap to insert
982 if ( remainingSegmentDistance >= currentRemainingGapLength )
983 {
984 // all of gap fits in
985 buffer << 0.0 << currentRemainingGapLength;
986 totalBufferLength += currentRemainingGapLength;
987 remainingSegmentDistance -= currentRemainingGapLength;
988 currentRemainingGapLength = 0.0;
989 patternIndex++;
990 }
991 else
992 {
993 // only part of remaining gap fits in
994 buffer << 0.0 << remainingSegmentDistance;
995 totalBufferLength += remainingSegmentDistance;
996 currentRemainingGapLength -= remainingSegmentDistance;
997 break;
998 }
999 }
1000
1001 if ( patternIndex + 1 >= sourcePattern.size() )
1002 {
1003 patternIndex = 0;
1004 }
1005
1006 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1007 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1008 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1009 {
1010 buffer << nextPatternDashLength << nextPatternGapLength;
1011 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1012 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1013 patternIndex += 2;
1014 }
1015 else if ( nextPatternDashLength <= remainingSegmentDistance )
1016 {
1017 // can fit in "dash", but not "gap"
1018 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1019 totalBufferLength += remainingSegmentDistance;
1020 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1021 currentRemainingDashLength = 0;
1022 patternIndex++;
1023 break;
1024 }
1025 else
1026 {
1027 // can't fit in "dash"
1028 buffer << remainingSegmentDistance << 0.0;
1029 totalBufferLength += remainingSegmentDistance;
1030 currentRemainingGapLength = 0;
1031 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1032 break;
1033 }
1034 }
1035
1036 bufferedPoints << p1;
1037 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1038 {
1039 QPointF nextPoint = *( ptIt + 1 );
1040
1041 // extreme angles form more than 45 degree angle at a node
1042 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1043 {
1044 // extreme angle. Rescale buffer and flush
1045 flushBuffer( &nextPoint );
1046 bufferedPoints << p1;
1047 // restart the line with the full length of the most recent dash element -- see
1048 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1049 if ( patternIndex % 2 == 1 )
1050 {
1051 patternIndex--;
1052 }
1053 currentRemainingDashLength = sourcePattern.at( patternIndex );
1054 }
1055 }
1056
1057 p2 = p1;
1058 }
1059
1060 flushBuffer( nullptr );
1061 if ( !previousSegmentBuffer.empty() )
1062 {
1063 QPen adjustedPen = pen;
1064 adjustedPen.setStyle( Qt::SolidLine );
1065 painter->setPen( adjustedPen );
1066 QPainterPath path;
1067 path.addPolygon( previousSegmentBuffer );
1068 painter->drawPath( path );
1069 previousSegmentBuffer.clear();
1070 }
1071}
1072
1074{
1075 if ( mDrawInsidePolygon )
1076 {
1077 //set to clip line to the interior of polygon, so we expect no bleed
1078 return 0;
1079 }
1080 else
1081 {
1082 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1084 }
1085}
1086
1088{
1089 unit = mCustomDashPatternUnit;
1090 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1091}
1092
1094{
1095 return mPenStyle;
1096}
1097
1099{
1100 double width = mWidth;
1102 {
1105 }
1106
1109 {
1111 }
1112 return width;
1113}
1114
1116{
1118 {
1121 }
1122 return mColor;
1123}
1124
1126{
1127 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1128}
1129
1131{
1132 return mAlignDashPattern;
1133}
1134
1136{
1137 mAlignDashPattern = enabled;
1138}
1139
1141{
1142 return mPatternCartographicTweakOnSharpCorners;
1143}
1144
1146{
1147 mPatternCartographicTweakOnSharpCorners = enabled;
1148}
1149
1151{
1152 double offset = mOffset;
1153
1155 {
1158 }
1159
1162 {
1164 }
1165 return -offset; //direction seems to be inverse to symbology offset
1166}
1167
1169
1171
1172class MyLine
1173{
1174 public:
1175 MyLine( QPointF p1, QPointF p2 )
1176 : mVertical( false )
1177 , mIncreasing( false )
1178 , mT( 0.0 )
1179 , mLength( 0.0 )
1180 {
1181 if ( p1 == p2 )
1182 return; // invalid
1183
1184 // tangent and direction
1185 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1186 {
1187 // vertical line - tangent undefined
1188 mVertical = true;
1189 mIncreasing = ( p2.y() > p1.y() );
1190 }
1191 else
1192 {
1193 mVertical = false;
1194 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1195 mIncreasing = ( p2.x() > p1.x() );
1196 }
1197
1198 // length
1199 double x = ( p2.x() - p1.x() );
1200 double y = ( p2.y() - p1.y() );
1201 mLength = std::sqrt( x * x + y * y );
1202 }
1203
1204 // return angle in radians
1205 double angle()
1206 {
1207 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1208
1209 if ( !mIncreasing )
1210 a += M_PI;
1211 return a;
1212 }
1213
1214 // return difference for x,y when going along the line with specified interval
1215 QPointF diffForInterval( double interval )
1216 {
1217 if ( mVertical )
1218 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1219
1220 double alpha = std::atan( mT );
1221 double dx = std::cos( alpha ) * interval;
1222 double dy = std::sin( alpha ) * interval;
1223 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1224 }
1225
1226 double length() const { return mLength; }
1227
1228 protected:
1229 bool mVertical;
1230 bool mIncreasing;
1231 double mT;
1232 double mLength;
1233};
1234
1236
1237//
1238// QgsTemplatedLineSymbolLayerBase
1239//
1241 : mRotateSymbols( rotateSymbol )
1242 , mInterval( interval )
1243{
1244
1245}
1246
1248{
1249 if ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1251 else if ( mPlacements & Qgis::MarkerLinePlacement::Vertex )
1253 else if ( ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1254 && ( mPlacements & Qgis::MarkerLinePlacement::InnerVertices )
1255 && ( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1256 return Qgis::MarkerLinePlacement::Vertex; // retain round trip for deprecated old API
1257 else if ( mPlacements & Qgis::MarkerLinePlacement::LastVertex )
1259 else if ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1261 else if ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1263 else if ( mPlacements & Qgis::MarkerLinePlacement::CurvePoint )
1265 else if ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter )
1267 else
1269}
1270
1272{
1273 mPlacements = placement;
1274}
1275
1277
1279{
1280 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1281 if ( mRenderingFeature )
1282 {
1283 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1284 // until after we've received the final part
1285 mFeatureSymbolOpacity = context.opacity();
1286 mCurrentFeatureIsSelected = useSelectedColor;
1287 }
1288
1289 double offset = mOffset;
1290
1292 {
1295 }
1296
1298
1300 {
1302 if ( !QgsVariantUtils::isNull( exprVal ) )
1303 {
1304 QString placementString = exprVal.toString();
1305 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1306 {
1308 }
1309 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1310 {
1312 }
1313 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1314 {
1316 }
1317 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1318 {
1320 }
1321 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1322 {
1324 }
1325 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1326 {
1328 }
1329 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1330 {
1332 }
1333 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1334 {
1336 }
1337 else
1338 {
1340 }
1341 }
1342 }
1343
1344 QgsScopedQPainterState painterState( context.renderContext().painter() );
1345
1346 double averageOver = mAverageAngleLength;
1348 {
1349 context.setOriginalValueVariable( mAverageAngleLength );
1351 }
1352 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1353
1354 if ( qgsDoubleNear( offset, 0.0 ) )
1355 {
1357 renderPolylineInterval( points, context, averageOver );
1359 renderPolylineCentral( points, context, averageOver );
1361 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1363 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1364 {
1365 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1366 mHasRenderedFirstPart = mRenderingFeature;
1367 }
1369 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1371 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1373 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1375 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1376 }
1377 else
1378 {
1379 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1381
1382 for ( int part = 0; part < mline.count(); ++part )
1383 {
1384 const QPolygonF &points2 = mline[ part ];
1385
1387 renderPolylineInterval( points2, context, averageOver );
1389 renderPolylineCentral( points2, context, averageOver );
1391 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1393 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1395 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1397 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1398 {
1399 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1400 mHasRenderedFirstPart = mRenderingFeature;
1401 }
1403 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1405 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1406 }
1407 }
1408}
1409
1410void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1411{
1412 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1413
1414 if ( curvePolygon )
1415 {
1416 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1417 }
1418
1419 QgsExpressionContextScope *scope = nullptr;
1420 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1422 {
1423 scope = new QgsExpressionContextScope();
1424 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1425 }
1426
1427 switch ( mRingFilter )
1428 {
1429 case AllRings:
1430 case ExteriorRingOnly:
1431 {
1432 if ( scope )
1434
1435 renderPolyline( points, context );
1436 break;
1437 }
1438 case InteriorRingsOnly:
1439 break;
1440 }
1441
1442 if ( rings )
1443 {
1444 switch ( mRingFilter )
1445 {
1446 case AllRings:
1447 case InteriorRingsOnly:
1448 {
1449 mOffset = -mOffset; // invert the offset for rings!
1450 for ( int i = 0; i < rings->size(); ++i )
1451 {
1452 if ( curvePolygon )
1453 {
1454 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1455 }
1456 if ( scope )
1458
1459 renderPolyline( rings->at( i ), context );
1460 }
1461 mOffset = -mOffset;
1462 }
1463 break;
1464 case ExteriorRingOnly:
1465 break;
1466 }
1467 }
1468}
1469
1471{
1473 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1474 {
1476 }
1477 return unit;
1478}
1479
1481{
1483 mIntervalUnit = unit;
1484 mOffsetAlongLineUnit = unit;
1485 mAverageAngleLengthUnit = unit;
1486}
1487
1489{
1491 setIntervalMapUnitScale( scale );
1492 mOffsetMapUnitScale = scale;
1494}
1495
1497{
1501 {
1502 return mOffsetMapUnitScale;
1503 }
1504 return QgsMapUnitScale();
1505}
1506
1508{
1509 QVariantMap map;
1510 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1511 map[QStringLiteral( "interval" )] = QString::number( interval() );
1512 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1513 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1514 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1515 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1516 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1517 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1518 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1519 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1520 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1521 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1522 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1523
1524 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1525
1526 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1527 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1528 return map;
1529}
1530
1532{
1533 return mPlaceOnEveryPart
1534 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1535 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1536 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1537}
1538
1540{
1541 mRenderingFeature = true;
1542 mHasRenderedFirstPart = false;
1543}
1544
1546{
1547 mRenderingFeature = false;
1548 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1549 return;
1550
1551 const double prevOpacity = subSymbol()->opacity();
1552 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1553
1554 // render final point
1555 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1556 mFeatureSymbolOpacity = 1;
1557 subSymbol()->setOpacity( prevOpacity );
1558}
1559
1561{
1562 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1563 destLayer->setOffset( mOffset );
1564 destLayer->setPlacements( placements() );
1565 destLayer->setOffsetUnit( mOffsetUnit );
1567 destLayer->setIntervalUnit( intervalUnit() );
1569 destLayer->setOffsetAlongLine( offsetAlongLine() );
1572 destLayer->setAverageAngleLength( mAverageAngleLength );
1573 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1574 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1575 destLayer->setRingFilter( mRingFilter );
1576 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1577 copyDataDefinedProperties( destLayer );
1578 copyPaintEffect( destLayer );
1579}
1580
1582{
1583 if ( properties.contains( QStringLiteral( "offset" ) ) )
1584 {
1585 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1586 }
1587 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1588 {
1589 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1590 }
1591 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1592 {
1593 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1594 }
1595 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1596 {
1597 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1598 }
1599 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1600 {
1601 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1602 }
1603 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1604 {
1605 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1606 }
1607
1608 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1609 {
1610 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1611 }
1612 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1613 {
1614 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1615 }
1616
1617 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1618 {
1619 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1620 }
1621 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1622 {
1623 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1624 }
1625 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1626 {
1627 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1628 }
1629
1630 if ( properties.contains( QStringLiteral( "placement" ) ) )
1631 {
1632 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1634 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1636 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1638 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1640 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1642 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1644 else
1646 }
1647 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1648 {
1649 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1650 destLayer->setPlacements( placements );
1651 }
1652
1653 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1654 {
1655 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1656 }
1657
1658 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1659
1661}
1662
1663void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1664{
1665 if ( points.isEmpty() )
1666 return;
1667
1668 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1669 double lengthLeft = 0; // how much is left until next marker
1670
1671 QgsRenderContext &rc = context.renderContext();
1672 double interval = mInterval;
1673
1675 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1676
1678 {
1679 context.setOriginalValueVariable( mInterval );
1681 }
1682 if ( interval <= 0 )
1683 {
1684 interval = 0.1;
1685 }
1686 double offsetAlongLine = mOffsetAlongLine;
1688 {
1689 context.setOriginalValueVariable( mOffsetAlongLine );
1691 }
1692
1693 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1695 {
1696 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1697 // and clamp it to a reasonable range. It's the best we can do in this situation!
1698 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1699 }
1700
1701 if ( painterUnitInterval < 0 )
1702 return;
1703
1704 double painterUnitOffsetAlongLine = 0;
1705
1706 // only calculated if we need it!
1707 double totalLength = -1;
1708
1709 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1710 {
1711 switch ( offsetAlongLineUnit() )
1712 {
1721 break;
1723 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1724 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1725 break;
1726 }
1727
1728 if ( points.isClosed() )
1729 {
1730 if ( painterUnitOffsetAlongLine > 0 )
1731 {
1732 if ( totalLength < 0 )
1733 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1734 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1735 }
1736 else if ( painterUnitOffsetAlongLine < 0 )
1737 {
1738 if ( totalLength < 0 )
1739 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1740 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1741 }
1742 }
1743 }
1744
1746 {
1747 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1748 // and clamp it to a reasonable range. It's the best we can do in this situation!
1749 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1750 }
1751
1752 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1753
1754 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1755 {
1756 QVector< QPointF > angleStartPoints;
1757 QVector< QPointF > symbolPoints;
1758 QVector< QPointF > angleEndPoints;
1759
1760 // we collect 3 arrays of points. These correspond to
1761 // 1. the actual point at which to render the symbol
1762 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1763 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1764 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1765 // (or trace past the final point)
1766 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1767
1768 if ( symbolPoints.empty() )
1769 {
1770 // no symbols to draw, shortcut out early
1771 return;
1772 }
1773
1774 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1775 {
1776 // avoid duplicate points at start and end of closed rings
1777 symbolPoints.pop_back();
1778 }
1779
1780 angleEndPoints.reserve( symbolPoints.size() );
1781 angleStartPoints.reserve( symbolPoints.size() );
1782 if ( averageOver <= painterUnitOffsetAlongLine )
1783 {
1784 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1785 }
1786 else
1787 {
1788 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1789 }
1790 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1791
1792 int pointNum = 0;
1793 for ( int i = 0; i < symbolPoints.size(); ++ i )
1794 {
1795 if ( context.renderContext().renderingStopped() )
1796 break;
1797
1798 const QPointF pt = symbolPoints[i];
1799 const QPointF startPt = angleStartPoints[i];
1800 const QPointF endPt = angleEndPoints[i];
1801
1802 MyLine l( startPt, endPt );
1803 // rotate marker (if desired)
1804 if ( rotateSymbols() )
1805 {
1806 setSymbolLineAngle( l.angle() * 180 / M_PI );
1807 }
1808
1810 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1811 }
1812 }
1813 else
1814 {
1815 // not averaging line angle -- always use exact section angle
1816 int pointNum = 0;
1817 QPointF lastPt = points[0];
1818 for ( int i = 1; i < points.count(); ++i )
1819 {
1820 if ( context.renderContext().renderingStopped() )
1821 break;
1822
1823 const QPointF &pt = points[i];
1824
1825 if ( lastPt == pt ) // must not be equal!
1826 continue;
1827
1828 // for each line, find out dx and dy, and length
1829 MyLine l( lastPt, pt );
1830 QPointF diff = l.diffForInterval( painterUnitInterval );
1831
1832 // if there's some length left from previous line
1833 // use only the rest for the first point in new line segment
1834 double c = 1 - lengthLeft / painterUnitInterval;
1835
1836 lengthLeft += l.length();
1837
1838 // rotate marker (if desired)
1839 if ( rotateSymbols() )
1840 {
1841 setSymbolLineAngle( l.angle() * 180 / M_PI );
1842 }
1843
1844 // while we're not at the end of line segment, draw!
1845 while ( lengthLeft > painterUnitInterval )
1846 {
1847 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1848 lastPt += c * diff;
1849 lengthLeft -= painterUnitInterval;
1851 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1852 c = 1; // reset c (if wasn't 1 already)
1853 }
1854
1855 lastPt = pt;
1856 }
1857
1858 }
1859}
1860
1861static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1862{
1863 // calc average angle between the previous and next point
1864 double a1 = MyLine( prevPt, pt ).angle();
1865 double a2 = MyLine( pt, nextPt ).angle();
1866 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1867
1868 return std::atan2( unitY, unitX );
1869}
1870
1871void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1872{
1873 if ( points.isEmpty() )
1874 return;
1875
1876 QgsRenderContext &rc = context.renderContext();
1877
1878 int i = -1, maxCount = 0;
1879 bool isRing = false;
1880
1882 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1884
1885 double offsetAlongLine = mOffsetAlongLine;
1887 {
1888 context.setOriginalValueVariable( mOffsetAlongLine );
1890 }
1891
1892 // only calculated if we need it!!
1893 double totalLength = -1;
1894 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1895 {
1896 //scale offset along line
1897 switch ( offsetAlongLineUnit() )
1898 {
1907 break;
1909 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1910 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1911 break;
1912 }
1913 if ( points.isClosed() )
1914 {
1915 if ( offsetAlongLine > 0 )
1916 {
1917 if ( totalLength < 0 )
1918 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1919 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1920 }
1921 else if ( offsetAlongLine < 0 )
1922 {
1923 if ( totalLength < 0 )
1924 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1925 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1926 }
1927 }
1928 }
1929
1930 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1931 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1935 {
1937 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1938
1939 QgsVertexId vId;
1940 QgsPoint vPoint;
1941 double x, y, z;
1942 QPointF mapPoint;
1943 int pointNum = 0;
1944 const int numPoints = context.renderContext().geometry()->nCoordinates();
1945 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1946 {
1947 if ( context.renderContext().renderingStopped() )
1948 break;
1949
1951
1952 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1953 continue;
1954
1955 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1956 continue;
1957
1960 {
1961 //transform
1962 x = vPoint.x();
1963 y = vPoint.y();
1964 z = 0.0;
1965 if ( ct.isValid() )
1966 {
1967 ct.transformInPlace( x, y, z );
1968 }
1969 mapPoint.setX( x );
1970 mapPoint.setY( y );
1971 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1972 if ( rotateSymbols() )
1973 {
1974 double angle = context.renderContext().geometry()->vertexAngle( vId );
1975 setSymbolLineAngle( angle * 180 / M_PI );
1976 }
1977 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
1978 }
1979 }
1980
1981 return;
1982 }
1983
1984 int pointNum = 0;
1985
1986 switch ( placement )
1987 {
1989 {
1990 i = 0;
1991 maxCount = 1;
1992 break;
1993 }
1994
1996 {
1997 i = points.count() - 1;
1998 pointNum = i;
1999 maxCount = points.count();
2000 break;
2001 }
2002
2004 {
2005 i = 1;
2006 pointNum = 1;
2007 maxCount = points.count() - 1;
2008 break;
2009 }
2010
2013 {
2015 maxCount = points.count();
2016 if ( points.first() == points.last() )
2017 isRing = true;
2018 break;
2019 }
2020
2024 {
2025 return;
2026 }
2027 }
2028
2030 {
2031 double distance;
2033 renderOffsetVertexAlongLine( points, i, distance, context, placement );
2034
2035 return;
2036 }
2037
2038 QPointF prevPoint;
2039 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2040 prevPoint = points.at( 0 );
2041
2042 QPointF symbolPoint;
2043 for ( ; i < maxCount; ++i )
2044 {
2046
2047 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2048 {
2049 continue; // don't draw the last marker - it has been drawn already
2050 }
2051
2053 {
2054 QPointF currentPoint = points.at( i );
2055 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2056 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2057 if ( rotateSymbols() )
2058 {
2059 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2060 currentPoint.x() - prevPoint.x() );
2061 setSymbolLineAngle( angle * 180 / M_PI );
2062 }
2063 prevPoint = currentPoint;
2064 }
2065 else
2066 {
2067 symbolPoint = points.at( i );
2068 // rotate marker (if desired)
2069 if ( rotateSymbols() )
2070 {
2071 double angle = markerAngle( points, isRing, i );
2072 setSymbolLineAngle( angle * 180 / M_PI );
2073 }
2074 }
2075
2076 mFinalVertex = symbolPoint;
2077 if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2078 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2079 }
2080}
2081
2082double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2083{
2084 double angle = 0;
2085 const QPointF &pt = points[vertex];
2086
2087 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2088 {
2089 int prevIndex = vertex - 1;
2090 int nextIndex = vertex + 1;
2091
2092 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2093 {
2094 prevIndex = points.count() - 2;
2095 nextIndex = 1;
2096 }
2097
2098 QPointF prevPoint, nextPoint;
2099 while ( prevIndex >= 0 )
2100 {
2101 prevPoint = points[ prevIndex ];
2102 if ( prevPoint != pt )
2103 {
2104 break;
2105 }
2106 --prevIndex;
2107 }
2108
2109 while ( nextIndex < points.count() )
2110 {
2111 nextPoint = points[ nextIndex ];
2112 if ( nextPoint != pt )
2113 {
2114 break;
2115 }
2116 ++nextIndex;
2117 }
2118
2119 if ( prevIndex >= 0 && nextIndex < points.count() )
2120 {
2121 angle = _averageAngle( prevPoint, pt, nextPoint );
2122 }
2123 }
2124 else //no ring and vertex is at start / at end
2125 {
2126 if ( vertex == 0 )
2127 {
2128 while ( vertex < points.size() - 1 )
2129 {
2130 const QPointF &nextPt = points[vertex + 1];
2131 if ( pt != nextPt )
2132 {
2133 angle = MyLine( pt, nextPt ).angle();
2134 return angle;
2135 }
2136 ++vertex;
2137 }
2138 }
2139 else
2140 {
2141 // use last segment's angle
2142 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2143 {
2144 const QPointF &prevPt = points[vertex - 1];
2145 if ( pt != prevPt )
2146 {
2147 angle = MyLine( prevPt, pt ).angle();
2148 return angle;
2149 }
2150 --vertex;
2151 }
2152 }
2153 }
2154 return angle;
2155}
2156
2157void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2158{
2159 if ( points.isEmpty() )
2160 return;
2161
2162 QgsRenderContext &rc = context.renderContext();
2163 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2164 if ( qgsDoubleNear( distance, 0.0 ) )
2165 {
2166 // rotate marker (if desired)
2167 if ( rotateSymbols() )
2168 {
2169 bool isRing = false;
2170 if ( points.first() == points.last() )
2171 isRing = true;
2172 double angle = markerAngle( points, isRing, vertex );
2173 setSymbolLineAngle( angle * 180 / M_PI );
2174 }
2175 mFinalVertex = points[vertex];
2176 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2177 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2178 return;
2179 }
2180
2181 int pointIncrement = distance > 0 ? 1 : -1;
2182 QPointF previousPoint = points[vertex];
2183 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2184 int endPoint = distance > 0 ? points.count() - 1 : 0;
2185 double distanceLeft = std::fabs( distance );
2186
2187 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2188 {
2189 const QPointF &pt = points[i];
2190
2191 if ( previousPoint == pt ) // must not be equal!
2192 continue;
2193
2194 // create line segment
2195 MyLine l( previousPoint, pt );
2196
2197 if ( distanceLeft < l.length() )
2198 {
2199 //destination point is in current segment
2200 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2201 // rotate marker (if desired)
2202 if ( rotateSymbols() )
2203 {
2204 setSymbolLineAngle( l.angle() * 180 / M_PI );
2205 }
2206 mFinalVertex = markerPoint;
2207 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2208 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2209 return;
2210 }
2211
2212 distanceLeft -= l.length();
2213 previousPoint = pt;
2214 }
2215
2216 //didn't find point
2217}
2218
2219void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2220{
2221 if ( p.empty() )
2222 return;
2223
2224 QVector< QPointF > points = p;
2225 const bool closedRing = points.first() == points.last();
2226
2227 double lengthLeft = initialOffset;
2228
2229 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2230 if ( initialLagLeft < 0 && closedRing )
2231 {
2232 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2233 QPointF lastPt = points.constLast();
2234 QVector< QPointF > pseudoPoints;
2235 for ( int i = points.count() - 2; i > 0; --i )
2236 {
2237 if ( initialLagLeft >= 0 )
2238 {
2239 break;
2240 }
2241
2242 const QPointF &pt = points[i];
2243
2244 if ( lastPt == pt ) // must not be equal!
2245 continue;
2246
2247 MyLine l( lastPt, pt );
2248 initialLagLeft += l.length();
2249 lastPt = pt;
2250
2251 pseudoPoints << pt;
2252 }
2253 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2254
2255 points = pseudoPoints;
2256 points.append( p );
2257 }
2258 else
2259 {
2260 while ( initialLagLeft < 0 )
2261 {
2262 dest << points.constFirst();
2263 initialLagLeft += intervalPainterUnits;
2264 }
2265 }
2266 if ( initialLag > 0 )
2267 {
2268 lengthLeft += intervalPainterUnits - initialLagLeft;
2269 }
2270
2271 QPointF lastPt = points[0];
2272 for ( int i = 1; i < points.count(); ++i )
2273 {
2274 const QPointF &pt = points[i];
2275
2276 if ( lastPt == pt ) // must not be equal!
2277 {
2278 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2279 {
2280 lastPt = points[0];
2281 i = 0;
2282 }
2283 continue;
2284 }
2285
2286 // for each line, find out dx and dy, and length
2287 MyLine l( lastPt, pt );
2288 QPointF diff = l.diffForInterval( intervalPainterUnits );
2289
2290 // if there's some length left from previous line
2291 // use only the rest for the first point in new line segment
2292 double c = 1 - lengthLeft / intervalPainterUnits;
2293
2294 lengthLeft += l.length();
2295
2296
2297 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2298 {
2299 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2300 lastPt += c * diff;
2301 lengthLeft -= intervalPainterUnits;
2302 dest << lastPt;
2303 c = 1; // reset c (if wasn't 1 already)
2304 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2305 break;
2306 }
2307 lastPt = pt;
2308
2309 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2310 break;
2311
2312 // if a closed ring, we keep looping around the ring until we hit the required number of points
2313 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2314 {
2315 lastPt = points[0];
2316 i = 0;
2317 }
2318 }
2319
2320 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2321 {
2322 // pad with repeating last point to match desired size
2323 while ( dest.size() < numberPointsRequired )
2324 dest << points.constLast();
2325 }
2326}
2327
2328void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2329{
2330 if ( !points.isEmpty() )
2331 {
2332 // calc length
2333 qreal length = 0;
2334 QPolygonF::const_iterator it = points.constBegin();
2335 QPointF last = *it;
2336 for ( ++it; it != points.constEnd(); ++it )
2337 {
2338 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2339 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2340 last = *it;
2341 }
2342 if ( qgsDoubleNear( length, 0.0 ) )
2343 return;
2344
2345 const double midPoint = length / 2;
2346
2347 QPointF pt;
2348 double thisSymbolAngle = 0;
2349
2350 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2351 {
2352 QVector< QPointF > angleStartPoints;
2353 QVector< QPointF > symbolPoints;
2354 QVector< QPointF > angleEndPoints;
2355 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2356 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2357 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2358 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2359
2360 pt = symbolPoints.at( 1 );
2361 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2362 thisSymbolAngle = l.angle();
2363 }
2364 else
2365 {
2366 // find the segment where the central point lies
2367 it = points.constBegin();
2368 last = *it;
2369 qreal last_at = 0, next_at = 0;
2370 QPointF next;
2371 for ( ++it; it != points.constEnd(); ++it )
2372 {
2373 next = *it;
2374 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2375 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2376 if ( next_at >= midPoint )
2377 break; // we have reached the center
2378 last = *it;
2379 last_at = next_at;
2380 }
2381
2382 // find out the central point on segment
2383 MyLine l( last, next ); // for line angle
2384 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2385 pt = last + ( next - last ) * k;
2386 thisSymbolAngle = l.angle();
2387 }
2388
2389 // draw the marker
2390 // rotate marker (if desired)
2391 if ( rotateSymbols() )
2392 {
2393 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2394 }
2395
2396 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2397 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2398 }
2399}
2400
2402{
2403 return mMarker.get();
2404}
2405
2407{
2408 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2409 {
2410 delete symbol;
2411 return false;
2412 }
2413
2414 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2415 mColor = mMarker->color();
2416 return true;
2417}
2418
2419
2420
2421//
2422// QgsMarkerLineSymbolLayer
2423//
2424
2425QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2426 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2427{
2429}
2430
2432
2434{
2435 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2437
2438 if ( props.contains( QStringLiteral( "interval" ) ) )
2439 interval = props[QStringLiteral( "interval" )].toDouble();
2440 if ( props.contains( QStringLiteral( "rotate" ) ) )
2441 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2442
2443 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2444 setCommonProperties( x.get(), props );
2445 return x.release();
2446}
2447
2449{
2450 return QStringLiteral( "MarkerLine" );
2451}
2452
2453void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2454{
2455 mMarker->setColor( color );
2456 mColor = color;
2457}
2458
2460{
2461 return mMarker ? mMarker->color() : mColor;
2462}
2463
2465{
2466 // if being rotated, it gets initialized with every line segment
2468 if ( rotateSymbols() )
2470 mMarker->setRenderHints( hints );
2471
2472 mMarker->startRender( context.renderContext(), context.fields() );
2473}
2474
2476{
2477 mMarker->stopRender( context.renderContext() );
2478}
2479
2480
2482{
2483 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2485 return x.release();
2486}
2487
2488void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2489{
2490 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2491 {
2492 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2493 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2494 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2495 element.appendChild( symbolizerElem );
2496
2497 // <Geometry>
2498 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2499
2500 QString gap;
2502 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2504 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2506 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2508 // no way to get line/polygon's vertices, use a VendorOption
2509 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2510
2512 {
2514 gap = qgsDoubleToString( interval );
2515 }
2516
2517 if ( !rotateSymbols() )
2518 {
2519 // markers in LineSymbolizer must be drawn following the line orientation,
2520 // use a VendorOption when no marker rotation
2521 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2522 }
2523
2524 // <Stroke>
2525 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2526 symbolizerElem.appendChild( strokeElem );
2527
2528 // <GraphicStroke>
2529 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2530 strokeElem.appendChild( graphicStrokeElem );
2531
2532 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2533 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2534 {
2535 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2536 }
2537 else if ( layer )
2538 {
2539 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2540 }
2541 else
2542 {
2543 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2544 }
2545
2546 if ( !gap.isEmpty() )
2547 {
2548 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2550 graphicStrokeElem.appendChild( gapElem );
2551 }
2552
2553 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2554 {
2555 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2557 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2558 symbolizerElem.appendChild( perpOffsetElem );
2559 }
2560 }
2561}
2562
2564{
2565 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2566
2567 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2568 if ( strokeElem.isNull() )
2569 return nullptr;
2570
2571 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2572 if ( graphicStrokeElem.isNull() )
2573 return nullptr;
2574
2575 // retrieve vendor options
2576 bool rotateMarker = true;
2578
2579 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2580 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2581 {
2582 if ( it.key() == QLatin1String( "placement" ) )
2583 {
2584 if ( it.value() == QLatin1String( "points" ) )
2586 else if ( it.value() == QLatin1String( "firstPoint" ) )
2588 else if ( it.value() == QLatin1String( "lastPoint" ) )
2590 else if ( it.value() == QLatin1String( "centralPoint" ) )
2592 }
2593 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2594 {
2595 rotateMarker = it.value() == QLatin1String( "0" );
2596 }
2597 }
2598
2599 std::unique_ptr< QgsMarkerSymbol > marker;
2600
2602 if ( l )
2603 {
2604 QgsSymbolLayerList layers;
2605 layers.append( l );
2606 marker.reset( new QgsMarkerSymbol( layers ) );
2607 }
2608
2609 if ( !marker )
2610 return nullptr;
2611
2612 double interval = 0.0;
2613 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2614 if ( !gapElem.isNull() )
2615 {
2616 bool ok;
2617 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2618 if ( ok )
2619 interval = d;
2620 }
2621
2622 double offset = 0.0;
2623 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2624 if ( !perpOffsetElem.isNull() )
2625 {
2626 bool ok;
2627 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2628 if ( ok )
2629 offset = d;
2630 }
2631
2632 double scaleFactor = 1.0;
2633 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2634 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2635 interval = interval * scaleFactor;
2636 offset = offset * scaleFactor;
2637
2639 x->setOutputUnit( sldUnitSize );
2641 x->setInterval( interval );
2642 x->setSubSymbol( marker.release() );
2643 x->setOffset( offset );
2644 return x;
2645}
2646
2648{
2649 mMarker->setSize( width );
2650}
2651
2653{
2654 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2655 {
2656 mMarker->setDataDefinedSize( property );
2657 }
2659}
2660
2662{
2663 const double prevOpacity = mMarker->opacity();
2664 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2666 mMarker->setOpacity( prevOpacity );
2667}
2668
2670{
2671 mMarker->setLineAngle( angle );
2672}
2673
2675{
2676 return mMarker->angle();
2677}
2678
2680{
2681 mMarker->setAngle( angle );
2682}
2683
2684void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2685{
2686 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2688
2689 mMarker->renderPoint( point, feature, context, layer, selected );
2690
2691 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2692}
2693
2695{
2696 return mMarker->size();
2697}
2698
2700{
2701 return mMarker->size( context );
2702}
2703
2705{
2707 mMarker->setOutputUnit( unit );
2708}
2709
2711{
2717 || ( mMarker && mMarker->usesMapUnits() );
2718}
2719
2721{
2722 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2723 if ( mMarker )
2724 attr.unite( mMarker->usedAttributes( context ) );
2725 return attr;
2726}
2727
2729{
2731 return true;
2732 if ( mMarker && mMarker->hasDataDefinedProperties() )
2733 return true;
2734 return false;
2735}
2736
2738{
2739 return ( mMarker->size( context ) / 2.0 ) +
2741}
2742
2743
2744//
2745// QgsHashedLineSymbolLayer
2746//
2747
2748QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2749 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2750{
2751 setSubSymbol( new QgsLineSymbol() );
2752}
2753
2755
2757{
2758 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2760
2761 if ( props.contains( QStringLiteral( "interval" ) ) )
2762 interval = props[QStringLiteral( "interval" )].toDouble();
2763 if ( props.contains( QStringLiteral( "rotate" ) ) )
2764 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2765
2766 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2767 setCommonProperties( x.get(), props );
2768 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2769 {
2770 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2771 }
2772
2773 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2774 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2775
2776 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2777 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2778
2779 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2780 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2781
2782 return x.release();
2783}
2784
2786{
2787 return QStringLiteral( "HashLine" );
2788}
2789
2791{
2792 // if being rotated, it gets initialized with every line segment
2794 if ( rotateSymbols() )
2796 mHashSymbol->setRenderHints( hints );
2797
2798 mHashSymbol->startRender( context.renderContext(), context.fields() );
2799}
2800
2802{
2803 mHashSymbol->stopRender( context.renderContext() );
2804}
2805
2807{
2809 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2810
2811 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2812 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2813 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2814
2815 return map;
2816}
2817
2819{
2820 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2822 x->setHashAngle( mHashAngle );
2823 x->setHashLength( mHashLength );
2824 x->setHashLengthUnit( mHashLengthUnit );
2825 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2826 return x.release();
2827}
2828
2829void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2830{
2831 mHashSymbol->setColor( color );
2832 mColor = color;
2833}
2834
2836{
2837 return mHashSymbol ? mHashSymbol->color() : mColor;
2838}
2839
2841{
2842 return mHashSymbol.get();
2843}
2844
2846{
2847 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2848 {
2849 delete symbol;
2850 return false;
2851 }
2852
2853 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2854 mColor = mHashSymbol->color();
2855 return true;
2856}
2857
2858void QgsHashedLineSymbolLayer::setWidth( const double width )
2859{
2860 mHashLength = width;
2861}
2862
2864{
2865 return mHashLength;
2866}
2867
2869{
2870 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2871}
2872
2874{
2875 return ( mHashSymbol->width( context ) / 2.0 )
2876 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2878}
2879
2881{
2883 mHashSymbol->setOutputUnit( unit );
2884}
2885
2887{
2888 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2889 if ( mHashSymbol )
2890 attr.unite( mHashSymbol->usedAttributes( context ) );
2891 return attr;
2892}
2893
2895{
2897 return true;
2898 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2899 return true;
2900 return false;
2901}
2902
2904{
2905 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
2906 {
2907 mHashSymbol->setDataDefinedWidth( property );
2908 }
2910}
2911
2913{
2914 return mHashLengthUnit == Qgis::RenderUnit::MapUnits || mHashLengthUnit == Qgis::RenderUnit::MetersInMapUnits
2920 || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2921}
2922
2924{
2925 mSymbolLineAngle = angle;
2926}
2927
2929{
2930 return mSymbolAngle;
2931}
2932
2934{
2935 mSymbolAngle = angle;
2936}
2937
2938void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2939{
2940 double lineLength = mHashLength;
2942 {
2943 context.expressionContext().setOriginalValueVariable( mHashLength );
2945 }
2946 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2947
2948 double hashAngle = mHashAngle;
2950 {
2951 context.expressionContext().setOriginalValueVariable( mHashAngle );
2953 }
2954
2955 QgsPointXY center( point );
2956 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2957 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2958
2959 QPolygonF points;
2960 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2961
2962 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2964
2965 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2966
2967 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2968}
2969
2971{
2972 return mHashAngle;
2973}
2974
2976{
2977 mHashAngle = angle;
2978}
2979
2981{
2982 const double prevOpacity = mHashSymbol->opacity();
2983 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2985 mHashSymbol->setOpacity( prevOpacity );
2986}
2987
2988//
2989// QgsAbstractBrushedLineSymbolLayer
2990//
2991
2992void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2993{
2994 if ( !context.renderContext().painter() )
2995 return;
2996
2997 double offset = mOffset;
2999 {
3002 }
3003
3004 QPolygonF offsetPoints;
3005 if ( qgsDoubleNear( offset, 0 ) )
3006 {
3007 renderLine( points, context, patternThickness, patternLength, brush );
3008 }
3009 else
3010 {
3011 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3012
3013 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3014 for ( const QPolygonF &part : offsetLine )
3015 {
3016 renderLine( part, context, patternThickness, patternLength, brush );
3017 }
3018 }
3019}
3020
3021void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3022 const double patternLength, const QBrush &sourceBrush )
3023{
3024 QPainter *p = context.renderContext().painter();
3025 if ( !p )
3026 return;
3027
3028 QBrush brush = sourceBrush;
3029
3030 // duplicate points mess up the calculations, we need to remove them first
3031 // we'll calculate the min/max coordinate at the same time, since we're already looping
3032 // through the points
3033 QPolygonF inputPoints;
3034 inputPoints.reserve( points.size() );
3035 QPointF prev;
3036 double minX = std::numeric_limits< double >::max();
3037 double minY = std::numeric_limits< double >::max();
3038 double maxX = std::numeric_limits< double >::lowest();
3039 double maxY = std::numeric_limits< double >::lowest();
3040
3041 for ( const QPointF &pt : std::as_const( points ) )
3042 {
3043 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3044 continue;
3045
3046 inputPoints << pt;
3047 prev = pt;
3048 minX = std::min( minX, pt.x() );
3049 minY = std::min( minY, pt.y() );
3050 maxX = std::max( maxX, pt.x() );
3051 maxY = std::max( maxY, pt.y() );
3052 }
3053
3054 if ( inputPoints.size() < 2 ) // nothing to render
3055 return;
3056
3057 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3058 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3059 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3060 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3061
3062 // 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
3063 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3064 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3065
3066 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3067 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3068
3069 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3070 if ( temporaryImage.isNull() )
3071 {
3072 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3073 return;
3074 }
3075
3076 // clear temporary image contents
3077 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3078 return;
3079 temporaryImage.fill( Qt::transparent );
3080 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3081 return;
3082
3083 Qt::PenJoinStyle join = mPenJoinStyle;
3085 {
3088 if ( !QgsVariantUtils::isNull( exprVal ) )
3089 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3090 }
3091
3092 Qt::PenCapStyle cap = mPenCapStyle;
3094 {
3097 if ( !QgsVariantUtils::isNull( exprVal ) )
3098 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3099 }
3100
3101 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3102 QPainterPathStroker stroker;
3103 stroker.setWidth( lineThickness );
3104 stroker.setCapStyle( cap );
3105 stroker.setJoinStyle( join );
3106
3107 QPainterPath path;
3108 path.addPolygon( inputPoints );
3109 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3110
3111 // prepare temporary image
3112 QPainter imagePainter;
3113 imagePainter.begin( &temporaryImage );
3114 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3115 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3116
3117 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3118 imagePainter.setPen( Qt::NoPen );
3119
3120 QPointF segmentStartPoint = inputPoints.at( 0 );
3121
3122 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3123 double progressThroughImage = 0;
3124
3125 QgsPoint prevSegmentPolygonEndLeft;
3126 QgsPoint prevSegmentPolygonEndRight;
3127
3128 // for closed rings this will store the left/right polygon points of the start/end of the line
3129 QgsPoint startLinePolygonLeft;
3130 QgsPoint startLinePolygonRight;
3131
3132 for ( int i = 1; i < inputPoints.size(); ++i )
3133 {
3134 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3135 break;
3136
3137 const QPointF segmentEndPoint = inputPoints.at( i );
3138 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3139 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3140
3141 // left/right end points of the current segment polygon
3142 QgsPoint thisSegmentPolygonEndLeft;
3143 QgsPoint thisSegmentPolygonEndRight;
3144 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3145 QgsPoint thisSegmentPolygonEndLeftForPainter;
3146 QgsPoint thisSegmentPolygonEndRightForPainter;
3147 if ( i == 1 )
3148 {
3149 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3150 // (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.)
3151 if ( isClosedLine )
3152 {
3153 // project the current segment out by half the image thickness to either side of the line
3154 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3155 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3156 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3157 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3158
3159 // 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
3160 // what angle the current segment polygon should START on.
3161 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3162 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3163
3164 // project out the LAST segment in the line by half the image thickness to either side of the line
3165 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3166 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3167 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3168 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3169
3170 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3171 // 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
3172 // join)
3173 QgsPoint intersectionPoint;
3174 bool isIntersection = false;
3175 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3176 if ( !isIntersection )
3177 prevSegmentPolygonEndLeft = startPointLeft;
3178 isIntersection = false;
3179 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3180 if ( !isIntersection )
3181 prevSegmentPolygonEndRight = startPointRight;
3182
3183 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3184 startLinePolygonRight = prevSegmentPolygonEndRight;
3185 }
3186 else
3187 {
3188 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3189 if ( cap != Qt::PenCapStyle::FlatCap )
3190 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3191 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3192 if ( cap != Qt::PenCapStyle::FlatCap )
3193 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3194 }
3195 }
3196
3197 if ( i < inputPoints.size() - 1 )
3198 {
3199 // for all other segments except the last
3200
3201 // project the current segment out by half the image thickness to either side of the line
3202 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3203 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3204 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3205 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3206
3207 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3208 // what angle the current segment polygon should end on
3209 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3210 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3211
3212 // project out the next segment by half the image thickness to either side of the line
3213 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3214 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3215 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3216 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3217
3218 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3219 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3220 // join)
3221 QgsPoint intersectionPoint;
3222 bool isIntersection = false;
3223 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3224 if ( !isIntersection )
3225 thisSegmentPolygonEndLeft = endPointLeft;
3226 isIntersection = false;
3227 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3228 if ( !isIntersection )
3229 thisSegmentPolygonEndRight = endPointRight;
3230
3231 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3232 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3233 }
3234 else
3235 {
3236 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3237 // unless it's a closed line
3238 if ( isClosedLine )
3239 {
3240 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3241 thisSegmentPolygonEndRight = startLinePolygonRight;
3242
3243 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3244 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3245 }
3246 else
3247 {
3248 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3249 if ( cap != Qt::PenCapStyle::FlatCap )
3250 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3251 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3252 if ( cap != Qt::PenCapStyle::FlatCap )
3253 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3254
3255 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3256 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3257 }
3258 }
3259
3260 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3261 // where we got with the previous segment), at the correct angle
3262 QTransform brushTransform;
3263 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3264 brushTransform.rotate( -segmentAngleDegrees );
3265 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3266 {
3267 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3268 // we need to also do the same for the brush transform
3269 brushTransform.translate( -( lineThickness / 2 ), 0 );
3270 }
3271 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3272
3273 brush.setTransform( brushTransform );
3274 imagePainter.setBrush( brush );
3275
3276 // now draw the segment polygon
3277 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3278 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3279 << thisSegmentPolygonEndRightForPainter.toQPointF()
3280 << prevSegmentPolygonEndRight.toQPointF()
3281 << prevSegmentPolygonEndLeft.toQPointF() );
3282
3283#if 0 // for debugging, will draw the segment polygons
3284 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3285 imagePainter.setBrush( Qt::NoBrush );
3286 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3287 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3288 << thisSegmentPolygonEndRightForPainter.toQPointF()
3289 << prevSegmentPolygonEndRight.toQPointF()
3290 << prevSegmentPolygonEndLeft.toQPointF() );
3291 imagePainter.setPen( Qt::NoPen );
3292#endif
3293
3294 // calculate the new progress horizontal through the source image to account for the length
3295 // of the segment we've just drawn
3296 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3297 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3298 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3299 progressThroughImage = fmod( progressThroughImage, patternLength );
3300
3301 // shuffle buffered variables for next loop
3302 segmentStartPoint = segmentEndPoint;
3303 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3304 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3305 }
3306 imagePainter.end();
3307
3308 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3309 return;
3310
3311 // lastly, draw the temporary image onto the destination painter at the correct place
3312 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3313 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3314}
3315
3316
3317//
3318// QgsRasterLineSymbolLayer
3319//
3320
3322 : mPath( path )
3323{
3324}
3325
3327
3328QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3329{
3330 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3331
3332 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3333 {
3334 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3335 }
3336 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3337 {
3338 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3339 }
3340 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3341 {
3342 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3343 }
3344
3345 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3346 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3347
3348 if ( properties.contains( QStringLiteral( "offset" ) ) )
3349 {
3350 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3351 }
3352 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3353 {
3354 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3355 }
3356 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3357 {
3358 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3359 }
3360
3361 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3362 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3363 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3364 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3365
3366 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3367 {
3368 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3369 }
3370
3371 return res.release();
3372}
3373
3374
3376{
3377 QVariantMap map;
3378 map[QStringLiteral( "imageFile" )] = mPath;
3379
3380 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3381 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3382 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3383
3384 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3385 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3386
3387 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3388 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3389 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3390
3391 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3392
3393 return map;
3394}
3395
3397{
3398 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3399 res->setWidth( mWidth );
3400 res->setWidthUnit( mWidthUnit );
3401 res->setWidthMapUnitScale( mWidthMapUnitScale );
3402 res->setPenJoinStyle( mPenJoinStyle );
3403 res->setPenCapStyle( mPenCapStyle );
3404 res->setOffsetUnit( mOffsetUnit );
3405 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3406 res->setOffset( mOffset );
3407 res->setOpacity( mOpacity );
3408 copyDataDefinedProperties( res.get() );
3409 copyPaintEffect( res.get() );
3410 return res.release();
3411}
3412
3413void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3414{
3415 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3416 if ( it != properties.end() && it.value().type() == QVariant::String )
3417 {
3418 if ( saving )
3419 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3420 else
3421 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3422 }
3423}
3424
3425void QgsRasterLineSymbolLayer::setPath( const QString &path )
3426{
3427 mPath = path;
3428}
3429
3431{
3432 return QStringLiteral( "RasterLine" );
3433}
3434
3436{
3437 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3438
3440
3441 double opacity = mOpacity * context.opacity();
3442 bool cached = false;
3444 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3445 static_cast< int >( std::ceil( scaledHeight ) ) ),
3446 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3447}
3448
3450{
3451}
3452
3454{
3455 if ( !context.renderContext().painter() )
3456 return;
3457
3458 QImage sourceImage = mLineImage;
3462 {
3463 QString path = mPath;
3465 {
3466 context.setOriginalValueVariable( path );
3468 }
3469
3470 double strokeWidth = mWidth;
3472 {
3473 context.setOriginalValueVariable( strokeWidth );
3475 }
3476 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3477
3479 double opacity = mOpacity;
3481 {
3484 }
3485 opacity *= context.opacity();
3486
3487 bool cached = false;
3489 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3490 static_cast< int >( std::ceil( scaledHeight ) ) ),
3491 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3492 }
3493
3494 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3495 if ( useSelectedColor )
3496 {
3498 }
3499
3500 const QBrush brush( sourceImage );
3501
3502 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3503}
3504
3506{
3508 mWidthUnit = unit;
3509 mOffsetUnit = unit;
3510}
3511
3513{
3515 if ( mWidthUnit != unit || mOffsetUnit != unit )
3516 {
3518 }
3519 return unit;
3520}
3521
3523{
3526}
3527
3529{
3531 mOffsetMapUnitScale = scale;
3532}
3533
3535{
3538 {
3539 return mWidthMapUnitScale;
3540 }
3541 return QgsMapUnitScale();
3542}
3543
3545{
3546 return ( mWidth / 2.0 ) + mOffset;
3547}
3548
3550{
3551 return QColor();
3552}
3553
3554
3555//
3556// QgsLineburstSymbolLayer
3557//
3558
3559QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3561 , mColor2( color2 )
3562{
3563 setColor( color );
3564}
3565
3567
3568QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3569{
3570 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3571
3572 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3573 {
3574 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3575 }
3576 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3577 {
3578 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3579 }
3580 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3581 {
3582 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3583 }
3584
3585 if ( properties.contains( QStringLiteral( "offset" ) ) )
3586 {
3587 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3588 }
3589 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3590 {
3591 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3592 }
3593 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3594 {
3595 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3596 }
3597
3598 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3599 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3600 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3601 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3602
3603 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3604 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3605
3606 if ( properties.contains( QStringLiteral( "color" ) ) )
3607 {
3608 res->setColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() ) );
3609 }
3610 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3611 {
3612 res->setColor2( QgsColorUtils::colorFromString( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3613 }
3614
3615 //attempt to create color ramp from props
3616 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3617 {
3618 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3619 }
3620 else
3621 {
3622 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3623 }
3624
3625 return res.release();
3626}
3627
3629{
3630 QVariantMap map;
3631
3632 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3633 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3634 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3635
3636 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3637 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3638
3639 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3640 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3641 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3642
3643 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
3644 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
3645 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3646 if ( mGradientRamp )
3647 {
3648 map.insert( mGradientRamp->properties() );
3649 }
3650
3651 return map;
3652}
3653
3655{
3656 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3657 res->setWidth( mWidth );
3658 res->setWidthUnit( mWidthUnit );
3659 res->setWidthMapUnitScale( mWidthMapUnitScale );
3660 res->setPenJoinStyle( mPenJoinStyle );
3661 res->setPenCapStyle( mPenCapStyle );
3662 res->setOffsetUnit( mOffsetUnit );
3663 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3664 res->setOffset( mOffset );
3665 res->setColor( mColor );
3666 res->setColor2( mColor2 );
3667 res->setGradientColorType( mGradientColorType );
3668 if ( mGradientRamp )
3669 res->setColorRamp( mGradientRamp->clone() );
3670 copyDataDefinedProperties( res.get() );
3671 copyPaintEffect( res.get() );
3672 return res.release();
3673}
3674
3676{
3677 return QStringLiteral( "Lineburst" );
3678}
3679
3681{
3682}
3683
3685{
3686}
3687
3689{
3690 if ( !context.renderContext().painter() )
3691 return;
3692
3693 double strokeWidth = mWidth;
3695 {
3696 context.setOriginalValueVariable( strokeWidth );
3698 }
3699 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3700
3701 //update alpha of gradient colors
3702 QColor color1 = mColor;
3704 {
3707 }
3708 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3709 if ( useSelectedColor )
3710 {
3711 color1 = context.renderContext().selectionColor();
3712 }
3713 color1.setAlphaF( context.opacity() * color1.alphaF() );
3714
3715 //second gradient color
3716 QColor color2 = mColor2;
3718 {
3721 }
3722
3723 //create a QGradient with the desired properties
3724 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3725 //add stops to gradient
3728 {
3729 //color ramp gradient
3730 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3731 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3732 }
3733 else
3734 {
3735 //two color gradient
3736 gradient.setColorAt( 0.0, color1 );
3737 gradient.setColorAt( 1.0, color2 );
3738 }
3739 const QBrush brush( gradient );
3740
3741 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3742}
3743
3745{
3747 mWidthUnit = unit;
3748 mOffsetUnit = unit;
3749}
3750
3752{
3754 if ( mWidthUnit != unit || mOffsetUnit != unit )
3755 {
3757 }
3758 return unit;
3759}
3760
3762{
3765}
3766
3768{
3770 mOffsetMapUnitScale = scale;
3771}
3772
3774{
3777 {
3778 return mWidthMapUnitScale;
3779 }
3780 return QgsMapUnitScale();
3781}
3782
3784{
3785 return ( mWidth / 2.0 ) + mOffset;
3786}
3787
3789{
3790 return mGradientRamp.get();
3791}
3792
3794{
3795 mGradientRamp.reset( ramp );
3796}
3797
3798//
3799// QgsFilledLineSymbolLayer
3800//
3801
3804{
3805 mWidth = width;
3806 mFill.reset( fillSymbol ? fillSymbol : static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
3807}
3808
3810
3812{
3814
3815 // throughout the history of QGIS and different layer types, we've used
3816 // a huge range of different strings for the same property. The logic here
3817 // is designed to be forgiving to this and accept a range of string keys:
3818 if ( props.contains( QStringLiteral( "line_width" ) ) )
3819 {
3820 width = props[QStringLiteral( "line_width" )].toDouble();
3821 }
3822 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
3823 {
3824 width = props[QStringLiteral( "outline_width" )].toDouble();
3825 }
3826 else if ( props.contains( QStringLiteral( "width" ) ) )
3827 {
3828 width = props[QStringLiteral( "width" )].toDouble();
3829 }
3830
3831 std::unique_ptr<QgsFilledLineSymbolLayer > l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ) );
3832
3833 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
3834 {
3835 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
3836 }
3837 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
3838 {
3839 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
3840 }
3841 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
3842 {
3843 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
3844 }
3845
3846 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3847 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3848 if ( props.contains( QStringLiteral( "offset" ) ) )
3849 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
3850 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
3851 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
3852 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3853 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3854 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
3855 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
3856 if ( props.contains( QStringLiteral( "capstyle" ) ) )
3857 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
3858
3859 l->restoreOldDataDefinedProperties( props );
3860
3861 return l.release();
3862}
3863
3865{
3866 return QStringLiteral( "FilledLine" );
3867}
3868
3870{
3871 if ( mFill )
3872 {
3873 mFill->startRender( context.renderContext(), context.fields() );
3874 }
3875}
3876
3878{
3879 if ( mFill )
3880 {
3881 mFill->stopRender( context.renderContext() );
3882 }
3883}
3884
3886{
3887 QPainter *p = context.renderContext().painter();
3888 if ( !p || !mFill )
3889 return;
3890
3891 double width = mWidth;
3893 {
3896 }
3897
3898 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
3899
3900 Qt::PenJoinStyle join = mPenJoinStyle;
3902 {
3905 if ( !QgsVariantUtils::isNull( exprVal ) )
3906 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3907 }
3908
3909 Qt::PenCapStyle cap = mPenCapStyle;
3911 {
3914 if ( !QgsVariantUtils::isNull( exprVal ) )
3915 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3916 }
3917
3918 double offset = mOffset;
3920 {
3923 }
3924
3925 const double prevOpacity = mFill->opacity();
3926 mFill->setOpacity( mFill->opacity() * context.opacity() );
3927
3928 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3930
3931 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3932
3933 // stroke out the path using the correct line cap/join style. We'll then use this as the fill polygon
3934 QPainterPathStroker stroker;
3935 stroker.setWidth( scaledWidth );
3936 stroker.setCapStyle( cap );
3937 stroker.setJoinStyle( join );
3938
3939 QPolygonF polygon;
3940 if ( qgsDoubleNear( offset, 0 ) )
3941 {
3942 QPainterPath path;
3943 path.addPolygon( points );
3944 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3945 const QPolygonF polygon = stroke.toFillPolygon();
3946 if ( !polygon.isEmpty() )
3947 {
3948 mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
3949 }
3950 }
3951 else
3952 {
3953 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3955 {
3956 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
3957 // and clamp it to a reasonable range. It's the best we can do in this situation!
3958 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
3959 }
3960
3961 const QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3962 for ( const QPolygonF &part : mline )
3963 {
3964 QPainterPath path;
3965 path.addPolygon( part );
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 }
3974
3976
3977 mFill->setOpacity( prevOpacity );
3978}
3979
3981{
3982 QVariantMap map;
3983
3984 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3985 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3986 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3987 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3988 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3989 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3990 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3991 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3992 if ( mFill )
3993 {
3994 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mFill->color() );
3995 }
3996 return map;
3997}
3998
4000{
4001 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4002 copyPaintEffect( res.get() );
4003 copyDataDefinedProperties( res.get() );
4004 res->setSubSymbol( mFill->clone() );
4005 return res.release();
4006}
4007
4009{
4010 return mFill.get();
4011}
4012
4014{
4015 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4016 {
4017 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4018 return true;
4019 }
4020 else
4021 {
4022 delete symbol;
4023 return false;
4024 }
4025}
4026
4028{
4029 if ( mFill )
4030 {
4031 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4032 }
4033 return 0;
4034}
4035
4037{
4038 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4039 if ( mFill )
4040 attr.unite( mFill->usedAttributes( context ) );
4041 return attr;
4042}
4043
4045{
4047 return true;
4048 if ( mFill && mFill->hasDataDefinedProperties() )
4049 return true;
4050 return false;
4051}
4052
4054{
4055 mColor = c;
4056 if ( mFill )
4057 mFill->setColor( c );
4058}
4059
4061{
4062 return mFill ? mFill->color() : mColor;
4063}
4064
4066{
4069 || ( mFill && mFill->usesMapUnits() );
4070}
4071
4073{
4075 if ( mFill )
4076 mFill->setMapUnitScale( scale );
4077}
4078
4080{
4081 if ( mFill )
4082 {
4083 return mFill->mapUnitScale();
4084 }
4085 return QgsMapUnitScale();
4086}
4087
4089{
4091 if ( mFill )
4092 mFill->setOutputUnit( unit );
4093}
4094
4096{
4097 if ( mFill )
4098 {
4099 return mFill->outputUnit();
4100 }
4102}
4103
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition: qgis.h:2540
@ 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.
GradientColorSource
Gradient color sources.
Definition: qgis.h:2562
@ ColorRamp
Gradient color ramp.
@ Curve
An intermediate point on a segment defining the curvature of the segment.
@ Segment
The actual start or end point of a segment.
@ Unknown
Unknown types.
RenderUnit
Rendering size units.
Definition: qgis.h:4255
@ 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:565
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
QFlags< MarkerLinePlacement > MarkerLinePlacements
Definition: qgis.h:2551
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.
Definition: qgscolorramp.h:29
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.
Definition: qgsdxfexport.h:66
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.
Definition: qgsdxfexport.h:252
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:56
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.
Definition: qgsfillsymbol.h:30
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.
Definition: qgslinesymbol.h:30
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...
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.
Definition: qgsmaptopixel.h:39
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 ...
Definition: qgspointxy.cpp:87
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
QPointF toQPointF() const
Returns the point as a QPointF.
Definition: qgspoint.h:382
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:701
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.
Definition: qgsproperty.h:228
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.
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...
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)
static bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static double rescaleUom(double size, Qgis::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static bool isSharpCorner(QPointF p1, QPointF p2, QPointF p3)
Returns true if the angle formed by the line p1 - p2 - p3 forms a "sharp" corner.
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static Qt::PenCapStyle decodePenCapStyle(const QString &str)
static QVector< qreal > decodeRealVector(const QString &s)
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QString encodePenCapStyle(Qt::PenCapStyle style)
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static void appendPolyline(QPolygonF &target, const QPolygonF &line)
Appends a polyline line to an existing target polyline.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static double polylineLength(const QPolygonF &polyline)
Returns the total length of a polyline.
static Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static Qt::PenStyle decodePenStyle(const QString &str)
static QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QPolygonF polylineSubstring(const QPolygonF &polyline, double startOffset, double endOffset)
Returns the substring of a polyline which starts at startOffset from the beginning of the line and en...
static QString encodeRealVector(const QVector< qreal > &v)
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
Property
Data definable properties.
@ SecondaryColor
Secondary color (eg for gradient fills)
@ File
Filename, eg for svg files.
@ DashPatternOffset
Dash pattern offset,.
@ OffsetAlongLine
Offset along line.