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