QGIS API Documentation 4.1.0-Master (ca2ac17535b)
Loading...
Searching...
No Matches
qgslinesymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinesymbollayer.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslinesymbollayer.h"
17
18#include <algorithm>
19#include <cmath>
20#include <line_p.h>
21#include <memory>
22
23#include "qgsapplication.h"
24#include "qgscolorrampimpl.h"
25#include "qgscolorutils.h"
26#include "qgscurvepolygon.h"
27#include "qgsdxfexport.h"
29#include "qgsfeedback.h"
30#include "qgsfillsymbol.h"
32#include "qgsgeometryutils.h"
33#include "qgsgeos.h"
34#include "qgsimagecache.h"
35#include "qgsimageoperation.h"
36#include "qgslinesymbol.h"
37#include "qgslogger.h"
38#include "qgsmarkersymbol.h"
39#include "qgsmultipolygon.h"
40#include "qgspolygon.h"
41#include "qgsproperty.h"
42#include "qgsrendercontext.h"
43#include "qgssldexportcontext.h"
44#include "qgssymbollayerutils.h"
45#include "qgsunittypes.h"
46
47#include <QDomDocument>
48#include <QDomElement>
49#include <QPainter>
50#include <QString>
51
52using namespace Qt::StringLiterals;
53
55 : mPenStyle( penStyle )
56{
57 mColor = color;
58 mWidth = width;
59 mCustomDashVector << 5 << 2;
60}
61
63
65{
67 mWidthUnit = unit;
68 mOffsetUnit = unit;
69 mCustomDashPatternUnit = unit;
70 mDashPatternOffsetUnit = unit;
71 mTrimDistanceStartUnit = unit;
72 mTrimDistanceEndUnit = unit;
73}
74
76{
78 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
79 {
81 }
82 return unit;
83}
84
89
91{
93 mWidthMapUnitScale = scale;
94 mOffsetMapUnitScale = scale;
95 mCustomDashPatternMapUnitScale = scale;
96}
97
106
108{
112
113 if ( props.contains( u"line_color"_s ) )
114 {
115 color = QgsColorUtils::colorFromString( props[u"line_color"_s].toString() );
116 }
117 else if ( props.contains( u"outline_color"_s ) )
118 {
119 color = QgsColorUtils::colorFromString( props[u"outline_color"_s].toString() );
120 }
121 else if ( props.contains( u"color"_s ) )
122 {
123 //pre 2.5 projects used "color"
124 color = QgsColorUtils::colorFromString( props[u"color"_s].toString() );
125 }
126 if ( props.contains( u"line_width"_s ) )
127 {
128 width = props[u"line_width"_s].toDouble();
129 }
130 else if ( props.contains( u"outline_width"_s ) )
131 {
132 width = props[u"outline_width"_s].toDouble();
133 }
134 else if ( props.contains( u"width"_s ) )
135 {
136 //pre 2.5 projects used "width"
137 width = props[u"width"_s].toDouble();
138 }
139 if ( props.contains( u"line_style"_s ) )
140 {
141 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[u"line_style"_s].toString() );
142 }
143 else if ( props.contains( u"outline_style"_s ) )
144 {
145 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[u"outline_style"_s].toString() );
146 }
147 else if ( props.contains( u"penstyle"_s ) )
148 {
149 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[u"penstyle"_s].toString() );
150 }
151
153 if ( props.contains( u"line_width_unit"_s ) )
154 {
155 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"line_width_unit"_s].toString() ) );
156 }
157 else if ( props.contains( u"outline_width_unit"_s ) )
158 {
159 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"outline_width_unit"_s].toString() ) );
160 }
161 else if ( props.contains( u"width_unit"_s ) )
162 {
163 //pre 2.5 projects used "width_unit"
164 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"width_unit"_s].toString() ) );
165 }
166 if ( props.contains( u"width_map_unit_scale"_s ) )
167 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"width_map_unit_scale"_s].toString() ) );
168 if ( props.contains( u"offset"_s ) )
169 l->setOffset( props[u"offset"_s].toDouble() );
170 if ( props.contains( u"offset_unit"_s ) )
171 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
172 if ( props.contains( u"offset_map_unit_scale"_s ) )
173 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
174 if ( props.contains( u"joinstyle"_s ) )
175 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[u"joinstyle"_s].toString() ) );
176 if ( props.contains( u"capstyle"_s ) )
177 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[u"capstyle"_s].toString() ) );
178
179 if ( props.contains( u"use_custom_dash"_s ) )
180 {
181 l->setUseCustomDashPattern( props[u"use_custom_dash"_s].toInt() );
182 }
183 if ( props.contains( u"customdash"_s ) )
184 {
185 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[u"customdash"_s].toString() ) );
186 }
187 if ( props.contains( u"customdash_unit"_s ) )
188 {
189 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[u"customdash_unit"_s].toString() ) );
190 }
191 if ( props.contains( u"customdash_map_unit_scale"_s ) )
192 {
193 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"customdash_map_unit_scale"_s].toString() ) );
194 }
195
196 if ( props.contains( u"draw_inside_polygon"_s ) )
197 {
198 l->setDrawInsidePolygon( props[u"draw_inside_polygon"_s].toInt() );
199 }
200
201 if ( props.contains( u"ring_filter"_s ) )
202 {
203 l->setRingFilter( static_cast< RenderRingFilter>( props[u"ring_filter"_s].toInt() ) );
204 }
205
206 if ( props.contains( u"dash_pattern_offset"_s ) )
207 l->setDashPatternOffset( props[u"dash_pattern_offset"_s].toDouble() );
208 if ( props.contains( u"dash_pattern_offset_unit"_s ) )
209 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"dash_pattern_offset_unit"_s].toString() ) );
210 if ( props.contains( u"dash_pattern_offset_map_unit_scale"_s ) )
211 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"dash_pattern_offset_map_unit_scale"_s].toString() ) );
212
213 if ( props.contains( u"trim_distance_start"_s ) )
214 l->setTrimDistanceStart( props[u"trim_distance_start"_s].toDouble() );
215 if ( props.contains( u"trim_distance_start_unit"_s ) )
216 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[u"trim_distance_start_unit"_s].toString() ) );
217 if ( props.contains( u"trim_distance_start_map_unit_scale"_s ) )
218 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"trim_distance_start_map_unit_scale"_s].toString() ) );
219 if ( props.contains( u"trim_distance_end"_s ) )
220 l->setTrimDistanceEnd( props[u"trim_distance_end"_s].toDouble() );
221 if ( props.contains( u"trim_distance_end_unit"_s ) )
222 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[u"trim_distance_end_unit"_s].toString() ) );
223 if ( props.contains( u"trim_distance_end_map_unit_scale"_s ) )
224 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"trim_distance_end_map_unit_scale"_s].toString() ) );
225
226 if ( props.contains( u"align_dash_pattern"_s ) )
227 l->setAlignDashPattern( props[u"align_dash_pattern"_s].toInt() );
228
229 if ( props.contains( u"tweak_dash_pattern_on_corners"_s ) )
230 l->setTweakDashPatternOnCorners( props[u"tweak_dash_pattern_on_corners"_s].toInt() );
231
233
234 return l;
235}
236
238{
239 return u"SimpleLine"_s;
240}
241
246
248{
249 QColor penColor = mColor;
250 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
251 mPen.setColor( penColor );
252 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
253 mPen.setWidthF( scaledWidth );
254
255 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
256 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
257 const double dashWidthDiv = std::max( 1.0, scaledWidth );
258 if ( mUseCustomDashPattern )
259 {
260 mPen.setStyle( Qt::CustomDashLine );
261
262 //scale pattern vector
263
264 QVector<qreal> scaledVector;
265 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
266 for ( ; it != mCustomDashVector.constEnd(); ++it )
267 {
268 //the dash is specified in terms of pen widths, therefore the division
269 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
270 }
271 mPen.setDashPattern( scaledVector );
272 }
273 else
274 {
275 mPen.setStyle( mPenStyle );
276 }
277
278 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
279 {
280 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
281 }
282
283 mPen.setJoinStyle( mPenJoinStyle );
284 mPen.setCapStyle( mPenCapStyle );
285
286 mSelPen = mPen;
287 QColor selColor = context.renderContext().selectionColor();
288 if ( !SELECTION_IS_OPAQUE )
289 selColor.setAlphaF( context.opacity() );
290 mSelPen.setColor( selColor );
291}
292
294{
295 Q_UNUSED( context )
296}
297
298void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
299{
300 QPainter *p = context.renderContext().painter();
301 if ( !p )
302 {
303 return;
304 }
305
306 QgsExpressionContextScope *scope = nullptr;
307 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
309 {
310 scope = new QgsExpressionContextScope();
311 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
312 }
313
314 if ( mDrawInsidePolygon )
315 p->save();
316
317 switch ( mRingFilter )
318 {
319 case AllRings:
320 case ExteriorRingOnly:
321 {
322 if ( mDrawInsidePolygon )
323 {
324 //only drawing the line on the interior of the polygon, so set clip path for painter
325 QPainterPath clipPath;
326 clipPath.addPolygon( points );
327
328 if ( rings )
329 {
330 //add polygon rings
331 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
332 {
333 QPolygonF ring = *it;
334 clipPath.addPolygon( ring );
335 }
336 }
337
338 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
339 p->setClipPath( clipPath, Qt::IntersectClip );
340 }
341
342 if ( scope )
344
345 renderPolyline( points, context );
346 }
347 break;
348
350 break;
351 }
352
353 if ( rings )
354 {
355 switch ( mRingFilter )
356 {
357 case AllRings:
359 {
360 mOffset = -mOffset; // invert the offset for rings!
361 int ringIndex = 1;
362 for ( const QPolygonF &ring : std::as_const( *rings ) )
363 {
364 if ( scope )
366
367 renderPolyline( ring, context );
368 ringIndex++;
369 }
370 mOffset = -mOffset;
371 }
372 break;
373 case ExteriorRingOnly:
374 break;
375 }
376 }
377
378 if ( mDrawInsidePolygon )
379 {
380 //restore painter to reset clip path
381 p->restore();
382 }
383}
384
390 QPolygonF &points,
391 double startTrim,
392 double endTrim,
393 Qgis::RenderUnit trimDistanceStartUnit,
394 Qgis::RenderUnit trimDistanceEndUnit,
395 const QgsMapUnitScale &trimDistanceStartMapUnitScale,
396 const QgsMapUnitScale &trimDistanceEndMapUnitScale,
397 const QgsPropertyCollection &mDataDefinedProperties,
399)
400{
401 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::TrimStart ) )
402 {
403 context.setOriginalValueVariable( startTrim );
404 startTrim = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::TrimStart, context.renderContext().expressionContext(), startTrim );
405 }
406
407 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::TrimEnd ) )
408 {
409 context.setOriginalValueVariable( endTrim );
410 endTrim = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::TrimEnd, context.renderContext().expressionContext(), endTrim );
411 }
412
413 double totalLength = -1;
414 if ( trimDistanceStartUnit == Qgis::RenderUnit::Percentage )
415 {
416 totalLength = QgsSymbolLayerUtils::polylineLength( points );
417 startTrim = startTrim * 0.01 * totalLength;
418 }
419 else
420 {
421 startTrim = context.renderContext().convertToPainterUnits( startTrim, trimDistanceStartUnit, trimDistanceStartMapUnitScale );
422 }
423 if ( trimDistanceEndUnit == Qgis::RenderUnit::Percentage )
424 {
425 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
426 totalLength = QgsSymbolLayerUtils::polylineLength( points );
427 endTrim = endTrim * 0.01 * totalLength;
428 }
429 else
430 {
431 endTrim = context.renderContext().convertToPainterUnits( endTrim, trimDistanceEndUnit, trimDistanceEndMapUnitScale );
432 }
433 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
434 {
435 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
436 }
437}
438
439
441{
442 QPainter *p = context.renderContext().painter();
443 if ( !p )
444 {
445 return;
446 }
447
448 QPolygonF points = pts;
449 trimPoints( points, mTrimDistanceStart, mTrimDistanceEnd, mTrimDistanceStartUnit, mTrimDistanceEndUnit, mTrimDistanceStartMapUnitScale, mTrimDistanceEndMapUnitScale, mDataDefinedProperties, context );
450
451 QColor penColor = mColor;
452 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
453 mPen.setColor( penColor );
454
455 double offset = mOffset;
456 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
457
458 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
459 const QPen pen = useSelectedColor ? mSelPen : mPen;
460
461 if ( !pen.dashPattern().isEmpty() )
462 {
463 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
464 const QVector<double> pattern = pen.dashPattern();
465 bool foundNonNull = false;
466 for ( int i = 0; i < pattern.size(); ++i )
467 {
468 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
469 {
470 foundNonNull = true;
471 break;
472 }
473 }
474 if ( !foundNonNull )
475 return;
476 }
477
478 p->setBrush( Qt::NoBrush );
479
480 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
481 std::unique_ptr< QgsScopedQPainterState > painterState;
482 if ( points.size() <= 2
485 && ( p->renderHints() & QPainter::Antialiasing ) )
486 {
487 painterState = std::make_unique< QgsScopedQPainterState >( p );
488 p->setRenderHint( QPainter::Antialiasing, false );
489 }
490
491 const bool applyPatternTweaks = mAlignDashPattern && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() ) && pen.dashOffset() == 0;
492
493 if ( qgsDoubleNear( offset, 0 ) )
494 {
495 if ( applyPatternTweaks )
496 {
497 drawPathWithDashPatternTweaks( p, points, pen );
498 }
499 else
500 {
501 p->setPen( pen );
502 QPainterPath path;
503 path.addPolygon( points );
504 p->drawPath( path );
505 }
506 }
507 else
508 {
509 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
512 {
513 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
514 // and clamp it to a reasonable range. It's the best we can do in this situation!
515 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
516 }
517
518 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
519 for ( const QPolygonF &part : mline )
520 {
521 if ( applyPatternTweaks )
522 {
523 drawPathWithDashPatternTweaks( p, part, pen );
524 }
525 else
526 {
527 p->setPen( pen );
528 QPainterPath path;
529 path.addPolygon( part );
530 p->drawPath( path );
531 }
532 }
533 }
534}
535
537{
538 QVariantMap map;
539 map[u"line_color"_s] = QgsColorUtils::colorToString( mColor );
540 map[u"line_width"_s] = QString::number( mWidth );
541 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
542 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
543 map[u"line_style"_s] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
544 map[u"joinstyle"_s] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
545 map[u"capstyle"_s] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
546 map[u"offset"_s] = QString::number( mOffset );
547 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
548 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
549 map[u"use_custom_dash"_s] = ( mUseCustomDashPattern ? u"1"_s : u"0"_s );
550 map[u"customdash"_s] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
551 map[u"customdash_unit"_s] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
552 map[u"customdash_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
553 map[u"dash_pattern_offset"_s] = QString::number( mDashPatternOffset );
554 map[u"dash_pattern_offset_unit"_s] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
555 map[u"dash_pattern_offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
556 map[u"trim_distance_start"_s] = QString::number( mTrimDistanceStart );
557 map[u"trim_distance_start_unit"_s] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
558 map[u"trim_distance_start_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
559 map[u"trim_distance_end"_s] = QString::number( mTrimDistanceEnd );
560 map[u"trim_distance_end_unit"_s] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
561 map[u"trim_distance_end_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
562 map[u"draw_inside_polygon"_s] = ( mDrawInsidePolygon ? u"1"_s : u"0"_s );
563 map[u"ring_filter"_s] = QString::number( static_cast< int >( mRingFilter ) );
564 map[u"align_dash_pattern"_s] = mAlignDashPattern ? u"1"_s : u"0"_s;
565 map[u"tweak_dash_pattern_on_corners"_s] = mPatternCartographicTweakOnSharpCorners ? u"1"_s : u"0"_s;
566 return map;
567}
568
570{
576 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
577 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
578 l->setOffset( mOffset );
579 l->setPenJoinStyle( mPenJoinStyle );
580 l->setPenCapStyle( mPenCapStyle );
581 l->setUseCustomDashPattern( mUseCustomDashPattern );
582 l->setCustomDashVector( mCustomDashVector );
583 l->setDrawInsidePolygon( mDrawInsidePolygon );
585 l->setDashPatternOffset( mDashPatternOffset );
586 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
587 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
588 l->setTrimDistanceStart( mTrimDistanceStart );
589 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
590 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
591 l->setTrimDistanceEnd( mTrimDistanceEnd );
592 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
593 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
594 l->setAlignDashPattern( mAlignDashPattern );
595 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
596
598 return l;
599}
600
601void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
602{
603 QgsSldExportContext context;
604 context.setExtraProperties( props );
605 toSld( doc, element, context );
606}
607
608bool QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
609{
610 if ( mPenStyle == Qt::NoPen )
611 return true;
612
613 const QVariantMap props = context.extraProperties();
614 QDomElement symbolizerElem = doc.createElement( u"se:LineSymbolizer"_s );
615 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
616 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
617 element.appendChild( symbolizerElem );
618
619 // <Geometry>
620 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
621
622 // <Stroke>
623 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
624 symbolizerElem.appendChild( strokeElem );
625
626 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
628 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
629 QgsSymbolLayerUtils::lineToSld( doc, strokeElem, penStyle, mColor, context, width, &mPenJoinStyle, &mPenCapStyle, &customDashVector );
630
631 // <se:PerpendicularOffset>
632 if ( !qgsDoubleNear( mOffset, 0.0 ) )
633 {
634 QDomElement perpOffsetElem = doc.createElement( u"se:PerpendicularOffset"_s );
636 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
637 symbolizerElem.appendChild( perpOffsetElem );
638 }
639 return true;
640}
641
642QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
643{
644 if ( mUseCustomDashPattern )
645 {
646 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle, mPenCapStyle, mOffset, &mCustomDashVector );
647 }
648 else
649 {
650 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle, mPenCapStyle, mOffset );
651 }
652}
653
655{
656 QgsDebugMsgLevel( u"Entered."_s, 4 );
657
658 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
659 if ( strokeElem.isNull() )
660 return nullptr;
661
662 Qt::PenStyle penStyle;
663 QColor color;
664 double width;
665 Qt::PenJoinStyle penJoinStyle;
666 Qt::PenCapStyle penCapStyle;
667 QVector<qreal> customDashVector;
668
670 return nullptr;
671
672 double offset = 0.0;
673 QDomElement perpOffsetElem = element.firstChildElement( u"PerpendicularOffset"_s );
674 if ( !perpOffsetElem.isNull() )
675 {
676 bool ok;
677 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
678 if ( ok )
679 offset = d;
680 }
681
682 double scaleFactor = 1.0;
683 const QString uom = element.attribute( u"uom"_s );
684 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
685 width = width * scaleFactor;
686 offset = offset * scaleFactor;
687
689 l->setOutputUnit( sldUnitSize );
690 l->setOffset( offset );
693 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
695 return l;
696}
697
698void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
699{
700 if ( !dataDefinedProperties().hasActiveProperties() )
701 return; // shortcut
702
703 //data defined properties
704 bool hasStrokeWidthExpression = false;
706 {
708 double scaledWidth
709 = context.renderContext()
711 pen.setWidthF( scaledWidth );
712 selPen.setWidthF( scaledWidth );
713 hasStrokeWidthExpression = true;
714 }
715
716 //color
718 {
720
722 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
723 pen.setColor( penColor );
724 }
725
726 //offset
728 {
731 }
732
733 //dash dot vector
734
735 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
736 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
737 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
738
740 {
741 const QString customDashString
743 const QStringList dashList = customDashString.split( ';' );
744 QVector<qreal> dashVector;
745 for ( const QString &dash : dashList )
746 {
747 dashVector.push_back( context.renderContext().convertToPainterUnits( dash.toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
748 }
749 pen.setDashPattern( dashVector );
750 }
751 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
752 {
753 //re-scale pattern vector after data defined pen width was applied
754
755 QVector<qreal> scaledVector;
756 for ( double v : std::as_const( mCustomDashVector ) )
757 {
758 //the dash is specified in terms of pen widths, therefore the division
759 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
760 }
761 mPen.setDashPattern( scaledVector );
762 }
763
764 // dash pattern offset
765 double patternOffset = mDashPatternOffset;
766 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
767 {
768 context.setOriginalValueVariable( patternOffset );
769 patternOffset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::DashPatternOffset, context.renderContext().expressionContext(), mDashPatternOffset );
770 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
771 }
772
773 //line style
775 {
777 const QString lineStyleString
779 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( lineStyleString ) );
780 }
781
782 //join style
784 {
786 const QString joinStyleString
788 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( joinStyleString ) );
789 }
790
791 //cap style
793 {
795 const QString capStyleString
797 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( capStyleString ) );
798 }
799}
800
801void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
802{
803 if ( pen.dashPattern().empty() || points.size() < 2 )
804 return;
805
806 if ( pen.widthF() <= 1.0 )
807 {
808 pen.setWidthF( 1.0001 );
809 }
810
811 QVector< qreal > sourcePattern = pen.dashPattern();
812 const double dashWidthDiv = pen.widthF();
813 // back to painter units
814 for ( int i = 0; i < sourcePattern.size(); ++i )
815 sourcePattern[i] *= pen.widthF();
816
817 QVector< qreal > buffer;
818 QPolygonF bufferedPoints;
819 QPolygonF previousSegmentBuffer;
820 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
821 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
822 // and then append the buffer to the output pattern.
823
824 auto ptIt = points.constBegin();
825 double totalBufferLength = 0;
826 int patternIndex = 0;
827 double currentRemainingDashLength = 0;
828 double currentRemainingGapLength = 0;
829
830 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal > {
831 QVector< qreal > result;
832 result.reserve( buffer.size() );
833 for ( auto it = buffer.begin(); it != buffer.end(); )
834 {
835 qreal dash = *it++;
836 qreal gap = *it++;
837 while ( dash == 0 && !result.empty() )
838 {
839 result.last() += gap;
840
841 if ( it == buffer.end() )
842 return result;
843 dash = *it++;
844 gap = *it++;
845 }
846 while ( gap == 0 && it != buffer.end() )
847 {
848 dash += *it++;
849 gap = *it++;
850 }
851 result << dash << gap;
852 }
853 return result;
854 };
855
856 double currentBufferLineLength = 0;
857 auto flushBuffer =
858 [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength, dashWidthDiv, &compressPattern](
859 QPointF *nextPoint
860 ) {
861 if ( buffer.empty() || bufferedPoints.size() < 2 )
862 {
863 return;
864 }
865
866 if ( currentRemainingDashLength )
867 {
868 // ended midway through a dash -- we want to finish this off
869 buffer << currentRemainingDashLength << 0.0;
870 totalBufferLength += currentRemainingDashLength;
871 }
872 QVector< qreal > compressed = compressPattern( buffer );
873 if ( !currentRemainingDashLength )
874 {
875 // ended midway through a gap -- we don't want this, we want to end at previous dash
876 totalBufferLength -= compressed.last();
877 compressed.last() = 0;
878 }
879
880 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
881 const double scaleFactor = currentBufferLineLength / totalBufferLength;
882
883 bool shouldFlushPreviousSegmentBuffer = false;
884
885 if ( !previousSegmentBuffer.empty() )
886 {
887 // add first dash from current buffer
888 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
889 if ( !firstDashSubstring.empty() )
890 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
891
892 // then we skip over the first dash and gap for this segment
893 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
894
895 compressed = compressed.mid( 2 );
896 shouldFlushPreviousSegmentBuffer = !compressed.empty();
897 }
898
899 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
900 {
901 QPen adjustedPen = pen;
902 adjustedPen.setStyle( Qt::SolidLine );
903 painter->setPen( adjustedPen );
904 QPainterPath path;
905 path.addPolygon( previousSegmentBuffer );
906 painter->drawPath( path );
907 previousSegmentBuffer.clear();
908 }
909
910 double finalDash = 0;
911 if ( nextPoint )
912 {
913 // sharp bend:
914 // 1. rewind buffered points line by final dash and gap length
915 // (later) 2. draw the bend with a solid line of length 2 * final dash size
916
917 if ( !compressed.empty() )
918 {
919 finalDash = compressed.at( compressed.size() - 2 );
920 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
921
922 const QPolygonF thisPoints = bufferedPoints;
923 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
924 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, -finalDash * scaleFactor, 0 );
925 }
926 else
927 {
928 previousSegmentBuffer << bufferedPoints;
929 }
930 }
931
932 currentBufferLineLength = 0;
933 currentRemainingDashLength = 0;
934 currentRemainingGapLength = 0;
935 totalBufferLength = 0;
936 buffer.clear();
937
938 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
939 {
940 QPen adjustedPen = pen;
941 if ( !compressed.empty() )
942 {
943 // maximum size of dash pattern is 32 elements
944 compressed = compressed.mid( 0, 32 );
945 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal &element ) { element *= scaleFactor / dashWidthDiv; } );
946 adjustedPen.setDashPattern( compressed );
947 }
948 else
949 {
950 adjustedPen.setStyle( Qt::SolidLine );
951 }
952
953 painter->setPen( adjustedPen );
954 QPainterPath path;
955 path.addPolygon( bufferedPoints );
956 painter->drawPath( path );
957 }
958
959 bufferedPoints.clear();
960 };
961
962 QPointF p1;
963 QPointF p2 = *ptIt;
964 ptIt++;
965 bufferedPoints << p2;
966 for ( ; ptIt != points.constEnd(); ++ptIt )
967 {
968 p1 = *ptIt;
969 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
970 {
971 continue;
972 }
973
974 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
975 currentBufferLineLength += remainingSegmentDistance;
976 while ( true )
977 {
978 // handle currentRemainingDashLength/currentRemainingGapLength
979 if ( currentRemainingDashLength > 0 )
980 {
981 // bit more of dash to insert
982 if ( remainingSegmentDistance >= currentRemainingDashLength )
983 {
984 // all of dash fits in
985 buffer << currentRemainingDashLength << 0.0;
986 totalBufferLength += currentRemainingDashLength;
987 remainingSegmentDistance -= currentRemainingDashLength;
988 patternIndex++;
989 currentRemainingDashLength = 0.0;
990 currentRemainingGapLength = sourcePattern.at( patternIndex );
991 if ( currentRemainingGapLength == 0.0 )
992 {
993 patternIndex++;
994 }
995 }
996 else
997 {
998 // only part of remaining dash fits in
999 buffer << remainingSegmentDistance << 0.0;
1000 totalBufferLength += remainingSegmentDistance;
1001 currentRemainingDashLength -= remainingSegmentDistance;
1002 break;
1003 }
1004 }
1005 if ( currentRemainingGapLength > 0 )
1006 {
1007 // bit more of gap to insert
1008 if ( remainingSegmentDistance >= currentRemainingGapLength )
1009 {
1010 // all of gap fits in
1011 buffer << 0.0 << currentRemainingGapLength;
1012 totalBufferLength += currentRemainingGapLength;
1013 remainingSegmentDistance -= currentRemainingGapLength;
1014 currentRemainingGapLength = 0.0;
1015 patternIndex++;
1016 }
1017 else
1018 {
1019 // only part of remaining gap fits in
1020 buffer << 0.0 << remainingSegmentDistance;
1021 totalBufferLength += remainingSegmentDistance;
1022 currentRemainingGapLength -= remainingSegmentDistance;
1023 break;
1024 }
1025 }
1026
1027 if ( patternIndex + 1 >= sourcePattern.size() )
1028 {
1029 patternIndex = 0;
1030 }
1031
1032 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1033 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1034 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1035 {
1036 buffer << nextPatternDashLength << nextPatternGapLength;
1037 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1038 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1039 patternIndex += 2;
1040 }
1041 else if ( nextPatternDashLength <= remainingSegmentDistance )
1042 {
1043 // can fit in "dash", but not "gap"
1044 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1045 totalBufferLength += remainingSegmentDistance;
1046 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1047 currentRemainingDashLength = 0;
1048 patternIndex++;
1049 break;
1050 }
1051 else
1052 {
1053 // can't fit in "dash"
1054 buffer << remainingSegmentDistance << 0.0;
1055 totalBufferLength += remainingSegmentDistance;
1056 currentRemainingGapLength = 0;
1057 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1058 break;
1059 }
1060 }
1061
1062 bufferedPoints << p1;
1063 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1064 {
1065 QPointF nextPoint = *( ptIt + 1 );
1066
1067 // extreme angles form more than 45 degree angle at a node
1068 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1069 {
1070 // extreme angle. Rescale buffer and flush
1071 flushBuffer( &nextPoint );
1072 bufferedPoints << p1;
1073 // restart the line with the full length of the most recent dash element -- see
1074 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1075 if ( patternIndex % 2 == 1 )
1076 {
1077 patternIndex--;
1078 }
1079 currentRemainingDashLength = sourcePattern.at( patternIndex );
1080 }
1081 }
1082
1083 p2 = p1;
1084 }
1085
1086 flushBuffer( nullptr );
1087 if ( !previousSegmentBuffer.empty() )
1088 {
1089 QPen adjustedPen = pen;
1090 adjustedPen.setStyle( Qt::SolidLine );
1091 painter->setPen( adjustedPen );
1092 QPainterPath path;
1093 path.addPolygon( previousSegmentBuffer );
1094 painter->drawPath( path );
1095 previousSegmentBuffer.clear();
1096 }
1097}
1098
1100{
1101 if ( mDrawInsidePolygon )
1102 {
1103 //set to clip line to the interior of polygon, so we expect no bleed
1104 return 0;
1105 }
1106 else
1107 {
1109 }
1110}
1111
1113{
1114 unit = mCustomDashPatternUnit;
1115 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1116}
1117
1119{
1120 return mPenStyle;
1121}
1122
1134
1144
1146{
1147 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1148}
1149
1151{
1152 return mAlignDashPattern;
1153}
1154
1156{
1157 mAlignDashPattern = enabled;
1158}
1159
1161{
1162 return mPatternCartographicTweakOnSharpCorners;
1163}
1164
1166{
1167 mPatternCartographicTweakOnSharpCorners = enabled;
1168}
1169
1171{
1172 double offset = mOffset;
1173
1175 {
1178 }
1179
1181 return -offset; //direction seems to be inverse to symbology offset
1182}
1183
1184//
1185// QgsTemplatedLineSymbolLayerBase
1186//
1188 : mRotateSymbols( rotateSymbol )
1189 , mInterval( interval )
1190{}
1191
1213
1218
1220
1222{
1223 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1224 if ( mRenderingFeature )
1225 {
1226 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1227 // until after we've received the final part
1228 mFeatureSymbolOpacity = context.opacity();
1229 mCurrentFeatureIsSelected = useSelectedColor;
1230 }
1231
1232 double offset = mOffset;
1233
1235 {
1238 }
1239
1241
1243 {
1245 if ( !QgsVariantUtils::isNull( exprVal ) )
1246 {
1247 QString placementString = exprVal.toString();
1248 if ( placementString.compare( "interval"_L1, Qt::CaseInsensitive ) == 0 )
1249 {
1251 }
1252 else if ( placementString.compare( "vertex"_L1, Qt::CaseInsensitive ) == 0 )
1253 {
1255 }
1256 else if ( placementString.compare( "innervertices"_L1, Qt::CaseInsensitive ) == 0 )
1257 {
1259 }
1260 else if ( placementString.compare( "lastvertex"_L1, Qt::CaseInsensitive ) == 0 )
1261 {
1263 }
1264 else if ( placementString.compare( "firstvertex"_L1, Qt::CaseInsensitive ) == 0 )
1265 {
1267 }
1268 else if ( placementString.compare( "centerpoint"_L1, Qt::CaseInsensitive ) == 0 )
1269 {
1271 }
1272 else if ( placementString.compare( "curvepoint"_L1, Qt::CaseInsensitive ) == 0 )
1273 {
1275 }
1276 else if ( placementString.compare( "segmentcenter"_L1, Qt::CaseInsensitive ) == 0 )
1277 {
1279 }
1280 else
1281 {
1283 }
1284 }
1285 }
1286
1287 QgsScopedQPainterState painterState( context.renderContext().painter() );
1288
1289 double averageOver = mAverageAngleLength;
1291 {
1292 context.setOriginalValueVariable( mAverageAngleLength );
1293 averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
1294 }
1295 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1296
1297 QgsSymbolLayerUtils::BlankSegments blankSegments;
1299 {
1300 const QString strBlankSegments = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::BlankSegments, context.renderContext().expressionContext() );
1301 QString error;
1302 QList<QList<QgsSymbolLayerUtils::BlankSegments>> allBlankSegments = QgsSymbolLayerUtils::parseBlankSegments( strBlankSegments, context.renderContext(), blankSegmentsUnit(), error );
1303
1304 if ( !error.isEmpty() )
1305 {
1306 QgsDebugError( u"Badly formatted blank segment '%1', skip it: %2"_s.arg( strBlankSegments ).arg( error ) );
1307 }
1308 else
1309 {
1310 // keep only the part/ring we are currently rendering
1311 const int iPart = context.geometryPartNum() - 1;
1312 if ( iPart >= 0 && mRingIndex >= 0 && iPart < allBlankSegments.count() && mRingIndex < allBlankSegments.at( iPart ).count() )
1313 {
1314 blankSegments = allBlankSegments.at( iPart ).at( mRingIndex );
1315 }
1316 }
1317 }
1318
1319 QPolygonF points = pts;
1320 trimPoints( points, mTrimDistanceStart, mTrimDistanceEnd, mTrimDistanceStartUnit, mTrimDistanceEndUnit, mTrimDistanceStartMapUnitScale, mTrimDistanceEndMapUnitScale, mDataDefinedProperties, context );
1321
1322 if ( qgsDoubleNear( offset, 0.0 ) )
1323 {
1325 renderPolylineInterval( points, context, averageOver, blankSegments );
1327 renderPolylineCentral( points, context, averageOver, blankSegments );
1329 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1330 if ( placements & Qgis::MarkerLinePlacement::FirstVertex && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1331 {
1332 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1333 mHasRenderedFirstPart = mRenderingFeature;
1334 }
1336 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1338 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1340 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1342 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1343 }
1344 else
1345 {
1346 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1347 QList<QPolygonF> mline = ::
1349
1350 for ( int part = 0; part < mline.count(); ++part )
1351 {
1352 const QPolygonF &points2 = mline[part];
1353
1355 renderPolylineInterval( points2, context, averageOver, blankSegments );
1357 renderPolylineCentral( points2, context, averageOver, blankSegments );
1359 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1361 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1363 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1364 if ( placements & Qgis::MarkerLinePlacement::FirstVertex && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1365 {
1366 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1367 mHasRenderedFirstPart = mRenderingFeature;
1368 }
1370 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1372 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1373 }
1374 }
1375
1376 QgsSymbolLayerUtils::ExtraItems extraItems;
1377 if ( !mBlockExtraItemsRendering && mDataDefinedProperties.isActive( QgsSymbolLayer::Property::ExtraItems ) )
1378 {
1379 const QString strExtraItems = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::ExtraItems, context.renderContext().expressionContext() );
1380 QString error;
1381 extraItems = QgsSymbolLayerUtils::parseExtraItems( strExtraItems, error );
1382
1383 if ( !error.isEmpty() )
1384 {
1385 QgsDebugError( u"Badly formatted extra items '%1', skip it: %2"_s.arg( strExtraItems ).arg( error ) );
1386 }
1387 else
1388 {
1389 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1390 for ( std::pair<QPointF, double> &extraItem : extraItems )
1391 {
1392 mtp.transformInPlace( extraItem.first.rx(), extraItem.first.ry() );
1393
1394 setSymbolLineAngle( extraItem.second );
1395
1396 renderSymbol( extraItem.first, context.feature(), context.renderContext(), -1, shouldRenderUsingSelectionColor( context ) );
1397 }
1398 }
1399 }
1400}
1401
1402void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1403{
1404 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1405
1406 if ( curvePolygon )
1407 {
1408 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1409 }
1410
1411 QgsExpressionContextScope *scope = nullptr;
1412 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1414 {
1415 scope = new QgsExpressionContextScope();
1416 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1417 }
1418
1419 switch ( mRingFilter )
1420 {
1421 case AllRings:
1422 case ExteriorRingOnly:
1423 {
1424 if ( scope )
1426
1427 renderPolyline( points, context );
1428 mBlockExtraItemsRendering = true; // render extra items only once
1429 break;
1430 }
1431 case InteriorRingsOnly:
1432 break;
1433 }
1434
1435 if ( rings )
1436 {
1437 switch ( mRingFilter )
1438 {
1439 case AllRings:
1440 case InteriorRingsOnly:
1441 {
1442 mOffset = -mOffset; // invert the offset for rings!
1443 for ( int i = 0; i < rings->size(); ++i )
1444 {
1445 mRingIndex = i + 1;
1446 if ( curvePolygon )
1447 {
1448 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1449 }
1450 if ( scope )
1452
1453 renderPolyline( rings->at( i ), context );
1454 mBlockExtraItemsRendering = true; // render extra items only once
1455 }
1456 mOffset = -mOffset;
1457 mRingIndex = 0;
1458 }
1459 break;
1460 case ExteriorRingOnly:
1461 break;
1462 }
1463 }
1464
1465 mBlockExtraItemsRendering = false;
1466}
1467
1469{
1471 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1472 {
1474 }
1475 return unit;
1476}
1477
1479{
1481 mIntervalUnit = unit;
1482 mOffsetAlongLineUnit = unit;
1483 mAverageAngleLengthUnit = unit;
1484}
1485
1493
1502
1504{
1505 QVariantMap map;
1506 map[u"rotate"_s] = ( rotateSymbols() ? u"1"_s : u"0"_s );
1507 map[u"interval"_s] = QString::number( interval() );
1508 map[u"offset"_s] = QString::number( mOffset );
1509 map[u"offset_along_line"_s] = QString::number( offsetAlongLine() );
1510 map[u"offset_along_line_unit"_s] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1511 map[u"offset_along_line_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1512 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1513 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1514 map[u"interval_unit"_s] = QgsUnitTypes::encodeUnit( intervalUnit() );
1515 map[u"interval_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1516 map[u"average_angle_length"_s] = QString::number( mAverageAngleLength );
1517 map[u"average_angle_unit"_s] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1518 map[u"average_angle_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1519 map[u"trim_distance_start"_s] = QString::number( mTrimDistanceStart );
1520 map[u"trim_distance_start_unit"_s] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
1521 map[u"trim_distance_start_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
1522 map[u"trim_distance_end"_s] = QString::number( mTrimDistanceEnd );
1523 map[u"trim_distance_end_unit"_s] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
1524 map[u"trim_distance_end_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
1525 map[u"blank_segments_unit"_s] = QgsUnitTypes::encodeUnit( mBlankSegmentsUnit );
1526
1527 map[u"placements"_s] = qgsFlagValueToKeys( mPlacements );
1528
1529 map[u"ring_filter"_s] = QString::number( static_cast< int >( mRingFilter ) );
1530 map[u"place_on_every_part"_s] = mPlaceOnEveryPart;
1531 return map;
1532}
1533
1535{
1536 return mPlaceOnEveryPart
1537 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1538 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1539 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1540}
1541
1543{
1544 installMasks( context, true );
1545
1546 mRenderingFeature = true;
1547 mHasRenderedFirstPart = false;
1548}
1549
1551{
1552 mRenderingFeature = false;
1553 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1554 {
1555 removeMasks( context, true );
1556 return;
1557 }
1558
1559 const double prevOpacity = subSymbol()->opacity();
1560 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1561
1562 // render final point
1563 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1564 mFeatureSymbolOpacity = 1;
1565 subSymbol()->setOpacity( prevOpacity );
1566
1567 removeMasks( context, true );
1568}
1569
1571{
1572 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1573 destLayer->setOffset( mOffset );
1574 destLayer->setPlacements( placements() );
1575 destLayer->setOffsetUnit( mOffsetUnit );
1577 destLayer->setIntervalUnit( intervalUnit() );
1579 destLayer->setOffsetAlongLine( offsetAlongLine() );
1582 destLayer->setAverageAngleLength( mAverageAngleLength );
1583 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1584 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1585 destLayer->setBlankSegmentsUnit( mBlankSegmentsUnit );
1586 destLayer->setRingFilter( mRingFilter );
1587 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1588 destLayer->setTrimDistanceStart( mTrimDistanceStart );
1589 destLayer->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
1590 destLayer->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
1591 destLayer->setTrimDistanceEnd( mTrimDistanceEnd );
1592 destLayer->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
1593 destLayer->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
1594
1595 copyCommonProperties( destLayer );
1596}
1597
1599{
1600 if ( properties.contains( u"offset"_s ) )
1601 {
1602 destLayer->setOffset( properties[u"offset"_s].toDouble() );
1603 }
1604 if ( properties.contains( u"offset_unit"_s ) )
1605 {
1606 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
1607 }
1608 if ( properties.contains( u"interval_unit"_s ) )
1609 {
1610 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[u"interval_unit"_s].toString() ) );
1611 }
1612 if ( properties.contains( u"offset_along_line"_s ) )
1613 {
1614 destLayer->setOffsetAlongLine( properties[u"offset_along_line"_s].toDouble() );
1615 }
1616 if ( properties.contains( u"offset_along_line_unit"_s ) )
1617 {
1618 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_along_line_unit"_s].toString() ) );
1619 }
1620 if ( properties.contains( ( u"offset_along_line_map_unit_scale"_s ) ) )
1621 {
1622 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_along_line_map_unit_scale"_s].toString() ) );
1623 }
1624
1625 if ( properties.contains( u"offset_map_unit_scale"_s ) )
1626 {
1627 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
1628 }
1629 if ( properties.contains( u"interval_map_unit_scale"_s ) )
1630 {
1631 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"interval_map_unit_scale"_s].toString() ) );
1632 }
1633
1634 if ( properties.contains( u"average_angle_length"_s ) )
1635 {
1636 destLayer->setAverageAngleLength( properties[u"average_angle_length"_s].toDouble() );
1637 }
1638 if ( properties.contains( u"average_angle_unit"_s ) )
1639 {
1640 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[u"average_angle_unit"_s].toString() ) );
1641 }
1642 if ( properties.contains( ( u"average_angle_map_unit_scale"_s ) ) )
1643 {
1644 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"average_angle_map_unit_scale"_s].toString() ) );
1645 }
1646 if ( properties.contains( u"blank_segments_unit"_s ) )
1647 {
1648 destLayer->setBlankSegmentsUnit( QgsUnitTypes::decodeRenderUnit( properties[u"blank_segments_unit"_s].toString() ) );
1649 }
1650
1651 if ( properties.contains( u"trim_distance_start"_s ) )
1652 destLayer->setTrimDistanceStart( properties[u"trim_distance_start"_s].toDouble() );
1653 if ( properties.contains( u"trim_distance_start_unit"_s ) )
1654 destLayer->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( properties[u"trim_distance_start_unit"_s].toString() ) );
1655 if ( properties.contains( u"trim_distance_start_map_unit_scale"_s ) )
1656 destLayer->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"trim_distance_start_map_unit_scale"_s].toString() ) );
1657 if ( properties.contains( u"trim_distance_end"_s ) )
1658 destLayer->setTrimDistanceEnd( properties[u"trim_distance_end"_s].toDouble() );
1659 if ( properties.contains( u"trim_distance_end_unit"_s ) )
1660 destLayer->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( properties[u"trim_distance_end_unit"_s].toString() ) );
1661 if ( properties.contains( u"trim_distance_end_map_unit_scale"_s ) )
1662 destLayer->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"trim_distance_end_map_unit_scale"_s].toString() ) );
1663
1664 if ( properties.contains( u"placement"_s ) )
1665 {
1666 if ( properties[u"placement"_s] == "vertex"_L1 )
1668 else if ( properties[u"placement"_s] == "lastvertex"_L1 )
1670 else if ( properties[u"placement"_s] == "firstvertex"_L1 )
1672 else if ( properties[u"placement"_s] == "centralpoint"_L1 )
1674 else if ( properties[u"placement"_s] == "curvepoint"_L1 )
1676 else if ( properties[u"placement"_s] == "segmentcenter"_L1 )
1678 else
1680 }
1681 else if ( properties.contains( u"placements"_s ) )
1682 {
1684 destLayer->setPlacements( placements );
1685 }
1686
1687 if ( properties.contains( u"ring_filter"_s ) )
1688 {
1689 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[u"ring_filter"_s].toInt() ) );
1690 }
1691
1692 destLayer->setPlaceOnEveryPart( properties.value( u"place_on_every_part"_s, true ).toBool() );
1693
1695}
1696
1698
1703class BlankSegmentsWalker
1704{
1705 public:
1706 BlankSegmentsWalker( const QPolygonF &points, const QgsSymbolLayerUtils::BlankSegments &blankSegments )
1707 : mBlankSegments( blankSegments )
1708 , mPoints( points )
1709 , mItBlankSegment( blankSegments.cbegin() )
1710 {
1711 mDistances.reserve( mPoints.count() );
1712 mDistances << 0; // first point is start, so distance is 0
1713 }
1714
1715 bool insideBlankSegment( double distance )
1716 {
1717 while ( mItBlankSegment != mBlankSegments.cend() && distance > mItBlankSegment->second )
1718 {
1719 ++mItBlankSegment;
1720 }
1721
1722 return ( mItBlankSegment != mBlankSegments.cend() && distance >= mItBlankSegment->first );
1723 }
1724
1725
1726 // pointIndex : index of the point before point
1727 bool insideBlankSegment( const QPointF &point, int pointIndex )
1728 {
1729 if ( pointIndex < 0 || pointIndex >= mPoints.count() )
1730 return false;
1731
1732 // compute distances and fill distances array
1733 if ( pointIndex >= mDistances.count() )
1734 {
1735 for ( int i = static_cast<int>( mDistances.count() ); i < pointIndex + 1; i++ )
1736 {
1737 const QPointF diff = mPoints.at( i ) - mPoints.at( i - 1 );
1738 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1739 const double totalDistance = distance + mDistances.last();
1740 mDistances << totalDistance;
1741 }
1742 }
1743
1744 const QPointF diff = mPoints.at( pointIndex ) - point;
1745 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1746 const double currentDistance = mDistances.at( pointIndex ) + distance;
1747
1748 return insideBlankSegment( currentDistance );
1749 }
1750
1751 private:
1752 const QgsSymbolLayerUtils::BlankSegments &mBlankSegments;
1753 const QPolygonF &mPoints;
1754 QList<double> mDistances;
1755 QgsSymbolLayerUtils::BlankSegments::const_iterator mItBlankSegment;
1756};
1757
1758
1760
1761
1762void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver, const QgsSymbolLayerUtils::BlankSegments &blankSegments )
1763{
1764 if ( points.isEmpty() )
1765 return;
1766
1767 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1768 double lengthLeft = 0; // how much is left until next marker
1769
1770 QgsRenderContext &rc = context.renderContext();
1771 double interval = mInterval;
1772
1773 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1774 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1775
1777 {
1778 context.setOriginalValueVariable( mInterval );
1780 }
1781 if ( interval <= 0 )
1782 {
1783 interval = 0.1;
1784 }
1785 double offsetAlongLine = mOffsetAlongLine;
1787 {
1788 context.setOriginalValueVariable( mOffsetAlongLine );
1790 }
1791
1792 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1794 {
1795 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1796 // and clamp it to a reasonable range. It's the best we can do in this situation!
1797 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1798 }
1799
1800 constexpr double EPSILON = 1e-5;
1801 if ( painterUnitInterval < EPSILON )
1802 return;
1803
1804 double painterUnitOffsetAlongLine = 0;
1805
1806 // only calculated if we need it!
1807 double totalLength = -1;
1808
1809 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1810 {
1811 switch ( offsetAlongLineUnit() )
1812 {
1821 break;
1823 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1824 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1825 break;
1826 }
1827
1828 if ( points.isClosed() )
1829 {
1830 if ( painterUnitOffsetAlongLine > 0 )
1831 {
1832 if ( totalLength < 0 )
1833 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1834 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1835 }
1836 else if ( painterUnitOffsetAlongLine < 0 )
1837 {
1838 if ( totalLength < 0 )
1839 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1840 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1841 }
1842 }
1843 }
1844
1846 {
1847 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1848 // and clamp it to a reasonable range. It's the best we can do in this situation!
1849 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1850 }
1851
1852 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1853
1854 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1855 {
1856 QVector< QPointF > angleStartPoints;
1857 QVector< QPointF > symbolPoints;
1858 QVector< QPointF > angleEndPoints;
1859
1860 // we collect 3 arrays of points. These correspond to
1861 // 1. the actual point at which to render the symbol
1862 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1863 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1864 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1865 // (or trace past the final point)
1866
1867 QList<int> pointIndices; // keep a track on original pointIndices so we can decide later whether or not symbol points belong to a blank segment
1868 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft, blankSegments.isEmpty() ? nullptr : &pointIndices );
1869
1870 if ( symbolPoints.empty() )
1871 {
1872 // no symbols to draw, shortcut out early
1873 return;
1874 }
1875
1876 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1877 {
1878 // avoid duplicate points at start and end of closed rings
1879 symbolPoints.pop_back();
1880 }
1881
1882 angleEndPoints.reserve( symbolPoints.size() );
1883 angleStartPoints.reserve( symbolPoints.size() );
1884 if ( averageOver <= painterUnitOffsetAlongLine )
1885 {
1886 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, nullptr, 0, symbolPoints.size() );
1887 }
1888 else
1889 {
1890 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, nullptr, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1891 }
1892 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, nullptr, 0, symbolPoints.size() );
1893
1894 int pointNum = 0;
1895 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
1896 for ( int i = 0; i < symbolPoints.size(); ++i )
1897 {
1898 if ( context.renderContext().renderingStopped() )
1899 break;
1900
1901 const QPointF pt = symbolPoints[i];
1902 if ( i < pointIndices.count() && blankSegmentsWalker.insideBlankSegment( pt, pointIndices.at( i ) ) )
1903 // skip the rendering
1904 continue;
1905
1906 const QPointF startPt = angleStartPoints[i];
1907 const QPointF endPt = angleEndPoints[i];
1908
1909 Line l( startPt, endPt );
1910 // rotate marker (if desired)
1911 if ( rotateSymbols() )
1912 {
1913 setSymbolLineAngle( l.angle() * 180 / M_PI );
1914 }
1915
1916 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1917 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1918 }
1919 }
1920 else
1921 {
1922 // not averaging line angle -- always use exact section angle
1923 int pointNum = 0;
1924 QPointF lastPt = points[0];
1925 BlankSegmentsWalker itBlankSegment( points, blankSegments );
1926 for ( int i = 1; i < points.count(); ++i )
1927 {
1928 if ( context.renderContext().renderingStopped() )
1929 break;
1930
1931 const QPointF &pt = points[i];
1932
1933 if ( lastPt == pt ) // must not be equal!
1934 continue;
1935
1936 // for each line, find out dx and dy, and length
1937 Line l( lastPt, pt );
1938 QPointF diff = l.diffForInterval( painterUnitInterval );
1939
1940 // if there's some length left from previous line
1941 // use only the rest for the first point in new line segment
1942 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1943 double c = 1 - lengthLeft / painterUnitInterval;
1944
1945 lengthLeft += l.length();
1946
1947 // rotate marker (if desired)
1948 if ( rotateSymbols() )
1949 {
1950 setSymbolLineAngle( l.angle() * 180 / M_PI );
1951 }
1952
1953 // while we're not at the end of line segment, draw!
1954 while ( lengthLeft > painterUnitInterval )
1955 {
1956 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1957 lastPt += c * diff;
1958 c = 1; // reset c (if wasn't 1 already)
1959
1960 // we draw
1961 if ( !itBlankSegment.insideBlankSegment( lastPt, i - 1 ) )
1962 {
1963 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1964 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1965 }
1966
1967 lengthLeft -= painterUnitInterval;
1968 }
1969
1970 lastPt = pt;
1971 }
1972 }
1973}
1974
1975static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1976{
1977 // calc average angle between the previous and next point
1978 double a1 = Line( prevPt, pt ).angle();
1979 double a2 = Line( pt, nextPt ).angle();
1980 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1981
1982 return std::atan2( unitY, unitX );
1983}
1984
1985void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex(
1986 const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsSymbolLayerUtils::BlankSegments &blankSegments
1987)
1988{
1989 if ( points.isEmpty() )
1990 return;
1991
1992 QgsRenderContext &rc = context.renderContext();
1993
1994 int i = -1, maxCount = 0;
1995 bool isRing = false;
1996
1997 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1998 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1999 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size(), true ) );
2000
2001 double offsetAlongLine = mOffsetAlongLine;
2003 {
2004 context.setOriginalValueVariable( mOffsetAlongLine );
2006 }
2007
2008 // only calculated if we need it!!
2009 double totalLength = -1;
2010 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
2011 {
2012 //scale offset along line
2013 switch ( offsetAlongLineUnit() )
2014 {
2023 break;
2025 totalLength = QgsSymbolLayerUtils::polylineLength( points );
2026 offsetAlongLine = offsetAlongLine / 100 * totalLength;
2027 break;
2028 }
2029 if ( points.isClosed() )
2030 {
2031 if ( offsetAlongLine > 0 )
2032 {
2033 if ( totalLength < 0 )
2034 totalLength = QgsSymbolLayerUtils::polylineLength( points );
2035 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
2036 }
2037 else if ( offsetAlongLine < 0 )
2038 {
2039 if ( totalLength < 0 )
2040 totalLength = QgsSymbolLayerUtils::polylineLength( points );
2041 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
2042 }
2043 }
2044 }
2045
2046 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2047 if ( qgsDoubleNear( offsetAlongLine, 0.0 )
2048 && context.renderContext().geometry()
2049 && context.renderContext().geometry()->hasCurvedSegments()
2051 {
2052 QgsCoordinateTransform ct = context.renderContext().coordinateTransform();
2053 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
2054
2055 QgsVertexId vId;
2056 QgsPoint vPoint;
2057 double x, y, z;
2058 QPointF mapPoint;
2059 int pointNum = 0;
2060 const int numPoints = context.renderContext().geometry()->nCoordinates();
2061 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
2062 {
2063 if ( context.renderContext().renderingStopped() )
2064 break;
2065
2066 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2067
2068 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
2069 continue;
2070
2071 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
2072 continue;
2073
2076 {
2077 //transform
2078 x = vPoint.x();
2079 y = vPoint.y();
2080 z = 0.0;
2081 if ( ct.isValid() )
2082 {
2083 ct.transformInPlace( x, y, z );
2084 }
2085 mapPoint.setX( x );
2086 mapPoint.setY( y );
2087 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
2088 if ( rotateSymbols() )
2089 {
2090 double angle = context.renderContext().geometry()->vertexAngle( vId );
2091 setSymbolLineAngle( angle * 180 / M_PI );
2092 }
2093 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
2094 }
2095 }
2096
2097 return;
2098 }
2099
2100 int pointNum = 0;
2101
2102 switch ( placement )
2103 {
2105 {
2106 i = 0;
2107 maxCount = 1;
2108 break;
2109 }
2110
2112 {
2113 i = points.count() - 1;
2114 pointNum = i;
2115 maxCount = points.count();
2116 break;
2117 }
2118
2120 {
2121 i = 1;
2122 pointNum = 1;
2123 maxCount = points.count() - 1;
2124 break;
2125 }
2126
2129 {
2131 maxCount = points.count();
2132 if ( points.first() == points.last() )
2133 isRing = true;
2134 break;
2135 }
2136
2140 {
2141 return;
2142 }
2143 }
2144
2146 {
2147 double distance;
2149 renderOffsetVertexAlongLine( points, i, distance, context, placement, blankSegments );
2150
2151 return;
2152 }
2153
2154 QPointF prevPoint;
2155 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2156 prevPoint = points.at( 0 );
2157
2158 QPointF symbolPoint;
2159 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2160 for ( ; i < maxCount; ++i )
2161 {
2162 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2163
2164 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2165 {
2166 continue; // don't draw the last marker - it has been drawn already
2167 }
2168
2170 {
2171 QPointF currentPoint = points.at( i );
2172 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ), 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2173 if ( rotateSymbols() )
2174 {
2175 double angle = std::atan2( currentPoint.y() - prevPoint.y(), currentPoint.x() - prevPoint.x() );
2176 setSymbolLineAngle( angle * 180 / M_PI );
2177 }
2178 prevPoint = currentPoint;
2179 }
2180 else
2181 {
2182 symbolPoint = points.at( i );
2183 // rotate marker (if desired)
2184 if ( rotateSymbols() )
2185 {
2186 double angle = markerAngle( points, isRing, i );
2187 setSymbolLineAngle( angle * 180 / M_PI );
2188 }
2189 }
2190
2191 mFinalVertex = symbolPoint;
2192 if ( ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature ) && !blankSegmentsWalker.insideBlankSegment( symbolPoint, i ) )
2193 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2194 }
2195}
2196
2197double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2198{
2199 double angle = 0;
2200 const QPointF &pt = points[vertex];
2201
2202 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2203 {
2204 int prevIndex = vertex - 1;
2205 int nextIndex = vertex + 1;
2206
2207 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2208 {
2209 prevIndex = points.count() - 2;
2210 nextIndex = 1;
2211 }
2212
2213 QPointF prevPoint, nextPoint;
2214 while ( prevIndex >= 0 )
2215 {
2216 prevPoint = points[prevIndex];
2217 if ( prevPoint != pt )
2218 {
2219 break;
2220 }
2221 --prevIndex;
2222 }
2223
2224 while ( nextIndex < points.count() )
2225 {
2226 nextPoint = points[nextIndex];
2227 if ( nextPoint != pt )
2228 {
2229 break;
2230 }
2231 ++nextIndex;
2232 }
2233
2234 if ( prevIndex >= 0 && nextIndex < points.count() )
2235 {
2236 angle = _averageAngle( prevPoint, pt, nextPoint );
2237 }
2238 }
2239 else //no ring and vertex is at start / at end
2240 {
2241 if ( vertex == 0 )
2242 {
2243 while ( vertex < points.size() - 1 )
2244 {
2245 const QPointF &nextPt = points[vertex + 1];
2246 if ( pt != nextPt )
2247 {
2248 angle = Line( pt, nextPt ).angle();
2249 return angle;
2250 }
2251 ++vertex;
2252 }
2253 }
2254 else
2255 {
2256 // use last segment's angle
2257 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2258 {
2259 const QPointF &prevPt = points[vertex - 1];
2260 if ( pt != prevPt )
2261 {
2262 angle = Line( prevPt, pt ).angle();
2263 return angle;
2264 }
2265 --vertex;
2266 }
2267 }
2268 }
2269 return angle;
2270}
2271
2272void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine(
2273 const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsSymbolLayerUtils::BlankSegments &blankSegments
2274)
2275{
2276 if ( points.isEmpty() )
2277 return;
2278
2279 QgsRenderContext &rc = context.renderContext();
2280 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2281 if ( qgsDoubleNear( distance, 0.0 ) )
2282 {
2283 // rotate marker (if desired)
2284 if ( rotateSymbols() )
2285 {
2286 bool isRing = false;
2287 if ( points.first() == points.last() )
2288 isRing = true;
2289 double angle = markerAngle( points, isRing, vertex );
2290 setSymbolLineAngle( angle * 180 / M_PI );
2291 }
2292 mFinalVertex = points[vertex];
2293 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2294 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2295 return;
2296 }
2297
2298 int pointIncrement = distance > 0 ? 1 : -1;
2299 QPointF previousPoint = points[vertex];
2300 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2301 int endPoint = distance > 0 ? points.count() - 1 : 0;
2302 double distanceLeft = std::fabs( distance );
2303 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2304
2305 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2306 {
2307 const QPointF &pt = points[i];
2308
2309 if ( previousPoint == pt ) // must not be equal!
2310 continue;
2311
2312 // create line segment
2313 Line l( previousPoint, pt );
2314
2315 if ( distanceLeft < l.length() )
2316 {
2317 //destination point is in current segment
2318 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2319 // rotate marker (if desired)
2320 if ( rotateSymbols() )
2321 {
2322 setSymbolLineAngle( l.angle() * 180 / M_PI );
2323 }
2324 mFinalVertex = markerPoint;
2325 if ( ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature ) && !blankSegmentsWalker.insideBlankSegment( markerPoint, i - 1 ) )
2326 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2327 return;
2328 }
2329
2330 distanceLeft -= l.length();
2331 previousPoint = pt;
2332 }
2333
2334 //didn't find point
2335}
2336
2337void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints(
2338 const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, QList<int> *pointIndices, double initialLag, int numberPointsRequired
2339)
2340{
2341 if ( p.empty() )
2342 return;
2343
2344 QVector< QPointF > points = p;
2345 const bool closedRing = points.first() == points.last();
2346
2347 double lengthLeft = initialOffset;
2348
2349 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2350 if ( initialLagLeft < 0 && closedRing )
2351 {
2352 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2353 QPointF lastPt = points.constLast();
2354 QVector< QPointF > pseudoPoints;
2355 for ( int i = points.count() - 2; i > 0; --i )
2356 {
2357 if ( initialLagLeft >= 0 )
2358 {
2359 break;
2360 }
2361
2362 const QPointF &pt = points[i];
2363
2364 if ( lastPt == pt ) // must not be equal!
2365 continue;
2366
2367 Line l( lastPt, pt );
2368 initialLagLeft += l.length();
2369 lastPt = pt;
2370
2371 pseudoPoints << pt;
2372 }
2373 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2374
2375 points = pseudoPoints;
2376 points.append( p );
2377 }
2378 else
2379 {
2380 while ( initialLagLeft < 0 )
2381 {
2382 dest << points.constFirst();
2383 initialLagLeft += intervalPainterUnits;
2384 }
2385 }
2386 if ( initialLag > 0 )
2387 {
2388 lengthLeft += intervalPainterUnits - initialLagLeft;
2389 }
2390
2391 QPointF lastPt = points[0];
2392 for ( int i = 1; i < points.count(); ++i )
2393 {
2394 const QPointF &pt = points[i];
2395
2396 if ( lastPt == pt ) // must not be equal!
2397 {
2398 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2399 {
2400 lastPt = points[0];
2401 i = 0;
2402 }
2403 continue;
2404 }
2405
2406 // for each line, find out dx and dy, and length
2407 Line l( lastPt, pt );
2408 QPointF diff = l.diffForInterval( intervalPainterUnits );
2409
2410 // if there's some length left from previous line
2411 // use only the rest for the first point in new line segment
2412 double c = 1 - lengthLeft / intervalPainterUnits;
2413
2414 lengthLeft += l.length();
2415
2416
2417 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2418 {
2419 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2420 lastPt += c * diff;
2421 lengthLeft -= intervalPainterUnits;
2422 dest << lastPt;
2423 if ( pointIndices )
2424 *pointIndices << i - 1;
2425 c = 1; // reset c (if wasn't 1 already)
2426
2427 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2428 break;
2429 }
2430 lastPt = pt;
2431
2432 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2433 break;
2434
2435 // if a closed ring, we keep looping around the ring until we hit the required number of points
2436 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2437 {
2438 lastPt = points[0];
2439 i = 0;
2440 }
2441 }
2442
2443 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2444 {
2445 // pad with repeating last point to match desired size
2446 while ( dest.size() < numberPointsRequired )
2447 dest << points.constLast();
2448 }
2449}
2450
2451void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver, const QgsSymbolLayerUtils::BlankSegments &blankSegments )
2452{
2453 if ( !points.isEmpty() )
2454 {
2455 // calc length
2456 qreal length = 0;
2457 QPolygonF::const_iterator it = points.constBegin();
2458 QPointF last = *it;
2459 for ( ++it; it != points.constEnd(); ++it )
2460 {
2461 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) + ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2462 last = *it;
2463 }
2464 if ( qgsDoubleNear( length, 0.0 ) )
2465 return;
2466
2467 const double midPoint = length / 2;
2468
2469 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2470 if ( blankSegmentsWalker.insideBlankSegment( midPoint ) )
2471 return;
2472
2473 QPointF pt;
2474 double thisSymbolAngle = 0;
2475
2476 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2477 {
2478 QVector< QPointF > angleStartPoints;
2479 QVector< QPointF > symbolPoints;
2480 QVector< QPointF > angleEndPoints;
2481
2482 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2483 // already dealt with blank segment before, no need to make them check again
2484 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, nullptr, 0.0, 2 );
2485 collectOffsetPoints( points, angleStartPoints, midPoint, 0, nullptr, averageAngleOver, 2 );
2486 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, nullptr, 0, 2 );
2487
2488 pt = symbolPoints.at( 1 );
2489 Line l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2490 thisSymbolAngle = l.angle();
2491 }
2492 else
2493 {
2494 // find the segment where the central point lies
2495 it = points.constBegin();
2496 last = *it;
2497 qreal last_at = 0, next_at = 0;
2498 QPointF next;
2499 for ( ++it; it != points.constEnd(); ++it )
2500 {
2501 next = *it;
2502 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) + ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2503 if ( next_at >= midPoint )
2504 break; // we have reached the center
2505 last = *it;
2506 last_at = next_at;
2507 }
2508
2509 // find out the central point on segment
2510 Line l( last, next ); // for line angle
2511 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2512 pt = last + ( next - last ) * k;
2513 thisSymbolAngle = l.angle();
2514 }
2515
2516 // draw the marker
2517 // rotate marker (if desired)
2518 if ( rotateSymbols() )
2519 {
2520 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2521 }
2522
2523 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2524 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2525 }
2526}
2527
2532
2534{
2535 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2536 {
2537 delete symbol;
2538 return false;
2539 }
2540
2541 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2542 mColor = mMarker->color();
2543 return true;
2544}
2545
2546
2547//
2548// QgsMarkerLineSymbolLayer
2549//
2550
2556
2558
2560{
2561 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2563
2564 if ( props.contains( u"interval"_s ) )
2565 interval = props[u"interval"_s].toDouble();
2566 if ( props.contains( u"rotate"_s ) )
2567 rotate = ( props[u"rotate"_s].toString() == "1"_L1 );
2568
2569 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2570 setCommonProperties( x.get(), props );
2571 return x.release();
2572}
2573
2575{
2576 return u"MarkerLine"_s;
2577}
2578
2580{
2581 mMarker->setColor( color );
2582 mColor = color;
2583}
2584
2586{
2587 return mMarker ? mMarker->color() : mColor;
2588}
2589
2591{
2592 // if being rotated, it gets initialized with every line segment
2594 if ( rotateSymbols() )
2596 mMarker->setRenderHints( hints );
2597
2598 mMarker->startRender( context.renderContext(), context.fields() );
2599}
2600
2602{
2603 mMarker->stopRender( context.renderContext() );
2604}
2605
2606
2608{
2609 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2611 return x.release();
2612}
2613
2614void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2615{
2616 QgsSldExportContext context;
2617 context.setExtraProperties( props );
2618 toSld( doc, element, context );
2619}
2620
2621bool QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
2622{
2623 const QVariantMap props = context.extraProperties();
2624 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2625 {
2626 QDomElement symbolizerElem = doc.createElement( u"se:LineSymbolizer"_s );
2627 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
2628 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
2629 element.appendChild( symbolizerElem );
2630
2631 // <Geometry>
2632 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
2633
2634 QString gap;
2636 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"firstPoint"_s ) );
2638 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"lastPoint"_s ) );
2640 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"centralPoint"_s ) );
2642 // no way to get line/polygon's vertices, use a VendorOption
2643 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"points"_s ) );
2644
2646 {
2648 gap = qgsDoubleToString( interval );
2649 }
2650
2651 if ( !rotateSymbols() )
2652 {
2653 // markers in LineSymbolizer must be drawn following the line orientation,
2654 // use a VendorOption when no marker rotation
2655 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"rotateMarker"_s, u"0"_s ) );
2656 }
2657
2658 // <Stroke>
2659 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
2660 symbolizerElem.appendChild( strokeElem );
2661
2662 // <GraphicStroke>
2663 QDomElement graphicStrokeElem = doc.createElement( u"se:GraphicStroke"_s );
2664 strokeElem.appendChild( graphicStrokeElem );
2665
2666 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2667 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2668 {
2669 markerLayer->writeSldMarker( doc, graphicStrokeElem, context );
2670 }
2671 else if ( layer )
2672 {
2673 QgsDebugError( u"QgsMarkerSymbolLayer expected, %1 found. Skip it."_s.arg( layer->layerType() ) );
2674 }
2675 else
2676 {
2677 QgsDebugError( u"Missing marker line symbol layer. Skip it."_s );
2678 }
2679
2680 if ( !gap.isEmpty() )
2681 {
2682 QDomElement gapElem = doc.createElement( u"se:Gap"_s );
2683 QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap, context );
2684 graphicStrokeElem.appendChild( gapElem );
2685 }
2686
2687 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2688 {
2689 QDomElement perpOffsetElem = doc.createElement( u"se:PerpendicularOffset"_s );
2691 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2692 symbolizerElem.appendChild( perpOffsetElem );
2693 }
2694 }
2695 return true;
2696}
2697
2699{
2700 QgsDebugMsgLevel( u"Entered."_s, 4 );
2701
2702 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2703 if ( strokeElem.isNull() )
2704 return nullptr;
2705
2706 QDomElement graphicStrokeElem = strokeElem.firstChildElement( u"GraphicStroke"_s );
2707 if ( graphicStrokeElem.isNull() )
2708 return nullptr;
2709
2710 // retrieve vendor options
2711 bool rotateMarker = true;
2713
2714 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2715 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2716 {
2717 if ( it.key() == "placement"_L1 )
2718 {
2719 if ( it.value() == "points"_L1 )
2721 else if ( it.value() == "firstPoint"_L1 )
2723 else if ( it.value() == "lastPoint"_L1 )
2725 else if ( it.value() == "centralPoint"_L1 )
2727 }
2728 else if ( it.value() == "rotateMarker"_L1 )
2729 {
2730 rotateMarker = it.value() == "0"_L1;
2731 }
2732 }
2733
2734 std::unique_ptr< QgsMarkerSymbol > marker;
2735
2736 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicStrokeElem );
2737 if ( l )
2738 {
2739 QgsSymbolLayerList layers;
2740 layers.append( l.release() );
2741 marker = std::make_unique<QgsMarkerSymbol>( layers );
2742 }
2743
2744 if ( !marker )
2745 return nullptr;
2746
2747 double interval = 0.0;
2748 QDomElement gapElem = graphicStrokeElem.firstChildElement( u"Gap"_s );
2749 if ( !gapElem.isNull() )
2750 {
2751 bool ok;
2752 double d = gapElem.firstChild().firstChild().nodeValue().toDouble( &ok );
2753 if ( ok )
2754 interval = d;
2755 }
2756
2757 double offset = 0.0;
2758 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( u"PerpendicularOffset"_s );
2759 if ( !perpOffsetElem.isNull() )
2760 {
2761 bool ok;
2762 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2763 if ( ok )
2764 offset = d;
2765 }
2766
2767 double scaleFactor = 1.0;
2768 const QString uom = element.attribute( u"uom"_s );
2769 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2770 interval = interval * scaleFactor;
2771 offset = offset * scaleFactor;
2772
2774 x->setOutputUnit( sldUnitSize );
2776 x->setInterval( interval );
2777 x->setSubSymbol( marker.release() );
2778 x->setOffset( offset );
2779 return x;
2780}
2781
2783{
2784 mMarker->setSize( width );
2785}
2786
2788{
2789 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2790 {
2791 mMarker->setDataDefinedSize( property );
2792 }
2794}
2795
2797{
2798 const double prevOpacity = mMarker->opacity();
2799 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2801 mMarker->setOpacity( prevOpacity );
2802}
2803
2805{
2806 mMarker->setLineAngle( angle );
2807}
2808
2810{
2811 return mMarker->angle();
2812}
2813
2815{
2816 mMarker->setAngle( angle );
2817}
2818
2819void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2820{
2821 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2823
2824 mMarker->renderPoint( point, feature, context, layer, selected );
2825
2826 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2827}
2828
2830{
2831 return mMarker->size();
2832}
2833
2835{
2836 return mMarker->size( context );
2837}
2838
2844
2860
2862{
2863 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2864 if ( mMarker )
2865 attr.unite( mMarker->usedAttributes( context ) );
2866 return attr;
2867}
2868
2870{
2872 return true;
2873 if ( mMarker && mMarker->hasDataDefinedProperties() )
2874 return true;
2875 return false;
2876}
2877
2879{
2880 return ( mMarker->size( context ) / 2.0 ) + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
2881}
2882
2883
2884//
2885// QgsHashedLineSymbolLayer
2886//
2887
2889 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2890{
2891 setSubSymbol( new QgsLineSymbol() );
2892}
2893
2895
2897{
2898 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2900
2901 if ( props.contains( u"interval"_s ) )
2902 interval = props[u"interval"_s].toDouble();
2903 if ( props.contains( u"rotate"_s ) )
2904 rotate = ( props[u"rotate"_s] == "1"_L1 );
2905
2906 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2907 setCommonProperties( x.get(), props );
2908 if ( props.contains( u"hash_angle"_s ) )
2909 {
2910 x->setHashAngle( props[u"hash_angle"_s].toDouble() );
2911 }
2912
2913 if ( props.contains( u"hash_length"_s ) )
2914 x->setHashLength( props[u"hash_length"_s].toDouble() );
2915
2916 if ( props.contains( u"hash_length_unit"_s ) )
2917 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[u"hash_length_unit"_s].toString() ) );
2918
2919 if ( props.contains( u"hash_length_map_unit_scale"_s ) )
2920 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"hash_length_map_unit_scale"_s].toString() ) );
2921
2922 return x.release();
2923}
2924
2926{
2927 return u"HashLine"_s;
2928}
2929
2931{
2932 // if being rotated, it gets initialized with every line segment
2934 if ( rotateSymbols() )
2936 mHashSymbol->setRenderHints( hints );
2937
2938 mHashSymbol->startRender( context.renderContext(), context.fields() );
2939}
2940
2942{
2943 mHashSymbol->stopRender( context.renderContext() );
2944}
2945
2947{
2949 map[u"hash_angle"_s] = QString::number( mHashAngle );
2950
2951 map[u"hash_length"_s] = QString::number( mHashLength );
2952 map[u"hash_length_unit"_s] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2953 map[u"hash_length_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2954
2955 return map;
2956}
2957
2959{
2960 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2962 x->setHashAngle( mHashAngle );
2963 x->setHashLength( mHashLength );
2964 x->setHashLengthUnit( mHashLengthUnit );
2965 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2966 return x.release();
2967}
2968
2970{
2971 mHashSymbol->setColor( color );
2972 mColor = color;
2973}
2974
2976{
2977 return mHashSymbol ? mHashSymbol->color() : mColor;
2978}
2979
2981{
2982 return mHashSymbol.get();
2983}
2984
2986{
2987 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2988 {
2989 delete symbol;
2990 return false;
2991 }
2992
2993 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2994 mColor = mHashSymbol->color();
2995 return true;
2996}
2997
2999{
3000 mHashLength = width;
3001}
3002
3004{
3005 return mHashLength;
3006}
3007
3009{
3010 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
3011}
3012
3014{
3015 return ( mHashSymbol->width( context ) / 2.0 )
3016 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
3018}
3019
3021{
3023 mHashSymbol->setOutputUnit( unit );
3024}
3025
3027{
3028 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
3029 if ( mHashSymbol )
3030 attr.unite( mHashSymbol->usedAttributes( context ) );
3031 return attr;
3032}
3033
3035{
3037 return true;
3038 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
3039 return true;
3040 return false;
3041}
3042
3044{
3045 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
3046 {
3047 mHashSymbol->setDataDefinedWidth( property );
3048 }
3050}
3051
3069
3071{
3072 mSymbolLineAngle = angle;
3073}
3074
3076{
3077 return mSymbolAngle;
3078}
3079
3081{
3082 mSymbolAngle = angle;
3083}
3084
3085void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
3086{
3087 double lineLength = mHashLength;
3089 {
3090 context.expressionContext().setOriginalValueVariable( mHashLength );
3091 lineLength = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::LineDistance, context.expressionContext(), lineLength );
3092 }
3093 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
3094
3095 double hashAngle = mHashAngle;
3097 {
3098 context.expressionContext().setOriginalValueVariable( mHashAngle );
3100 }
3101
3102 QgsPointXY center( point );
3103 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3104 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3105
3106 QPolygonF points;
3107 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
3108
3109 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3111
3112 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
3113
3114 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
3115}
3116
3118{
3119 return mHashAngle;
3120}
3121
3123{
3124 mHashAngle = angle;
3125}
3126
3128{
3129 const double prevOpacity = mHashSymbol->opacity();
3130 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
3132 mHashSymbol->setOpacity( prevOpacity );
3133}
3134
3135//
3136// QgsAbstractBrushedLineSymbolLayer
3137//
3138
3139void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
3140{
3141 if ( !context.renderContext().painter() )
3142 return;
3143
3144 double offset = mOffset;
3146 {
3149 }
3150
3151 QPolygonF offsetPoints;
3152 if ( qgsDoubleNear( offset, 0 ) )
3153 {
3154 renderLine( points, context, patternThickness, patternLength, brush );
3155 }
3156 else
3157 {
3158 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3159
3160 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3161 for ( const QPolygonF &part : offsetLine )
3162 {
3163 renderLine( part, context, patternThickness, patternLength, brush );
3164 }
3165 }
3166}
3167
3168void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness, const double patternLength, const QBrush &sourceBrush )
3169{
3170 QPainter *p = context.renderContext().painter();
3171 if ( !p )
3172 return;
3173
3174 QBrush brush = sourceBrush;
3175
3176 // duplicate points mess up the calculations, we need to remove them first
3177 // we'll calculate the min/max coordinate at the same time, since we're already looping
3178 // through the points
3179 QPolygonF inputPoints;
3180 inputPoints.reserve( points.size() );
3181 QPointF prev;
3182 double minX = std::numeric_limits< double >::max();
3183 double minY = std::numeric_limits< double >::max();
3184 double maxX = std::numeric_limits< double >::lowest();
3185 double maxY = std::numeric_limits< double >::lowest();
3186
3187 for ( const QPointF &pt : std::as_const( points ) )
3188 {
3189 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3190 continue;
3191
3192 inputPoints << pt;
3193 prev = pt;
3194 minX = std::min( minX, pt.x() );
3195 minY = std::min( minY, pt.y() );
3196 maxX = std::max( maxX, pt.x() );
3197 maxY = std::max( maxY, pt.y() );
3198 }
3199
3200 if ( inputPoints.size() < 2 ) // nothing to render
3201 return;
3202
3203 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3204 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3205 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3206 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3207
3208 // 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
3209 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3210 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3211
3212 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 ) && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3213
3214 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3215 if ( temporaryImage.isNull() )
3216 {
3217 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3218 return;
3219 }
3220
3221 // clear temporary image contents
3222 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3223 return;
3224 temporaryImage.fill( Qt::transparent );
3225 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3226 return;
3227
3228 Qt::PenJoinStyle join = mPenJoinStyle;
3230 {
3233 if ( !QgsVariantUtils::isNull( exprVal ) )
3234 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3235 }
3236
3237 Qt::PenCapStyle cap = mPenCapStyle;
3239 {
3242 if ( !QgsVariantUtils::isNull( exprVal ) )
3243 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3244 }
3245
3246 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3247 QPainterPathStroker stroker;
3248 stroker.setWidth( lineThickness );
3249 stroker.setCapStyle( cap );
3250 stroker.setJoinStyle( join );
3251
3252 QPainterPath path;
3253 path.addPolygon( inputPoints );
3254 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3255
3256 // prepare temporary image
3257 QPainter imagePainter;
3258 imagePainter.begin( &temporaryImage );
3259 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3260 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3261
3262 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3263 imagePainter.setPen( Qt::NoPen );
3264
3265 QPointF segmentStartPoint = inputPoints.at( 0 );
3266
3267 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3268 double progressThroughImage = 0;
3269
3270 QgsPoint prevSegmentPolygonEndLeft;
3271 QgsPoint prevSegmentPolygonEndRight;
3272
3273 // for closed rings this will store the left/right polygon points of the start/end of the line
3274 QgsPoint startLinePolygonLeft;
3275 QgsPoint startLinePolygonRight;
3276
3277 for ( int i = 1; i < inputPoints.size(); ++i )
3278 {
3279 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3280 break;
3281
3282 const QPointF segmentEndPoint = inputPoints.at( i );
3283 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(), segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3284
3285 // left/right end points of the current segment polygon
3286 QgsPoint thisSegmentPolygonEndLeft;
3287 QgsPoint thisSegmentPolygonEndRight;
3288 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3289 QgsPoint thisSegmentPolygonEndLeftForPainter;
3290 QgsPoint thisSegmentPolygonEndRightForPainter;
3291 if ( i == 1 )
3292 {
3293 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3294 // (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.)
3295 if ( isClosedLine )
3296 {
3297 // project the current segment out by half the image thickness to either side of the line
3298 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3299 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3300 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3301 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3302
3303 // 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
3304 // what angle the current segment polygon should START on.
3305 const double lastSegmentAngleDegrees = 180.0
3306 / M_PI
3307 * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(), segmentStartPoint.x(), segmentStartPoint.y() )
3308 - 90;
3309
3310 // project out the LAST segment in the line by half the image thickness to either side of the line
3311 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3312 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3313 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3314 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3315
3316 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3317 // 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
3318 // join)
3319 QgsPoint intersectionPoint;
3320 bool isIntersection = false;
3321 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3322 if ( !isIntersection )
3323 prevSegmentPolygonEndLeft = startPointLeft;
3324 isIntersection = false;
3325 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3326 if ( !isIntersection )
3327 prevSegmentPolygonEndRight = startPointRight;
3328
3329 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3330 startLinePolygonRight = prevSegmentPolygonEndRight;
3331 }
3332 else
3333 {
3334 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3335 if ( cap != Qt::PenCapStyle::FlatCap )
3336 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3337 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3338 if ( cap != Qt::PenCapStyle::FlatCap )
3339 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3340 }
3341 }
3342
3343 if ( i < inputPoints.size() - 1 )
3344 {
3345 // for all other segments except the last
3346
3347 // project the current segment out by half the image thickness to either side of the line
3348 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3349 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3350 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3351 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3352
3353 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3354 // what angle the current segment polygon should end on
3355 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(), inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3356
3357 // project out the next segment by half the image thickness to either side of the line
3358 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3359 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3360 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3361 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3362
3363 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3364 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3365 // join)
3366 QgsPoint intersectionPoint;
3367 bool isIntersection = false;
3368 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3369 if ( !isIntersection )
3370 thisSegmentPolygonEndLeft = endPointLeft;
3371 isIntersection = false;
3372 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3373 if ( !isIntersection )
3374 thisSegmentPolygonEndRight = endPointRight;
3375
3376 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3377 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3378 }
3379 else
3380 {
3381 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3382 // unless it's a closed line
3383 if ( isClosedLine )
3384 {
3385 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3386 thisSegmentPolygonEndRight = startLinePolygonRight;
3387
3388 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3389 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3390 }
3391 else
3392 {
3393 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3394 if ( cap != Qt::PenCapStyle::FlatCap )
3395 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3396 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3397 if ( cap != Qt::PenCapStyle::FlatCap )
3398 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3399
3400 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3401 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3402 }
3403 }
3404
3405 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3406 // where we got with the previous segment), at the correct angle
3407 QTransform brushTransform;
3408 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3409 brushTransform.rotate( -segmentAngleDegrees );
3410 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3411 {
3412 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3413 // we need to also do the same for the brush transform
3414 brushTransform.translate( -( lineThickness / 2 ), 0 );
3415 }
3416 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3417
3418 brush.setTransform( brushTransform );
3419 imagePainter.setBrush( brush );
3420
3421 // now draw the segment polygon
3422 imagePainter.drawPolygon(
3423 QPolygonF()
3424 << prevSegmentPolygonEndLeft.toQPointF()
3425 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3426 << thisSegmentPolygonEndRightForPainter.toQPointF()
3427 << prevSegmentPolygonEndRight.toQPointF()
3428 << prevSegmentPolygonEndLeft.toQPointF()
3429 );
3430
3431#if 0 // for debugging, will draw the segment polygons
3432 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3433 imagePainter.setBrush( Qt::NoBrush );
3434 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3435 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3436 << thisSegmentPolygonEndRightForPainter.toQPointF()
3437 << prevSegmentPolygonEndRight.toQPointF()
3438 << prevSegmentPolygonEndLeft.toQPointF() );
3439 imagePainter.setPen( Qt::NoPen );
3440#endif
3441
3442 // calculate the new progress horizontal through the source image to account for the length
3443 // of the segment we've just drawn
3444 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 ) + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3445 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3446 progressThroughImage = fmod( progressThroughImage, patternLength );
3447
3448 // shuffle buffered variables for next loop
3449 segmentStartPoint = segmentEndPoint;
3450 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3451 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3452 }
3453 imagePainter.end();
3454
3455 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3456 return;
3457
3458 // lastly, draw the temporary image onto the destination painter at the correct place
3459 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS, minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3460}
3461
3462
3463//
3464// QgsRasterLineSymbolLayer
3465//
3466
3470
3472
3474{
3475 auto res = std::make_unique<QgsRasterLineSymbolLayer>();
3476
3477 if ( properties.contains( u"line_width"_s ) )
3478 {
3479 res->setWidth( properties[u"line_width"_s].toDouble() );
3480 }
3481 if ( properties.contains( u"line_width_unit"_s ) )
3482 {
3483 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
3484 }
3485 if ( properties.contains( u"width_map_unit_scale"_s ) )
3486 {
3487 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
3488 }
3489
3490 if ( properties.contains( u"imageFile"_s ) )
3491 res->setPath( properties[u"imageFile"_s].toString() );
3492
3493 if ( properties.contains( u"offset"_s ) )
3494 {
3495 res->setOffset( properties[u"offset"_s].toDouble() );
3496 }
3497 if ( properties.contains( u"offset_unit"_s ) )
3498 {
3499 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
3500 }
3501 if ( properties.contains( u"offset_map_unit_scale"_s ) )
3502 {
3503 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
3504 }
3505
3506 if ( properties.contains( u"joinstyle"_s ) )
3507 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[u"joinstyle"_s].toString() ) );
3508 if ( properties.contains( u"capstyle"_s ) )
3509 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[u"capstyle"_s].toString() ) );
3510
3511 if ( properties.contains( u"alpha"_s ) )
3512 {
3513 res->setOpacity( properties[u"alpha"_s].toDouble() );
3514 }
3515
3516 return res.release();
3517}
3518
3519
3521{
3522 QVariantMap map;
3523 map[u"imageFile"_s] = mPath;
3524
3525 map[u"line_width"_s] = QString::number( mWidth );
3526 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
3527 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3528
3531
3532 map[u"offset"_s] = QString::number( mOffset );
3533 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3534 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3535
3536 map[u"alpha"_s] = QString::number( mOpacity );
3537
3538 return map;
3539}
3540
3542{
3543 auto res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3544 res->setWidth( mWidth );
3545 res->setWidthUnit( mWidthUnit );
3546 res->setWidthMapUnitScale( mWidthMapUnitScale );
3547 res->setPenJoinStyle( mPenJoinStyle );
3548 res->setPenCapStyle( mPenCapStyle );
3549 res->setOffsetUnit( mOffsetUnit );
3550 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3551 res->setOffset( mOffset );
3552 res->setOpacity( mOpacity );
3553 copyCommonProperties( res.get() );
3554 return res.release();
3555}
3556
3557void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3558{
3559 const QVariantMap::iterator it = properties.find( u"imageFile"_s );
3560 if ( it != properties.end() && it.value().userType() == QMetaType::Type::QString )
3561 {
3562 if ( saving )
3563 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3564 else
3565 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3566 }
3567}
3568
3570{
3571 mPath = path;
3572}
3573
3575{
3576 return u"RasterLine"_s;
3577}
3578
3583
3585{
3586 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3587
3589
3590 double opacity = mOpacity * context.opacity();
3591 bool cached = false;
3593 mPath,
3594 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ), static_cast< int >( std::ceil( scaledHeight ) ) ),
3595 true,
3596 opacity,
3597 cached,
3599 );
3600}
3601
3604
3606{
3607 if ( !context.renderContext().painter() )
3608 return;
3609
3610 QImage sourceImage = mLineImage;
3614 {
3615 QString path = mPath;
3617 {
3618 context.setOriginalValueVariable( path );
3620 }
3621
3622 double strokeWidth = mWidth;
3624 {
3625 context.setOriginalValueVariable( strokeWidth );
3626 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3627 }
3628 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3629
3631 double opacity = mOpacity;
3633 {
3636 }
3637 opacity *= context.opacity();
3638
3639 bool cached = false;
3640 sourceImage = QgsApplication::imageCache()->pathAsImage(
3641 path,
3642 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ), static_cast< int >( std::ceil( scaledHeight ) ) ),
3643 true,
3644 opacity,
3645 cached,
3647 );
3648 }
3649
3650 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3651 if ( useSelectedColor )
3652 {
3654 }
3655
3656 const QBrush brush( sourceImage );
3657
3658 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3659}
3660
3667
3669{
3671 if ( mWidthUnit != unit || mOffsetUnit != unit )
3672 {
3674 }
3675 return unit;
3676}
3677
3682
3688
3697
3699{
3700 return ( mWidth / 2.0 ) + mOffset;
3701}
3702
3704{
3705 return QColor();
3706}
3707
3708
3709//
3710// QgsLineburstSymbolLayer
3711//
3712
3719
3721
3723{
3724 auto res = std::make_unique<QgsLineburstSymbolLayer>();
3725
3726 if ( properties.contains( u"line_width"_s ) )
3727 {
3728 res->setWidth( properties[u"line_width"_s].toDouble() );
3729 }
3730 if ( properties.contains( u"line_width_unit"_s ) )
3731 {
3732 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
3733 }
3734 if ( properties.contains( u"width_map_unit_scale"_s ) )
3735 {
3736 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
3737 }
3738
3739 if ( properties.contains( u"offset"_s ) )
3740 {
3741 res->setOffset( properties[u"offset"_s].toDouble() );
3742 }
3743 if ( properties.contains( u"offset_unit"_s ) )
3744 {
3745 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
3746 }
3747 if ( properties.contains( u"offset_map_unit_scale"_s ) )
3748 {
3749 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
3750 }
3751
3752 if ( properties.contains( u"joinstyle"_s ) )
3753 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[u"joinstyle"_s].toString() ) );
3754 if ( properties.contains( u"capstyle"_s ) )
3755 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[u"capstyle"_s].toString() ) );
3756
3757 if ( properties.contains( u"color_type"_s ) )
3758 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[u"color_type"_s].toInt() ) );
3759
3760 if ( properties.contains( u"color"_s ) )
3761 {
3762 res->setColor( QgsColorUtils::colorFromString( properties[u"color"_s].toString() ) );
3763 }
3764 if ( properties.contains( u"gradient_color2"_s ) )
3765 {
3766 res->setColor2( QgsColorUtils::colorFromString( properties[u"gradient_color2"_s].toString() ) );
3767 }
3768
3769 //attempt to create color ramp from props
3770 if ( properties.contains( u"rampType"_s ) && properties[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
3771 {
3772 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3773 }
3774 else
3775 {
3776 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3777 }
3778
3779 return res.release();
3780}
3781
3783{
3784 QVariantMap map;
3785
3786 map[u"line_width"_s] = QString::number( mWidth );
3787 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
3788 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3789
3792
3793 map[u"offset"_s] = QString::number( mOffset );
3794 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3795 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3796
3797 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
3798 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
3799 map[u"color_type"_s] = QString::number( static_cast< int >( mGradientColorType ) );
3800 if ( mGradientRamp )
3801 {
3802 map.insert( mGradientRamp->properties() );
3803 }
3804
3805 return map;
3806}
3807
3809{
3810 auto res = std::make_unique< QgsLineburstSymbolLayer >();
3811 res->setWidth( mWidth );
3812 res->setWidthUnit( mWidthUnit );
3813 res->setWidthMapUnitScale( mWidthMapUnitScale );
3814 res->setPenJoinStyle( mPenJoinStyle );
3815 res->setPenCapStyle( mPenCapStyle );
3816 res->setOffsetUnit( mOffsetUnit );
3817 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3818 res->setOffset( mOffset );
3819 res->setColor( mColor );
3820 res->setColor2( mColor2 );
3821 res->setGradientColorType( mGradientColorType );
3822 if ( mGradientRamp )
3823 res->setColorRamp( mGradientRamp->clone() );
3824 copyCommonProperties( res.get() );
3825 return res.release();
3826}
3827
3829{
3830 return u"Lineburst"_s;
3831}
3832
3837
3840
3843
3845{
3846 if ( !context.renderContext().painter() )
3847 return;
3848
3849 double strokeWidth = mWidth;
3851 {
3852 context.setOriginalValueVariable( strokeWidth );
3853 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3854 }
3855 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3856
3857 //update alpha of gradient colors
3858 QColor color1 = mColor;
3860 {
3863 }
3864 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3865 if ( useSelectedColor )
3866 {
3867 color1 = context.renderContext().selectionColor();
3868 }
3869 color1.setAlphaF( context.opacity() * color1.alphaF() );
3870
3871 //second gradient color
3872 QColor color2 = mColor2;
3874 {
3877 }
3878
3879 //create a QGradient with the desired properties
3880 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3881 //add stops to gradient
3883 && mGradientRamp
3885 {
3886 //color ramp gradient
3887 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3888 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3889 }
3890 else
3891 {
3892 //two color gradient
3893 gradient.setColorAt( 0.0, color1 );
3894 gradient.setColorAt( 1.0, color2 );
3895 }
3896 const QBrush brush( gradient );
3897
3898 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3899}
3900
3907
3909{
3911 if ( mWidthUnit != unit || mOffsetUnit != unit )
3912 {
3914 }
3915 return unit;
3916}
3917
3922
3928
3937
3939{
3940 return ( mWidth / 2.0 ) + mOffset;
3941}
3942
3947
3949{
3950 mGradientRamp.reset( ramp );
3951}
3952
3953//
3954// QgsFilledLineSymbolLayer
3955//
3956
3959{
3960 mWidth = width;
3961 mFill = fillSymbol ? std::unique_ptr< QgsFillSymbol >( fillSymbol ) : QgsFillSymbol::createSimple( QVariantMap() );
3962}
3963
3965
3967{
3969
3970 // throughout the history of QGIS and different layer types, we've used
3971 // a huge range of different strings for the same property. The logic here
3972 // is designed to be forgiving to this and accept a range of string keys:
3973 if ( props.contains( u"line_width"_s ) )
3974 {
3975 width = props[u"line_width"_s].toDouble();
3976 }
3977 else if ( props.contains( u"outline_width"_s ) )
3978 {
3979 width = props[u"outline_width"_s].toDouble();
3980 }
3981 else if ( props.contains( u"width"_s ) )
3982 {
3983 width = props[u"width"_s].toDouble();
3984 }
3985
3986 auto l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ).release() );
3987
3988 if ( props.contains( u"line_width_unit"_s ) )
3989 {
3990 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"line_width_unit"_s].toString() ) );
3991 }
3992 else if ( props.contains( u"outline_width_unit"_s ) )
3993 {
3994 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"outline_width_unit"_s].toString() ) );
3995 }
3996 else if ( props.contains( u"width_unit"_s ) )
3997 {
3998 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"width_unit"_s].toString() ) );
3999 }
4000
4001 if ( props.contains( u"width_map_unit_scale"_s ) )
4002 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"width_map_unit_scale"_s].toString() ) );
4003 if ( props.contains( u"offset"_s ) )
4004 l->setOffset( props[u"offset"_s].toDouble() );
4005 if ( props.contains( u"offset_unit"_s ) )
4006 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
4007 if ( props.contains( u"offset_map_unit_scale"_s ) )
4008 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
4009 if ( props.contains( u"joinstyle"_s ) )
4010 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[u"joinstyle"_s].toString() ) );
4011 if ( props.contains( u"capstyle"_s ) )
4012 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[u"capstyle"_s].toString() ) );
4013
4014 l->restoreOldDataDefinedProperties( props );
4015
4016 return l.release();
4017}
4018
4020{
4021 return u"FilledLine"_s;
4022}
4023
4025{
4026 if ( mFill )
4027 {
4028 mFill->setRenderHints( mFill->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
4029 mFill->startRender( context.renderContext(), context.fields() );
4030 }
4031}
4032
4034{
4035 if ( mFill )
4036 {
4037 mFill->stopRender( context.renderContext() );
4038 }
4039}
4040
4042{
4043 installMasks( context, true );
4044
4045 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4046}
4047
4049{
4050 removeMasks( context, true );
4051
4052 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4053}
4054
4056{
4057 QPainter *p = context.renderContext().painter();
4058 if ( !p || !mFill )
4059 return;
4060
4061 double width = mWidth;
4063 {
4066 }
4067
4068 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
4069
4070 Qt::PenJoinStyle join = mPenJoinStyle;
4072 {
4075 if ( !QgsVariantUtils::isNull( exprVal ) )
4076 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
4077 }
4078
4079 Qt::PenCapStyle cap = mPenCapStyle;
4081 {
4084 if ( !QgsVariantUtils::isNull( exprVal ) )
4085 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
4086 }
4087
4088 double offset = mOffset;
4090 {
4093 }
4094
4095 const double prevOpacity = mFill->opacity();
4096 mFill->setOpacity( mFill->opacity() * context.opacity() );
4097
4098 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4100
4101 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4102
4103 if ( points.count() >= 2 )
4104 {
4105 std::unique_ptr< QgsAbstractGeometry > ls = QgsLineString::fromQPolygonF( points );
4106 geos::unique_ptr lineGeom;
4107
4108 if ( !qgsDoubleNear( offset, 0 ) )
4109 {
4110 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
4113 {
4114 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
4115 // and clamp it to a reasonable range. It's the best we can do in this situation!
4116 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
4117 }
4118
4120 if ( geometryType == Qgis::GeometryType::Polygon )
4121 {
4122 auto inputPoly = std::make_unique< QgsPolygon >( static_cast< QgsLineString * >( ls.release() ) );
4123 geos::unique_ptr g( QgsGeos::asGeos( inputPoly.get() ) );
4124 lineGeom = QgsGeos::buffer( g.get(), -scaledOffset, 0, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Miter, 2, nullptr, context.renderContext().feedback() );
4125 // the result is a polygon => extract line work
4126 QgsGeometry polygon( QgsGeos::fromGeos( lineGeom.get() ) );
4127 QVector<QgsGeometry> parts = polygon.coerceToType( Qgis::WkbType::MultiLineString );
4128 if ( !parts.empty() )
4129 {
4130 lineGeom = QgsGeos::asGeos( parts.at( 0 ).constGet() );
4131 }
4132 else
4133 {
4134 lineGeom.reset();
4135 }
4136 }
4137 else
4138 {
4139 geos::unique_ptr g( QgsGeos::asGeos( ls.get() ) );
4140 lineGeom = QgsGeos::offsetCurve( g.get(), scaledOffset, 0, Qgis::JoinStyle::Miter, 8.0 );
4141 }
4142 }
4143 else
4144 {
4145 lineGeom = QgsGeos::asGeos( ls.get() );
4146 }
4147
4148 if ( lineGeom )
4149 {
4150 geos::unique_ptr buffered = QgsGeos::
4151 buffer( lineGeom.get(), scaledWidth / 2, 8, QgsSymbolLayerUtils::penCapStyleToEndCapStyle( cap ), QgsSymbolLayerUtils::penJoinStyleToJoinStyle( join ), 8, nullptr, context.renderContext().feedback() );
4152 if ( buffered )
4153 {
4154 // convert to rings
4155 std::unique_ptr< QgsAbstractGeometry > bufferedGeom = QgsGeos::fromGeos( buffered.get() );
4156 const QList< QList< QPolygonF > > parts = QgsSymbolLayerUtils::toQPolygonF( bufferedGeom.get(), Qgis::SymbolType::Fill );
4157 for ( const QList< QPolygonF > &polygon : parts )
4158 {
4159 QVector< QPolygonF > rings;
4160 for ( int i = 1; i < polygon.size(); ++i )
4161 rings << polygon.at( i );
4162 mFill->renderPolygon( polygon.value( 0 ), &rings, context.feature(), context.renderContext(), -1, useSelectedColor );
4163 }
4164 }
4165 }
4166 }
4167
4169
4170 mFill->setOpacity( prevOpacity );
4171}
4172
4174{
4175 QVariantMap map;
4176
4177 map[u"line_width"_s] = QString::number( mWidth );
4178 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
4179 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4180 map[u"joinstyle"_s] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
4181 map[u"capstyle"_s] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
4182 map[u"offset"_s] = QString::number( mOffset );
4183 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4184 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4185 if ( mFill )
4186 {
4187 map[u"color"_s] = QgsColorUtils::colorToString( mFill->color() );
4188 }
4189 return map;
4190}
4191
4193{
4194 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4195 copyCommonProperties( res.get() );
4196 res->setSubSymbol( mFill->clone() );
4197 return res.release();
4198}
4199
4201{
4202 return mFill.get();
4203}
4204
4206{
4207 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4208 {
4209 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4210 return true;
4211 }
4212 else
4213 {
4214 delete symbol;
4215 return false;
4216 }
4217}
4218
4220{
4221 if ( mFill )
4222 {
4223 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4224 }
4225 return 0;
4226}
4227
4229{
4230 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4231 if ( mFill )
4232 attr.unite( mFill->usedAttributes( context ) );
4233 return attr;
4234}
4235
4237{
4239 return true;
4240 if ( mFill && mFill->hasDataDefinedProperties() )
4241 return true;
4242 return false;
4243}
4244
4246{
4247 mColor = c;
4248 if ( mFill )
4249 mFill->setColor( c );
4250}
4251
4253{
4254 return mFill ? mFill->color() : mColor;
4255}
4256
4265
4267{
4269 if ( mFill )
4270 mFill->setMapUnitScale( scale );
4271}
4272
4274{
4275 if ( mFill )
4276 {
4277 return mFill->mapUnitScale();
4278 }
4279 return QgsMapUnitScale();
4280}
4281
4283{
4285 if ( mFill )
4286 mFill->setOutputUnit( unit );
4287}
4288
4290{
4291 if ( mFill )
4292 {
4293 return mFill->outputUnit();
4294 }
4296}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:3310
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
Definition qgis.h:3316
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex).
Definition qgis.h:3318
@ LastVertex
Place symbols on the last vertex in the line.
Definition qgis.h:3313
@ CentralPoint
Place symbols at the mid point of the line.
Definition qgis.h:3315
@ SegmentCenter
Place symbols at the center of every line segment.
Definition qgis.h:3317
@ Vertex
Place symbols on every vertex in the line.
Definition qgis.h:3312
@ Interval
Place symbols at regular intervals.
Definition qgis.h:3311
@ FirstVertex
Place symbols on the first vertex in the line.
Definition qgis.h:3314
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
Definition qgis.h:797
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:798
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
Definition qgis.h:3200
GradientColorSource
Gradient color sources.
Definition qgis.h:3359
@ ColorRamp
Gradient color ramp.
Definition qgis.h:3361
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
Definition qgis.h:930
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3248
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3247
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:935
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:379
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Miter
Use mitered joins.
Definition qgis.h:2244
RenderUnit
Rendering size units.
Definition qgis.h:5566
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5570
@ Millimeters
Millimeters.
Definition qgis.h:5567
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5571
@ Unknown
Mixed or unknown units.
Definition qgis.h:5573
@ MapUnits
Map units.
Definition qgis.h:5568
@ Pixels
Pixels.
Definition qgis.h:5569
@ Inches
Inches.
Definition qgis.h:5572
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5574
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2231
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2929
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2924
@ RenderLayerTree
The render is for a layer tree display where map based properties are not available and where avoidan...
Definition qgis.h:2935
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2923
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:804
@ Marker
Marker symbol.
Definition qgis.h:638
@ Line
Line symbol.
Definition qgis.h:639
@ Fill
Fill symbol.
Definition qgis.h:640
@ MultiLineString
MultiLineString.
Definition qgis.h:301
QFlags< MarkerLinePlacement > MarkerLinePlacements
Definition qgis.h:3321
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.
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.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
Abstract base class for color ramps.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
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.
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:60
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static std::unique_ptr< QgsFillSymbol > createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
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.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
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.
A geometry is the spatial representation of a feature.
QVector< QgsGeometry > coerceToType(Qgis::WkbType type, double defaultZ=0, double defaultM=0, bool avoidDuplicates=true) const
Attempts to coerce this geometry into the specified destination type.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:260
static geos::unique_ptr offsetCurve(const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr)
Directly calculates the offset curve for a GEOS geometry object and returns a GEOS geometry result.
Definition qgsgeos.cpp:2832
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const override
Buffers the geometry.
Definition qgsgeos.cpp:2106
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition qgsgeos.cpp:1585
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.
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.
Line string geometry type, with support for z-dimension and m-values.
static std::unique_ptr< QgsLineString > fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
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
QgsLineSymbolLayer(const QgsLineSymbolLayer &other)=delete
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.
std::unique_ptr< QgsColorRamp > mGradientRamp
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient line.
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
Qgis::GradientColorSource mGradientColorType
QString layerType() const override
Returns a string that represents this layer type.
~QgsLineburstSymbolLayer() override
QgsLineburstSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLineburstSymbolLayer, using the settings serialized in the properties map (correspon...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QgsLineburstSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, const QColor &color2=Qt::white)
Constructor for QgsLineburstSymbolLayer, with the specified start and end gradient colors.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsColorRamp * colorRamp()
Returns the color ramp used for the gradient line.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
Perform transforms between map coordinates and device coordinates.
void transformInPlace(double &x, double &y) const
Transforms map coordinates to device coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
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.
Q_DECL_DEPRECATED 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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
Represents a 2D point.
Definition qgspointxy.h:62
QgsPointXY project(double distance, double bearing) const
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
QPointF toQPointF() const
Returns the point as a QPointF.
Definition qgspoint.h:426
double x
Definition qgspoint.h:56
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:745
double y
Definition qgspoint.h:57
A grouped map of multiple QgsProperty objects, each referenced by an integer key value.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
A store for object properties.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsRasterLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double opacity() const
Returns the line opacity.
QString path() const
Returns the raster image path.
void setPath(const QString &path)
Set the raster image path.
QString layerType() const override
Returns a string that represents this layer type.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QgsRasterLineSymbolLayer(const QString &path=QString())
Constructor for QgsRasterLineSymbolLayer, with the specified raster image path.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRasterLineSymbolLayer, using the settings serialized in the properties map (correspo...
QColor color() const override
Returns the "representative" color of the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
~QgsRasterLineSymbolLayer() override
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.
Contains information about the context of a rendering operation.
double convertToMapUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
QgsVectorSimplifyMethod & vectorSimplifyMethod()
Returns the simplification settings to use when rendering vector layers.
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 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.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setDrawInsidePolygon(bool drawInsidePolygon)
Sets whether the line should only be drawn inside polygons, and any portion of the line which falls o...
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsMapUnitScale mapUnitScale() const override
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleLineSymbolLayer, using the settings serialized in the properties map (correspo...
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QVector< qreal > customDashVector() const
Returns the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ...
Qt::PenJoinStyle penJoinStyle() const
Returns the pen join style used to render the line (e.g.
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the outline of polygon, using the given render context.
double dxfWidth(QgsSymbolRenderContext &context) const override
Gets line width.
double dxfOffset(QgsSymbolRenderContext &context) const override
Gets offset.
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...
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.
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.
Q_DECL_DEPRECATED 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.
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
QVariantMap extraProperties() const
Returns the open ended set of properties that can drive/inform the SLD encoding.
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static Q_DECL_DEPRECATED 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 std::unique_ptr< QgsSymbolLayer > createMarkerLayerFromSld(QDomElement &element)
Creates a new marker layer from a SLD DOM element.
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 QList< QList< QPolygonF > > toQPolygonF(const QgsGeometry &geometry, Qgis::SymbolType type)
Converts a geometry to a set of QPolygonF objects representing how the geometry should be drawn for a...
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 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 Qgis::EndCapStyle penCapStyleToEndCapStyle(Qt::PenCapStyle style)
Converts a Qt pen cap style to a QGIS end cap style.
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 Q_DECL_DEPRECATED void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
Creates an SLD geometry element.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static Qt::PenStyle decodePenStyle(const QString &str)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static Qgis::JoinStyle penJoinStyleToJoinStyle(Qt::PenJoinStyle style)
Converts a Qt pen joinstyle to a QGIS join style.
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 void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, QgsSldExportContext &context, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QString encodeRealVector(const QVector< qreal > &v)
void copyCommonProperties(QgsSymbolLayer *destLayer) const
Copies all common base class properties from this layer to another symbol layer.
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.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
Property
Data definable properties.
@ SecondaryColor
Secondary color (eg for gradient fills).
@ File
Filename, eg for svg files.
@ BlankSegments
String list of distance to define blank segments along line for templated line symbol layers.
@ DashPatternOffset
Dash pattern offset,.
@ OffsetAlongLine
Offset along line.
@ CustomDash
Custom dash pattern.
@ StrokeStyle
Stroke style (eg solid, dashed).
@ TrimStart
Trim distance from start of line.
@ CapStyle
Line cap style.
@ Placement
Line marker placement.
@ LineAngle
Line angle, or angle of hash lines for hash line symbols.
@ JoinStyle
Line join style.
@ AverageAngleLength
Length to average symbol angles over.
@ Interval
Line marker interval.
@ LineDistance
Distance between lines, or length of lines for hash line symbols.
@ ExtraItems
String list of tuple (x, y and rotation angle) to define extra items to be rendered for templated lin...
@ TrimEnd
Trim distance from end of line.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
virtual void setColor(const QColor &color)
Sets the "representative" color for the symbol layer.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
QgsPropertyCollection mDataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsSymbolLayer(const QgsSymbolLayer &other)
Encapsulates the context in which a symbol is being rendered.
const QgsFeature * feature() const
Returns the current feature being rendered.
Qgis::GeometryType originalGeometryType() const
Returns the geometry type for the original feature geometry being rendered.
QgsFields fields() const
Fields of the layer.
int geometryPartNum() const
Part number of current geometry.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:227
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:677
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition qgssymbol.h:684
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:296
bool rotateSymbols() const
Returns true if the repeating symbols be rotated to match their line segment orientation.
void setTrimDistanceStartMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the start of the line.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static void setCommonProperties(QgsTemplatedLineSymbolLayerBase *destLayer, const QVariantMap &properties)
Sets all common symbol properties in the destLayer, using the settings serialized in the properties m...
void setMapUnitScale(const QgsMapUnitScale &scale) final
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) final
Renders the line symbol layer along the outline of polygon, using the given render context.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void renderPolyline(const QPolygonF &pts, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setIntervalUnit(Qgis::RenderUnit unit)
Sets the units for the interval between symbols.
void setAverageAngleUnit(Qgis::RenderUnit unit)
Sets the unit for the length over which the line's direction is averaged when calculating individual ...
double interval() const
Returns the interval between individual symbols.
void setOffsetAlongLineUnit(Qgis::RenderUnit unit)
Sets the unit used for calculating the offset along line for symbols.
void setAverageAngleMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the length over which the line's direction is averaged when calculating i...
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
double offsetAlongLine() const
Returns the offset along the line for the symbol placement.
void copyTemplateSymbolProperties(QgsTemplatedLineSymbolLayerBase *destLayer) const
Copies all common properties of this layer to another templated symbol layer destLayer.
Qgis::MarkerLinePlacements placements() const
Returns the placement of the symbols.
void setOffsetAlongLine(double offsetAlongLine)
Sets the the offset along the line for the symbol placement.
void setTrimDistanceEndMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the end of the line.
Qgis::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
Qgis::RenderUnit outputUnit() const final
Returns the units to use for sizes and widths within the symbol layer.
QgsTemplatedLineSymbolLayerBase(bool rotateSymbol=true, double interval=3)
Constructor for QgsTemplatedLineSymbolLayerBase.
virtual void setSymbolLineAngle(double angle)=0
Sets the line angle modification for the symbol's angle.
QgsMapUnitScale mapUnitScale() const final
void setInterval(double interval)
Sets the interval between individual symbols.
Qgis::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
Q_DECL_DEPRECATED Qgis::MarkerLinePlacement placement() const
Returns the placement of the symbols.
void setTrimDistanceStartUnit(Qgis::RenderUnit unit)
Sets the unit for the trim distance for the start of the line.
void setPlaceOnEveryPart(bool respect)
Sets whether the placement applies for every part of multi-part feature geometries.
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setTrimDistanceStart(double distance)
Sets the trim distance for the start of the line, which dictates a length from the start of the line ...
Qgis::RenderUnit averageAngleUnit() const
Returns the unit for the length over which the line's direction is averaged when calculating individu...
Q_DECL_DEPRECATED void setPlacement(Qgis::MarkerLinePlacement placement)
Sets the placement of the symbols.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
Qgis::RenderUnit blankSegmentsUnit() const
Returns the unit for for blank segments start and end distances.
void setBlankSegmentsUnit(Qgis::RenderUnit unit)
Sets the unit for blank segments start and end distances.
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...
const QgsMapUnitScale & offsetAlongLineMapUnitScale() const
Returns the map unit scale used for calculating the offset in map units along line for symbols.
void setTrimDistanceEndUnit(Qgis::RenderUnit unit)
Sets the unit for the trim distance for the end of the line.
virtual void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false)=0
Renders the templated symbol at the specified point, using the given render context.
void setIntervalMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the interval between symbols.
void setOffsetAlongLineMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for calculating the offset in map units along line for symbols.
void setAverageAngleLength(double length)
Sets the length of line over which the line's direction is averaged when calculating individual symbo...
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:148
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7154
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7476
T qgsFlagKeysToValue(const QString &keys, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given keys of a flag.
Definition qgis.h:7505
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7236
QMap< QString, QString > QgsStringMap
Definition qgis.h:7751
void trimPoints(QPolygonF &points, double startTrim, double endTrim, Qgis::RenderUnit trimDistanceStartUnit, Qgis::RenderUnit trimDistanceEndUnit, const QgsMapUnitScale &trimDistanceStartMapUnitScale, const QgsMapUnitScale &trimDistanceEndMapUnitScale, const QgsPropertyCollection &mDataDefinedProperties, QgsSymbolRenderContext &context)
trim points according to start and end trim distance based on data defined properties and render cont...
#define DEFAULT_MARKERLINE_INTERVAL
#define DEFAULT_SIMPLELINE_WIDTH
#define DEFAULT_MARKERLINE_ROTATE
#define DEFAULT_SIMPLELINE_PENSTYLE
#define DEFAULT_SIMPLELINE_COLOR
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
QList< QPolygonF > offsetLine(QPolygonF polyline, double dist, Qgis::GeometryType geometryType)
calculate geometry shifted by a specified distance
Single variable definition for use within a QgsExpressionContextScope.
Qgis::VertexType type
Vertex type.
#define EPSILON
Definition util.h:80