QGIS API Documentation 3.99.0-Master (a8f284845db)
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 return l;
586}
587
588void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
589{
590 QgsSldExportContext context;
591 context.setExtraProperties( props );
592 toSld( doc, element, context );
593}
594
595bool QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
596{
597 if ( mPenStyle == Qt::NoPen )
598 return true;
599
600 const QVariantMap props = context.extraProperties();
601 QDomElement symbolizerElem = doc.createElement( u"se:LineSymbolizer"_s );
602 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
603 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
604 element.appendChild( symbolizerElem );
605
606 // <Geometry>
607 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
608
609 // <Stroke>
610 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
611 symbolizerElem.appendChild( strokeElem );
612
613 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
615 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
616 QgsSymbolLayerUtils::lineToSld( doc, strokeElem, penStyle, mColor, context, width,
617 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
618
619 // <se:PerpendicularOffset>
620 if ( !qgsDoubleNear( mOffset, 0.0 ) )
621 {
622 QDomElement perpOffsetElem = doc.createElement( u"se:PerpendicularOffset"_s );
624 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
625 symbolizerElem.appendChild( perpOffsetElem );
626 }
627 return true;
628}
629
630QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
631{
632 if ( mUseCustomDashPattern )
633 {
634 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
635 mPen.color(), mPenJoinStyle,
636 mPenCapStyle, mOffset, &mCustomDashVector );
637 }
638 else
639 {
640 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
641 mPenCapStyle, mOffset );
642 }
643}
644
646{
647 QgsDebugMsgLevel( u"Entered."_s, 4 );
648
649 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
650 if ( strokeElem.isNull() )
651 return nullptr;
652
653 Qt::PenStyle penStyle;
654 QColor color;
655 double width;
656 Qt::PenJoinStyle penJoinStyle;
657 Qt::PenCapStyle penCapStyle;
658 QVector<qreal> customDashVector;
659
661 color, width,
664 return nullptr;
665
666 double offset = 0.0;
667 QDomElement perpOffsetElem = element.firstChildElement( u"PerpendicularOffset"_s );
668 if ( !perpOffsetElem.isNull() )
669 {
670 bool ok;
671 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
672 if ( ok )
673 offset = d;
674 }
675
676 double scaleFactor = 1.0;
677 const QString uom = element.attribute( u"uom"_s );
678 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
679 width = width * scaleFactor;
680 offset = offset * scaleFactor;
681
683 l->setOutputUnit( sldUnitSize );
684 l->setOffset( offset );
687 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
689 return l;
690}
691
692void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
693{
694 if ( !dataDefinedProperties().hasActiveProperties() )
695 return; // shortcut
696
697 //data defined properties
698 bool hasStrokeWidthExpression = false;
700 {
702 double scaledWidth = context.renderContext().convertToPainterUnits(
705 pen.setWidthF( scaledWidth );
706 selPen.setWidthF( scaledWidth );
707 hasStrokeWidthExpression = true;
708 }
709
710 //color
712 {
714
716 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
717 pen.setColor( penColor );
718 }
719
720 //offset
722 {
725 }
726
727 //dash dot vector
728
729 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
730 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
731 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
732
734 {
735 const QString customDashString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::CustomDash, context.renderContext().expressionContext(), QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector ) );
736 const QStringList dashList = customDashString.split( ';' );
737 QVector<qreal> dashVector;
738 for ( const QString &dash : dashList )
739 {
740 dashVector.push_back( context.renderContext().convertToPainterUnits( dash.toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
741 }
742 pen.setDashPattern( dashVector );
743 }
744 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
745 {
746 //re-scale pattern vector after data defined pen width was applied
747
748 QVector<qreal> scaledVector;
749 for ( double v : std::as_const( mCustomDashVector ) )
750 {
751 //the dash is specified in terms of pen widths, therefore the division
752 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
753 }
754 mPen.setDashPattern( scaledVector );
755 }
756
757 // dash pattern offset
758 double patternOffset = mDashPatternOffset;
759 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
760 {
761 context.setOriginalValueVariable( patternOffset );
762 patternOffset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::DashPatternOffset, context.renderContext().expressionContext(), mDashPatternOffset );
763 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
764 }
765
766 //line style
768 {
770 const QString lineStyleString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::StrokeStyle, context.renderContext().expressionContext(), QgsSymbolLayerUtils::encodePenStyle( mPenStyle ) );
771 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( lineStyleString ) );
772 }
773
774 //join style
776 {
778 const QString joinStyleString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::JoinStyle, context.renderContext().expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle ) );
779 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( joinStyleString ) );
780 }
781
782 //cap style
784 {
786 const QString capStyleString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::CapStyle, context.renderContext().expressionContext(), QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle ) );
787 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( capStyleString ) );
788 }
789}
790
791void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
792{
793 if ( pen.dashPattern().empty() || points.size() < 2 )
794 return;
795
796 if ( pen.widthF() <= 1.0 )
797 {
798 pen.setWidthF( 1.0001 );
799 }
800
801 QVector< qreal > sourcePattern = pen.dashPattern();
802 const double dashWidthDiv = pen.widthF();
803 // back to painter units
804 for ( int i = 0; i < sourcePattern.size(); ++ i )
805 sourcePattern[i] *= pen.widthF();
806
807 QVector< qreal > buffer;
808 QPolygonF bufferedPoints;
809 QPolygonF previousSegmentBuffer;
810 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
811 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
812 // and then append the buffer to the output pattern.
813
814 auto ptIt = points.constBegin();
815 double totalBufferLength = 0;
816 int patternIndex = 0;
817 double currentRemainingDashLength = 0;
818 double currentRemainingGapLength = 0;
819
820 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
821 {
822 QVector< qreal > result;
823 result.reserve( buffer.size() );
824 for ( auto it = buffer.begin(); it != buffer.end(); )
825 {
826 qreal dash = *it++;
827 qreal gap = *it++;
828 while ( dash == 0 && !result.empty() )
829 {
830 result.last() += gap;
831
832 if ( it == buffer.end() )
833 return result;
834 dash = *it++;
835 gap = *it++;
836 }
837 while ( gap == 0 && it != buffer.end() )
838 {
839 dash += *it++;
840 gap = *it++;
841 }
842 result << dash << gap;
843 }
844 return result;
845 };
846
847 double currentBufferLineLength = 0;
848 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
849 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
850 {
851 if ( buffer.empty() || bufferedPoints.size() < 2 )
852 {
853 return;
854 }
855
856 if ( currentRemainingDashLength )
857 {
858 // ended midway through a dash -- we want to finish this off
859 buffer << currentRemainingDashLength << 0.0;
860 totalBufferLength += currentRemainingDashLength;
861 }
862 QVector< qreal > compressed = compressPattern( buffer );
863 if ( !currentRemainingDashLength )
864 {
865 // ended midway through a gap -- we don't want this, we want to end at previous dash
866 totalBufferLength -= compressed.last();
867 compressed.last() = 0;
868 }
869
870 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
871 const double scaleFactor = currentBufferLineLength / totalBufferLength;
872
873 bool shouldFlushPreviousSegmentBuffer = false;
874
875 if ( !previousSegmentBuffer.empty() )
876 {
877 // add first dash from current buffer
878 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
879 if ( !firstDashSubstring.empty() )
880 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
881
882 // then we skip over the first dash and gap for this segment
883 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
884
885 compressed = compressed.mid( 2 );
886 shouldFlushPreviousSegmentBuffer = !compressed.empty();
887 }
888
889 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
890 {
891 QPen adjustedPen = pen;
892 adjustedPen.setStyle( Qt::SolidLine );
893 painter->setPen( adjustedPen );
894 QPainterPath path;
895 path.addPolygon( previousSegmentBuffer );
896 painter->drawPath( path );
897 previousSegmentBuffer.clear();
898 }
899
900 double finalDash = 0;
901 if ( nextPoint )
902 {
903 // sharp bend:
904 // 1. rewind buffered points line by final dash and gap length
905 // (later) 2. draw the bend with a solid line of length 2 * final dash size
906
907 if ( !compressed.empty() )
908 {
909 finalDash = compressed.at( compressed.size() - 2 );
910 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
911
912 const QPolygonF thisPoints = bufferedPoints;
913 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
914 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
915 }
916 else
917 {
918 previousSegmentBuffer << bufferedPoints;
919 }
920 }
921
922 currentBufferLineLength = 0;
923 currentRemainingDashLength = 0;
924 currentRemainingGapLength = 0;
925 totalBufferLength = 0;
926 buffer.clear();
927
928 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
929 {
930 QPen adjustedPen = pen;
931 if ( !compressed.empty() )
932 {
933 // maximum size of dash pattern is 32 elements
934 compressed = compressed.mid( 0, 32 );
935 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
936 adjustedPen.setDashPattern( compressed );
937 }
938 else
939 {
940 adjustedPen.setStyle( Qt::SolidLine );
941 }
942
943 painter->setPen( adjustedPen );
944 QPainterPath path;
945 path.addPolygon( bufferedPoints );
946 painter->drawPath( path );
947 }
948
949 bufferedPoints.clear();
950 };
951
952 QPointF p1;
953 QPointF p2 = *ptIt;
954 ptIt++;
955 bufferedPoints << p2;
956 for ( ; ptIt != points.constEnd(); ++ptIt )
957 {
958 p1 = *ptIt;
959 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
960 {
961 continue;
962 }
963
964 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
965 currentBufferLineLength += remainingSegmentDistance;
966 while ( true )
967 {
968 // handle currentRemainingDashLength/currentRemainingGapLength
969 if ( currentRemainingDashLength > 0 )
970 {
971 // bit more of dash to insert
972 if ( remainingSegmentDistance >= currentRemainingDashLength )
973 {
974 // all of dash fits in
975 buffer << currentRemainingDashLength << 0.0;
976 totalBufferLength += currentRemainingDashLength;
977 remainingSegmentDistance -= currentRemainingDashLength;
978 patternIndex++;
979 currentRemainingDashLength = 0.0;
980 currentRemainingGapLength = sourcePattern.at( patternIndex );
981 if ( currentRemainingGapLength == 0.0 )
982 {
983 patternIndex++;
984 }
985 }
986 else
987 {
988 // only part of remaining dash fits in
989 buffer << remainingSegmentDistance << 0.0;
990 totalBufferLength += remainingSegmentDistance;
991 currentRemainingDashLength -= remainingSegmentDistance;
992 break;
993 }
994 }
995 if ( currentRemainingGapLength > 0 )
996 {
997 // bit more of gap to insert
998 if ( remainingSegmentDistance >= currentRemainingGapLength )
999 {
1000 // all of gap fits in
1001 buffer << 0.0 << currentRemainingGapLength;
1002 totalBufferLength += currentRemainingGapLength;
1003 remainingSegmentDistance -= currentRemainingGapLength;
1004 currentRemainingGapLength = 0.0;
1005 patternIndex++;
1006 }
1007 else
1008 {
1009 // only part of remaining gap fits in
1010 buffer << 0.0 << remainingSegmentDistance;
1011 totalBufferLength += remainingSegmentDistance;
1012 currentRemainingGapLength -= remainingSegmentDistance;
1013 break;
1014 }
1015 }
1016
1017 if ( patternIndex + 1 >= sourcePattern.size() )
1018 {
1019 patternIndex = 0;
1020 }
1021
1022 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1023 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1024 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1025 {
1026 buffer << nextPatternDashLength << nextPatternGapLength;
1027 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1028 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1029 patternIndex += 2;
1030 }
1031 else if ( nextPatternDashLength <= remainingSegmentDistance )
1032 {
1033 // can fit in "dash", but not "gap"
1034 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1035 totalBufferLength += remainingSegmentDistance;
1036 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1037 currentRemainingDashLength = 0;
1038 patternIndex++;
1039 break;
1040 }
1041 else
1042 {
1043 // can't fit in "dash"
1044 buffer << remainingSegmentDistance << 0.0;
1045 totalBufferLength += remainingSegmentDistance;
1046 currentRemainingGapLength = 0;
1047 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1048 break;
1049 }
1050 }
1051
1052 bufferedPoints << p1;
1053 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1054 {
1055 QPointF nextPoint = *( ptIt + 1 );
1056
1057 // extreme angles form more than 45 degree angle at a node
1058 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1059 {
1060 // extreme angle. Rescale buffer and flush
1061 flushBuffer( &nextPoint );
1062 bufferedPoints << p1;
1063 // restart the line with the full length of the most recent dash element -- see
1064 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1065 if ( patternIndex % 2 == 1 )
1066 {
1067 patternIndex--;
1068 }
1069 currentRemainingDashLength = sourcePattern.at( patternIndex );
1070 }
1071 }
1072
1073 p2 = p1;
1074 }
1075
1076 flushBuffer( nullptr );
1077 if ( !previousSegmentBuffer.empty() )
1078 {
1079 QPen adjustedPen = pen;
1080 adjustedPen.setStyle( Qt::SolidLine );
1081 painter->setPen( adjustedPen );
1082 QPainterPath path;
1083 path.addPolygon( previousSegmentBuffer );
1084 painter->drawPath( path );
1085 previousSegmentBuffer.clear();
1086 }
1087}
1088
1090{
1091 if ( mDrawInsidePolygon )
1092 {
1093 //set to clip line to the interior of polygon, so we expect no bleed
1094 return 0;
1095 }
1096 else
1097 {
1098 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1100 }
1101}
1102
1104{
1105 unit = mCustomDashPatternUnit;
1106 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1107}
1108
1110{
1111 return mPenStyle;
1112}
1113
1130
1140
1142{
1143 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1144}
1145
1147{
1148 return mAlignDashPattern;
1149}
1150
1152{
1153 mAlignDashPattern = enabled;
1154}
1155
1157{
1158 return mPatternCartographicTweakOnSharpCorners;
1159}
1160
1162{
1163 mPatternCartographicTweakOnSharpCorners = enabled;
1164}
1165
1167{
1168 double offset = mOffset;
1169
1171 {
1174 }
1175
1178 {
1180 }
1181 return -offset; //direction seems to be inverse to symbology offset
1182}
1183
1184//
1185// QgsTemplatedLineSymbolLayerBase
1186//
1188 : mRotateSymbols( rotateSymbol )
1189 , mInterval( interval )
1190{
1191
1192}
1193
1217
1222
1224
1226{
1227 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1228 if ( mRenderingFeature )
1229 {
1230 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1231 // until after we've received the final part
1232 mFeatureSymbolOpacity = context.opacity();
1233 mCurrentFeatureIsSelected = useSelectedColor;
1234 }
1235
1236 double offset = mOffset;
1237
1239 {
1242 }
1243
1245
1247 {
1249 if ( !QgsVariantUtils::isNull( exprVal ) )
1250 {
1251 QString placementString = exprVal.toString();
1252 if ( placementString.compare( "interval"_L1, Qt::CaseInsensitive ) == 0 )
1253 {
1255 }
1256 else if ( placementString.compare( "vertex"_L1, Qt::CaseInsensitive ) == 0 )
1257 {
1259 }
1260 else if ( placementString.compare( "innervertices"_L1, Qt::CaseInsensitive ) == 0 )
1261 {
1263 }
1264 else if ( placementString.compare( "lastvertex"_L1, Qt::CaseInsensitive ) == 0 )
1265 {
1267 }
1268 else if ( placementString.compare( "firstvertex"_L1, Qt::CaseInsensitive ) == 0 )
1269 {
1271 }
1272 else if ( placementString.compare( "centerpoint"_L1, Qt::CaseInsensitive ) == 0 )
1273 {
1275 }
1276 else if ( placementString.compare( "curvepoint"_L1, Qt::CaseInsensitive ) == 0 )
1277 {
1279 }
1280 else if ( placementString.compare( "segmentcenter"_L1, Qt::CaseInsensitive ) == 0 )
1281 {
1283 }
1284 else
1285 {
1287 }
1288 }
1289 }
1290
1291 QgsScopedQPainterState painterState( context.renderContext().painter() );
1292
1293 double averageOver = mAverageAngleLength;
1295 {
1296 context.setOriginalValueVariable( mAverageAngleLength );
1297 averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
1298 }
1299 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1300
1303 {
1304 const QString strBlankSegments = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::BlankSegments, context.renderContext().expressionContext() );
1305 QString error;
1306 QList<QList<QgsBlankSegmentUtils::BlankSegments>> allBlankSegments = QgsBlankSegmentUtils::parseBlankSegments( strBlankSegments, context.renderContext(), blankSegmentsUnit(), error );
1307
1308 if ( !error.isEmpty() )
1309 {
1310 QgsDebugError( u"Badly formatted blank segment '%1', skip it: %2"_s.arg( strBlankSegments ).arg( error ) );
1311 }
1312 else
1313 {
1314 // keep only the part/ring we are currently rendering
1315 const int iPart = context.geometryPartNum() - 1;
1316 if ( iPart >= 0 && mRingIndex >= 0 && iPart < allBlankSegments.count() && mRingIndex < allBlankSegments.at( iPart ).count() )
1317 {
1318 blankSegments = allBlankSegments.at( iPart ).at( mRingIndex );
1319 }
1320 }
1321 }
1322
1323 if ( qgsDoubleNear( offset, 0.0 ) )
1324 {
1326 renderPolylineInterval( points, context, averageOver, blankSegments );
1328 renderPolylineCentral( points, context, averageOver, blankSegments );
1330 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1332 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1333 {
1334 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1335 mHasRenderedFirstPart = mRenderingFeature;
1336 }
1338 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1340 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1342 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1344 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1345 }
1346 else
1347 {
1348 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1350
1351 for ( int part = 0; part < mline.count(); ++part )
1352 {
1353 const QPolygonF &points2 = mline[ part ];
1354
1356 renderPolylineInterval( points2, context, averageOver, blankSegments );
1358 renderPolylineCentral( points2, context, averageOver, blankSegments );
1360 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1362 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1364 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1366 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1367 {
1368 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1369 mHasRenderedFirstPart = mRenderingFeature;
1370 }
1372 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1374 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1375 }
1376 }
1377}
1378
1379void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1380{
1381 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1382
1383 if ( curvePolygon )
1384 {
1385 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1386 }
1387
1388 QgsExpressionContextScope *scope = nullptr;
1389 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1391 {
1392 scope = new QgsExpressionContextScope();
1393 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1394 }
1395
1396 switch ( mRingFilter )
1397 {
1398 case AllRings:
1399 case ExteriorRingOnly:
1400 {
1401 if ( scope )
1403
1404 renderPolyline( points, context );
1405 break;
1406 }
1407 case InteriorRingsOnly:
1408 break;
1409 }
1410
1411 if ( rings )
1412 {
1413 switch ( mRingFilter )
1414 {
1415 case AllRings:
1416 case InteriorRingsOnly:
1417 {
1418 mOffset = -mOffset; // invert the offset for rings!
1419 for ( int i = 0; i < rings->size(); ++i )
1420 {
1421 mRingIndex = i + 1;
1422 if ( curvePolygon )
1423 {
1424 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1425 }
1426 if ( scope )
1428
1429 renderPolyline( rings->at( i ), context );
1430 }
1431 mOffset = -mOffset;
1432 mRingIndex = 0;
1433 }
1434 break;
1435 case ExteriorRingOnly:
1436 break;
1437 }
1438 }
1439}
1440
1442{
1444 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1445 {
1447 }
1448 return unit;
1449}
1450
1452{
1454 mIntervalUnit = unit;
1455 mOffsetAlongLineUnit = unit;
1456 mAverageAngleLengthUnit = unit;
1457}
1458
1466
1477
1479{
1480 QVariantMap map;
1481 map[u"rotate"_s] = ( rotateSymbols() ? u"1"_s : u"0"_s );
1482 map[u"interval"_s] = QString::number( interval() );
1483 map[u"offset"_s] = QString::number( mOffset );
1484 map[u"offset_along_line"_s] = QString::number( offsetAlongLine() );
1485 map[u"offset_along_line_unit"_s] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1486 map[u"offset_along_line_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1487 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1488 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1489 map[u"interval_unit"_s] = QgsUnitTypes::encodeUnit( intervalUnit() );
1490 map[u"interval_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1491 map[u"average_angle_length"_s] = QString::number( mAverageAngleLength );
1492 map[u"average_angle_unit"_s] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1493 map[u"average_angle_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1494 map[u"blank_segments_unit"_s] = QgsUnitTypes::encodeUnit( mBlankSegmentsUnit );
1495
1496 map[u"placements"_s] = qgsFlagValueToKeys( mPlacements );
1497
1498 map[u"ring_filter"_s] = QString::number( static_cast< int >( mRingFilter ) );
1499 map[u"place_on_every_part"_s] = mPlaceOnEveryPart;
1500 return map;
1501}
1502
1504{
1505 return mPlaceOnEveryPart
1506 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1507 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1508 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1509}
1510
1512{
1513 installMasks( context, true );
1514
1515 mRenderingFeature = true;
1516 mHasRenderedFirstPart = false;
1517}
1518
1520{
1521 mRenderingFeature = false;
1522 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1523 {
1524 removeMasks( context, true );
1525 return;
1526 }
1527
1528 const double prevOpacity = subSymbol()->opacity();
1529 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1530
1531 // render final point
1532 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1533 mFeatureSymbolOpacity = 1;
1534 subSymbol()->setOpacity( prevOpacity );
1535
1536 removeMasks( context, true );
1537}
1538
1540{
1541 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1542 destLayer->setOffset( mOffset );
1543 destLayer->setPlacements( placements() );
1544 destLayer->setOffsetUnit( mOffsetUnit );
1546 destLayer->setIntervalUnit( intervalUnit() );
1548 destLayer->setOffsetAlongLine( offsetAlongLine() );
1551 destLayer->setAverageAngleLength( mAverageAngleLength );
1552 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1553 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1554 destLayer->setBlankSegmentsUnit( mBlankSegmentsUnit );
1555 destLayer->setRingFilter( mRingFilter );
1556 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1557
1558 copyCommonProperties( destLayer );
1559}
1560
1562{
1563 if ( properties.contains( u"offset"_s ) )
1564 {
1565 destLayer->setOffset( properties[u"offset"_s].toDouble() );
1566 }
1567 if ( properties.contains( u"offset_unit"_s ) )
1568 {
1569 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
1570 }
1571 if ( properties.contains( u"interval_unit"_s ) )
1572 {
1573 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[u"interval_unit"_s].toString() ) );
1574 }
1575 if ( properties.contains( u"offset_along_line"_s ) )
1576 {
1577 destLayer->setOffsetAlongLine( properties[u"offset_along_line"_s].toDouble() );
1578 }
1579 if ( properties.contains( u"offset_along_line_unit"_s ) )
1580 {
1581 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_along_line_unit"_s].toString() ) );
1582 }
1583 if ( properties.contains( ( u"offset_along_line_map_unit_scale"_s ) ) )
1584 {
1585 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_along_line_map_unit_scale"_s].toString() ) );
1586 }
1587
1588 if ( properties.contains( u"offset_map_unit_scale"_s ) )
1589 {
1590 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
1591 }
1592 if ( properties.contains( u"interval_map_unit_scale"_s ) )
1593 {
1594 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"interval_map_unit_scale"_s].toString() ) );
1595 }
1596
1597 if ( properties.contains( u"average_angle_length"_s ) )
1598 {
1599 destLayer->setAverageAngleLength( properties[u"average_angle_length"_s].toDouble() );
1600 }
1601 if ( properties.contains( u"average_angle_unit"_s ) )
1602 {
1603 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[u"average_angle_unit"_s].toString() ) );
1604 }
1605 if ( properties.contains( ( u"average_angle_map_unit_scale"_s ) ) )
1606 {
1607 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"average_angle_map_unit_scale"_s].toString() ) );
1608 }
1609 if ( properties.contains( u"blank_segments_unit"_s ) )
1610 {
1611 destLayer->setBlankSegmentsUnit( QgsUnitTypes::decodeRenderUnit( properties[u"blank_segments_unit"_s].toString() ) );
1612 }
1613
1614 if ( properties.contains( u"placement"_s ) )
1615 {
1616 if ( properties[u"placement"_s] == "vertex"_L1 )
1618 else if ( properties[u"placement"_s] == "lastvertex"_L1 )
1620 else if ( properties[u"placement"_s] == "firstvertex"_L1 )
1622 else if ( properties[u"placement"_s] == "centralpoint"_L1 )
1624 else if ( properties[u"placement"_s] == "curvepoint"_L1 )
1626 else if ( properties[u"placement"_s] == "segmentcenter"_L1 )
1628 else
1630 }
1631 else if ( properties.contains( u"placements"_s ) )
1632 {
1634 destLayer->setPlacements( placements );
1635 }
1636
1637 if ( properties.contains( u"ring_filter"_s ) )
1638 {
1639 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[u"ring_filter"_s].toInt() ) );
1640 }
1641
1642 destLayer->setPlaceOnEveryPart( properties.value( u"place_on_every_part"_s, true ).toBool() );
1643
1645}
1646
1648
1653class BlankSegmentsWalker
1654{
1655 public :
1656
1657 BlankSegmentsWalker( const QPolygonF &points, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1658 : mBlankSegments( blankSegments )
1659 , mPoints( points )
1660 , mItBlankSegment( blankSegments.cbegin() )
1661 {
1662 mDistances.reserve( mPoints.count() );
1663 mDistances << 0; // first point is start, so distance is 0
1664 }
1665
1666 bool insideBlankSegment( double distance )
1667 {
1668 while ( mItBlankSegment != mBlankSegments.cend() && distance > mItBlankSegment->second )
1669 {
1670 ++mItBlankSegment;
1671 }
1672
1673 return ( mItBlankSegment != mBlankSegments.cend() && distance >= mItBlankSegment->first );
1674 }
1675
1676
1677 // pointIndex : index of the point before point
1678 bool insideBlankSegment( const QPointF &point, int pointIndex )
1679 {
1680 if ( pointIndex < 0 || pointIndex >= mPoints.count() )
1681 return false;
1682
1683 // compute distances and fill distances array
1684 if ( pointIndex >= mDistances.count() )
1685 {
1686 for ( int i = static_cast<int>( mDistances.count() ); i < pointIndex + 1; i++ )
1687 {
1688 const QPointF diff = mPoints.at( i ) - mPoints.at( i - 1 );
1689 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1690 const double totalDistance = distance + mDistances.last();
1691 mDistances << totalDistance;
1692 }
1693 }
1694
1695 const QPointF diff = mPoints.at( pointIndex ) - point;
1696 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1697 const double currentDistance = mDistances.at( pointIndex ) + distance;
1698
1699 return insideBlankSegment( currentDistance );
1700 }
1701
1702 private:
1703
1704 const QgsBlankSegmentUtils::BlankSegments &mBlankSegments;
1705 const QPolygonF &mPoints;
1706 QList<double> mDistances;
1707 QgsBlankSegmentUtils::BlankSegments::const_iterator mItBlankSegment;
1708};
1709
1710
1712
1713
1714void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1715{
1716 if ( points.isEmpty() )
1717 return;
1718
1719 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1720 double lengthLeft = 0; // how much is left until next marker
1721
1722 QgsRenderContext &rc = context.renderContext();
1723 double interval = mInterval;
1724
1725 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1726 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1727
1729 {
1730 context.setOriginalValueVariable( mInterval );
1732 }
1733 if ( interval <= 0 )
1734 {
1735 interval = 0.1;
1736 }
1737 double offsetAlongLine = mOffsetAlongLine;
1739 {
1740 context.setOriginalValueVariable( mOffsetAlongLine );
1742 }
1743
1744 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1746 {
1747 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1748 // and clamp it to a reasonable range. It's the best we can do in this situation!
1749 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1750 }
1751
1752 constexpr double EPSILON = 1e-5;
1753 if ( painterUnitInterval < EPSILON )
1754 return;
1755
1756 double painterUnitOffsetAlongLine = 0;
1757
1758 // only calculated if we need it!
1759 double totalLength = -1;
1760
1761 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1762 {
1763 switch ( offsetAlongLineUnit() )
1764 {
1773 break;
1775 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1776 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1777 break;
1778 }
1779
1780 if ( points.isClosed() )
1781 {
1782 if ( painterUnitOffsetAlongLine > 0 )
1783 {
1784 if ( totalLength < 0 )
1785 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1786 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1787 }
1788 else if ( painterUnitOffsetAlongLine < 0 )
1789 {
1790 if ( totalLength < 0 )
1791 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1792 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1793 }
1794 }
1795 }
1796
1798 {
1799 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1800 // and clamp it to a reasonable range. It's the best we can do in this situation!
1801 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1802 }
1803
1804 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1805
1806 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1807 {
1808 QVector< QPointF > angleStartPoints;
1809 QVector< QPointF > symbolPoints;
1810 QVector< QPointF > angleEndPoints;
1811
1812 // we collect 3 arrays of points. These correspond to
1813 // 1. the actual point at which to render the symbol
1814 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1815 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1816 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1817 // (or trace past the final point)
1818
1819 QList<int> pointIndices; // keep a track on original pointIndices so we can decide later whether or not symbol points belong to a blank segment
1820 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft, blankSegments.isEmpty() ? nullptr : &pointIndices );
1821
1822 if ( symbolPoints.empty() )
1823 {
1824 // no symbols to draw, shortcut out early
1825 return;
1826 }
1827
1828 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1829 {
1830 // avoid duplicate points at start and end of closed rings
1831 symbolPoints.pop_back();
1832 }
1833
1834 angleEndPoints.reserve( symbolPoints.size() );
1835 angleStartPoints.reserve( symbolPoints.size() );
1836 if ( averageOver <= painterUnitOffsetAlongLine )
1837 {
1838 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, nullptr, 0, symbolPoints.size() );
1839 }
1840 else
1841 {
1842 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, nullptr, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1843 }
1844 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, nullptr, 0, symbolPoints.size() );
1845
1846 int pointNum = 0;
1847 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
1848 for ( int i = 0; i < symbolPoints.size(); ++ i )
1849 {
1850 if ( context.renderContext().renderingStopped() )
1851 break;
1852
1853 const QPointF pt = symbolPoints[i];
1854 if ( i < pointIndices.count() && blankSegmentsWalker.insideBlankSegment( pt, pointIndices.at( i ) ) )
1855 // skip the rendering
1856 continue;
1857
1858 const QPointF startPt = angleStartPoints[i];
1859 const QPointF endPt = angleEndPoints[i];
1860
1861 Line l( startPt, endPt );
1862 // rotate marker (if desired)
1863 if ( rotateSymbols() )
1864 {
1865 setSymbolLineAngle( l.angle() * 180 / M_PI );
1866 }
1867
1868 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1869 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1870 }
1871 }
1872 else
1873 {
1874
1875 // not averaging line angle -- always use exact section angle
1876 int pointNum = 0;
1877 QPointF lastPt = points[0];
1878 BlankSegmentsWalker itBlankSegment( points, blankSegments );
1879 for ( int i = 1; i < points.count(); ++i )
1880 {
1881 if ( context.renderContext().renderingStopped() )
1882 break;
1883
1884 const QPointF &pt = points[i];
1885
1886 if ( lastPt == pt ) // must not be equal!
1887 continue;
1888
1889 // for each line, find out dx and dy, and length
1890 Line l( lastPt, pt );
1891 QPointF diff = l.diffForInterval( painterUnitInterval );
1892
1893 // if there's some length left from previous line
1894 // use only the rest for the first point in new line segment
1895 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1896 double c = 1 - lengthLeft / painterUnitInterval;
1897
1898 lengthLeft += l.length();
1899
1900 // rotate marker (if desired)
1901 if ( rotateSymbols() )
1902 {
1903 setSymbolLineAngle( l.angle() * 180 / M_PI );
1904 }
1905
1906 // while we're not at the end of line segment, draw!
1907 while ( lengthLeft > painterUnitInterval )
1908 {
1909 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1910 lastPt += c * diff;
1911 c = 1; // reset c (if wasn't 1 already)
1912
1913 // we draw
1914 if ( !itBlankSegment.insideBlankSegment( lastPt, i - 1 ) )
1915 {
1916 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1917 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1918 }
1919
1920 lengthLeft -= painterUnitInterval;
1921 }
1922
1923 lastPt = pt;
1924 }
1925
1926 }
1927}
1928
1929static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1930{
1931 // calc average angle between the previous and next point
1932 double a1 = Line( prevPt, pt ).angle();
1933 double a2 = Line( pt, nextPt ).angle();
1934 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1935
1936 return std::atan2( unitY, unitX );
1937}
1938
1939void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1940{
1941 if ( points.isEmpty() )
1942 return;
1943
1944 QgsRenderContext &rc = context.renderContext();
1945
1946 int i = -1, maxCount = 0;
1947 bool isRing = false;
1948
1949 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1950 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1951 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size(), true ) );
1952
1953 double offsetAlongLine = mOffsetAlongLine;
1955 {
1956 context.setOriginalValueVariable( mOffsetAlongLine );
1958 }
1959
1960 // only calculated if we need it!!
1961 double totalLength = -1;
1962 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1963 {
1964 //scale offset along line
1965 switch ( offsetAlongLineUnit() )
1966 {
1975 break;
1977 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1978 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1979 break;
1980 }
1981 if ( points.isClosed() )
1982 {
1983 if ( offsetAlongLine > 0 )
1984 {
1985 if ( totalLength < 0 )
1986 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1987 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1988 }
1989 else if ( offsetAlongLine < 0 )
1990 {
1991 if ( totalLength < 0 )
1992 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1993 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1994 }
1995 }
1996 }
1997
1998 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1999 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
2003 {
2004 QgsCoordinateTransform ct = context.renderContext().coordinateTransform();
2005 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
2006
2007 QgsVertexId vId;
2008 QgsPoint vPoint;
2009 double x, y, z;
2010 QPointF mapPoint;
2011 int pointNum = 0;
2012 const int numPoints = context.renderContext().geometry()->nCoordinates();
2013 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
2014 {
2015 if ( context.renderContext().renderingStopped() )
2016 break;
2017
2018 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2019
2020 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
2021 continue;
2022
2023 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
2024 continue;
2025
2028 {
2029 //transform
2030 x = vPoint.x();
2031 y = vPoint.y();
2032 z = 0.0;
2033 if ( ct.isValid() )
2034 {
2035 ct.transformInPlace( x, y, z );
2036 }
2037 mapPoint.setX( x );
2038 mapPoint.setY( y );
2039 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
2040 if ( rotateSymbols() )
2041 {
2042 double angle = context.renderContext().geometry()->vertexAngle( vId );
2043 setSymbolLineAngle( angle * 180 / M_PI );
2044 }
2045 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
2046 }
2047 }
2048
2049 return;
2050 }
2051
2052 int pointNum = 0;
2053
2054 switch ( placement )
2055 {
2057 {
2058 i = 0;
2059 maxCount = 1;
2060 break;
2061 }
2062
2064 {
2065 i = points.count() - 1;
2066 pointNum = i;
2067 maxCount = points.count();
2068 break;
2069 }
2070
2072 {
2073 i = 1;
2074 pointNum = 1;
2075 maxCount = points.count() - 1;
2076 break;
2077 }
2078
2081 {
2083 maxCount = points.count();
2084 if ( points.first() == points.last() )
2085 isRing = true;
2086 break;
2087 }
2088
2092 {
2093 return;
2094 }
2095 }
2096
2098 {
2099 double distance;
2101 renderOffsetVertexAlongLine( points, i, distance, context, placement, blankSegments );
2102
2103 return;
2104 }
2105
2106 QPointF prevPoint;
2107 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2108 prevPoint = points.at( 0 );
2109
2110 QPointF symbolPoint;
2111 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2112 for ( ; i < maxCount; ++i )
2113 {
2114 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2115
2116 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2117 {
2118 continue; // don't draw the last marker - it has been drawn already
2119 }
2120
2122 {
2123 QPointF currentPoint = points.at( i );
2124 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2125 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2126 if ( rotateSymbols() )
2127 {
2128 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2129 currentPoint.x() - prevPoint.x() );
2130 setSymbolLineAngle( angle * 180 / M_PI );
2131 }
2132 prevPoint = currentPoint;
2133 }
2134 else
2135 {
2136 symbolPoint = points.at( i );
2137 // rotate marker (if desired)
2138 if ( rotateSymbols() )
2139 {
2140 double angle = markerAngle( points, isRing, i );
2141 setSymbolLineAngle( angle * 180 / M_PI );
2142 }
2143 }
2144
2145 mFinalVertex = symbolPoint;
2146 if ( ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2147 && !blankSegmentsWalker.insideBlankSegment( symbolPoint, i ) )
2148 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2149 }
2150}
2151
2152double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2153{
2154 double angle = 0;
2155 const QPointF &pt = points[vertex];
2156
2157 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2158 {
2159 int prevIndex = vertex - 1;
2160 int nextIndex = vertex + 1;
2161
2162 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2163 {
2164 prevIndex = points.count() - 2;
2165 nextIndex = 1;
2166 }
2167
2168 QPointF prevPoint, nextPoint;
2169 while ( prevIndex >= 0 )
2170 {
2171 prevPoint = points[ prevIndex ];
2172 if ( prevPoint != pt )
2173 {
2174 break;
2175 }
2176 --prevIndex;
2177 }
2178
2179 while ( nextIndex < points.count() )
2180 {
2181 nextPoint = points[ nextIndex ];
2182 if ( nextPoint != pt )
2183 {
2184 break;
2185 }
2186 ++nextIndex;
2187 }
2188
2189 if ( prevIndex >= 0 && nextIndex < points.count() )
2190 {
2191 angle = _averageAngle( prevPoint, pt, nextPoint );
2192 }
2193 }
2194 else //no ring and vertex is at start / at end
2195 {
2196 if ( vertex == 0 )
2197 {
2198 while ( vertex < points.size() - 1 )
2199 {
2200 const QPointF &nextPt = points[vertex + 1];
2201 if ( pt != nextPt )
2202 {
2203 angle = Line( pt, nextPt ).angle();
2204 return angle;
2205 }
2206 ++vertex;
2207 }
2208 }
2209 else
2210 {
2211 // use last segment's angle
2212 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2213 {
2214 const QPointF &prevPt = points[vertex - 1];
2215 if ( pt != prevPt )
2216 {
2217 angle = Line( prevPt, pt ).angle();
2218 return angle;
2219 }
2220 --vertex;
2221 }
2222 }
2223 }
2224 return angle;
2225}
2226
2227void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
2228{
2229 if ( points.isEmpty() )
2230 return;
2231
2232 QgsRenderContext &rc = context.renderContext();
2233 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2234 if ( qgsDoubleNear( distance, 0.0 ) )
2235 {
2236 // rotate marker (if desired)
2237 if ( rotateSymbols() )
2238 {
2239 bool isRing = false;
2240 if ( points.first() == points.last() )
2241 isRing = true;
2242 double angle = markerAngle( points, isRing, vertex );
2243 setSymbolLineAngle( angle * 180 / M_PI );
2244 }
2245 mFinalVertex = points[vertex];
2246 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2247 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2248 return;
2249 }
2250
2251 int pointIncrement = distance > 0 ? 1 : -1;
2252 QPointF previousPoint = points[vertex];
2253 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2254 int endPoint = distance > 0 ? points.count() - 1 : 0;
2255 double distanceLeft = std::fabs( distance );
2256 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2257
2258 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2259 {
2260 const QPointF &pt = points[i];
2261
2262 if ( previousPoint == pt ) // must not be equal!
2263 continue;
2264
2265 // create line segment
2266 Line l( previousPoint, pt );
2267
2268 if ( distanceLeft < l.length() )
2269 {
2270 //destination point is in current segment
2271 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2272 // rotate marker (if desired)
2273 if ( rotateSymbols() )
2274 {
2275 setSymbolLineAngle( l.angle() * 180 / M_PI );
2276 }
2277 mFinalVertex = markerPoint;
2278 if ( ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2279 && !blankSegmentsWalker.insideBlankSegment( markerPoint, i - 1 ) )
2280 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2281 return;
2282 }
2283
2284 distanceLeft -= l.length();
2285 previousPoint = pt;
2286 }
2287
2288 //didn't find point
2289}
2290
2291void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset,
2292 QList<int> *pointIndices,
2293 double initialLag, int numberPointsRequired )
2294{
2295 if ( p.empty() )
2296 return;
2297
2298 QVector< QPointF > points = p;
2299 const bool closedRing = points.first() == points.last();
2300
2301 double lengthLeft = initialOffset;
2302
2303 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2304 if ( initialLagLeft < 0 && closedRing )
2305 {
2306 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2307 QPointF lastPt = points.constLast();
2308 QVector< QPointF > pseudoPoints;
2309 for ( int i = points.count() - 2; i > 0; --i )
2310 {
2311 if ( initialLagLeft >= 0 )
2312 {
2313 break;
2314 }
2315
2316 const QPointF &pt = points[i];
2317
2318 if ( lastPt == pt ) // must not be equal!
2319 continue;
2320
2321 Line l( lastPt, pt );
2322 initialLagLeft += l.length();
2323 lastPt = pt;
2324
2325 pseudoPoints << pt;
2326 }
2327 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2328
2329 points = pseudoPoints;
2330 points.append( p );
2331 }
2332 else
2333 {
2334 while ( initialLagLeft < 0 )
2335 {
2336 dest << points.constFirst();
2337 initialLagLeft += intervalPainterUnits;
2338 }
2339 }
2340 if ( initialLag > 0 )
2341 {
2342 lengthLeft += intervalPainterUnits - initialLagLeft;
2343 }
2344
2345 QPointF lastPt = points[0];
2346 for ( int i = 1; i < points.count(); ++i )
2347 {
2348 const QPointF &pt = points[i];
2349
2350 if ( lastPt == pt ) // must not be equal!
2351 {
2352 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2353 {
2354 lastPt = points[0];
2355 i = 0;
2356 }
2357 continue;
2358 }
2359
2360 // for each line, find out dx and dy, and length
2361 Line l( lastPt, pt );
2362 QPointF diff = l.diffForInterval( intervalPainterUnits );
2363
2364 // if there's some length left from previous line
2365 // use only the rest for the first point in new line segment
2366 double c = 1 - lengthLeft / intervalPainterUnits;
2367
2368 lengthLeft += l.length();
2369
2370
2371 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2372 {
2373 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2374 lastPt += c * diff;
2375 lengthLeft -= intervalPainterUnits;
2376 dest << lastPt;
2377 if ( pointIndices )
2378 *pointIndices << i - 1;
2379 c = 1; // reset c (if wasn't 1 already)
2380
2381 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2382 break;
2383 }
2384 lastPt = pt;
2385
2386 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2387 break;
2388
2389 // if a closed ring, we keep looping around the ring until we hit the required number of points
2390 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2391 {
2392 lastPt = points[0];
2393 i = 0;
2394 }
2395 }
2396
2397 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2398 {
2399 // pad with repeating last point to match desired size
2400 while ( dest.size() < numberPointsRequired )
2401 dest << points.constLast();
2402 }
2403}
2404
2405void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
2406{
2407 if ( !points.isEmpty() )
2408 {
2409 // calc length
2410 qreal length = 0;
2411 QPolygonF::const_iterator it = points.constBegin();
2412 QPointF last = *it;
2413 for ( ++it; it != points.constEnd(); ++it )
2414 {
2415 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2416 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2417 last = *it;
2418 }
2419 if ( qgsDoubleNear( length, 0.0 ) )
2420 return;
2421
2422 const double midPoint = length / 2;
2423
2424 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2425 if ( blankSegmentsWalker.insideBlankSegment( midPoint ) )
2426 return;
2427
2428 QPointF pt;
2429 double thisSymbolAngle = 0;
2430
2431 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2432 {
2433 QVector< QPointF > angleStartPoints;
2434 QVector< QPointF > symbolPoints;
2435 QVector< QPointF > angleEndPoints;
2436
2437 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2438 // already dealt with blank segment before, no need to make them check again
2439 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, nullptr, 0.0, 2 );
2440 collectOffsetPoints( points, angleStartPoints, midPoint, 0, nullptr, averageAngleOver, 2 );
2441 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, nullptr, 0, 2 );
2442
2443 pt = symbolPoints.at( 1 );
2444 Line l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2445 thisSymbolAngle = l.angle();
2446 }
2447 else
2448 {
2449 // find the segment where the central point lies
2450 it = points.constBegin();
2451 last = *it;
2452 qreal last_at = 0, next_at = 0;
2453 QPointF next;
2454 for ( ++it; it != points.constEnd(); ++it )
2455 {
2456 next = *it;
2457 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2458 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2459 if ( next_at >= midPoint )
2460 break; // we have reached the center
2461 last = *it;
2462 last_at = next_at;
2463 }
2464
2465 // find out the central point on segment
2466 Line l( last, next ); // for line angle
2467 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2468 pt = last + ( next - last ) * k;
2469 thisSymbolAngle = l.angle();
2470 }
2471
2472 // draw the marker
2473 // rotate marker (if desired)
2474 if ( rotateSymbols() )
2475 {
2476 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2477 }
2478
2479 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2480 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2481 }
2482}
2483
2488
2490{
2491 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2492 {
2493 delete symbol;
2494 return false;
2495 }
2496
2497 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2498 mColor = mMarker->color();
2499 return true;
2500}
2501
2502
2503
2504//
2505// QgsMarkerLineSymbolLayer
2506//
2507
2513
2515
2517{
2518 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2520
2521 if ( props.contains( u"interval"_s ) )
2522 interval = props[u"interval"_s].toDouble();
2523 if ( props.contains( u"rotate"_s ) )
2524 rotate = ( props[u"rotate"_s].toString() == "1"_L1 );
2525
2526 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2527 setCommonProperties( x.get(), props );
2528 return x.release();
2529}
2530
2532{
2533 return u"MarkerLine"_s;
2534}
2535
2537{
2538 mMarker->setColor( color );
2539 mColor = color;
2540}
2541
2543{
2544 return mMarker ? mMarker->color() : mColor;
2545}
2546
2548{
2549 // if being rotated, it gets initialized with every line segment
2551 if ( rotateSymbols() )
2553 mMarker->setRenderHints( hints );
2554
2555 mMarker->startRender( context.renderContext(), context.fields() );
2556}
2557
2559{
2560 mMarker->stopRender( context.renderContext() );
2561}
2562
2563
2565{
2566 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2568 return x.release();
2569}
2570
2571void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2572{
2573 QgsSldExportContext context;
2574 context.setExtraProperties( props );
2575 toSld( doc, element, context );
2576}
2577
2578bool QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
2579{
2580 const QVariantMap props = context.extraProperties();
2581 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2582 {
2583 QDomElement symbolizerElem = doc.createElement( u"se:LineSymbolizer"_s );
2584 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
2585 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
2586 element.appendChild( symbolizerElem );
2587
2588 // <Geometry>
2589 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
2590
2591 QString gap;
2593 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"firstPoint"_s ) );
2595 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"lastPoint"_s ) );
2597 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"centralPoint"_s ) );
2599 // no way to get line/polygon's vertices, use a VendorOption
2600 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"points"_s ) );
2601
2603 {
2605 gap = qgsDoubleToString( interval );
2606 }
2607
2608 if ( !rotateSymbols() )
2609 {
2610 // markers in LineSymbolizer must be drawn following the line orientation,
2611 // use a VendorOption when no marker rotation
2612 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"rotateMarker"_s, u"0"_s ) );
2613 }
2614
2615 // <Stroke>
2616 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
2617 symbolizerElem.appendChild( strokeElem );
2618
2619 // <GraphicStroke>
2620 QDomElement graphicStrokeElem = doc.createElement( u"se:GraphicStroke"_s );
2621 strokeElem.appendChild( graphicStrokeElem );
2622
2623 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2624 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2625 {
2626 markerLayer->writeSldMarker( doc, graphicStrokeElem, context );
2627 }
2628 else if ( layer )
2629 {
2630 QgsDebugError( u"QgsMarkerSymbolLayer expected, %1 found. Skip it."_s.arg( layer->layerType() ) );
2631 }
2632 else
2633 {
2634 QgsDebugError( u"Missing marker line symbol layer. Skip it."_s );
2635 }
2636
2637 if ( !gap.isEmpty() )
2638 {
2639 QDomElement gapElem = doc.createElement( u"se:Gap"_s );
2640 QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap, context );
2641 graphicStrokeElem.appendChild( gapElem );
2642 }
2643
2644 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2645 {
2646 QDomElement perpOffsetElem = doc.createElement( u"se:PerpendicularOffset"_s );
2648 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2649 symbolizerElem.appendChild( perpOffsetElem );
2650 }
2651 }
2652 return true;
2653}
2654
2656{
2657 QgsDebugMsgLevel( u"Entered."_s, 4 );
2658
2659 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2660 if ( strokeElem.isNull() )
2661 return nullptr;
2662
2663 QDomElement graphicStrokeElem = strokeElem.firstChildElement( u"GraphicStroke"_s );
2664 if ( graphicStrokeElem.isNull() )
2665 return nullptr;
2666
2667 // retrieve vendor options
2668 bool rotateMarker = true;
2670
2671 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2672 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2673 {
2674 if ( it.key() == "placement"_L1 )
2675 {
2676 if ( it.value() == "points"_L1 )
2678 else if ( it.value() == "firstPoint"_L1 )
2680 else if ( it.value() == "lastPoint"_L1 )
2682 else if ( it.value() == "centralPoint"_L1 )
2684 }
2685 else if ( it.value() == "rotateMarker"_L1 )
2686 {
2687 rotateMarker = it.value() == "0"_L1;
2688 }
2689 }
2690
2691 std::unique_ptr< QgsMarkerSymbol > marker;
2692
2693 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicStrokeElem );
2694 if ( l )
2695 {
2696 QgsSymbolLayerList layers;
2697 layers.append( l.release() );
2698 marker = std::make_unique<QgsMarkerSymbol>( layers );
2699 }
2700
2701 if ( !marker )
2702 return nullptr;
2703
2704 double interval = 0.0;
2705 QDomElement gapElem = graphicStrokeElem.firstChildElement( u"Gap"_s );
2706 if ( !gapElem.isNull() )
2707 {
2708 bool ok;
2709 double d = gapElem.firstChild().firstChild().nodeValue().toDouble( &ok );
2710 if ( ok )
2711 interval = d;
2712 }
2713
2714 double offset = 0.0;
2715 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( u"PerpendicularOffset"_s );
2716 if ( !perpOffsetElem.isNull() )
2717 {
2718 bool ok;
2719 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2720 if ( ok )
2721 offset = d;
2722 }
2723
2724 double scaleFactor = 1.0;
2725 const QString uom = element.attribute( u"uom"_s );
2726 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2727 interval = interval * scaleFactor;
2728 offset = offset * scaleFactor;
2729
2731 x->setOutputUnit( sldUnitSize );
2733 x->setInterval( interval );
2734 x->setSubSymbol( marker.release() );
2735 x->setOffset( offset );
2736 return x;
2737}
2738
2740{
2741 mMarker->setSize( width );
2742}
2743
2745{
2746 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2747 {
2748 mMarker->setDataDefinedSize( property );
2749 }
2751}
2752
2754{
2755 const double prevOpacity = mMarker->opacity();
2756 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2758 mMarker->setOpacity( prevOpacity );
2759}
2760
2762{
2763 mMarker->setLineAngle( angle );
2764}
2765
2767{
2768 return mMarker->angle();
2769}
2770
2772{
2773 mMarker->setAngle( angle );
2774}
2775
2776void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2777{
2778 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2780
2781 mMarker->renderPoint( point, feature, context, layer, selected );
2782
2783 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2784}
2785
2787{
2788 return mMarker->size();
2789}
2790
2792{
2793 return mMarker->size( context );
2794}
2795
2801
2812
2814{
2815 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2816 if ( mMarker )
2817 attr.unite( mMarker->usedAttributes( context ) );
2818 return attr;
2819}
2820
2822{
2824 return true;
2825 if ( mMarker && mMarker->hasDataDefinedProperties() )
2826 return true;
2827 return false;
2828}
2829
2831{
2832 return ( mMarker->size( context ) / 2.0 ) +
2834}
2835
2836
2837//
2838// QgsHashedLineSymbolLayer
2839//
2840
2842 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2843{
2844 setSubSymbol( new QgsLineSymbol() );
2845}
2846
2848
2850{
2851 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2853
2854 if ( props.contains( u"interval"_s ) )
2855 interval = props[u"interval"_s].toDouble();
2856 if ( props.contains( u"rotate"_s ) )
2857 rotate = ( props[u"rotate"_s] == "1"_L1 );
2858
2859 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2860 setCommonProperties( x.get(), props );
2861 if ( props.contains( u"hash_angle"_s ) )
2862 {
2863 x->setHashAngle( props[u"hash_angle"_s].toDouble() );
2864 }
2865
2866 if ( props.contains( u"hash_length"_s ) )
2867 x->setHashLength( props[u"hash_length"_s].toDouble() );
2868
2869 if ( props.contains( u"hash_length_unit"_s ) )
2870 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[u"hash_length_unit"_s].toString() ) );
2871
2872 if ( props.contains( u"hash_length_map_unit_scale"_s ) )
2873 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"hash_length_map_unit_scale"_s].toString() ) );
2874
2875 return x.release();
2876}
2877
2879{
2880 return u"HashLine"_s;
2881}
2882
2884{
2885 // if being rotated, it gets initialized with every line segment
2887 if ( rotateSymbols() )
2889 mHashSymbol->setRenderHints( hints );
2890
2891 mHashSymbol->startRender( context.renderContext(), context.fields() );
2892}
2893
2895{
2896 mHashSymbol->stopRender( context.renderContext() );
2897}
2898
2900{
2902 map[ u"hash_angle"_s ] = QString::number( mHashAngle );
2903
2904 map[u"hash_length"_s] = QString::number( mHashLength );
2905 map[u"hash_length_unit"_s] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2906 map[u"hash_length_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2907
2908 return map;
2909}
2910
2912{
2913 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2915 x->setHashAngle( mHashAngle );
2916 x->setHashLength( mHashLength );
2917 x->setHashLengthUnit( mHashLengthUnit );
2918 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2919 return x.release();
2920}
2921
2923{
2924 mHashSymbol->setColor( color );
2925 mColor = color;
2926}
2927
2929{
2930 return mHashSymbol ? mHashSymbol->color() : mColor;
2931}
2932
2934{
2935 return mHashSymbol.get();
2936}
2937
2939{
2940 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2941 {
2942 delete symbol;
2943 return false;
2944 }
2945
2946 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2947 mColor = mHashSymbol->color();
2948 return true;
2949}
2950
2952{
2953 mHashLength = width;
2954}
2955
2957{
2958 return mHashLength;
2959}
2960
2962{
2963 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2964}
2965
2967{
2968 return ( mHashSymbol->width( context ) / 2.0 )
2969 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2971}
2972
2974{
2976 mHashSymbol->setOutputUnit( unit );
2977}
2978
2980{
2981 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2982 if ( mHashSymbol )
2983 attr.unite( mHashSymbol->usedAttributes( context ) );
2984 return attr;
2985}
2986
2988{
2990 return true;
2991 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2992 return true;
2993 return false;
2994}
2995
2997{
2998 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
2999 {
3000 mHashSymbol->setDataDefinedWidth( property );
3001 }
3003}
3004
3016
3018{
3019 mSymbolLineAngle = angle;
3020}
3021
3023{
3024 return mSymbolAngle;
3025}
3026
3028{
3029 mSymbolAngle = angle;
3030}
3031
3032void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
3033{
3034 double lineLength = mHashLength;
3036 {
3037 context.expressionContext().setOriginalValueVariable( mHashLength );
3038 lineLength = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::LineDistance, context.expressionContext(), lineLength );
3039 }
3040 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
3041
3042 double hashAngle = mHashAngle;
3044 {
3045 context.expressionContext().setOriginalValueVariable( mHashAngle );
3047 }
3048
3049 QgsPointXY center( point );
3050 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3051 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3052
3053 QPolygonF points;
3054 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
3055
3056 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3058
3059 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
3060
3061 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
3062}
3063
3065{
3066 return mHashAngle;
3067}
3068
3070{
3071 mHashAngle = angle;
3072}
3073
3075{
3076 const double prevOpacity = mHashSymbol->opacity();
3077 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
3079 mHashSymbol->setOpacity( prevOpacity );
3080}
3081
3082//
3083// QgsAbstractBrushedLineSymbolLayer
3084//
3085
3086void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
3087{
3088 if ( !context.renderContext().painter() )
3089 return;
3090
3091 double offset = mOffset;
3093 {
3096 }
3097
3098 QPolygonF offsetPoints;
3099 if ( qgsDoubleNear( offset, 0 ) )
3100 {
3101 renderLine( points, context, patternThickness, patternLength, brush );
3102 }
3103 else
3104 {
3105 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3106
3107 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3108 for ( const QPolygonF &part : offsetLine )
3109 {
3110 renderLine( part, context, patternThickness, patternLength, brush );
3111 }
3112 }
3113}
3114
3115void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3116 const double patternLength, const QBrush &sourceBrush )
3117{
3118 QPainter *p = context.renderContext().painter();
3119 if ( !p )
3120 return;
3121
3122 QBrush brush = sourceBrush;
3123
3124 // duplicate points mess up the calculations, we need to remove them first
3125 // we'll calculate the min/max coordinate at the same time, since we're already looping
3126 // through the points
3127 QPolygonF inputPoints;
3128 inputPoints.reserve( points.size() );
3129 QPointF prev;
3130 double minX = std::numeric_limits< double >::max();
3131 double minY = std::numeric_limits< double >::max();
3132 double maxX = std::numeric_limits< double >::lowest();
3133 double maxY = std::numeric_limits< double >::lowest();
3134
3135 for ( const QPointF &pt : std::as_const( points ) )
3136 {
3137 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3138 continue;
3139
3140 inputPoints << pt;
3141 prev = pt;
3142 minX = std::min( minX, pt.x() );
3143 minY = std::min( minY, pt.y() );
3144 maxX = std::max( maxX, pt.x() );
3145 maxY = std::max( maxY, pt.y() );
3146 }
3147
3148 if ( inputPoints.size() < 2 ) // nothing to render
3149 return;
3150
3151 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3152 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3153 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3154 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3155
3156 // 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
3157 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3158 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3159
3160 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3161 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3162
3163 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3164 if ( temporaryImage.isNull() )
3165 {
3166 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3167 return;
3168 }
3169
3170 // clear temporary image contents
3171 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3172 return;
3173 temporaryImage.fill( Qt::transparent );
3174 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3175 return;
3176
3177 Qt::PenJoinStyle join = mPenJoinStyle;
3179 {
3182 if ( !QgsVariantUtils::isNull( exprVal ) )
3183 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3184 }
3185
3186 Qt::PenCapStyle cap = mPenCapStyle;
3188 {
3191 if ( !QgsVariantUtils::isNull( exprVal ) )
3192 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3193 }
3194
3195 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3196 QPainterPathStroker stroker;
3197 stroker.setWidth( lineThickness );
3198 stroker.setCapStyle( cap );
3199 stroker.setJoinStyle( join );
3200
3201 QPainterPath path;
3202 path.addPolygon( inputPoints );
3203 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3204
3205 // prepare temporary image
3206 QPainter imagePainter;
3207 imagePainter.begin( &temporaryImage );
3208 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3209 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3210
3211 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3212 imagePainter.setPen( Qt::NoPen );
3213
3214 QPointF segmentStartPoint = inputPoints.at( 0 );
3215
3216 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3217 double progressThroughImage = 0;
3218
3219 QgsPoint prevSegmentPolygonEndLeft;
3220 QgsPoint prevSegmentPolygonEndRight;
3221
3222 // for closed rings this will store the left/right polygon points of the start/end of the line
3223 QgsPoint startLinePolygonLeft;
3224 QgsPoint startLinePolygonRight;
3225
3226 for ( int i = 1; i < inputPoints.size(); ++i )
3227 {
3228 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3229 break;
3230
3231 const QPointF segmentEndPoint = inputPoints.at( i );
3232 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3233 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3234
3235 // left/right end points of the current segment polygon
3236 QgsPoint thisSegmentPolygonEndLeft;
3237 QgsPoint thisSegmentPolygonEndRight;
3238 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3239 QgsPoint thisSegmentPolygonEndLeftForPainter;
3240 QgsPoint thisSegmentPolygonEndRightForPainter;
3241 if ( i == 1 )
3242 {
3243 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3244 // (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.)
3245 if ( isClosedLine )
3246 {
3247 // project the current segment out by half the image thickness to either side of the line
3248 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3249 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3250 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3251 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3252
3253 // 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
3254 // what angle the current segment polygon should START on.
3255 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3256 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3257
3258 // project out the LAST segment in the line by half the image thickness to either side of the line
3259 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3260 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3261 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3262 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3263
3264 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3265 // 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
3266 // join)
3267 QgsPoint intersectionPoint;
3268 bool isIntersection = false;
3269 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3270 if ( !isIntersection )
3271 prevSegmentPolygonEndLeft = startPointLeft;
3272 isIntersection = false;
3273 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3274 if ( !isIntersection )
3275 prevSegmentPolygonEndRight = startPointRight;
3276
3277 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3278 startLinePolygonRight = prevSegmentPolygonEndRight;
3279 }
3280 else
3281 {
3282 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3283 if ( cap != Qt::PenCapStyle::FlatCap )
3284 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3285 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3286 if ( cap != Qt::PenCapStyle::FlatCap )
3287 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3288 }
3289 }
3290
3291 if ( i < inputPoints.size() - 1 )
3292 {
3293 // for all other segments except the last
3294
3295 // project the current segment out by half the image thickness to either side of the line
3296 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3297 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3298 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3299 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3300
3301 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3302 // what angle the current segment polygon should end on
3303 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3304 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3305
3306 // project out the next segment by half the image thickness to either side of the line
3307 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3308 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3309 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3310 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3311
3312 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3313 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3314 // join)
3315 QgsPoint intersectionPoint;
3316 bool isIntersection = false;
3317 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3318 if ( !isIntersection )
3319 thisSegmentPolygonEndLeft = endPointLeft;
3320 isIntersection = false;
3321 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3322 if ( !isIntersection )
3323 thisSegmentPolygonEndRight = endPointRight;
3324
3325 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3326 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3327 }
3328 else
3329 {
3330 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3331 // unless it's a closed line
3332 if ( isClosedLine )
3333 {
3334 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3335 thisSegmentPolygonEndRight = startLinePolygonRight;
3336
3337 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3338 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3339 }
3340 else
3341 {
3342 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3343 if ( cap != Qt::PenCapStyle::FlatCap )
3344 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3345 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3346 if ( cap != Qt::PenCapStyle::FlatCap )
3347 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3348
3349 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3350 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3351 }
3352 }
3353
3354 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3355 // where we got with the previous segment), at the correct angle
3356 QTransform brushTransform;
3357 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3358 brushTransform.rotate( -segmentAngleDegrees );
3359 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3360 {
3361 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3362 // we need to also do the same for the brush transform
3363 brushTransform.translate( -( lineThickness / 2 ), 0 );
3364 }
3365 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3366
3367 brush.setTransform( brushTransform );
3368 imagePainter.setBrush( brush );
3369
3370 // now draw the segment polygon
3371 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3372 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3373 << thisSegmentPolygonEndRightForPainter.toQPointF()
3374 << prevSegmentPolygonEndRight.toQPointF()
3375 << prevSegmentPolygonEndLeft.toQPointF() );
3376
3377#if 0 // for debugging, will draw the segment polygons
3378 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3379 imagePainter.setBrush( Qt::NoBrush );
3380 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3381 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3382 << thisSegmentPolygonEndRightForPainter.toQPointF()
3383 << prevSegmentPolygonEndRight.toQPointF()
3384 << prevSegmentPolygonEndLeft.toQPointF() );
3385 imagePainter.setPen( Qt::NoPen );
3386#endif
3387
3388 // calculate the new progress horizontal through the source image to account for the length
3389 // of the segment we've just drawn
3390 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3391 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3392 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3393 progressThroughImage = fmod( progressThroughImage, patternLength );
3394
3395 // shuffle buffered variables for next loop
3396 segmentStartPoint = segmentEndPoint;
3397 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3398 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3399 }
3400 imagePainter.end();
3401
3402 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3403 return;
3404
3405 // lastly, draw the temporary image onto the destination painter at the correct place
3406 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3407 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3408}
3409
3410
3411//
3412// QgsRasterLineSymbolLayer
3413//
3414
3419
3421
3423{
3424 auto res = std::make_unique<QgsRasterLineSymbolLayer>();
3425
3426 if ( properties.contains( u"line_width"_s ) )
3427 {
3428 res->setWidth( properties[u"line_width"_s].toDouble() );
3429 }
3430 if ( properties.contains( u"line_width_unit"_s ) )
3431 {
3432 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
3433 }
3434 if ( properties.contains( u"width_map_unit_scale"_s ) )
3435 {
3436 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
3437 }
3438
3439 if ( properties.contains( u"imageFile"_s ) )
3440 res->setPath( properties[u"imageFile"_s].toString() );
3441
3442 if ( properties.contains( u"offset"_s ) )
3443 {
3444 res->setOffset( properties[u"offset"_s].toDouble() );
3445 }
3446 if ( properties.contains( u"offset_unit"_s ) )
3447 {
3448 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
3449 }
3450 if ( properties.contains( u"offset_map_unit_scale"_s ) )
3451 {
3452 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
3453 }
3454
3455 if ( properties.contains( u"joinstyle"_s ) )
3456 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[u"joinstyle"_s].toString() ) );
3457 if ( properties.contains( u"capstyle"_s ) )
3458 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[u"capstyle"_s].toString() ) );
3459
3460 if ( properties.contains( u"alpha"_s ) )
3461 {
3462 res->setOpacity( properties[u"alpha"_s].toDouble() );
3463 }
3464
3465 return res.release();
3466}
3467
3468
3470{
3471 QVariantMap map;
3472 map[u"imageFile"_s] = mPath;
3473
3474 map[u"line_width"_s] = QString::number( mWidth );
3475 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
3476 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3477
3480
3481 map[u"offset"_s] = QString::number( mOffset );
3482 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3483 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3484
3485 map[u"alpha"_s] = QString::number( mOpacity );
3486
3487 return map;
3488}
3489
3491{
3492 auto res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3493 res->setWidth( mWidth );
3494 res->setWidthUnit( mWidthUnit );
3495 res->setWidthMapUnitScale( mWidthMapUnitScale );
3496 res->setPenJoinStyle( mPenJoinStyle );
3497 res->setPenCapStyle( mPenCapStyle );
3498 res->setOffsetUnit( mOffsetUnit );
3499 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3500 res->setOffset( mOffset );
3501 res->setOpacity( mOpacity );
3502 copyCommonProperties( res.get() );
3503 return res.release();
3504}
3505
3506void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3507{
3508 const QVariantMap::iterator it = properties.find( u"imageFile"_s );
3509 if ( it != properties.end() && it.value().userType() == QMetaType::Type::QString )
3510 {
3511 if ( saving )
3512 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3513 else
3514 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3515 }
3516}
3517
3519{
3520 mPath = path;
3521}
3522
3524{
3525 return u"RasterLine"_s;
3526}
3527
3532
3534{
3535 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3536
3538
3539 double opacity = mOpacity * context.opacity();
3540 bool cached = false;
3542 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3543 static_cast< int >( std::ceil( scaledHeight ) ) ),
3544 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3545}
3546
3550
3552{
3553 if ( !context.renderContext().painter() )
3554 return;
3555
3556 QImage sourceImage = mLineImage;
3560 {
3561 QString path = mPath;
3563 {
3564 context.setOriginalValueVariable( path );
3566 }
3567
3568 double strokeWidth = mWidth;
3570 {
3571 context.setOriginalValueVariable( strokeWidth );
3572 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3573 }
3574 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3575
3577 double opacity = mOpacity;
3579 {
3582 }
3583 opacity *= context.opacity();
3584
3585 bool cached = false;
3587 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3588 static_cast< int >( std::ceil( scaledHeight ) ) ),
3589 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3590 }
3591
3592 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3593 if ( useSelectedColor )
3594 {
3596 }
3597
3598 const QBrush brush( sourceImage );
3599
3600 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3601}
3602
3609
3611{
3613 if ( mWidthUnit != unit || mOffsetUnit != unit )
3614 {
3616 }
3617 return unit;
3618}
3619
3625
3631
3641
3643{
3644 return ( mWidth / 2.0 ) + mOffset;
3645}
3646
3648{
3649 return QColor();
3650}
3651
3652
3653//
3654// QgsLineburstSymbolLayer
3655//
3656
3663
3665
3667{
3668 auto res = std::make_unique<QgsLineburstSymbolLayer>();
3669
3670 if ( properties.contains( u"line_width"_s ) )
3671 {
3672 res->setWidth( properties[u"line_width"_s].toDouble() );
3673 }
3674 if ( properties.contains( u"line_width_unit"_s ) )
3675 {
3676 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
3677 }
3678 if ( properties.contains( u"width_map_unit_scale"_s ) )
3679 {
3680 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
3681 }
3682
3683 if ( properties.contains( u"offset"_s ) )
3684 {
3685 res->setOffset( properties[u"offset"_s].toDouble() );
3686 }
3687 if ( properties.contains( u"offset_unit"_s ) )
3688 {
3689 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
3690 }
3691 if ( properties.contains( u"offset_map_unit_scale"_s ) )
3692 {
3693 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
3694 }
3695
3696 if ( properties.contains( u"joinstyle"_s ) )
3697 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[u"joinstyle"_s].toString() ) );
3698 if ( properties.contains( u"capstyle"_s ) )
3699 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[u"capstyle"_s].toString() ) );
3700
3701 if ( properties.contains( u"color_type"_s ) )
3702 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[u"color_type"_s].toInt() ) );
3703
3704 if ( properties.contains( u"color"_s ) )
3705 {
3706 res->setColor( QgsColorUtils::colorFromString( properties[u"color"_s].toString() ) );
3707 }
3708 if ( properties.contains( u"gradient_color2"_s ) )
3709 {
3710 res->setColor2( QgsColorUtils::colorFromString( properties[u"gradient_color2"_s].toString() ) );
3711 }
3712
3713 //attempt to create color ramp from props
3714 if ( properties.contains( u"rampType"_s ) && properties[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
3715 {
3716 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3717 }
3718 else
3719 {
3720 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3721 }
3722
3723 return res.release();
3724}
3725
3727{
3728 QVariantMap map;
3729
3730 map[u"line_width"_s] = QString::number( mWidth );
3731 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
3732 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3733
3736
3737 map[u"offset"_s] = QString::number( mOffset );
3738 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3739 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3740
3741 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
3742 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
3743 map[u"color_type"_s] = QString::number( static_cast< int >( mGradientColorType ) );
3744 if ( mGradientRamp )
3745 {
3746 map.insert( mGradientRamp->properties() );
3747 }
3748
3749 return map;
3750}
3751
3753{
3754 auto res = std::make_unique< QgsLineburstSymbolLayer >();
3755 res->setWidth( mWidth );
3756 res->setWidthUnit( mWidthUnit );
3757 res->setWidthMapUnitScale( mWidthMapUnitScale );
3758 res->setPenJoinStyle( mPenJoinStyle );
3759 res->setPenCapStyle( mPenCapStyle );
3760 res->setOffsetUnit( mOffsetUnit );
3761 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3762 res->setOffset( mOffset );
3763 res->setColor( mColor );
3764 res->setColor2( mColor2 );
3765 res->setGradientColorType( mGradientColorType );
3766 if ( mGradientRamp )
3767 res->setColorRamp( mGradientRamp->clone() );
3768 copyCommonProperties( res.get() );
3769 return res.release();
3770}
3771
3773{
3774 return u"Lineburst"_s;
3775}
3776
3781
3785
3789
3791{
3792 if ( !context.renderContext().painter() )
3793 return;
3794
3795 double strokeWidth = mWidth;
3797 {
3798 context.setOriginalValueVariable( strokeWidth );
3799 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3800 }
3801 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3802
3803 //update alpha of gradient colors
3804 QColor color1 = mColor;
3806 {
3809 }
3810 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3811 if ( useSelectedColor )
3812 {
3813 color1 = context.renderContext().selectionColor();
3814 }
3815 color1.setAlphaF( context.opacity() * color1.alphaF() );
3816
3817 //second gradient color
3818 QColor color2 = mColor2;
3820 {
3823 }
3824
3825 //create a QGradient with the desired properties
3826 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3827 //add stops to gradient
3830 {
3831 //color ramp gradient
3832 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3833 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3834 }
3835 else
3836 {
3837 //two color gradient
3838 gradient.setColorAt( 0.0, color1 );
3839 gradient.setColorAt( 1.0, color2 );
3840 }
3841 const QBrush brush( gradient );
3842
3843 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3844}
3845
3852
3854{
3856 if ( mWidthUnit != unit || mOffsetUnit != unit )
3857 {
3859 }
3860 return unit;
3861}
3862
3868
3874
3884
3886{
3887 return ( mWidth / 2.0 ) + mOffset;
3888}
3889
3894
3896{
3897 mGradientRamp.reset( ramp );
3898}
3899
3900//
3901// QgsFilledLineSymbolLayer
3902//
3903
3906{
3907 mWidth = width;
3908 mFill = fillSymbol ? std::unique_ptr< QgsFillSymbol >( fillSymbol ) : QgsFillSymbol::createSimple( QVariantMap() );
3909}
3910
3912
3914{
3916
3917 // throughout the history of QGIS and different layer types, we've used
3918 // a huge range of different strings for the same property. The logic here
3919 // is designed to be forgiving to this and accept a range of string keys:
3920 if ( props.contains( u"line_width"_s ) )
3921 {
3922 width = props[u"line_width"_s].toDouble();
3923 }
3924 else if ( props.contains( u"outline_width"_s ) )
3925 {
3926 width = props[u"outline_width"_s].toDouble();
3927 }
3928 else if ( props.contains( u"width"_s ) )
3929 {
3930 width = props[u"width"_s].toDouble();
3931 }
3932
3933 auto l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ).release() );
3934
3935 if ( props.contains( u"line_width_unit"_s ) )
3936 {
3937 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"line_width_unit"_s].toString() ) );
3938 }
3939 else if ( props.contains( u"outline_width_unit"_s ) )
3940 {
3941 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"outline_width_unit"_s].toString() ) );
3942 }
3943 else if ( props.contains( u"width_unit"_s ) )
3944 {
3945 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"width_unit"_s].toString() ) );
3946 }
3947
3948 if ( props.contains( u"width_map_unit_scale"_s ) )
3949 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"width_map_unit_scale"_s].toString() ) );
3950 if ( props.contains( u"offset"_s ) )
3951 l->setOffset( props[u"offset"_s].toDouble() );
3952 if ( props.contains( u"offset_unit"_s ) )
3953 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
3954 if ( props.contains( u"offset_map_unit_scale"_s ) )
3955 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
3956 if ( props.contains( u"joinstyle"_s ) )
3957 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[u"joinstyle"_s].toString() ) );
3958 if ( props.contains( u"capstyle"_s ) )
3959 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[u"capstyle"_s].toString() ) );
3960
3961 l->restoreOldDataDefinedProperties( props );
3962
3963 return l.release();
3964}
3965
3967{
3968 return u"FilledLine"_s;
3969}
3970
3972{
3973 if ( mFill )
3974 {
3975 mFill->setRenderHints( mFill->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3976 mFill->startRender( context.renderContext(), context.fields() );
3977 }
3978}
3979
3981{
3982 if ( mFill )
3983 {
3984 mFill->stopRender( context.renderContext() );
3985 }
3986}
3987
3989{
3990 installMasks( context, true );
3991
3992 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3993}
3994
3996{
3997 removeMasks( context, true );
3998
3999 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4000}
4001
4003{
4004 QPainter *p = context.renderContext().painter();
4005 if ( !p || !mFill )
4006 return;
4007
4008 double width = mWidth;
4010 {
4013 }
4014
4015 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
4016
4017 Qt::PenJoinStyle join = mPenJoinStyle;
4019 {
4022 if ( !QgsVariantUtils::isNull( exprVal ) )
4023 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
4024 }
4025
4026 Qt::PenCapStyle cap = mPenCapStyle;
4028 {
4031 if ( !QgsVariantUtils::isNull( exprVal ) )
4032 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
4033 }
4034
4035 double offset = mOffset;
4037 {
4040 }
4041
4042 const double prevOpacity = mFill->opacity();
4043 mFill->setOpacity( mFill->opacity() * context.opacity() );
4044
4045 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4047
4048 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4049
4050 if ( points.count() >= 2 )
4051 {
4052 std::unique_ptr< QgsAbstractGeometry > ls = QgsLineString::fromQPolygonF( points );
4053 geos::unique_ptr lineGeom;
4054
4055 if ( !qgsDoubleNear( offset, 0 ) )
4056 {
4057 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
4059 {
4060 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
4061 // and clamp it to a reasonable range. It's the best we can do in this situation!
4062 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
4063 }
4064
4066 if ( geometryType == Qgis::GeometryType::Polygon )
4067 {
4068 auto inputPoly = std::make_unique< QgsPolygon >( static_cast< QgsLineString * >( ls.release() ) );
4069 geos::unique_ptr g( QgsGeos::asGeos( inputPoly.get() ) );
4070 lineGeom = QgsGeos::buffer( g.get(), -scaledOffset, 0, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Miter, 2 );
4071 // the result is a polygon => extract line work
4072 QgsGeometry polygon( QgsGeos::fromGeos( lineGeom.get() ) );
4073 QVector<QgsGeometry> parts = polygon.coerceToType( Qgis::WkbType::MultiLineString );
4074 if ( !parts.empty() )
4075 {
4076 lineGeom = QgsGeos::asGeos( parts.at( 0 ).constGet() );
4077 }
4078 else
4079 {
4080 lineGeom.reset();
4081 }
4082 }
4083 else
4084 {
4085 geos::unique_ptr g( QgsGeos::asGeos( ls.get() ) );
4086 lineGeom = QgsGeos::offsetCurve( g.get(), scaledOffset, 0, Qgis::JoinStyle::Miter, 8.0 );
4087 }
4088 }
4089 else
4090 {
4091 lineGeom = QgsGeos::asGeos( ls.get() );
4092 }
4093
4094 if ( lineGeom )
4095 {
4096 geos::unique_ptr buffered = QgsGeos::buffer( lineGeom.get(), scaledWidth / 2, 8,
4099 if ( buffered )
4100 {
4101 // convert to rings
4102 std::unique_ptr< QgsAbstractGeometry > bufferedGeom = QgsGeos::fromGeos( buffered.get() );
4103 const QList< QList< QPolygonF > > parts = QgsSymbolLayerUtils::toQPolygonF( bufferedGeom.get(), Qgis::SymbolType::Fill );
4104 for ( const QList< QPolygonF > &polygon : parts )
4105 {
4106 QVector< QPolygonF > rings;
4107 for ( int i = 1; i < polygon.size(); ++i )
4108 rings << polygon.at( i );
4109 mFill->renderPolygon( polygon.value( 0 ), &rings, context.feature(), context.renderContext(), -1, useSelectedColor );
4110 }
4111 }
4112 }
4113 }
4114
4116
4117 mFill->setOpacity( prevOpacity );
4118}
4119
4121{
4122 QVariantMap map;
4123
4124 map[u"line_width"_s] = QString::number( mWidth );
4125 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
4126 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4127 map[u"joinstyle"_s] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
4128 map[u"capstyle"_s] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
4129 map[u"offset"_s] = QString::number( mOffset );
4130 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4131 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4132 if ( mFill )
4133 {
4134 map[u"color"_s] = QgsColorUtils::colorToString( mFill->color() );
4135 }
4136 return map;
4137}
4138
4140{
4141 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4142 copyCommonProperties( res.get() );
4143 res->setSubSymbol( mFill->clone() );
4144 return res.release();
4145}
4146
4148{
4149 return mFill.get();
4150}
4151
4153{
4154 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4155 {
4156 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4157 return true;
4158 }
4159 else
4160 {
4161 delete symbol;
4162 return false;
4163 }
4164}
4165
4167{
4168 if ( mFill )
4169 {
4170 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4171 }
4172 return 0;
4173}
4174
4176{
4177 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4178 if ( mFill )
4179 attr.unite( mFill->usedAttributes( context ) );
4180 return attr;
4181}
4182
4184{
4186 return true;
4187 if ( mFill && mFill->hasDataDefinedProperties() )
4188 return true;
4189 return false;
4190}
4191
4193{
4194 mColor = c;
4195 if ( mFill )
4196 mFill->setColor( c );
4197}
4198
4200{
4201 return mFill ? mFill->color() : mColor;
4202}
4203
4210
4212{
4214 if ( mFill )
4215 mFill->setMapUnitScale( scale );
4216}
4217
4219{
4220 if ( mFill )
4221 {
4222 return mFill->mapUnitScale();
4223 }
4224 return QgsMapUnitScale();
4225}
4226
4228{
4230 if ( mFill )
4231 mFill->setOutputUnit( unit );
4232}
4233
4235{
4236 if ( mFill )
4237 {
4238 return mFill->outputUnit();
4239 }
4241}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:3215
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
Definition qgis.h:3221
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex).
Definition qgis.h:3223
@ LastVertex
Place symbols on the last vertex in the line.
Definition qgis.h:3218
@ CentralPoint
Place symbols at the mid point of the line.
Definition qgis.h:3220
@ SegmentCenter
Place symbols at the center of every line segment.
Definition qgis.h:3222
@ Vertex
Place symbols on every vertex in the line.
Definition qgis.h:3217
@ Interval
Place symbols at regular intervals.
Definition qgis.h:3216
@ FirstVertex
Place symbols on the first vertex in the line.
Definition qgis.h:3219
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
Definition qgis.h:791
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:792
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
Definition qgis.h:3105
GradientColorSource
Gradient color sources.
Definition qgis.h:3264
@ ColorRamp
Gradient color ramp.
Definition qgis.h:3266
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
Definition qgis.h:903
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3153
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3152
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:908
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:376
@ Line
Lines.
Definition qgis.h:378
@ Polygon
Polygons.
Definition qgis.h:379
@ Unknown
Unknown types.
Definition qgis.h:380
@ Miter
Use mitered joins.
Definition qgis.h:2196
RenderUnit
Rendering size units.
Definition qgis.h:5305
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5309
@ Millimeters
Millimeters.
Definition qgis.h:5306
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5310
@ Unknown
Mixed or unknown units.
Definition qgis.h:5312
@ MapUnits
Map units.
Definition qgis.h:5307
@ Pixels
Pixels.
Definition qgis.h:5308
@ Inches
Inches.
Definition qgis.h:5311
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5313
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2183
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2838
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2833
@ RenderLayerTree
The render is for a layer tree display where map based properties are not available and where avoidan...
Definition qgis.h:2844
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2832
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:798
@ Marker
Marker symbol.
Definition qgis.h:632
@ Line
Line symbol.
Definition qgis.h:633
@ Fill
Fill symbol.
Definition qgis.h:634
@ MultiLineString
MultiLineString.
Definition qgis.h:298
QFlags< MarkerLinePlacement > MarkerLinePlacements
Definition qgis.h:3226
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:2758
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2094
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:1575
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
Represents a 2D point.
Definition qgspointxy.h:62
QgsPointXY project(double distance, double bearing) const
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
QPointF toQPointF() const
Returns the point as a QPointF.
Definition qgspoint.h:409
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:728
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)
void copyCommonProperties(QgsSymbolLayer *destLayer) const
Copies all common base class properties from this layer to another symbol layer.
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
Property
Data definable properties.
@ SecondaryColor
Secondary color (eg for gradient fills).
@ File
Filename, eg for svg files.
@ BlankSegments
String list of distance to define blank segments along line for templated line symbol layers.
@ DashPatternOffset
Dash pattern offset,.
@ OffsetAlongLine
Offset along line.
@ CustomDash
Custom dash pattern.
@ StrokeStyle
Stroke style (eg solid, dashed).
@ TrimStart
Trim distance from start of line.
@ CapStyle
Line cap style.
@ Placement
Line marker placement.
@ LineAngle
Line angle, or angle of hash lines for hash line symbols.
@ JoinStyle
Line join style.
@ AverageAngleLength
Length to average symbol angles over.
@ Interval
Line marker interval.
@ LineDistance
Distance between lines, or length of lines for hash line symbols.
@ TrimEnd
Trim distance from end of line.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
virtual void setColor(const QColor &color)
Sets the "representative" color for the symbol layer.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
QgsPropertyCollection mDataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsSymbolLayer(const QgsSymbolLayer &other)
Encapsulates the context in which a symbol is being rendered.
const QgsFeature * feature() const
Returns the current feature being rendered.
Qgis::GeometryType originalGeometryType() const
Returns the geometry type for the original feature geometry being rendered.
QgsFields fields() const
Fields of the layer.
int geometryPartNum() const
Part number of current geometry.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h: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:6867
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7199
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:7221
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6950
QMap< QString, QString > QgsStringMap
Definition qgis.h:7463
#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:82