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