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