QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
386{
387 QPainter *p = context.renderContext().painter();
388 if ( !p )
389 {
390 return;
391 }
392
393 QPolygonF points = pts;
394
395 double startTrim = mTrimDistanceStart;
397 {
398 context.setOriginalValueVariable( startTrim );
399 startTrim = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::TrimStart, context.renderContext().expressionContext(), mTrimDistanceStart );
400 }
401 double endTrim = mTrimDistanceEnd;
403 {
404 context.setOriginalValueVariable( endTrim );
405 endTrim = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::TrimEnd, context.renderContext().expressionContext(), mTrimDistanceEnd );
406 }
407
408 double totalLength = -1;
409 if ( mTrimDistanceStartUnit == Qgis::RenderUnit::Percentage )
410 {
411 totalLength = QgsSymbolLayerUtils::polylineLength( points );
412 startTrim = startTrim * 0.01 * totalLength;
413 }
414 else
415 {
416 startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
417 }
418 if ( mTrimDistanceEndUnit == Qgis::RenderUnit::Percentage )
419 {
420 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
421 totalLength = QgsSymbolLayerUtils::polylineLength( points );
422 endTrim = endTrim * 0.01 * totalLength;
423 }
424 else
425 {
426 endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
427 }
428 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
429 {
430 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
431 }
432
433 QColor penColor = mColor;
434 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
435 mPen.setColor( penColor );
436
437 double offset = mOffset;
438 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
439
440 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
441 const QPen pen = useSelectedColor ? mSelPen : mPen;
442
443 if ( !pen.dashPattern().isEmpty() )
444 {
445 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
446 const QVector<double> pattern = pen.dashPattern();
447 bool foundNonNull = false;
448 for ( int i = 0; i < pattern.size(); ++i )
449 {
450 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
451 {
452 foundNonNull = true;
453 break;
454 }
455 }
456 if ( !foundNonNull )
457 return;
458 }
459
460 p->setBrush( Qt::NoBrush );
461
462 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
463 std::unique_ptr< QgsScopedQPainterState > painterState;
464 if ( points.size() <= 2
467 && ( p->renderHints() & QPainter::Antialiasing ) )
468 {
469 painterState = std::make_unique< QgsScopedQPainterState >( p );
470 p->setRenderHint( QPainter::Antialiasing, false );
471 }
472
473 const bool applyPatternTweaks = mAlignDashPattern && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() ) && pen.dashOffset() == 0;
474
475 if ( qgsDoubleNear( offset, 0 ) )
476 {
477 if ( applyPatternTweaks )
478 {
479 drawPathWithDashPatternTweaks( p, points, pen );
480 }
481 else
482 {
483 p->setPen( pen );
484 QPainterPath path;
485 path.addPolygon( points );
486 p->drawPath( path );
487 }
488 }
489 else
490 {
491 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
494 {
495 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
496 // and clamp it to a reasonable range. It's the best we can do in this situation!
497 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
498 }
499
500 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
501 for ( const QPolygonF &part : mline )
502 {
503 if ( applyPatternTweaks )
504 {
505 drawPathWithDashPatternTweaks( p, part, pen );
506 }
507 else
508 {
509 p->setPen( pen );
510 QPainterPath path;
511 path.addPolygon( part );
512 p->drawPath( path );
513 }
514 }
515 }
516}
517
519{
520 QVariantMap map;
521 map[u"line_color"_s] = QgsColorUtils::colorToString( mColor );
522 map[u"line_width"_s] = QString::number( mWidth );
523 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
524 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
525 map[u"line_style"_s] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
526 map[u"joinstyle"_s] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
527 map[u"capstyle"_s] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
528 map[u"offset"_s] = QString::number( mOffset );
529 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
530 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
531 map[u"use_custom_dash"_s] = ( mUseCustomDashPattern ? u"1"_s : u"0"_s );
532 map[u"customdash"_s] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
533 map[u"customdash_unit"_s] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
534 map[u"customdash_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
535 map[u"dash_pattern_offset"_s] = QString::number( mDashPatternOffset );
536 map[u"dash_pattern_offset_unit"_s] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
537 map[u"dash_pattern_offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
538 map[u"trim_distance_start"_s] = QString::number( mTrimDistanceStart );
539 map[u"trim_distance_start_unit"_s] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
540 map[u"trim_distance_start_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
541 map[u"trim_distance_end"_s] = QString::number( mTrimDistanceEnd );
542 map[u"trim_distance_end_unit"_s] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
543 map[u"trim_distance_end_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
544 map[u"draw_inside_polygon"_s] = ( mDrawInsidePolygon ? u"1"_s : u"0"_s );
545 map[u"ring_filter"_s] = QString::number( static_cast< int >( mRingFilter ) );
546 map[u"align_dash_pattern"_s] = mAlignDashPattern ? u"1"_s : u"0"_s;
547 map[u"tweak_dash_pattern_on_corners"_s] = mPatternCartographicTweakOnSharpCorners ? u"1"_s : u"0"_s;
548 return map;
549}
550
552{
558 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
559 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
560 l->setOffset( mOffset );
561 l->setPenJoinStyle( mPenJoinStyle );
562 l->setPenCapStyle( mPenCapStyle );
563 l->setUseCustomDashPattern( mUseCustomDashPattern );
564 l->setCustomDashVector( mCustomDashVector );
565 l->setDrawInsidePolygon( mDrawInsidePolygon );
567 l->setDashPatternOffset( mDashPatternOffset );
568 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
569 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
570 l->setTrimDistanceStart( mTrimDistanceStart );
571 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
572 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
573 l->setTrimDistanceEnd( mTrimDistanceEnd );
574 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
575 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
576 l->setAlignDashPattern( mAlignDashPattern );
577 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
578
580 return l;
581}
582
583void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
584{
585 QgsSldExportContext context;
586 context.setExtraProperties( props );
587 toSld( doc, element, context );
588}
589
590bool QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
591{
592 if ( mPenStyle == Qt::NoPen )
593 return true;
594
595 const QVariantMap props = context.extraProperties();
596 QDomElement symbolizerElem = doc.createElement( u"se:LineSymbolizer"_s );
597 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
598 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
599 element.appendChild( symbolizerElem );
600
601 // <Geometry>
602 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
603
604 // <Stroke>
605 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
606 symbolizerElem.appendChild( strokeElem );
607
608 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
610 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
611 QgsSymbolLayerUtils::lineToSld( doc, strokeElem, penStyle, mColor, context, width, &mPenJoinStyle, &mPenCapStyle, &customDashVector );
612
613 // <se:PerpendicularOffset>
614 if ( !qgsDoubleNear( mOffset, 0.0 ) )
615 {
616 QDomElement perpOffsetElem = doc.createElement( u"se:PerpendicularOffset"_s );
618 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
619 symbolizerElem.appendChild( perpOffsetElem );
620 }
621 return true;
622}
623
624QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
625{
626 if ( mUseCustomDashPattern )
627 {
628 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle, mPenCapStyle, mOffset, &mCustomDashVector );
629 }
630 else
631 {
632 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle, mPenCapStyle, mOffset );
633 }
634}
635
637{
638 QgsDebugMsgLevel( u"Entered."_s, 4 );
639
640 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
641 if ( strokeElem.isNull() )
642 return nullptr;
643
644 Qt::PenStyle penStyle;
645 QColor color;
646 double width;
647 Qt::PenJoinStyle penJoinStyle;
648 Qt::PenCapStyle penCapStyle;
649 QVector<qreal> customDashVector;
650
652 return nullptr;
653
654 double offset = 0.0;
655 QDomElement perpOffsetElem = element.firstChildElement( u"PerpendicularOffset"_s );
656 if ( !perpOffsetElem.isNull() )
657 {
658 bool ok;
659 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
660 if ( ok )
661 offset = d;
662 }
663
664 double scaleFactor = 1.0;
665 const QString uom = element.attribute( u"uom"_s );
666 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
667 width = width * scaleFactor;
668 offset = offset * scaleFactor;
669
671 l->setOutputUnit( sldUnitSize );
672 l->setOffset( offset );
675 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
677 return l;
678}
679
680void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
681{
682 if ( !dataDefinedProperties().hasActiveProperties() )
683 return; // shortcut
684
685 //data defined properties
686 bool hasStrokeWidthExpression = false;
688 {
690 double scaledWidth
691 = context.renderContext()
693 pen.setWidthF( scaledWidth );
694 selPen.setWidthF( scaledWidth );
695 hasStrokeWidthExpression = true;
696 }
697
698 //color
700 {
702
704 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
705 pen.setColor( penColor );
706 }
707
708 //offset
710 {
713 }
714
715 //dash dot vector
716
717 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
718 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
719 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
720
722 {
723 const QString customDashString
725 const QStringList dashList = customDashString.split( ';' );
726 QVector<qreal> dashVector;
727 for ( const QString &dash : dashList )
728 {
729 dashVector.push_back( context.renderContext().convertToPainterUnits( dash.toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
730 }
731 pen.setDashPattern( dashVector );
732 }
733 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
734 {
735 //re-scale pattern vector after data defined pen width was applied
736
737 QVector<qreal> scaledVector;
738 for ( double v : std::as_const( mCustomDashVector ) )
739 {
740 //the dash is specified in terms of pen widths, therefore the division
741 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
742 }
743 mPen.setDashPattern( scaledVector );
744 }
745
746 // dash pattern offset
747 double patternOffset = mDashPatternOffset;
748 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
749 {
750 context.setOriginalValueVariable( patternOffset );
751 patternOffset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::DashPatternOffset, context.renderContext().expressionContext(), mDashPatternOffset );
752 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
753 }
754
755 //line style
757 {
759 const QString lineStyleString
761 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( lineStyleString ) );
762 }
763
764 //join style
766 {
768 const QString joinStyleString
770 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( joinStyleString ) );
771 }
772
773 //cap style
775 {
777 const QString capStyleString
779 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( capStyleString ) );
780 }
781}
782
783void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
784{
785 if ( pen.dashPattern().empty() || points.size() < 2 )
786 return;
787
788 if ( pen.widthF() <= 1.0 )
789 {
790 pen.setWidthF( 1.0001 );
791 }
792
793 QVector< qreal > sourcePattern = pen.dashPattern();
794 const double dashWidthDiv = pen.widthF();
795 // back to painter units
796 for ( int i = 0; i < sourcePattern.size(); ++i )
797 sourcePattern[i] *= pen.widthF();
798
799 QVector< qreal > buffer;
800 QPolygonF bufferedPoints;
801 QPolygonF previousSegmentBuffer;
802 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
803 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
804 // and then append the buffer to the output pattern.
805
806 auto ptIt = points.constBegin();
807 double totalBufferLength = 0;
808 int patternIndex = 0;
809 double currentRemainingDashLength = 0;
810 double currentRemainingGapLength = 0;
811
812 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal > {
813 QVector< qreal > result;
814 result.reserve( buffer.size() );
815 for ( auto it = buffer.begin(); it != buffer.end(); )
816 {
817 qreal dash = *it++;
818 qreal gap = *it++;
819 while ( dash == 0 && !result.empty() )
820 {
821 result.last() += gap;
822
823 if ( it == buffer.end() )
824 return result;
825 dash = *it++;
826 gap = *it++;
827 }
828 while ( gap == 0 && it != buffer.end() )
829 {
830 dash += *it++;
831 gap = *it++;
832 }
833 result << dash << gap;
834 }
835 return result;
836 };
837
838 double currentBufferLineLength = 0;
839 auto flushBuffer =
840 [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength, dashWidthDiv, &compressPattern](
841 QPointF *nextPoint
842 ) {
843 if ( buffer.empty() || bufferedPoints.size() < 2 )
844 {
845 return;
846 }
847
848 if ( currentRemainingDashLength )
849 {
850 // ended midway through a dash -- we want to finish this off
851 buffer << currentRemainingDashLength << 0.0;
852 totalBufferLength += currentRemainingDashLength;
853 }
854 QVector< qreal > compressed = compressPattern( buffer );
855 if ( !currentRemainingDashLength )
856 {
857 // ended midway through a gap -- we don't want this, we want to end at previous dash
858 totalBufferLength -= compressed.last();
859 compressed.last() = 0;
860 }
861
862 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
863 const double scaleFactor = currentBufferLineLength / totalBufferLength;
864
865 bool shouldFlushPreviousSegmentBuffer = false;
866
867 if ( !previousSegmentBuffer.empty() )
868 {
869 // add first dash from current buffer
870 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
871 if ( !firstDashSubstring.empty() )
872 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
873
874 // then we skip over the first dash and gap for this segment
875 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
876
877 compressed = compressed.mid( 2 );
878 shouldFlushPreviousSegmentBuffer = !compressed.empty();
879 }
880
881 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
882 {
883 QPen adjustedPen = pen;
884 adjustedPen.setStyle( Qt::SolidLine );
885 painter->setPen( adjustedPen );
886 QPainterPath path;
887 path.addPolygon( previousSegmentBuffer );
888 painter->drawPath( path );
889 previousSegmentBuffer.clear();
890 }
891
892 double finalDash = 0;
893 if ( nextPoint )
894 {
895 // sharp bend:
896 // 1. rewind buffered points line by final dash and gap length
897 // (later) 2. draw the bend with a solid line of length 2 * final dash size
898
899 if ( !compressed.empty() )
900 {
901 finalDash = compressed.at( compressed.size() - 2 );
902 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
903
904 const QPolygonF thisPoints = bufferedPoints;
905 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
906 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, -finalDash * scaleFactor, 0 );
907 }
908 else
909 {
910 previousSegmentBuffer << bufferedPoints;
911 }
912 }
913
914 currentBufferLineLength = 0;
915 currentRemainingDashLength = 0;
916 currentRemainingGapLength = 0;
917 totalBufferLength = 0;
918 buffer.clear();
919
920 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
921 {
922 QPen adjustedPen = pen;
923 if ( !compressed.empty() )
924 {
925 // maximum size of dash pattern is 32 elements
926 compressed = compressed.mid( 0, 32 );
927 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal &element ) { element *= scaleFactor / dashWidthDiv; } );
928 adjustedPen.setDashPattern( compressed );
929 }
930 else
931 {
932 adjustedPen.setStyle( Qt::SolidLine );
933 }
934
935 painter->setPen( adjustedPen );
936 QPainterPath path;
937 path.addPolygon( bufferedPoints );
938 painter->drawPath( path );
939 }
940
941 bufferedPoints.clear();
942 };
943
944 QPointF p1;
945 QPointF p2 = *ptIt;
946 ptIt++;
947 bufferedPoints << p2;
948 for ( ; ptIt != points.constEnd(); ++ptIt )
949 {
950 p1 = *ptIt;
951 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
952 {
953 continue;
954 }
955
956 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
957 currentBufferLineLength += remainingSegmentDistance;
958 while ( true )
959 {
960 // handle currentRemainingDashLength/currentRemainingGapLength
961 if ( currentRemainingDashLength > 0 )
962 {
963 // bit more of dash to insert
964 if ( remainingSegmentDistance >= currentRemainingDashLength )
965 {
966 // all of dash fits in
967 buffer << currentRemainingDashLength << 0.0;
968 totalBufferLength += currentRemainingDashLength;
969 remainingSegmentDistance -= currentRemainingDashLength;
970 patternIndex++;
971 currentRemainingDashLength = 0.0;
972 currentRemainingGapLength = sourcePattern.at( patternIndex );
973 if ( currentRemainingGapLength == 0.0 )
974 {
975 patternIndex++;
976 }
977 }
978 else
979 {
980 // only part of remaining dash fits in
981 buffer << remainingSegmentDistance << 0.0;
982 totalBufferLength += remainingSegmentDistance;
983 currentRemainingDashLength -= remainingSegmentDistance;
984 break;
985 }
986 }
987 if ( currentRemainingGapLength > 0 )
988 {
989 // bit more of gap to insert
990 if ( remainingSegmentDistance >= currentRemainingGapLength )
991 {
992 // all of gap fits in
993 buffer << 0.0 << currentRemainingGapLength;
994 totalBufferLength += currentRemainingGapLength;
995 remainingSegmentDistance -= currentRemainingGapLength;
996 currentRemainingGapLength = 0.0;
997 patternIndex++;
998 }
999 else
1000 {
1001 // only part of remaining gap fits in
1002 buffer << 0.0 << remainingSegmentDistance;
1003 totalBufferLength += remainingSegmentDistance;
1004 currentRemainingGapLength -= remainingSegmentDistance;
1005 break;
1006 }
1007 }
1008
1009 if ( patternIndex + 1 >= sourcePattern.size() )
1010 {
1011 patternIndex = 0;
1012 }
1013
1014 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1015 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1016 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1017 {
1018 buffer << nextPatternDashLength << nextPatternGapLength;
1019 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1020 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1021 patternIndex += 2;
1022 }
1023 else if ( nextPatternDashLength <= remainingSegmentDistance )
1024 {
1025 // can fit in "dash", but not "gap"
1026 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1027 totalBufferLength += remainingSegmentDistance;
1028 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1029 currentRemainingDashLength = 0;
1030 patternIndex++;
1031 break;
1032 }
1033 else
1034 {
1035 // can't fit in "dash"
1036 buffer << remainingSegmentDistance << 0.0;
1037 totalBufferLength += remainingSegmentDistance;
1038 currentRemainingGapLength = 0;
1039 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1040 break;
1041 }
1042 }
1043
1044 bufferedPoints << p1;
1045 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1046 {
1047 QPointF nextPoint = *( ptIt + 1 );
1048
1049 // extreme angles form more than 45 degree angle at a node
1050 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1051 {
1052 // extreme angle. Rescale buffer and flush
1053 flushBuffer( &nextPoint );
1054 bufferedPoints << p1;
1055 // restart the line with the full length of the most recent dash element -- see
1056 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1057 if ( patternIndex % 2 == 1 )
1058 {
1059 patternIndex--;
1060 }
1061 currentRemainingDashLength = sourcePattern.at( patternIndex );
1062 }
1063 }
1064
1065 p2 = p1;
1066 }
1067
1068 flushBuffer( nullptr );
1069 if ( !previousSegmentBuffer.empty() )
1070 {
1071 QPen adjustedPen = pen;
1072 adjustedPen.setStyle( Qt::SolidLine );
1073 painter->setPen( adjustedPen );
1074 QPainterPath path;
1075 path.addPolygon( previousSegmentBuffer );
1076 painter->drawPath( path );
1077 previousSegmentBuffer.clear();
1078 }
1079}
1080
1082{
1083 if ( mDrawInsidePolygon )
1084 {
1085 //set to clip line to the interior of polygon, so we expect no bleed
1086 return 0;
1087 }
1088 else
1089 {
1091 }
1092}
1093
1095{
1096 unit = mCustomDashPatternUnit;
1097 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1098}
1099
1101{
1102 return mPenStyle;
1103}
1104
1121
1131
1133{
1134 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1135}
1136
1138{
1139 return mAlignDashPattern;
1140}
1141
1143{
1144 mAlignDashPattern = enabled;
1145}
1146
1148{
1149 return mPatternCartographicTweakOnSharpCorners;
1150}
1151
1153{
1154 mPatternCartographicTweakOnSharpCorners = enabled;
1155}
1156
1158{
1159 double offset = mOffset;
1160
1162 {
1165 }
1166
1169 {
1171 }
1172 return -offset; //direction seems to be inverse to symbology offset
1173}
1174
1175//
1176// QgsTemplatedLineSymbolLayerBase
1177//
1179 : mRotateSymbols( rotateSymbol )
1180 , mInterval( interval )
1181{}
1182
1204
1209
1211
1213{
1214 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1215 if ( mRenderingFeature )
1216 {
1217 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1218 // until after we've received the final part
1219 mFeatureSymbolOpacity = context.opacity();
1220 mCurrentFeatureIsSelected = useSelectedColor;
1221 }
1222
1223 double offset = mOffset;
1224
1226 {
1229 }
1230
1232
1234 {
1236 if ( !QgsVariantUtils::isNull( exprVal ) )
1237 {
1238 QString placementString = exprVal.toString();
1239 if ( placementString.compare( "interval"_L1, Qt::CaseInsensitive ) == 0 )
1240 {
1242 }
1243 else if ( placementString.compare( "vertex"_L1, Qt::CaseInsensitive ) == 0 )
1244 {
1246 }
1247 else if ( placementString.compare( "innervertices"_L1, Qt::CaseInsensitive ) == 0 )
1248 {
1250 }
1251 else if ( placementString.compare( "lastvertex"_L1, Qt::CaseInsensitive ) == 0 )
1252 {
1254 }
1255 else if ( placementString.compare( "firstvertex"_L1, Qt::CaseInsensitive ) == 0 )
1256 {
1258 }
1259 else if ( placementString.compare( "centerpoint"_L1, Qt::CaseInsensitive ) == 0 )
1260 {
1262 }
1263 else if ( placementString.compare( "curvepoint"_L1, Qt::CaseInsensitive ) == 0 )
1264 {
1266 }
1267 else if ( placementString.compare( "segmentcenter"_L1, Qt::CaseInsensitive ) == 0 )
1268 {
1270 }
1271 else
1272 {
1274 }
1275 }
1276 }
1277
1278 QgsScopedQPainterState painterState( context.renderContext().painter() );
1279
1280 double averageOver = mAverageAngleLength;
1282 {
1283 context.setOriginalValueVariable( mAverageAngleLength );
1284 averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
1285 }
1286 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1287
1290 {
1291 const QString strBlankSegments = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::BlankSegments, context.renderContext().expressionContext() );
1292 QString error;
1293 QList<QList<QgsBlankSegmentUtils::BlankSegments>> allBlankSegments = QgsBlankSegmentUtils::parseBlankSegments( strBlankSegments, context.renderContext(), blankSegmentsUnit(), error );
1294
1295 if ( !error.isEmpty() )
1296 {
1297 QgsDebugError( u"Badly formatted blank segment '%1', skip it: %2"_s.arg( strBlankSegments ).arg( error ) );
1298 }
1299 else
1300 {
1301 // keep only the part/ring we are currently rendering
1302 const int iPart = context.geometryPartNum() - 1;
1303 if ( iPart >= 0 && mRingIndex >= 0 && iPart < allBlankSegments.count() && mRingIndex < allBlankSegments.at( iPart ).count() )
1304 {
1305 blankSegments = allBlankSegments.at( iPart ).at( mRingIndex );
1306 }
1307 }
1308 }
1309
1310 if ( qgsDoubleNear( offset, 0.0 ) )
1311 {
1313 renderPolylineInterval( points, context, averageOver, blankSegments );
1315 renderPolylineCentral( points, context, averageOver, blankSegments );
1317 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1318 if ( placements & Qgis::MarkerLinePlacement::FirstVertex && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1319 {
1320 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1321 mHasRenderedFirstPart = mRenderingFeature;
1322 }
1324 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1326 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1328 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1330 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1331 }
1332 else
1333 {
1334 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1335 QList<QPolygonF> mline = ::
1337
1338 for ( int part = 0; part < mline.count(); ++part )
1339 {
1340 const QPolygonF &points2 = mline[part];
1341
1343 renderPolylineInterval( points2, context, averageOver, blankSegments );
1345 renderPolylineCentral( points2, context, averageOver, blankSegments );
1347 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1349 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1351 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1352 if ( placements & Qgis::MarkerLinePlacement::FirstVertex && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1353 {
1354 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1355 mHasRenderedFirstPart = mRenderingFeature;
1356 }
1358 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1360 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1361 }
1362 }
1363}
1364
1365void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1366{
1367 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1368
1369 if ( curvePolygon )
1370 {
1371 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1372 }
1373
1374 QgsExpressionContextScope *scope = nullptr;
1375 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1377 {
1378 scope = new QgsExpressionContextScope();
1379 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1380 }
1381
1382 switch ( mRingFilter )
1383 {
1384 case AllRings:
1385 case ExteriorRingOnly:
1386 {
1387 if ( scope )
1389
1390 renderPolyline( points, context );
1391 break;
1392 }
1393 case InteriorRingsOnly:
1394 break;
1395 }
1396
1397 if ( rings )
1398 {
1399 switch ( mRingFilter )
1400 {
1401 case AllRings:
1402 case InteriorRingsOnly:
1403 {
1404 mOffset = -mOffset; // invert the offset for rings!
1405 for ( int i = 0; i < rings->size(); ++i )
1406 {
1407 mRingIndex = i + 1;
1408 if ( curvePolygon )
1409 {
1410 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1411 }
1412 if ( scope )
1414
1415 renderPolyline( rings->at( i ), context );
1416 }
1417 mOffset = -mOffset;
1418 mRingIndex = 0;
1419 }
1420 break;
1421 case ExteriorRingOnly:
1422 break;
1423 }
1424 }
1425}
1426
1428{
1430 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1431 {
1433 }
1434 return unit;
1435}
1436
1438{
1440 mIntervalUnit = unit;
1441 mOffsetAlongLineUnit = unit;
1442 mAverageAngleLengthUnit = unit;
1443}
1444
1452
1461
1463{
1464 QVariantMap map;
1465 map[u"rotate"_s] = ( rotateSymbols() ? u"1"_s : u"0"_s );
1466 map[u"interval"_s] = QString::number( interval() );
1467 map[u"offset"_s] = QString::number( mOffset );
1468 map[u"offset_along_line"_s] = QString::number( offsetAlongLine() );
1469 map[u"offset_along_line_unit"_s] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1470 map[u"offset_along_line_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1471 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1472 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1473 map[u"interval_unit"_s] = QgsUnitTypes::encodeUnit( intervalUnit() );
1474 map[u"interval_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1475 map[u"average_angle_length"_s] = QString::number( mAverageAngleLength );
1476 map[u"average_angle_unit"_s] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1477 map[u"average_angle_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1478 map[u"blank_segments_unit"_s] = QgsUnitTypes::encodeUnit( mBlankSegmentsUnit );
1479
1480 map[u"placements"_s] = qgsFlagValueToKeys( mPlacements );
1481
1482 map[u"ring_filter"_s] = QString::number( static_cast< int >( mRingFilter ) );
1483 map[u"place_on_every_part"_s] = mPlaceOnEveryPart;
1484 return map;
1485}
1486
1488{
1489 return mPlaceOnEveryPart
1490 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1491 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1492 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1493}
1494
1496{
1497 installMasks( context, true );
1498
1499 mRenderingFeature = true;
1500 mHasRenderedFirstPart = false;
1501}
1502
1504{
1505 mRenderingFeature = false;
1506 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1507 {
1508 removeMasks( context, true );
1509 return;
1510 }
1511
1512 const double prevOpacity = subSymbol()->opacity();
1513 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1514
1515 // render final point
1516 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1517 mFeatureSymbolOpacity = 1;
1518 subSymbol()->setOpacity( prevOpacity );
1519
1520 removeMasks( context, true );
1521}
1522
1524{
1525 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1526 destLayer->setOffset( mOffset );
1527 destLayer->setPlacements( placements() );
1528 destLayer->setOffsetUnit( mOffsetUnit );
1530 destLayer->setIntervalUnit( intervalUnit() );
1532 destLayer->setOffsetAlongLine( offsetAlongLine() );
1535 destLayer->setAverageAngleLength( mAverageAngleLength );
1536 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1537 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1538 destLayer->setBlankSegmentsUnit( mBlankSegmentsUnit );
1539 destLayer->setRingFilter( mRingFilter );
1540 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1541
1542 copyCommonProperties( destLayer );
1543}
1544
1546{
1547 if ( properties.contains( u"offset"_s ) )
1548 {
1549 destLayer->setOffset( properties[u"offset"_s].toDouble() );
1550 }
1551 if ( properties.contains( u"offset_unit"_s ) )
1552 {
1553 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
1554 }
1555 if ( properties.contains( u"interval_unit"_s ) )
1556 {
1557 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[u"interval_unit"_s].toString() ) );
1558 }
1559 if ( properties.contains( u"offset_along_line"_s ) )
1560 {
1561 destLayer->setOffsetAlongLine( properties[u"offset_along_line"_s].toDouble() );
1562 }
1563 if ( properties.contains( u"offset_along_line_unit"_s ) )
1564 {
1565 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_along_line_unit"_s].toString() ) );
1566 }
1567 if ( properties.contains( ( u"offset_along_line_map_unit_scale"_s ) ) )
1568 {
1569 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_along_line_map_unit_scale"_s].toString() ) );
1570 }
1571
1572 if ( properties.contains( u"offset_map_unit_scale"_s ) )
1573 {
1574 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
1575 }
1576 if ( properties.contains( u"interval_map_unit_scale"_s ) )
1577 {
1578 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"interval_map_unit_scale"_s].toString() ) );
1579 }
1580
1581 if ( properties.contains( u"average_angle_length"_s ) )
1582 {
1583 destLayer->setAverageAngleLength( properties[u"average_angle_length"_s].toDouble() );
1584 }
1585 if ( properties.contains( u"average_angle_unit"_s ) )
1586 {
1587 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[u"average_angle_unit"_s].toString() ) );
1588 }
1589 if ( properties.contains( ( u"average_angle_map_unit_scale"_s ) ) )
1590 {
1591 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"average_angle_map_unit_scale"_s].toString() ) );
1592 }
1593 if ( properties.contains( u"blank_segments_unit"_s ) )
1594 {
1595 destLayer->setBlankSegmentsUnit( QgsUnitTypes::decodeRenderUnit( properties[u"blank_segments_unit"_s].toString() ) );
1596 }
1597
1598 if ( properties.contains( u"placement"_s ) )
1599 {
1600 if ( properties[u"placement"_s] == "vertex"_L1 )
1602 else if ( properties[u"placement"_s] == "lastvertex"_L1 )
1604 else if ( properties[u"placement"_s] == "firstvertex"_L1 )
1606 else if ( properties[u"placement"_s] == "centralpoint"_L1 )
1608 else if ( properties[u"placement"_s] == "curvepoint"_L1 )
1610 else if ( properties[u"placement"_s] == "segmentcenter"_L1 )
1612 else
1614 }
1615 else if ( properties.contains( u"placements"_s ) )
1616 {
1618 destLayer->setPlacements( placements );
1619 }
1620
1621 if ( properties.contains( u"ring_filter"_s ) )
1622 {
1623 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[u"ring_filter"_s].toInt() ) );
1624 }
1625
1626 destLayer->setPlaceOnEveryPart( properties.value( u"place_on_every_part"_s, true ).toBool() );
1627
1629}
1630
1632
1637class BlankSegmentsWalker
1638{
1639 public:
1640 BlankSegmentsWalker( const QPolygonF &points, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1641 : mBlankSegments( blankSegments )
1642 , mPoints( points )
1643 , mItBlankSegment( blankSegments.cbegin() )
1644 {
1645 mDistances.reserve( mPoints.count() );
1646 mDistances << 0; // first point is start, so distance is 0
1647 }
1648
1649 bool insideBlankSegment( double distance )
1650 {
1651 while ( mItBlankSegment != mBlankSegments.cend() && distance > mItBlankSegment->second )
1652 {
1653 ++mItBlankSegment;
1654 }
1655
1656 return ( mItBlankSegment != mBlankSegments.cend() && distance >= mItBlankSegment->first );
1657 }
1658
1659
1660 // pointIndex : index of the point before point
1661 bool insideBlankSegment( const QPointF &point, int pointIndex )
1662 {
1663 if ( pointIndex < 0 || pointIndex >= mPoints.count() )
1664 return false;
1665
1666 // compute distances and fill distances array
1667 if ( pointIndex >= mDistances.count() )
1668 {
1669 for ( int i = static_cast<int>( mDistances.count() ); i < pointIndex + 1; i++ )
1670 {
1671 const QPointF diff = mPoints.at( i ) - mPoints.at( i - 1 );
1672 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1673 const double totalDistance = distance + mDistances.last();
1674 mDistances << totalDistance;
1675 }
1676 }
1677
1678 const QPointF diff = mPoints.at( pointIndex ) - point;
1679 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1680 const double currentDistance = mDistances.at( pointIndex ) + distance;
1681
1682 return insideBlankSegment( currentDistance );
1683 }
1684
1685 private:
1686 const QgsBlankSegmentUtils::BlankSegments &mBlankSegments;
1687 const QPolygonF &mPoints;
1688 QList<double> mDistances;
1689 QgsBlankSegmentUtils::BlankSegments::const_iterator mItBlankSegment;
1690};
1691
1692
1694
1695
1696void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1697{
1698 if ( points.isEmpty() )
1699 return;
1700
1701 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1702 double lengthLeft = 0; // how much is left until next marker
1703
1704 QgsRenderContext &rc = context.renderContext();
1705 double interval = mInterval;
1706
1707 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1708 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1709
1711 {
1712 context.setOriginalValueVariable( mInterval );
1714 }
1715 if ( interval <= 0 )
1716 {
1717 interval = 0.1;
1718 }
1719 double offsetAlongLine = mOffsetAlongLine;
1721 {
1722 context.setOriginalValueVariable( mOffsetAlongLine );
1724 }
1725
1726 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1728 {
1729 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1730 // and clamp it to a reasonable range. It's the best we can do in this situation!
1731 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1732 }
1733
1734 constexpr double EPSILON = 1e-5;
1735 if ( painterUnitInterval < EPSILON )
1736 return;
1737
1738 double painterUnitOffsetAlongLine = 0;
1739
1740 // only calculated if we need it!
1741 double totalLength = -1;
1742
1743 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1744 {
1745 switch ( offsetAlongLineUnit() )
1746 {
1755 break;
1757 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1758 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1759 break;
1760 }
1761
1762 if ( points.isClosed() )
1763 {
1764 if ( painterUnitOffsetAlongLine > 0 )
1765 {
1766 if ( totalLength < 0 )
1767 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1768 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1769 }
1770 else if ( painterUnitOffsetAlongLine < 0 )
1771 {
1772 if ( totalLength < 0 )
1773 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1774 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1775 }
1776 }
1777 }
1778
1780 {
1781 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1782 // and clamp it to a reasonable range. It's the best we can do in this situation!
1783 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1784 }
1785
1786 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1787
1788 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1789 {
1790 QVector< QPointF > angleStartPoints;
1791 QVector< QPointF > symbolPoints;
1792 QVector< QPointF > angleEndPoints;
1793
1794 // we collect 3 arrays of points. These correspond to
1795 // 1. the actual point at which to render the symbol
1796 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1797 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1798 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1799 // (or trace past the final point)
1800
1801 QList<int> pointIndices; // keep a track on original pointIndices so we can decide later whether or not symbol points belong to a blank segment
1802 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft, blankSegments.isEmpty() ? nullptr : &pointIndices );
1803
1804 if ( symbolPoints.empty() )
1805 {
1806 // no symbols to draw, shortcut out early
1807 return;
1808 }
1809
1810 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1811 {
1812 // avoid duplicate points at start and end of closed rings
1813 symbolPoints.pop_back();
1814 }
1815
1816 angleEndPoints.reserve( symbolPoints.size() );
1817 angleStartPoints.reserve( symbolPoints.size() );
1818 if ( averageOver <= painterUnitOffsetAlongLine )
1819 {
1820 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, nullptr, 0, symbolPoints.size() );
1821 }
1822 else
1823 {
1824 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, nullptr, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1825 }
1826 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, nullptr, 0, symbolPoints.size() );
1827
1828 int pointNum = 0;
1829 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
1830 for ( int i = 0; i < symbolPoints.size(); ++i )
1831 {
1832 if ( context.renderContext().renderingStopped() )
1833 break;
1834
1835 const QPointF pt = symbolPoints[i];
1836 if ( i < pointIndices.count() && blankSegmentsWalker.insideBlankSegment( pt, pointIndices.at( i ) ) )
1837 // skip the rendering
1838 continue;
1839
1840 const QPointF startPt = angleStartPoints[i];
1841 const QPointF endPt = angleEndPoints[i];
1842
1843 Line l( startPt, endPt );
1844 // rotate marker (if desired)
1845 if ( rotateSymbols() )
1846 {
1847 setSymbolLineAngle( l.angle() * 180 / M_PI );
1848 }
1849
1850 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1851 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1852 }
1853 }
1854 else
1855 {
1856 // not averaging line angle -- always use exact section angle
1857 int pointNum = 0;
1858 QPointF lastPt = points[0];
1859 BlankSegmentsWalker itBlankSegment( points, blankSegments );
1860 for ( int i = 1; i < points.count(); ++i )
1861 {
1862 if ( context.renderContext().renderingStopped() )
1863 break;
1864
1865 const QPointF &pt = points[i];
1866
1867 if ( lastPt == pt ) // must not be equal!
1868 continue;
1869
1870 // for each line, find out dx and dy, and length
1871 Line l( lastPt, pt );
1872 QPointF diff = l.diffForInterval( painterUnitInterval );
1873
1874 // if there's some length left from previous line
1875 // use only the rest for the first point in new line segment
1876 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1877 double c = 1 - lengthLeft / painterUnitInterval;
1878
1879 lengthLeft += l.length();
1880
1881 // rotate marker (if desired)
1882 if ( rotateSymbols() )
1883 {
1884 setSymbolLineAngle( l.angle() * 180 / M_PI );
1885 }
1886
1887 // while we're not at the end of line segment, draw!
1888 while ( lengthLeft > painterUnitInterval )
1889 {
1890 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1891 lastPt += c * diff;
1892 c = 1; // reset c (if wasn't 1 already)
1893
1894 // we draw
1895 if ( !itBlankSegment.insideBlankSegment( lastPt, i - 1 ) )
1896 {
1897 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1898 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1899 }
1900
1901 lengthLeft -= painterUnitInterval;
1902 }
1903
1904 lastPt = pt;
1905 }
1906 }
1907}
1908
1909static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1910{
1911 // calc average angle between the previous and next point
1912 double a1 = Line( prevPt, pt ).angle();
1913 double a2 = Line( pt, nextPt ).angle();
1914 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1915
1916 return std::atan2( unitY, unitX );
1917}
1918
1919void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex(
1920 const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsBlankSegmentUtils::BlankSegments &blankSegments
1921)
1922{
1923 if ( points.isEmpty() )
1924 return;
1925
1926 QgsRenderContext &rc = context.renderContext();
1927
1928 int i = -1, maxCount = 0;
1929 bool isRing = false;
1930
1931 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1932 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1933 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size(), true ) );
1934
1935 double offsetAlongLine = mOffsetAlongLine;
1937 {
1938 context.setOriginalValueVariable( mOffsetAlongLine );
1940 }
1941
1942 // only calculated if we need it!!
1943 double totalLength = -1;
1944 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1945 {
1946 //scale offset along line
1947 switch ( offsetAlongLineUnit() )
1948 {
1957 break;
1959 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1960 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1961 break;
1962 }
1963 if ( points.isClosed() )
1964 {
1965 if ( offsetAlongLine > 0 )
1966 {
1967 if ( totalLength < 0 )
1968 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1969 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1970 }
1971 else if ( offsetAlongLine < 0 )
1972 {
1973 if ( totalLength < 0 )
1974 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1975 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1976 }
1977 }
1978 }
1979
1980 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1981 if ( qgsDoubleNear( offsetAlongLine, 0.0 )
1982 && context.renderContext().geometry()
1983 && context.renderContext().geometry()->hasCurvedSegments()
1985 {
1986 QgsCoordinateTransform ct = context.renderContext().coordinateTransform();
1987 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1988
1989 QgsVertexId vId;
1990 QgsPoint vPoint;
1991 double x, y, z;
1992 QPointF mapPoint;
1993 int pointNum = 0;
1994 const int numPoints = context.renderContext().geometry()->nCoordinates();
1995 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1996 {
1997 if ( context.renderContext().renderingStopped() )
1998 break;
1999
2000 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2001
2002 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
2003 continue;
2004
2005 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
2006 continue;
2007
2010 {
2011 //transform
2012 x = vPoint.x();
2013 y = vPoint.y();
2014 z = 0.0;
2015 if ( ct.isValid() )
2016 {
2017 ct.transformInPlace( x, y, z );
2018 }
2019 mapPoint.setX( x );
2020 mapPoint.setY( y );
2021 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
2022 if ( rotateSymbols() )
2023 {
2024 double angle = context.renderContext().geometry()->vertexAngle( vId );
2025 setSymbolLineAngle( angle * 180 / M_PI );
2026 }
2027 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
2028 }
2029 }
2030
2031 return;
2032 }
2033
2034 int pointNum = 0;
2035
2036 switch ( placement )
2037 {
2039 {
2040 i = 0;
2041 maxCount = 1;
2042 break;
2043 }
2044
2046 {
2047 i = points.count() - 1;
2048 pointNum = i;
2049 maxCount = points.count();
2050 break;
2051 }
2052
2054 {
2055 i = 1;
2056 pointNum = 1;
2057 maxCount = points.count() - 1;
2058 break;
2059 }
2060
2063 {
2065 maxCount = points.count();
2066 if ( points.first() == points.last() )
2067 isRing = true;
2068 break;
2069 }
2070
2074 {
2075 return;
2076 }
2077 }
2078
2080 {
2081 double distance;
2083 renderOffsetVertexAlongLine( points, i, distance, context, placement, blankSegments );
2084
2085 return;
2086 }
2087
2088 QPointF prevPoint;
2089 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2090 prevPoint = points.at( 0 );
2091
2092 QPointF symbolPoint;
2093 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2094 for ( ; i < maxCount; ++i )
2095 {
2096 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2097
2098 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2099 {
2100 continue; // don't draw the last marker - it has been drawn already
2101 }
2102
2104 {
2105 QPointF currentPoint = points.at( i );
2106 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ), 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2107 if ( rotateSymbols() )
2108 {
2109 double angle = std::atan2( currentPoint.y() - prevPoint.y(), currentPoint.x() - prevPoint.x() );
2110 setSymbolLineAngle( angle * 180 / M_PI );
2111 }
2112 prevPoint = currentPoint;
2113 }
2114 else
2115 {
2116 symbolPoint = points.at( i );
2117 // rotate marker (if desired)
2118 if ( rotateSymbols() )
2119 {
2120 double angle = markerAngle( points, isRing, i );
2121 setSymbolLineAngle( angle * 180 / M_PI );
2122 }
2123 }
2124
2125 mFinalVertex = symbolPoint;
2126 if ( ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature ) && !blankSegmentsWalker.insideBlankSegment( symbolPoint, i ) )
2127 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2128 }
2129}
2130
2131double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2132{
2133 double angle = 0;
2134 const QPointF &pt = points[vertex];
2135
2136 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2137 {
2138 int prevIndex = vertex - 1;
2139 int nextIndex = vertex + 1;
2140
2141 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2142 {
2143 prevIndex = points.count() - 2;
2144 nextIndex = 1;
2145 }
2146
2147 QPointF prevPoint, nextPoint;
2148 while ( prevIndex >= 0 )
2149 {
2150 prevPoint = points[prevIndex];
2151 if ( prevPoint != pt )
2152 {
2153 break;
2154 }
2155 --prevIndex;
2156 }
2157
2158 while ( nextIndex < points.count() )
2159 {
2160 nextPoint = points[nextIndex];
2161 if ( nextPoint != pt )
2162 {
2163 break;
2164 }
2165 ++nextIndex;
2166 }
2167
2168 if ( prevIndex >= 0 && nextIndex < points.count() )
2169 {
2170 angle = _averageAngle( prevPoint, pt, nextPoint );
2171 }
2172 }
2173 else //no ring and vertex is at start / at end
2174 {
2175 if ( vertex == 0 )
2176 {
2177 while ( vertex < points.size() - 1 )
2178 {
2179 const QPointF &nextPt = points[vertex + 1];
2180 if ( pt != nextPt )
2181 {
2182 angle = Line( pt, nextPt ).angle();
2183 return angle;
2184 }
2185 ++vertex;
2186 }
2187 }
2188 else
2189 {
2190 // use last segment's angle
2191 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2192 {
2193 const QPointF &prevPt = points[vertex - 1];
2194 if ( pt != prevPt )
2195 {
2196 angle = Line( prevPt, pt ).angle();
2197 return angle;
2198 }
2199 --vertex;
2200 }
2201 }
2202 }
2203 return angle;
2204}
2205
2206void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine(
2207 const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsBlankSegmentUtils::BlankSegments &blankSegments
2208)
2209{
2210 if ( points.isEmpty() )
2211 return;
2212
2213 QgsRenderContext &rc = context.renderContext();
2214 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2215 if ( qgsDoubleNear( distance, 0.0 ) )
2216 {
2217 // rotate marker (if desired)
2218 if ( rotateSymbols() )
2219 {
2220 bool isRing = false;
2221 if ( points.first() == points.last() )
2222 isRing = true;
2223 double angle = markerAngle( points, isRing, vertex );
2224 setSymbolLineAngle( angle * 180 / M_PI );
2225 }
2226 mFinalVertex = points[vertex];
2227 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2228 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2229 return;
2230 }
2231
2232 int pointIncrement = distance > 0 ? 1 : -1;
2233 QPointF previousPoint = points[vertex];
2234 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2235 int endPoint = distance > 0 ? points.count() - 1 : 0;
2236 double distanceLeft = std::fabs( distance );
2237 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2238
2239 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2240 {
2241 const QPointF &pt = points[i];
2242
2243 if ( previousPoint == pt ) // must not be equal!
2244 continue;
2245
2246 // create line segment
2247 Line l( previousPoint, pt );
2248
2249 if ( distanceLeft < l.length() )
2250 {
2251 //destination point is in current segment
2252 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2253 // rotate marker (if desired)
2254 if ( rotateSymbols() )
2255 {
2256 setSymbolLineAngle( l.angle() * 180 / M_PI );
2257 }
2258 mFinalVertex = markerPoint;
2259 if ( ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature ) && !blankSegmentsWalker.insideBlankSegment( markerPoint, i - 1 ) )
2260 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2261 return;
2262 }
2263
2264 distanceLeft -= l.length();
2265 previousPoint = pt;
2266 }
2267
2268 //didn't find point
2269}
2270
2271void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints(
2272 const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, QList<int> *pointIndices, double initialLag, int numberPointsRequired
2273)
2274{
2275 if ( p.empty() )
2276 return;
2277
2278 QVector< QPointF > points = p;
2279 const bool closedRing = points.first() == points.last();
2280
2281 double lengthLeft = initialOffset;
2282
2283 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2284 if ( initialLagLeft < 0 && closedRing )
2285 {
2286 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2287 QPointF lastPt = points.constLast();
2288 QVector< QPointF > pseudoPoints;
2289 for ( int i = points.count() - 2; i > 0; --i )
2290 {
2291 if ( initialLagLeft >= 0 )
2292 {
2293 break;
2294 }
2295
2296 const QPointF &pt = points[i];
2297
2298 if ( lastPt == pt ) // must not be equal!
2299 continue;
2300
2301 Line l( lastPt, pt );
2302 initialLagLeft += l.length();
2303 lastPt = pt;
2304
2305 pseudoPoints << pt;
2306 }
2307 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2308
2309 points = pseudoPoints;
2310 points.append( p );
2311 }
2312 else
2313 {
2314 while ( initialLagLeft < 0 )
2315 {
2316 dest << points.constFirst();
2317 initialLagLeft += intervalPainterUnits;
2318 }
2319 }
2320 if ( initialLag > 0 )
2321 {
2322 lengthLeft += intervalPainterUnits - initialLagLeft;
2323 }
2324
2325 QPointF lastPt = points[0];
2326 for ( int i = 1; i < points.count(); ++i )
2327 {
2328 const QPointF &pt = points[i];
2329
2330 if ( lastPt == pt ) // must not be equal!
2331 {
2332 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2333 {
2334 lastPt = points[0];
2335 i = 0;
2336 }
2337 continue;
2338 }
2339
2340 // for each line, find out dx and dy, and length
2341 Line l( lastPt, pt );
2342 QPointF diff = l.diffForInterval( intervalPainterUnits );
2343
2344 // if there's some length left from previous line
2345 // use only the rest for the first point in new line segment
2346 double c = 1 - lengthLeft / intervalPainterUnits;
2347
2348 lengthLeft += l.length();
2349
2350
2351 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2352 {
2353 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2354 lastPt += c * diff;
2355 lengthLeft -= intervalPainterUnits;
2356 dest << lastPt;
2357 if ( pointIndices )
2358 *pointIndices << i - 1;
2359 c = 1; // reset c (if wasn't 1 already)
2360
2361 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2362 break;
2363 }
2364 lastPt = pt;
2365
2366 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2367 break;
2368
2369 // if a closed ring, we keep looping around the ring until we hit the required number of points
2370 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2371 {
2372 lastPt = points[0];
2373 i = 0;
2374 }
2375 }
2376
2377 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2378 {
2379 // pad with repeating last point to match desired size
2380 while ( dest.size() < numberPointsRequired )
2381 dest << points.constLast();
2382 }
2383}
2384
2385void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
2386{
2387 if ( !points.isEmpty() )
2388 {
2389 // calc length
2390 qreal length = 0;
2391 QPolygonF::const_iterator it = points.constBegin();
2392 QPointF last = *it;
2393 for ( ++it; it != points.constEnd(); ++it )
2394 {
2395 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) + ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2396 last = *it;
2397 }
2398 if ( qgsDoubleNear( length, 0.0 ) )
2399 return;
2400
2401 const double midPoint = length / 2;
2402
2403 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2404 if ( blankSegmentsWalker.insideBlankSegment( midPoint ) )
2405 return;
2406
2407 QPointF pt;
2408 double thisSymbolAngle = 0;
2409
2410 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2411 {
2412 QVector< QPointF > angleStartPoints;
2413 QVector< QPointF > symbolPoints;
2414 QVector< QPointF > angleEndPoints;
2415
2416 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2417 // already dealt with blank segment before, no need to make them check again
2418 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, nullptr, 0.0, 2 );
2419 collectOffsetPoints( points, angleStartPoints, midPoint, 0, nullptr, averageAngleOver, 2 );
2420 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, nullptr, 0, 2 );
2421
2422 pt = symbolPoints.at( 1 );
2423 Line l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2424 thisSymbolAngle = l.angle();
2425 }
2426 else
2427 {
2428 // find the segment where the central point lies
2429 it = points.constBegin();
2430 last = *it;
2431 qreal last_at = 0, next_at = 0;
2432 QPointF next;
2433 for ( ++it; it != points.constEnd(); ++it )
2434 {
2435 next = *it;
2436 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) + ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2437 if ( next_at >= midPoint )
2438 break; // we have reached the center
2439 last = *it;
2440 last_at = next_at;
2441 }
2442
2443 // find out the central point on segment
2444 Line l( last, next ); // for line angle
2445 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2446 pt = last + ( next - last ) * k;
2447 thisSymbolAngle = l.angle();
2448 }
2449
2450 // draw the marker
2451 // rotate marker (if desired)
2452 if ( rotateSymbols() )
2453 {
2454 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2455 }
2456
2457 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2458 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2459 }
2460}
2461
2466
2468{
2469 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2470 {
2471 delete symbol;
2472 return false;
2473 }
2474
2475 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2476 mColor = mMarker->color();
2477 return true;
2478}
2479
2480
2481//
2482// QgsMarkerLineSymbolLayer
2483//
2484
2490
2492
2494{
2495 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2497
2498 if ( props.contains( u"interval"_s ) )
2499 interval = props[u"interval"_s].toDouble();
2500 if ( props.contains( u"rotate"_s ) )
2501 rotate = ( props[u"rotate"_s].toString() == "1"_L1 );
2502
2503 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2504 setCommonProperties( x.get(), props );
2505 return x.release();
2506}
2507
2509{
2510 return u"MarkerLine"_s;
2511}
2512
2514{
2515 mMarker->setColor( color );
2516 mColor = color;
2517}
2518
2520{
2521 return mMarker ? mMarker->color() : mColor;
2522}
2523
2525{
2526 // if being rotated, it gets initialized with every line segment
2528 if ( rotateSymbols() )
2530 mMarker->setRenderHints( hints );
2531
2532 mMarker->startRender( context.renderContext(), context.fields() );
2533}
2534
2536{
2537 mMarker->stopRender( context.renderContext() );
2538}
2539
2540
2542{
2543 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2545 return x.release();
2546}
2547
2548void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2549{
2550 QgsSldExportContext context;
2551 context.setExtraProperties( props );
2552 toSld( doc, element, context );
2553}
2554
2555bool QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
2556{
2557 const QVariantMap props = context.extraProperties();
2558 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2559 {
2560 QDomElement symbolizerElem = doc.createElement( u"se:LineSymbolizer"_s );
2561 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
2562 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
2563 element.appendChild( symbolizerElem );
2564
2565 // <Geometry>
2566 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
2567
2568 QString gap;
2570 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"firstPoint"_s ) );
2572 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"lastPoint"_s ) );
2574 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"centralPoint"_s ) );
2576 // no way to get line/polygon's vertices, use a VendorOption
2577 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"placement"_s, u"points"_s ) );
2578
2580 {
2582 gap = qgsDoubleToString( interval );
2583 }
2584
2585 if ( !rotateSymbols() )
2586 {
2587 // markers in LineSymbolizer must be drawn following the line orientation,
2588 // use a VendorOption when no marker rotation
2589 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, u"rotateMarker"_s, u"0"_s ) );
2590 }
2591
2592 // <Stroke>
2593 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
2594 symbolizerElem.appendChild( strokeElem );
2595
2596 // <GraphicStroke>
2597 QDomElement graphicStrokeElem = doc.createElement( u"se:GraphicStroke"_s );
2598 strokeElem.appendChild( graphicStrokeElem );
2599
2600 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2601 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2602 {
2603 markerLayer->writeSldMarker( doc, graphicStrokeElem, context );
2604 }
2605 else if ( layer )
2606 {
2607 QgsDebugError( u"QgsMarkerSymbolLayer expected, %1 found. Skip it."_s.arg( layer->layerType() ) );
2608 }
2609 else
2610 {
2611 QgsDebugError( u"Missing marker line symbol layer. Skip it."_s );
2612 }
2613
2614 if ( !gap.isEmpty() )
2615 {
2616 QDomElement gapElem = doc.createElement( u"se:Gap"_s );
2617 QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap, context );
2618 graphicStrokeElem.appendChild( gapElem );
2619 }
2620
2621 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2622 {
2623 QDomElement perpOffsetElem = doc.createElement( u"se:PerpendicularOffset"_s );
2625 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2626 symbolizerElem.appendChild( perpOffsetElem );
2627 }
2628 }
2629 return true;
2630}
2631
2633{
2634 QgsDebugMsgLevel( u"Entered."_s, 4 );
2635
2636 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2637 if ( strokeElem.isNull() )
2638 return nullptr;
2639
2640 QDomElement graphicStrokeElem = strokeElem.firstChildElement( u"GraphicStroke"_s );
2641 if ( graphicStrokeElem.isNull() )
2642 return nullptr;
2643
2644 // retrieve vendor options
2645 bool rotateMarker = true;
2647
2648 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2649 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2650 {
2651 if ( it.key() == "placement"_L1 )
2652 {
2653 if ( it.value() == "points"_L1 )
2655 else if ( it.value() == "firstPoint"_L1 )
2657 else if ( it.value() == "lastPoint"_L1 )
2659 else if ( it.value() == "centralPoint"_L1 )
2661 }
2662 else if ( it.value() == "rotateMarker"_L1 )
2663 {
2664 rotateMarker = it.value() == "0"_L1;
2665 }
2666 }
2667
2668 std::unique_ptr< QgsMarkerSymbol > marker;
2669
2670 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicStrokeElem );
2671 if ( l )
2672 {
2673 QgsSymbolLayerList layers;
2674 layers.append( l.release() );
2675 marker = std::make_unique<QgsMarkerSymbol>( layers );
2676 }
2677
2678 if ( !marker )
2679 return nullptr;
2680
2681 double interval = 0.0;
2682 QDomElement gapElem = graphicStrokeElem.firstChildElement( u"Gap"_s );
2683 if ( !gapElem.isNull() )
2684 {
2685 bool ok;
2686 double d = gapElem.firstChild().firstChild().nodeValue().toDouble( &ok );
2687 if ( ok )
2688 interval = d;
2689 }
2690
2691 double offset = 0.0;
2692 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( u"PerpendicularOffset"_s );
2693 if ( !perpOffsetElem.isNull() )
2694 {
2695 bool ok;
2696 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2697 if ( ok )
2698 offset = d;
2699 }
2700
2701 double scaleFactor = 1.0;
2702 const QString uom = element.attribute( u"uom"_s );
2703 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2704 interval = interval * scaleFactor;
2705 offset = offset * scaleFactor;
2706
2708 x->setOutputUnit( sldUnitSize );
2710 x->setInterval( interval );
2711 x->setSubSymbol( marker.release() );
2712 x->setOffset( offset );
2713 return x;
2714}
2715
2717{
2718 mMarker->setSize( width );
2719}
2720
2722{
2723 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2724 {
2725 mMarker->setDataDefinedSize( property );
2726 }
2728}
2729
2731{
2732 const double prevOpacity = mMarker->opacity();
2733 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2735 mMarker->setOpacity( prevOpacity );
2736}
2737
2739{
2740 mMarker->setLineAngle( angle );
2741}
2742
2744{
2745 return mMarker->angle();
2746}
2747
2749{
2750 mMarker->setAngle( angle );
2751}
2752
2753void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2754{
2755 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2757
2758 mMarker->renderPoint( point, feature, context, layer, selected );
2759
2760 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2761}
2762
2764{
2765 return mMarker->size();
2766}
2767
2769{
2770 return mMarker->size( context );
2771}
2772
2778
2794
2796{
2797 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2798 if ( mMarker )
2799 attr.unite( mMarker->usedAttributes( context ) );
2800 return attr;
2801}
2802
2804{
2806 return true;
2807 if ( mMarker && mMarker->hasDataDefinedProperties() )
2808 return true;
2809 return false;
2810}
2811
2813{
2814 return ( mMarker->size( context ) / 2.0 ) + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
2815}
2816
2817
2818//
2819// QgsHashedLineSymbolLayer
2820//
2821
2823 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2824{
2825 setSubSymbol( new QgsLineSymbol() );
2826}
2827
2829
2831{
2832 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2834
2835 if ( props.contains( u"interval"_s ) )
2836 interval = props[u"interval"_s].toDouble();
2837 if ( props.contains( u"rotate"_s ) )
2838 rotate = ( props[u"rotate"_s] == "1"_L1 );
2839
2840 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2841 setCommonProperties( x.get(), props );
2842 if ( props.contains( u"hash_angle"_s ) )
2843 {
2844 x->setHashAngle( props[u"hash_angle"_s].toDouble() );
2845 }
2846
2847 if ( props.contains( u"hash_length"_s ) )
2848 x->setHashLength( props[u"hash_length"_s].toDouble() );
2849
2850 if ( props.contains( u"hash_length_unit"_s ) )
2851 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[u"hash_length_unit"_s].toString() ) );
2852
2853 if ( props.contains( u"hash_length_map_unit_scale"_s ) )
2854 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"hash_length_map_unit_scale"_s].toString() ) );
2855
2856 return x.release();
2857}
2858
2860{
2861 return u"HashLine"_s;
2862}
2863
2865{
2866 // if being rotated, it gets initialized with every line segment
2868 if ( rotateSymbols() )
2870 mHashSymbol->setRenderHints( hints );
2871
2872 mHashSymbol->startRender( context.renderContext(), context.fields() );
2873}
2874
2876{
2877 mHashSymbol->stopRender( context.renderContext() );
2878}
2879
2881{
2883 map[u"hash_angle"_s] = QString::number( mHashAngle );
2884
2885 map[u"hash_length"_s] = QString::number( mHashLength );
2886 map[u"hash_length_unit"_s] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2887 map[u"hash_length_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2888
2889 return map;
2890}
2891
2893{
2894 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2896 x->setHashAngle( mHashAngle );
2897 x->setHashLength( mHashLength );
2898 x->setHashLengthUnit( mHashLengthUnit );
2899 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2900 return x.release();
2901}
2902
2904{
2905 mHashSymbol->setColor( color );
2906 mColor = color;
2907}
2908
2910{
2911 return mHashSymbol ? mHashSymbol->color() : mColor;
2912}
2913
2915{
2916 return mHashSymbol.get();
2917}
2918
2920{
2921 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2922 {
2923 delete symbol;
2924 return false;
2925 }
2926
2927 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2928 mColor = mHashSymbol->color();
2929 return true;
2930}
2931
2933{
2934 mHashLength = width;
2935}
2936
2938{
2939 return mHashLength;
2940}
2941
2943{
2944 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2945}
2946
2948{
2949 return ( mHashSymbol->width( context ) / 2.0 )
2950 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2952}
2953
2955{
2957 mHashSymbol->setOutputUnit( unit );
2958}
2959
2961{
2962 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2963 if ( mHashSymbol )
2964 attr.unite( mHashSymbol->usedAttributes( context ) );
2965 return attr;
2966}
2967
2969{
2971 return true;
2972 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2973 return true;
2974 return false;
2975}
2976
2978{
2979 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
2980 {
2981 mHashSymbol->setDataDefinedWidth( property );
2982 }
2984}
2985
3003
3005{
3006 mSymbolLineAngle = angle;
3007}
3008
3010{
3011 return mSymbolAngle;
3012}
3013
3015{
3016 mSymbolAngle = angle;
3017}
3018
3019void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
3020{
3021 double lineLength = mHashLength;
3023 {
3024 context.expressionContext().setOriginalValueVariable( mHashLength );
3025 lineLength = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::LineDistance, context.expressionContext(), lineLength );
3026 }
3027 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
3028
3029 double hashAngle = mHashAngle;
3031 {
3032 context.expressionContext().setOriginalValueVariable( mHashAngle );
3034 }
3035
3036 QgsPointXY center( point );
3037 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3038 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3039
3040 QPolygonF points;
3041 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
3042
3043 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3045
3046 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
3047
3048 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
3049}
3050
3052{
3053 return mHashAngle;
3054}
3055
3057{
3058 mHashAngle = angle;
3059}
3060
3062{
3063 const double prevOpacity = mHashSymbol->opacity();
3064 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
3066 mHashSymbol->setOpacity( prevOpacity );
3067}
3068
3069//
3070// QgsAbstractBrushedLineSymbolLayer
3071//
3072
3073void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
3074{
3075 if ( !context.renderContext().painter() )
3076 return;
3077
3078 double offset = mOffset;
3080 {
3083 }
3084
3085 QPolygonF offsetPoints;
3086 if ( qgsDoubleNear( offset, 0 ) )
3087 {
3088 renderLine( points, context, patternThickness, patternLength, brush );
3089 }
3090 else
3091 {
3092 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3093
3094 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3095 for ( const QPolygonF &part : offsetLine )
3096 {
3097 renderLine( part, context, patternThickness, patternLength, brush );
3098 }
3099 }
3100}
3101
3102void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness, const double patternLength, const QBrush &sourceBrush )
3103{
3104 QPainter *p = context.renderContext().painter();
3105 if ( !p )
3106 return;
3107
3108 QBrush brush = sourceBrush;
3109
3110 // duplicate points mess up the calculations, we need to remove them first
3111 // we'll calculate the min/max coordinate at the same time, since we're already looping
3112 // through the points
3113 QPolygonF inputPoints;
3114 inputPoints.reserve( points.size() );
3115 QPointF prev;
3116 double minX = std::numeric_limits< double >::max();
3117 double minY = std::numeric_limits< double >::max();
3118 double maxX = std::numeric_limits< double >::lowest();
3119 double maxY = std::numeric_limits< double >::lowest();
3120
3121 for ( const QPointF &pt : std::as_const( points ) )
3122 {
3123 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3124 continue;
3125
3126 inputPoints << pt;
3127 prev = pt;
3128 minX = std::min( minX, pt.x() );
3129 minY = std::min( minY, pt.y() );
3130 maxX = std::max( maxX, pt.x() );
3131 maxY = std::max( maxY, pt.y() );
3132 }
3133
3134 if ( inputPoints.size() < 2 ) // nothing to render
3135 return;
3136
3137 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3138 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3139 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3140 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3141
3142 // 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
3143 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3144 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3145
3146 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 ) && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3147
3148 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3149 if ( temporaryImage.isNull() )
3150 {
3151 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3152 return;
3153 }
3154
3155 // clear temporary image contents
3156 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3157 return;
3158 temporaryImage.fill( Qt::transparent );
3159 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3160 return;
3161
3162 Qt::PenJoinStyle join = mPenJoinStyle;
3164 {
3167 if ( !QgsVariantUtils::isNull( exprVal ) )
3168 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3169 }
3170
3171 Qt::PenCapStyle cap = mPenCapStyle;
3173 {
3176 if ( !QgsVariantUtils::isNull( exprVal ) )
3177 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3178 }
3179
3180 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3181 QPainterPathStroker stroker;
3182 stroker.setWidth( lineThickness );
3183 stroker.setCapStyle( cap );
3184 stroker.setJoinStyle( join );
3185
3186 QPainterPath path;
3187 path.addPolygon( inputPoints );
3188 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3189
3190 // prepare temporary image
3191 QPainter imagePainter;
3192 imagePainter.begin( &temporaryImage );
3193 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3194 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3195
3196 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3197 imagePainter.setPen( Qt::NoPen );
3198
3199 QPointF segmentStartPoint = inputPoints.at( 0 );
3200
3201 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3202 double progressThroughImage = 0;
3203
3204 QgsPoint prevSegmentPolygonEndLeft;
3205 QgsPoint prevSegmentPolygonEndRight;
3206
3207 // for closed rings this will store the left/right polygon points of the start/end of the line
3208 QgsPoint startLinePolygonLeft;
3209 QgsPoint startLinePolygonRight;
3210
3211 for ( int i = 1; i < inputPoints.size(); ++i )
3212 {
3213 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3214 break;
3215
3216 const QPointF segmentEndPoint = inputPoints.at( i );
3217 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(), segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3218
3219 // left/right end points of the current segment polygon
3220 QgsPoint thisSegmentPolygonEndLeft;
3221 QgsPoint thisSegmentPolygonEndRight;
3222 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3223 QgsPoint thisSegmentPolygonEndLeftForPainter;
3224 QgsPoint thisSegmentPolygonEndRightForPainter;
3225 if ( i == 1 )
3226 {
3227 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3228 // (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.)
3229 if ( isClosedLine )
3230 {
3231 // project the current segment out by half the image thickness to either side of the line
3232 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3233 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3234 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3235 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3236
3237 // 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
3238 // what angle the current segment polygon should START on.
3239 const double lastSegmentAngleDegrees = 180.0
3240 / M_PI
3241 * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(), segmentStartPoint.x(), segmentStartPoint.y() )
3242 - 90;
3243
3244 // project out the LAST segment in the line by half the image thickness to either side of the line
3245 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3246 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3247 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3248 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3249
3250 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3251 // 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
3252 // join)
3253 QgsPoint intersectionPoint;
3254 bool isIntersection = false;
3255 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3256 if ( !isIntersection )
3257 prevSegmentPolygonEndLeft = startPointLeft;
3258 isIntersection = false;
3259 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3260 if ( !isIntersection )
3261 prevSegmentPolygonEndRight = startPointRight;
3262
3263 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3264 startLinePolygonRight = prevSegmentPolygonEndRight;
3265 }
3266 else
3267 {
3268 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3269 if ( cap != Qt::PenCapStyle::FlatCap )
3270 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3271 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3272 if ( cap != Qt::PenCapStyle::FlatCap )
3273 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3274 }
3275 }
3276
3277 if ( i < inputPoints.size() - 1 )
3278 {
3279 // for all other segments except the last
3280
3281 // project the current segment out by half the image thickness to either side of the line
3282 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3283 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3284 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3285 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3286
3287 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3288 // what angle the current segment polygon should end on
3289 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(), inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3290
3291 // project out the next segment by half the image thickness to either side of the line
3292 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3293 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3294 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3295 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3296
3297 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3298 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3299 // join)
3300 QgsPoint intersectionPoint;
3301 bool isIntersection = false;
3302 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3303 if ( !isIntersection )
3304 thisSegmentPolygonEndLeft = endPointLeft;
3305 isIntersection = false;
3306 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3307 if ( !isIntersection )
3308 thisSegmentPolygonEndRight = endPointRight;
3309
3310 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3311 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3312 }
3313 else
3314 {
3315 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3316 // unless it's a closed line
3317 if ( isClosedLine )
3318 {
3319 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3320 thisSegmentPolygonEndRight = startLinePolygonRight;
3321
3322 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3323 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3324 }
3325 else
3326 {
3327 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3328 if ( cap != Qt::PenCapStyle::FlatCap )
3329 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3330 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3331 if ( cap != Qt::PenCapStyle::FlatCap )
3332 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3333
3334 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3335 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3336 }
3337 }
3338
3339 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3340 // where we got with the previous segment), at the correct angle
3341 QTransform brushTransform;
3342 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3343 brushTransform.rotate( -segmentAngleDegrees );
3344 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3345 {
3346 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3347 // we need to also do the same for the brush transform
3348 brushTransform.translate( -( lineThickness / 2 ), 0 );
3349 }
3350 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3351
3352 brush.setTransform( brushTransform );
3353 imagePainter.setBrush( brush );
3354
3355 // now draw the segment polygon
3356 imagePainter.drawPolygon(
3357 QPolygonF()
3358 << prevSegmentPolygonEndLeft.toQPointF()
3359 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3360 << thisSegmentPolygonEndRightForPainter.toQPointF()
3361 << prevSegmentPolygonEndRight.toQPointF()
3362 << prevSegmentPolygonEndLeft.toQPointF()
3363 );
3364
3365#if 0 // for debugging, will draw the segment polygons
3366 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3367 imagePainter.setBrush( Qt::NoBrush );
3368 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3369 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3370 << thisSegmentPolygonEndRightForPainter.toQPointF()
3371 << prevSegmentPolygonEndRight.toQPointF()
3372 << prevSegmentPolygonEndLeft.toQPointF() );
3373 imagePainter.setPen( Qt::NoPen );
3374#endif
3375
3376 // calculate the new progress horizontal through the source image to account for the length
3377 // of the segment we've just drawn
3378 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 ) + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3379 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3380 progressThroughImage = fmod( progressThroughImage, patternLength );
3381
3382 // shuffle buffered variables for next loop
3383 segmentStartPoint = segmentEndPoint;
3384 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3385 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3386 }
3387 imagePainter.end();
3388
3389 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3390 return;
3391
3392 // lastly, draw the temporary image onto the destination painter at the correct place
3393 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS, minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3394}
3395
3396
3397//
3398// QgsRasterLineSymbolLayer
3399//
3400
3404
3406
3408{
3409 auto res = std::make_unique<QgsRasterLineSymbolLayer>();
3410
3411 if ( properties.contains( u"line_width"_s ) )
3412 {
3413 res->setWidth( properties[u"line_width"_s].toDouble() );
3414 }
3415 if ( properties.contains( u"line_width_unit"_s ) )
3416 {
3417 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
3418 }
3419 if ( properties.contains( u"width_map_unit_scale"_s ) )
3420 {
3421 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
3422 }
3423
3424 if ( properties.contains( u"imageFile"_s ) )
3425 res->setPath( properties[u"imageFile"_s].toString() );
3426
3427 if ( properties.contains( u"offset"_s ) )
3428 {
3429 res->setOffset( properties[u"offset"_s].toDouble() );
3430 }
3431 if ( properties.contains( u"offset_unit"_s ) )
3432 {
3433 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
3434 }
3435 if ( properties.contains( u"offset_map_unit_scale"_s ) )
3436 {
3437 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
3438 }
3439
3440 if ( properties.contains( u"joinstyle"_s ) )
3441 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[u"joinstyle"_s].toString() ) );
3442 if ( properties.contains( u"capstyle"_s ) )
3443 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[u"capstyle"_s].toString() ) );
3444
3445 if ( properties.contains( u"alpha"_s ) )
3446 {
3447 res->setOpacity( properties[u"alpha"_s].toDouble() );
3448 }
3449
3450 return res.release();
3451}
3452
3453
3455{
3456 QVariantMap map;
3457 map[u"imageFile"_s] = mPath;
3458
3459 map[u"line_width"_s] = QString::number( mWidth );
3460 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
3461 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3462
3465
3466 map[u"offset"_s] = QString::number( mOffset );
3467 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3468 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3469
3470 map[u"alpha"_s] = QString::number( mOpacity );
3471
3472 return map;
3473}
3474
3476{
3477 auto res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3478 res->setWidth( mWidth );
3479 res->setWidthUnit( mWidthUnit );
3480 res->setWidthMapUnitScale( mWidthMapUnitScale );
3481 res->setPenJoinStyle( mPenJoinStyle );
3482 res->setPenCapStyle( mPenCapStyle );
3483 res->setOffsetUnit( mOffsetUnit );
3484 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3485 res->setOffset( mOffset );
3486 res->setOpacity( mOpacity );
3487 copyCommonProperties( res.get() );
3488 return res.release();
3489}
3490
3491void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3492{
3493 const QVariantMap::iterator it = properties.find( u"imageFile"_s );
3494 if ( it != properties.end() && it.value().userType() == QMetaType::Type::QString )
3495 {
3496 if ( saving )
3497 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3498 else
3499 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3500 }
3501}
3502
3504{
3505 mPath = path;
3506}
3507
3509{
3510 return u"RasterLine"_s;
3511}
3512
3517
3519{
3520 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3521
3523
3524 double opacity = mOpacity * context.opacity();
3525 bool cached = false;
3527 mPath,
3528 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ), static_cast< int >( std::ceil( scaledHeight ) ) ),
3529 true,
3530 opacity,
3531 cached,
3533 );
3534}
3535
3538
3540{
3541 if ( !context.renderContext().painter() )
3542 return;
3543
3544 QImage sourceImage = mLineImage;
3548 {
3549 QString path = mPath;
3551 {
3552 context.setOriginalValueVariable( path );
3554 }
3555
3556 double strokeWidth = mWidth;
3558 {
3559 context.setOriginalValueVariable( strokeWidth );
3560 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3561 }
3562 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3563
3565 double opacity = mOpacity;
3567 {
3570 }
3571 opacity *= context.opacity();
3572
3573 bool cached = false;
3574 sourceImage = QgsApplication::imageCache()->pathAsImage(
3575 path,
3576 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ), static_cast< int >( std::ceil( scaledHeight ) ) ),
3577 true,
3578 opacity,
3579 cached,
3581 );
3582 }
3583
3584 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3585 if ( useSelectedColor )
3586 {
3588 }
3589
3590 const QBrush brush( sourceImage );
3591
3592 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3593}
3594
3601
3603{
3605 if ( mWidthUnit != unit || mOffsetUnit != unit )
3606 {
3608 }
3609 return unit;
3610}
3611
3616
3622
3631
3633{
3634 return ( mWidth / 2.0 ) + mOffset;
3635}
3636
3638{
3639 return QColor();
3640}
3641
3642
3643//
3644// QgsLineburstSymbolLayer
3645//
3646
3653
3655
3657{
3658 auto res = std::make_unique<QgsLineburstSymbolLayer>();
3659
3660 if ( properties.contains( u"line_width"_s ) )
3661 {
3662 res->setWidth( properties[u"line_width"_s].toDouble() );
3663 }
3664 if ( properties.contains( u"line_width_unit"_s ) )
3665 {
3666 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
3667 }
3668 if ( properties.contains( u"width_map_unit_scale"_s ) )
3669 {
3670 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
3671 }
3672
3673 if ( properties.contains( u"offset"_s ) )
3674 {
3675 res->setOffset( properties[u"offset"_s].toDouble() );
3676 }
3677 if ( properties.contains( u"offset_unit"_s ) )
3678 {
3679 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
3680 }
3681 if ( properties.contains( u"offset_map_unit_scale"_s ) )
3682 {
3683 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
3684 }
3685
3686 if ( properties.contains( u"joinstyle"_s ) )
3687 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[u"joinstyle"_s].toString() ) );
3688 if ( properties.contains( u"capstyle"_s ) )
3689 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[u"capstyle"_s].toString() ) );
3690
3691 if ( properties.contains( u"color_type"_s ) )
3692 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[u"color_type"_s].toInt() ) );
3693
3694 if ( properties.contains( u"color"_s ) )
3695 {
3696 res->setColor( QgsColorUtils::colorFromString( properties[u"color"_s].toString() ) );
3697 }
3698 if ( properties.contains( u"gradient_color2"_s ) )
3699 {
3700 res->setColor2( QgsColorUtils::colorFromString( properties[u"gradient_color2"_s].toString() ) );
3701 }
3702
3703 //attempt to create color ramp from props
3704 if ( properties.contains( u"rampType"_s ) && properties[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
3705 {
3706 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3707 }
3708 else
3709 {
3710 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3711 }
3712
3713 return res.release();
3714}
3715
3717{
3718 QVariantMap map;
3719
3720 map[u"line_width"_s] = QString::number( mWidth );
3721 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
3722 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3723
3726
3727 map[u"offset"_s] = QString::number( mOffset );
3728 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3729 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3730
3731 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
3732 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
3733 map[u"color_type"_s] = QString::number( static_cast< int >( mGradientColorType ) );
3734 if ( mGradientRamp )
3735 {
3736 map.insert( mGradientRamp->properties() );
3737 }
3738
3739 return map;
3740}
3741
3743{
3744 auto res = std::make_unique< QgsLineburstSymbolLayer >();
3745 res->setWidth( mWidth );
3746 res->setWidthUnit( mWidthUnit );
3747 res->setWidthMapUnitScale( mWidthMapUnitScale );
3748 res->setPenJoinStyle( mPenJoinStyle );
3749 res->setPenCapStyle( mPenCapStyle );
3750 res->setOffsetUnit( mOffsetUnit );
3751 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3752 res->setOffset( mOffset );
3753 res->setColor( mColor );
3754 res->setColor2( mColor2 );
3755 res->setGradientColorType( mGradientColorType );
3756 if ( mGradientRamp )
3757 res->setColorRamp( mGradientRamp->clone() );
3758 copyCommonProperties( res.get() );
3759 return res.release();
3760}
3761
3763{
3764 return u"Lineburst"_s;
3765}
3766
3771
3774
3777
3779{
3780 if ( !context.renderContext().painter() )
3781 return;
3782
3783 double strokeWidth = mWidth;
3785 {
3786 context.setOriginalValueVariable( strokeWidth );
3787 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3788 }
3789 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3790
3791 //update alpha of gradient colors
3792 QColor color1 = mColor;
3794 {
3797 }
3798 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3799 if ( useSelectedColor )
3800 {
3801 color1 = context.renderContext().selectionColor();
3802 }
3803 color1.setAlphaF( context.opacity() * color1.alphaF() );
3804
3805 //second gradient color
3806 QColor color2 = mColor2;
3808 {
3811 }
3812
3813 //create a QGradient with the desired properties
3814 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3815 //add stops to gradient
3817 && mGradientRamp
3819 {
3820 //color ramp gradient
3821 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3822 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3823 }
3824 else
3825 {
3826 //two color gradient
3827 gradient.setColorAt( 0.0, color1 );
3828 gradient.setColorAt( 1.0, color2 );
3829 }
3830 const QBrush brush( gradient );
3831
3832 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3833}
3834
3841
3843{
3845 if ( mWidthUnit != unit || mOffsetUnit != unit )
3846 {
3848 }
3849 return unit;
3850}
3851
3856
3862
3871
3873{
3874 return ( mWidth / 2.0 ) + mOffset;
3875}
3876
3881
3883{
3884 mGradientRamp.reset( ramp );
3885}
3886
3887//
3888// QgsFilledLineSymbolLayer
3889//
3890
3893{
3894 mWidth = width;
3895 mFill = fillSymbol ? std::unique_ptr< QgsFillSymbol >( fillSymbol ) : QgsFillSymbol::createSimple( QVariantMap() );
3896}
3897
3899
3901{
3903
3904 // throughout the history of QGIS and different layer types, we've used
3905 // a huge range of different strings for the same property. The logic here
3906 // is designed to be forgiving to this and accept a range of string keys:
3907 if ( props.contains( u"line_width"_s ) )
3908 {
3909 width = props[u"line_width"_s].toDouble();
3910 }
3911 else if ( props.contains( u"outline_width"_s ) )
3912 {
3913 width = props[u"outline_width"_s].toDouble();
3914 }
3915 else if ( props.contains( u"width"_s ) )
3916 {
3917 width = props[u"width"_s].toDouble();
3918 }
3919
3920 auto l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ).release() );
3921
3922 if ( props.contains( u"line_width_unit"_s ) )
3923 {
3924 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"line_width_unit"_s].toString() ) );
3925 }
3926 else if ( props.contains( u"outline_width_unit"_s ) )
3927 {
3928 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"outline_width_unit"_s].toString() ) );
3929 }
3930 else if ( props.contains( u"width_unit"_s ) )
3931 {
3932 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"width_unit"_s].toString() ) );
3933 }
3934
3935 if ( props.contains( u"width_map_unit_scale"_s ) )
3936 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"width_map_unit_scale"_s].toString() ) );
3937 if ( props.contains( u"offset"_s ) )
3938 l->setOffset( props[u"offset"_s].toDouble() );
3939 if ( props.contains( u"offset_unit"_s ) )
3940 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
3941 if ( props.contains( u"offset_map_unit_scale"_s ) )
3942 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
3943 if ( props.contains( u"joinstyle"_s ) )
3944 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[u"joinstyle"_s].toString() ) );
3945 if ( props.contains( u"capstyle"_s ) )
3946 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[u"capstyle"_s].toString() ) );
3947
3948 l->restoreOldDataDefinedProperties( props );
3949
3950 return l.release();
3951}
3952
3954{
3955 return u"FilledLine"_s;
3956}
3957
3959{
3960 if ( mFill )
3961 {
3962 mFill->setRenderHints( mFill->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3963 mFill->startRender( context.renderContext(), context.fields() );
3964 }
3965}
3966
3968{
3969 if ( mFill )
3970 {
3971 mFill->stopRender( context.renderContext() );
3972 }
3973}
3974
3976{
3977 installMasks( context, true );
3978
3979 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3980}
3981
3983{
3984 removeMasks( context, true );
3985
3986 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3987}
3988
3990{
3991 QPainter *p = context.renderContext().painter();
3992 if ( !p || !mFill )
3993 return;
3994
3995 double width = mWidth;
3997 {
4000 }
4001
4002 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
4003
4004 Qt::PenJoinStyle join = mPenJoinStyle;
4006 {
4009 if ( !QgsVariantUtils::isNull( exprVal ) )
4010 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
4011 }
4012
4013 Qt::PenCapStyle cap = mPenCapStyle;
4015 {
4018 if ( !QgsVariantUtils::isNull( exprVal ) )
4019 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
4020 }
4021
4022 double offset = mOffset;
4024 {
4027 }
4028
4029 const double prevOpacity = mFill->opacity();
4030 mFill->setOpacity( mFill->opacity() * context.opacity() );
4031
4032 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4034
4035 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4036
4037 if ( points.count() >= 2 )
4038 {
4039 std::unique_ptr< QgsAbstractGeometry > ls = QgsLineString::fromQPolygonF( points );
4040 geos::unique_ptr lineGeom;
4041
4042 if ( !qgsDoubleNear( offset, 0 ) )
4043 {
4044 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
4047 {
4048 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
4049 // and clamp it to a reasonable range. It's the best we can do in this situation!
4050 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
4051 }
4052
4054 if ( geometryType == Qgis::GeometryType::Polygon )
4055 {
4056 auto inputPoly = std::make_unique< QgsPolygon >( static_cast< QgsLineString * >( ls.release() ) );
4057 geos::unique_ptr g( QgsGeos::asGeos( inputPoly.get() ) );
4058 lineGeom = QgsGeos::buffer( g.get(), -scaledOffset, 0, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Miter, 2 );
4059 // the result is a polygon => extract line work
4060 QgsGeometry polygon( QgsGeos::fromGeos( lineGeom.get() ) );
4061 QVector<QgsGeometry> parts = polygon.coerceToType( Qgis::WkbType::MultiLineString );
4062 if ( !parts.empty() )
4063 {
4064 lineGeom = QgsGeos::asGeos( parts.at( 0 ).constGet() );
4065 }
4066 else
4067 {
4068 lineGeom.reset();
4069 }
4070 }
4071 else
4072 {
4073 geos::unique_ptr g( QgsGeos::asGeos( ls.get() ) );
4074 lineGeom = QgsGeos::offsetCurve( g.get(), scaledOffset, 0, Qgis::JoinStyle::Miter, 8.0 );
4075 }
4076 }
4077 else
4078 {
4079 lineGeom = QgsGeos::asGeos( ls.get() );
4080 }
4081
4082 if ( lineGeom )
4083 {
4085 if ( buffered )
4086 {
4087 // convert to rings
4088 std::unique_ptr< QgsAbstractGeometry > bufferedGeom = QgsGeos::fromGeos( buffered.get() );
4089 const QList< QList< QPolygonF > > parts = QgsSymbolLayerUtils::toQPolygonF( bufferedGeom.get(), Qgis::SymbolType::Fill );
4090 for ( const QList< QPolygonF > &polygon : parts )
4091 {
4092 QVector< QPolygonF > rings;
4093 for ( int i = 1; i < polygon.size(); ++i )
4094 rings << polygon.at( i );
4095 mFill->renderPolygon( polygon.value( 0 ), &rings, context.feature(), context.renderContext(), -1, useSelectedColor );
4096 }
4097 }
4098 }
4099 }
4100
4102
4103 mFill->setOpacity( prevOpacity );
4104}
4105
4107{
4108 QVariantMap map;
4109
4110 map[u"line_width"_s] = QString::number( mWidth );
4111 map[u"line_width_unit"_s] = QgsUnitTypes::encodeUnit( mWidthUnit );
4112 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4113 map[u"joinstyle"_s] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
4114 map[u"capstyle"_s] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
4115 map[u"offset"_s] = QString::number( mOffset );
4116 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4117 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4118 if ( mFill )
4119 {
4120 map[u"color"_s] = QgsColorUtils::colorToString( mFill->color() );
4121 }
4122 return map;
4123}
4124
4126{
4127 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4128 copyCommonProperties( res.get() );
4129 res->setSubSymbol( mFill->clone() );
4130 return res.release();
4131}
4132
4134{
4135 return mFill.get();
4136}
4137
4139{
4140 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4141 {
4142 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4143 return true;
4144 }
4145 else
4146 {
4147 delete symbol;
4148 return false;
4149 }
4150}
4151
4153{
4154 if ( mFill )
4155 {
4156 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4157 }
4158 return 0;
4159}
4160
4162{
4163 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4164 if ( mFill )
4165 attr.unite( mFill->usedAttributes( context ) );
4166 return attr;
4167}
4168
4170{
4172 return true;
4173 if ( mFill && mFill->hasDataDefinedProperties() )
4174 return true;
4175 return false;
4176}
4177
4179{
4180 mColor = c;
4181 if ( mFill )
4182 mFill->setColor( c );
4183}
4184
4186{
4187 return mFill ? mFill->color() : mColor;
4188}
4189
4198
4200{
4202 if ( mFill )
4203 mFill->setMapUnitScale( scale );
4204}
4205
4207{
4208 if ( mFill )
4209 {
4210 return mFill->mapUnitScale();
4211 }
4212 return QgsMapUnitScale();
4213}
4214
4216{
4218 if ( mFill )
4219 mFill->setOutputUnit( unit );
4220}
4221
4223{
4224 if ( mFill )
4225 {
4226 return mFill->outputUnit();
4227 }
4229}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:3243
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
Definition qgis.h:3249
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex).
Definition qgis.h:3251
@ LastVertex
Place symbols on the last vertex in the line.
Definition qgis.h:3246
@ CentralPoint
Place symbols at the mid point of the line.
Definition qgis.h:3248
@ SegmentCenter
Place symbols at the center of every line segment.
Definition qgis.h:3250
@ Vertex
Place symbols on every vertex in the line.
Definition qgis.h:3245
@ Interval
Place symbols at regular intervals.
Definition qgis.h:3244
@ FirstVertex
Place symbols on the first vertex in the line.
Definition qgis.h:3247
@ 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:3133
GradientColorSource
Gradient color sources.
Definition qgis.h:3292
@ ColorRamp
Gradient color ramp.
Definition qgis.h:3294
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
Definition qgis.h:908
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3181
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3180
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:913
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:2203
RenderUnit
Rendering size units.
Definition qgis.h:5340
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5344
@ Millimeters
Millimeters.
Definition qgis.h:5341
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5345
@ Unknown
Mixed or unknown units.
Definition qgis.h:5347
@ MapUnits
Map units.
Definition qgis.h:5342
@ Pixels
Pixels.
Definition qgis.h:5343
@ Inches
Inches.
Definition qgis.h:5346
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5348
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2190
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2862
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2857
@ RenderLayerTree
The render is for a layer tree display where map based properties are not available and where avoidan...
Definition qgis.h:2868
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2856
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:3254
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 offsetCurve(const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg=nullptr)
Directly calculates the offset curve for a GEOS geometry object and returns a GEOS geometry result.
Definition qgsgeos.cpp:2742
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2079
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:254
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition qgsgeos.cpp:1562
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
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 startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static void setCommonProperties(QgsTemplatedLineSymbolLayerBase *destLayer, const QVariantMap &properties)
Sets all common symbol properties in the destLayer, using the settings serialized in the properties m...
void setMapUnitScale(const QgsMapUnitScale &scale) final
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) final
Renders the line symbol layer along the outline of polygon, using the given render context.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setIntervalUnit(Qgis::RenderUnit unit)
Sets the units for the interval between symbols.
void setAverageAngleUnit(Qgis::RenderUnit unit)
Sets the unit for the length over which the line's direction is averaged when calculating individual ...
double interval() const
Returns the interval between individual symbols.
void setOffsetAlongLineUnit(Qgis::RenderUnit unit)
Sets the unit used for calculating the offset along line for symbols.
void setAverageAngleMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the length over which the line's direction is averaged when calculating i...
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
double offsetAlongLine() const
Returns the offset along the line for the symbol placement.
void copyTemplateSymbolProperties(QgsTemplatedLineSymbolLayerBase *destLayer) const
Copies all common properties of this layer to another templated symbol layer destLayer.
Qgis::MarkerLinePlacements placements() const
Returns the placement of the symbols.
void setOffsetAlongLine(double offsetAlongLine)
Sets the the offset along the line for the symbol placement.
Qgis::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
Qgis::RenderUnit outputUnit() const final
Returns the units to use for sizes and widths within the symbol layer.
QgsTemplatedLineSymbolLayerBase(bool rotateSymbol=true, double interval=3)
Constructor for QgsTemplatedLineSymbolLayerBase.
virtual void setSymbolLineAngle(double angle)=0
Sets the line angle modification for the symbol's angle.
QgsMapUnitScale mapUnitScale() const final
void setInterval(double interval)
Sets the interval between individual symbols.
Qgis::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
Q_DECL_DEPRECATED Qgis::MarkerLinePlacement placement() const
Returns the placement of the symbols.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setPlaceOnEveryPart(bool respect)
Sets whether the placement applies for every part of multi-part feature geometries.
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qgis::RenderUnit averageAngleUnit() const
Returns the unit for the length over which the line's direction is averaged when calculating individu...
Q_DECL_DEPRECATED void setPlacement(Qgis::MarkerLinePlacement placement)
Sets the placement of the symbols.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
Qgis::RenderUnit blankSegmentsUnit() const
Returns the unit for for blank segments start and end distances.
void setBlankSegmentsUnit(Qgis::RenderUnit unit)
Sets the unit for blank segments start and end distances.
const QgsMapUnitScale & offsetAlongLineMapUnitScale() const
Returns the map unit scale used for calculating the offset in map units along line for symbols.
virtual void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false)=0
Renders the templated symbol at the specified point, using the given render context.
void setIntervalMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the interval between symbols.
void setOffsetAlongLineMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for calculating the offset in map units along line for symbols.
void setAverageAngleLength(double length)
Sets the length of line over which the line's direction is averaged when calculating individual symbo...
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:112
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:6893
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7215
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:7237
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
QMap< QString, QString > QgsStringMap
Definition qgis.h:7475
#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