QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 <memory>
21
22#include "qgsapplication.h"
23#include "qgscolorrampimpl.h"
24#include "qgscolorutils.h"
25#include "qgscurvepolygon.h"
26#include "qgsdxfexport.h"
28#include "qgsfeedback.h"
29#include "qgsfillsymbol.h"
31#include "qgsgeometryutils.h"
32#include "qgsgeos.h"
33#include "qgsimagecache.h"
34#include "qgsimageoperation.h"
35#include "qgslinesymbol.h"
36#include "qgslogger.h"
37#include "qgsmarkersymbol.h"
38#include "qgsmultipolygon.h"
39#include "qgspolygon.h"
40#include "qgsproperty.h"
41#include "qgsrendercontext.h"
42#include "qgssldexportcontext.h"
43#include "qgssymbollayerutils.h"
44#include "qgsunittypes.h"
45
46#include <QDomDocument>
47#include <QDomElement>
48#include <QPainter>
49
51 : mPenStyle( penStyle )
52{
53 mColor = color;
54 mWidth = width;
55 mCustomDashVector << 5 << 2;
56}
57
59
61{
63 mWidthUnit = unit;
64 mOffsetUnit = unit;
65 mCustomDashPatternUnit = unit;
66 mDashPatternOffsetUnit = unit;
67 mTrimDistanceStartUnit = unit;
68 mTrimDistanceEndUnit = unit;
69}
70
72{
74 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
75 {
77 }
78 return unit;
79}
80
86
88{
90 mWidthMapUnitScale = scale;
91 mOffsetMapUnitScale = scale;
92 mCustomDashPatternMapUnitScale = scale;
93}
94
105
107{
111
112 if ( props.contains( QStringLiteral( "line_color" ) ) )
113 {
114 color = QgsColorUtils::colorFromString( props[QStringLiteral( "line_color" )].toString() );
115 }
116 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
117 {
118 color = QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() );
119 }
120 else if ( props.contains( QStringLiteral( "color" ) ) )
121 {
122 //pre 2.5 projects used "color"
123 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
124 }
125 if ( props.contains( QStringLiteral( "line_width" ) ) )
126 {
127 width = props[QStringLiteral( "line_width" )].toDouble();
128 }
129 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
130 {
131 width = props[QStringLiteral( "outline_width" )].toDouble();
132 }
133 else if ( props.contains( QStringLiteral( "width" ) ) )
134 {
135 //pre 2.5 projects used "width"
136 width = props[QStringLiteral( "width" )].toDouble();
137 }
138 if ( props.contains( QStringLiteral( "line_style" ) ) )
139 {
140 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
141 }
142 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
143 {
144 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
145 }
146 else if ( props.contains( QStringLiteral( "penstyle" ) ) )
147 {
148 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
149 }
150
152 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
153 {
154 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
155 }
156 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
157 {
158 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
159 }
160 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
161 {
162 //pre 2.5 projects used "width_unit"
163 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
164 }
165 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
166 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
167 if ( props.contains( QStringLiteral( "offset" ) ) )
168 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
169 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
170 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
171 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
172 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
173 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
174 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
175 if ( props.contains( QStringLiteral( "capstyle" ) ) )
176 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
177
178 if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
179 {
180 l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
181 }
182 if ( props.contains( QStringLiteral( "customdash" ) ) )
183 {
184 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
185 }
186 if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
187 {
188 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
189 }
190 if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
191 {
192 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
193 }
194
195 if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
196 {
197 l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
198 }
199
200 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
201 {
202 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
203 }
204
205 if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
206 l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
207 if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
208 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
209 if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
210 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
211
212 if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
213 l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
214 if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
215 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
216 if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
217 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
218 if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
219 l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
220 if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
221 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
222 if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
223 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
224
225 if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
226 l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
227
228 if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
229 l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
230
232
233 return l;
234}
235
237{
238 return QStringLiteral( "SimpleLine" );
239}
240
245
247{
248 QColor penColor = mColor;
249 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
250 mPen.setColor( penColor );
251 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
252 mPen.setWidthF( scaledWidth );
253
254 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
255 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
256 const double dashWidthDiv = std::max( 1.0, scaledWidth );
257 if ( mUseCustomDashPattern )
258 {
259 mPen.setStyle( Qt::CustomDashLine );
260
261 //scale pattern vector
262
263 QVector<qreal> scaledVector;
264 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
265 for ( ; it != mCustomDashVector.constEnd(); ++it )
266 {
267 //the dash is specified in terms of pen widths, therefore the division
268 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
269 }
270 mPen.setDashPattern( scaledVector );
271 }
272 else
273 {
274 mPen.setStyle( mPenStyle );
275 }
276
277 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
278 {
279 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
280 }
281
282 mPen.setJoinStyle( mPenJoinStyle );
283 mPen.setCapStyle( mPenCapStyle );
284
285 mSelPen = mPen;
286 QColor selColor = context.renderContext().selectionColor();
287 if ( ! SELECTION_IS_OPAQUE )
288 selColor.setAlphaF( context.opacity() );
289 mSelPen.setColor( selColor );
290}
291
293{
294 Q_UNUSED( context )
295}
296
297void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
298{
299 QPainter *p = context.renderContext().painter();
300 if ( !p )
301 {
302 return;
303 }
304
305 QgsExpressionContextScope *scope = nullptr;
306 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
308 {
309 scope = new QgsExpressionContextScope();
310 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
311 }
312
313 if ( mDrawInsidePolygon )
314 p->save();
315
316 switch ( mRingFilter )
317 {
318 case AllRings:
319 case ExteriorRingOnly:
320 {
321 if ( mDrawInsidePolygon )
322 {
323 //only drawing the line on the interior of the polygon, so set clip path for painter
324 QPainterPath clipPath;
325 clipPath.addPolygon( points );
326
327 if ( rings )
328 {
329 //add polygon rings
330 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
331 {
332 QPolygonF ring = *it;
333 clipPath.addPolygon( ring );
334 }
335 }
336
337 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
338 p->setClipPath( clipPath, Qt::IntersectClip );
339 }
340
341 if ( scope )
343
344 renderPolyline( points, context );
345 }
346 break;
347
349 break;
350 }
351
352 if ( rings )
353 {
354 switch ( mRingFilter )
355 {
356 case AllRings:
358 {
359 mOffset = -mOffset; // invert the offset for rings!
360 int ringIndex = 1;
361 for ( const QPolygonF &ring : std::as_const( *rings ) )
362 {
363 if ( scope )
365
366 renderPolyline( ring, context );
367 ringIndex++;
368 }
369 mOffset = -mOffset;
370 }
371 break;
372 case ExteriorRingOnly:
373 break;
374 }
375 }
376
377 if ( mDrawInsidePolygon )
378 {
379 //restore painter to reset clip path
380 p->restore();
381 }
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
474 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
475 && pen.dashOffset() == 0;
476
477 if ( qgsDoubleNear( offset, 0 ) )
478 {
479 if ( applyPatternTweaks )
480 {
481 drawPathWithDashPatternTweaks( p, points, pen );
482 }
483 else
484 {
485 p->setPen( pen );
486 QPainterPath path;
487 path.addPolygon( points );
488 p->drawPath( path );
489 }
490 }
491 else
492 {
493 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
495 {
496 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
497 // and clamp it to a reasonable range. It's the best we can do in this situation!
498 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
499 }
500
501 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
502 for ( const QPolygonF &part : mline )
503 {
504 if ( applyPatternTweaks )
505 {
506 drawPathWithDashPatternTweaks( p, part, pen );
507 }
508 else
509 {
510 p->setPen( pen );
511 QPainterPath path;
512 path.addPolygon( part );
513 p->drawPath( path );
514 }
515 }
516 }
517}
518
520{
521 QVariantMap map;
522 map[QStringLiteral( "line_color" )] = QgsColorUtils::colorToString( mColor );
523 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
524 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
525 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
526 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
527 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
528 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
529 map[QStringLiteral( "offset" )] = QString::number( mOffset );
530 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
531 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
532 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
533 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
534 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
535 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
536 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
537 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
538 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
539 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
540 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
541 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
542 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
543 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
544 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
545 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
546 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
547 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
548 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
549 return map;
550}
551
553{
559 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
560 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
561 l->setOffset( mOffset );
562 l->setPenJoinStyle( mPenJoinStyle );
563 l->setPenCapStyle( mPenCapStyle );
564 l->setUseCustomDashPattern( mUseCustomDashPattern );
565 l->setCustomDashVector( mCustomDashVector );
566 l->setDrawInsidePolygon( mDrawInsidePolygon );
568 l->setDashPatternOffset( mDashPatternOffset );
569 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
570 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
571 l->setTrimDistanceStart( mTrimDistanceStart );
572 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
573 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
574 l->setTrimDistanceEnd( mTrimDistanceEnd );
575 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
576 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
577 l->setAlignDashPattern( mAlignDashPattern );
578 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
579
581 copyPaintEffect( l );
582 return l;
583}
584
585void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
586{
587 QgsSldExportContext context;
588 context.setExtraProperties( props );
589 toSld( doc, element, context );
590}
591
592bool QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
593{
594 if ( mPenStyle == Qt::NoPen )
595 return true;
596
597 const QVariantMap props = context.extraProperties();
598 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
599 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
600 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
601 element.appendChild( symbolizerElem );
602
603 // <Geometry>
604 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString(), context );
605
606 // <Stroke>
607 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
608 symbolizerElem.appendChild( strokeElem );
609
610 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
612 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
613 QgsSymbolLayerUtils::lineToSld( doc, strokeElem, penStyle, mColor, context, width,
614 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
615
616 // <se:PerpendicularOffset>
617 if ( !qgsDoubleNear( mOffset, 0.0 ) )
618 {
619 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
621 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
622 symbolizerElem.appendChild( perpOffsetElem );
623 }
624 return true;
625}
626
627QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
628{
629 if ( mUseCustomDashPattern )
630 {
631 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
632 mPen.color(), mPenJoinStyle,
633 mPenCapStyle, mOffset, &mCustomDashVector );
634 }
635 else
636 {
637 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
638 mPenCapStyle, mOffset );
639 }
640}
641
643{
644 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
645
646 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
647 if ( strokeElem.isNull() )
648 return nullptr;
649
650 Qt::PenStyle penStyle;
651 QColor color;
652 double width;
653 Qt::PenJoinStyle penJoinStyle;
654 Qt::PenCapStyle penCapStyle;
655 QVector<qreal> customDashVector;
656
658 color, width,
661 return nullptr;
662
663 double offset = 0.0;
664 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
665 if ( !perpOffsetElem.isNull() )
666 {
667 bool ok;
668 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
669 if ( ok )
670 offset = d;
671 }
672
673 double scaleFactor = 1.0;
674 const QString uom = element.attribute( QStringLiteral( "uom" ) );
675 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
676 width = width * scaleFactor;
677 offset = offset * scaleFactor;
678
680 l->setOutputUnit( sldUnitSize );
681 l->setOffset( offset );
684 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
686 return l;
687}
688
689void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
690{
691 if ( !dataDefinedProperties().hasActiveProperties() )
692 return; // shortcut
693
694 //data defined properties
695 bool hasStrokeWidthExpression = false;
697 {
699 double scaledWidth = context.renderContext().convertToPainterUnits(
702 pen.setWidthF( scaledWidth );
703 selPen.setWidthF( scaledWidth );
704 hasStrokeWidthExpression = true;
705 }
706
707 //color
709 {
711
713 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
714 pen.setColor( penColor );
715 }
716
717 //offset
719 {
722 }
723
724 //dash dot vector
725
726 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
727 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
728 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
729
731 {
732 QVector<qreal> dashVector;
734 if ( !QgsVariantUtils::isNull( exprVal ) )
735 {
736 QStringList dashList = exprVal.toString().split( ';' );
737 QStringList::const_iterator dashIt = dashList.constBegin();
738 for ( ; dashIt != dashList.constEnd(); ++dashIt )
739 {
740 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
741 }
742 pen.setDashPattern( dashVector );
743 }
744 }
745 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
746 {
747 //re-scale pattern vector after data defined pen width was applied
748
749 QVector<qreal> scaledVector;
750 for ( double v : std::as_const( mCustomDashVector ) )
751 {
752 //the dash is specified in terms of pen widths, therefore the division
753 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
754 }
755 mPen.setDashPattern( scaledVector );
756 }
757
758 // dash pattern offset
759 double patternOffset = mDashPatternOffset;
760 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
761 {
762 context.setOriginalValueVariable( patternOffset );
763 patternOffset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::DashPatternOffset, context.renderContext().expressionContext(), mDashPatternOffset );
764 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
765 }
766
767 //line style
769 {
772 if ( !QgsVariantUtils::isNull( exprVal ) )
773 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
774 }
775
776 //join style
778 {
781 if ( !QgsVariantUtils::isNull( exprVal ) )
782 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
783 }
784
785 //cap style
787 {
790 if ( !QgsVariantUtils::isNull( exprVal ) )
791 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
792 }
793}
794
795void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
796{
797 if ( pen.dashPattern().empty() || points.size() < 2 )
798 return;
799
800 if ( pen.widthF() <= 1.0 )
801 {
802 pen.setWidthF( 1.0001 );
803 }
804
805 QVector< qreal > sourcePattern = pen.dashPattern();
806 const double dashWidthDiv = pen.widthF();
807 // back to painter units
808 for ( int i = 0; i < sourcePattern.size(); ++ i )
809 sourcePattern[i] *= pen.widthF();
810
811 QVector< qreal > buffer;
812 QPolygonF bufferedPoints;
813 QPolygonF previousSegmentBuffer;
814 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
815 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
816 // and then append the buffer to the output pattern.
817
818 auto ptIt = points.constBegin();
819 double totalBufferLength = 0;
820 int patternIndex = 0;
821 double currentRemainingDashLength = 0;
822 double currentRemainingGapLength = 0;
823
824 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
825 {
826 QVector< qreal > result;
827 result.reserve( buffer.size() );
828 for ( auto it = buffer.begin(); it != buffer.end(); )
829 {
830 qreal dash = *it++;
831 qreal gap = *it++;
832 while ( dash == 0 && !result.empty() )
833 {
834 result.last() += gap;
835
836 if ( it == buffer.end() )
837 return result;
838 dash = *it++;
839 gap = *it++;
840 }
841 while ( gap == 0 && it != buffer.end() )
842 {
843 dash += *it++;
844 gap = *it++;
845 }
846 result << dash << gap;
847 }
848 return result;
849 };
850
851 double currentBufferLineLength = 0;
852 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
853 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
854 {
855 if ( buffer.empty() || bufferedPoints.size() < 2 )
856 {
857 return;
858 }
859
860 if ( currentRemainingDashLength )
861 {
862 // ended midway through a dash -- we want to finish this off
863 buffer << currentRemainingDashLength << 0.0;
864 totalBufferLength += currentRemainingDashLength;
865 }
866 QVector< qreal > compressed = compressPattern( buffer );
867 if ( !currentRemainingDashLength )
868 {
869 // ended midway through a gap -- we don't want this, we want to end at previous dash
870 totalBufferLength -= compressed.last();
871 compressed.last() = 0;
872 }
873
874 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
875 const double scaleFactor = currentBufferLineLength / totalBufferLength;
876
877 bool shouldFlushPreviousSegmentBuffer = false;
878
879 if ( !previousSegmentBuffer.empty() )
880 {
881 // add first dash from current buffer
882 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
883 if ( !firstDashSubstring.empty() )
884 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
885
886 // then we skip over the first dash and gap for this segment
887 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
888
889 compressed = compressed.mid( 2 );
890 shouldFlushPreviousSegmentBuffer = !compressed.empty();
891 }
892
893 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
894 {
895 QPen adjustedPen = pen;
896 adjustedPen.setStyle( Qt::SolidLine );
897 painter->setPen( adjustedPen );
898 QPainterPath path;
899 path.addPolygon( previousSegmentBuffer );
900 painter->drawPath( path );
901 previousSegmentBuffer.clear();
902 }
903
904 double finalDash = 0;
905 if ( nextPoint )
906 {
907 // sharp bend:
908 // 1. rewind buffered points line by final dash and gap length
909 // (later) 2. draw the bend with a solid line of length 2 * final dash size
910
911 if ( !compressed.empty() )
912 {
913 finalDash = compressed.at( compressed.size() - 2 );
914 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
915
916 const QPolygonF thisPoints = bufferedPoints;
917 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
918 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
919 }
920 else
921 {
922 previousSegmentBuffer << bufferedPoints;
923 }
924 }
925
926 currentBufferLineLength = 0;
927 currentRemainingDashLength = 0;
928 currentRemainingGapLength = 0;
929 totalBufferLength = 0;
930 buffer.clear();
931
932 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
933 {
934 QPen adjustedPen = pen;
935 if ( !compressed.empty() )
936 {
937 // maximum size of dash pattern is 32 elements
938 compressed = compressed.mid( 0, 32 );
939 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
940 adjustedPen.setDashPattern( compressed );
941 }
942 else
943 {
944 adjustedPen.setStyle( Qt::SolidLine );
945 }
946
947 painter->setPen( adjustedPen );
948 QPainterPath path;
949 path.addPolygon( bufferedPoints );
950 painter->drawPath( path );
951 }
952
953 bufferedPoints.clear();
954 };
955
956 QPointF p1;
957 QPointF p2 = *ptIt;
958 ptIt++;
959 bufferedPoints << p2;
960 for ( ; ptIt != points.constEnd(); ++ptIt )
961 {
962 p1 = *ptIt;
963 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
964 {
965 continue;
966 }
967
968 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
969 currentBufferLineLength += remainingSegmentDistance;
970 while ( true )
971 {
972 // handle currentRemainingDashLength/currentRemainingGapLength
973 if ( currentRemainingDashLength > 0 )
974 {
975 // bit more of dash to insert
976 if ( remainingSegmentDistance >= currentRemainingDashLength )
977 {
978 // all of dash fits in
979 buffer << currentRemainingDashLength << 0.0;
980 totalBufferLength += currentRemainingDashLength;
981 remainingSegmentDistance -= currentRemainingDashLength;
982 patternIndex++;
983 currentRemainingDashLength = 0.0;
984 currentRemainingGapLength = sourcePattern.at( patternIndex );
985 if ( currentRemainingGapLength == 0.0 )
986 {
987 patternIndex++;
988 }
989 }
990 else
991 {
992 // only part of remaining dash fits in
993 buffer << remainingSegmentDistance << 0.0;
994 totalBufferLength += remainingSegmentDistance;
995 currentRemainingDashLength -= remainingSegmentDistance;
996 break;
997 }
998 }
999 if ( currentRemainingGapLength > 0 )
1000 {
1001 // bit more of gap to insert
1002 if ( remainingSegmentDistance >= currentRemainingGapLength )
1003 {
1004 // all of gap fits in
1005 buffer << 0.0 << currentRemainingGapLength;
1006 totalBufferLength += currentRemainingGapLength;
1007 remainingSegmentDistance -= currentRemainingGapLength;
1008 currentRemainingGapLength = 0.0;
1009 patternIndex++;
1010 }
1011 else
1012 {
1013 // only part of remaining gap fits in
1014 buffer << 0.0 << remainingSegmentDistance;
1015 totalBufferLength += remainingSegmentDistance;
1016 currentRemainingGapLength -= remainingSegmentDistance;
1017 break;
1018 }
1019 }
1020
1021 if ( patternIndex + 1 >= sourcePattern.size() )
1022 {
1023 patternIndex = 0;
1024 }
1025
1026 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1027 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1028 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1029 {
1030 buffer << nextPatternDashLength << nextPatternGapLength;
1031 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1032 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1033 patternIndex += 2;
1034 }
1035 else if ( nextPatternDashLength <= remainingSegmentDistance )
1036 {
1037 // can fit in "dash", but not "gap"
1038 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1039 totalBufferLength += remainingSegmentDistance;
1040 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1041 currentRemainingDashLength = 0;
1042 patternIndex++;
1043 break;
1044 }
1045 else
1046 {
1047 // can't fit in "dash"
1048 buffer << remainingSegmentDistance << 0.0;
1049 totalBufferLength += remainingSegmentDistance;
1050 currentRemainingGapLength = 0;
1051 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1052 break;
1053 }
1054 }
1055
1056 bufferedPoints << p1;
1057 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1058 {
1059 QPointF nextPoint = *( ptIt + 1 );
1060
1061 // extreme angles form more than 45 degree angle at a node
1062 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1063 {
1064 // extreme angle. Rescale buffer and flush
1065 flushBuffer( &nextPoint );
1066 bufferedPoints << p1;
1067 // restart the line with the full length of the most recent dash element -- see
1068 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1069 if ( patternIndex % 2 == 1 )
1070 {
1071 patternIndex--;
1072 }
1073 currentRemainingDashLength = sourcePattern.at( patternIndex );
1074 }
1075 }
1076
1077 p2 = p1;
1078 }
1079
1080 flushBuffer( nullptr );
1081 if ( !previousSegmentBuffer.empty() )
1082 {
1083 QPen adjustedPen = pen;
1084 adjustedPen.setStyle( Qt::SolidLine );
1085 painter->setPen( adjustedPen );
1086 QPainterPath path;
1087 path.addPolygon( previousSegmentBuffer );
1088 painter->drawPath( path );
1089 previousSegmentBuffer.clear();
1090 }
1091}
1092
1094{
1095 if ( mDrawInsidePolygon )
1096 {
1097 //set to clip line to the interior of polygon, so we expect no bleed
1098 return 0;
1099 }
1100 else
1101 {
1102 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1104 }
1105}
1106
1108{
1109 unit = mCustomDashPatternUnit;
1110 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1111}
1112
1114{
1115 return mPenStyle;
1116}
1117
1134
1144
1146{
1147 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1148}
1149
1151{
1152 return mAlignDashPattern;
1153}
1154
1156{
1157 mAlignDashPattern = enabled;
1158}
1159
1161{
1162 return mPatternCartographicTweakOnSharpCorners;
1163}
1164
1166{
1167 mPatternCartographicTweakOnSharpCorners = enabled;
1168}
1169
1171{
1172 double offset = mOffset;
1173
1175 {
1178 }
1179
1182 {
1184 }
1185 return -offset; //direction seems to be inverse to symbology offset
1186}
1187
1189
1191
1192class MyLine
1193{
1194 public:
1195 MyLine( QPointF p1, QPointF p2 )
1196 {
1197 if ( p1 == p2 )
1198 return; // invalid
1199
1200 // tangent and direction
1201 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1202 {
1203 // vertical line - tangent undefined
1204 mVertical = true;
1205 mIncreasing = ( p2.y() > p1.y() );
1206 }
1207 else
1208 {
1209 mVertical = false;
1210 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1211 mIncreasing = ( p2.x() > p1.x() );
1212 }
1213
1214 // length
1215 double x = ( p2.x() - p1.x() );
1216 double y = ( p2.y() - p1.y() );
1217 mLength = std::sqrt( x * x + y * y );
1218 }
1219
1220 // return angle in radians
1221 double angle()
1222 {
1223 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1224
1225 if ( !mIncreasing )
1226 a += M_PI;
1227 return a;
1228 }
1229
1230 // return difference for x,y when going along the line with specified interval
1231 QPointF diffForInterval( double interval ) const
1232 {
1233 if ( mVertical )
1234 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1235
1236 double alpha = std::atan( mT );
1237 double dx = std::cos( alpha ) * interval;
1238 double dy = std::sin( alpha ) * interval;
1239 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1240 }
1241
1242 double length() const { return mLength; }
1243
1244 protected:
1245 bool mVertical = false;
1246 bool mIncreasing = false;
1247 double mT = 0.0;
1248 double mLength = 0.0;
1249};
1250
1252
1253//
1254// QgsTemplatedLineSymbolLayerBase
1255//
1257 : mRotateSymbols( rotateSymbol )
1258 , mInterval( interval )
1259{
1260
1261}
1262
1286
1291
1293
1295{
1296 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1297 if ( mRenderingFeature )
1298 {
1299 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1300 // until after we've received the final part
1301 mFeatureSymbolOpacity = context.opacity();
1302 mCurrentFeatureIsSelected = useSelectedColor;
1303 }
1304
1305 double offset = mOffset;
1306
1308 {
1311 }
1312
1314
1316 {
1318 if ( !QgsVariantUtils::isNull( exprVal ) )
1319 {
1320 QString placementString = exprVal.toString();
1321 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1322 {
1324 }
1325 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1326 {
1328 }
1329 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1330 {
1332 }
1333 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1334 {
1336 }
1337 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1338 {
1340 }
1341 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1342 {
1344 }
1345 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1346 {
1348 }
1349 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1350 {
1352 }
1353 else
1354 {
1356 }
1357 }
1358 }
1359
1360 QgsScopedQPainterState painterState( context.renderContext().painter() );
1361
1362 double averageOver = mAverageAngleLength;
1364 {
1365 context.setOriginalValueVariable( mAverageAngleLength );
1366 averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
1367 }
1368 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1369
1372 {
1373 const QString strBlankSegments = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::BlankSegments, context.renderContext().expressionContext() );
1374 QString error;
1375 QList<QList<QgsBlankSegmentUtils::BlankSegments>> allBlankSegments = QgsBlankSegmentUtils::parseBlankSegments( strBlankSegments, context.renderContext(), blankSegmentsUnit(), error );
1376
1377 if ( !error.isEmpty() )
1378 {
1379 QgsDebugError( QStringLiteral( "Badly formatted blank segment '%1', skip it: %2" ).arg( strBlankSegments ).arg( error ) );
1380 }
1381 else
1382 {
1383 // keep only the part/ring we are currently rendering
1384 const int iPart = context.geometryPartNum() - 1;
1385 if ( iPart >= 0 && mRingIndex >= 0 && iPart < allBlankSegments.count() && mRingIndex < allBlankSegments.at( iPart ).count() )
1386 {
1387 blankSegments = allBlankSegments.at( iPart ).at( mRingIndex );
1388 }
1389 }
1390 }
1391
1392 if ( qgsDoubleNear( offset, 0.0 ) )
1393 {
1395 renderPolylineInterval( points, context, averageOver, blankSegments );
1397 renderPolylineCentral( points, context, averageOver, blankSegments );
1399 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1401 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1402 {
1403 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1404 mHasRenderedFirstPart = mRenderingFeature;
1405 }
1407 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1409 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1411 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1413 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1414 }
1415 else
1416 {
1417 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1419
1420 for ( int part = 0; part < mline.count(); ++part )
1421 {
1422 const QPolygonF &points2 = mline[ part ];
1423
1425 renderPolylineInterval( points2, context, averageOver, blankSegments );
1427 renderPolylineCentral( points2, context, averageOver, blankSegments );
1429 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex, blankSegments );
1431 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices, blankSegments );
1433 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex, blankSegments );
1435 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1436 {
1437 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex, blankSegments );
1438 mHasRenderedFirstPart = mRenderingFeature;
1439 }
1441 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint, blankSegments );
1443 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter, blankSegments );
1444 }
1445 }
1446}
1447
1448void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1449{
1450 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1451
1452 if ( curvePolygon )
1453 {
1454 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1455 }
1456
1457 QgsExpressionContextScope *scope = nullptr;
1458 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1460 {
1461 scope = new QgsExpressionContextScope();
1462 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1463 }
1464
1465 switch ( mRingFilter )
1466 {
1467 case AllRings:
1468 case ExteriorRingOnly:
1469 {
1470 if ( scope )
1472
1473 renderPolyline( points, context );
1474 break;
1475 }
1476 case InteriorRingsOnly:
1477 break;
1478 }
1479
1480 if ( rings )
1481 {
1482 switch ( mRingFilter )
1483 {
1484 case AllRings:
1485 case InteriorRingsOnly:
1486 {
1487 mOffset = -mOffset; // invert the offset for rings!
1488 for ( int i = 0; i < rings->size(); ++i )
1489 {
1490 mRingIndex = i + 1;
1491 if ( curvePolygon )
1492 {
1493 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1494 }
1495 if ( scope )
1497
1498 renderPolyline( rings->at( i ), context );
1499 }
1500 mOffset = -mOffset;
1501 mRingIndex = 0;
1502 }
1503 break;
1504 case ExteriorRingOnly:
1505 break;
1506 }
1507 }
1508}
1509
1511{
1513 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1514 {
1516 }
1517 return unit;
1518}
1519
1521{
1523 mIntervalUnit = unit;
1524 mOffsetAlongLineUnit = unit;
1525 mAverageAngleLengthUnit = unit;
1526}
1527
1535
1546
1548{
1549 QVariantMap map;
1550 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1551 map[QStringLiteral( "interval" )] = QString::number( interval() );
1552 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1553 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1554 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1555 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1556 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1557 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1558 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1559 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1560 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1561 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1562 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1563 map[QStringLiteral( "blank_segments_unit" )] = QgsUnitTypes::encodeUnit( mBlankSegmentsUnit );
1564
1565 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1566
1567 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1568 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1569 return map;
1570}
1571
1573{
1574 return mPlaceOnEveryPart
1575 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1576 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1577 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1578}
1579
1581{
1582 installMasks( context, true );
1583
1584 mRenderingFeature = true;
1585 mHasRenderedFirstPart = false;
1586}
1587
1589{
1590 mRenderingFeature = false;
1591 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1592 {
1593 removeMasks( context, true );
1594 return;
1595 }
1596
1597 const double prevOpacity = subSymbol()->opacity();
1598 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1599
1600 // render final point
1601 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1602 mFeatureSymbolOpacity = 1;
1603 subSymbol()->setOpacity( prevOpacity );
1604
1605 removeMasks( context, true );
1606}
1607
1609{
1610 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1611 destLayer->setOffset( mOffset );
1612 destLayer->setPlacements( placements() );
1613 destLayer->setOffsetUnit( mOffsetUnit );
1615 destLayer->setIntervalUnit( intervalUnit() );
1617 destLayer->setOffsetAlongLine( offsetAlongLine() );
1620 destLayer->setAverageAngleLength( mAverageAngleLength );
1621 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1622 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1623 destLayer->setBlankSegmentsUnit( mBlankSegmentsUnit );
1624 destLayer->setRingFilter( mRingFilter );
1625 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1626
1627 copyDataDefinedProperties( destLayer );
1628 copyPaintEffect( destLayer );
1629}
1630
1632{
1633 if ( properties.contains( QStringLiteral( "offset" ) ) )
1634 {
1635 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1636 }
1637 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1638 {
1639 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1640 }
1641 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1642 {
1643 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1644 }
1645 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1646 {
1647 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1648 }
1649 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1650 {
1651 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1652 }
1653 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1654 {
1655 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1656 }
1657
1658 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1659 {
1660 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1661 }
1662 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1663 {
1664 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1665 }
1666
1667 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1668 {
1669 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1670 }
1671 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1672 {
1673 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1674 }
1675 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1676 {
1677 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1678 }
1679 if ( properties.contains( QStringLiteral( "blank_segments_unit" ) ) )
1680 {
1681 destLayer->setBlankSegmentsUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "blank_segments_unit" )].toString() ) );
1682 }
1683
1684 if ( properties.contains( QStringLiteral( "placement" ) ) )
1685 {
1686 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1688 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1690 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1692 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1694 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1696 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1698 else
1700 }
1701 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1702 {
1703 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1704 destLayer->setPlacements( placements );
1705 }
1706
1707 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1708 {
1709 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1710 }
1711
1712 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1713
1715}
1716
1718
1723class BlankSegmentsWalker
1724{
1725 public :
1726
1727 BlankSegmentsWalker( const QPolygonF &points, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1728 : mBlankSegments( blankSegments )
1729 , mPoints( points )
1730 , mItBlankSegment( blankSegments.cbegin() )
1731 {
1732 mDistances.reserve( mPoints.count() );
1733 mDistances << 0; // first point is start, so distance is 0
1734 }
1735
1736 bool insideBlankSegment( double distance )
1737 {
1738 while ( mItBlankSegment != mBlankSegments.cend() && distance > mItBlankSegment->second )
1739 {
1740 ++mItBlankSegment;
1741 }
1742
1743 return ( mItBlankSegment != mBlankSegments.cend() && distance >= mItBlankSegment->first );
1744 }
1745
1746
1747 // pointIndex : index of the point before point
1748 bool insideBlankSegment( const QPointF &point, int pointIndex )
1749 {
1750 if ( pointIndex < 0 || pointIndex >= mPoints.count() )
1751 return false;
1752
1753 // compute distances and fill distances array
1754 if ( pointIndex >= mDistances.count() )
1755 {
1756 for ( int i = static_cast<int>( mDistances.count() ); i < pointIndex + 1; i++ )
1757 {
1758 const QPointF diff = mPoints.at( i ) - mPoints.at( i - 1 );
1759 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1760 const double totalDistance = distance + mDistances.last();
1761 mDistances << totalDistance;
1762 }
1763 }
1764
1765 const QPointF diff = mPoints.at( pointIndex ) - point;
1766 const double distance = std::sqrt( std::pow( diff.x(), 2 ) + std::pow( diff.y(), 2 ) );
1767 const double currentDistance = mDistances.at( pointIndex ) + distance;
1768
1769 return insideBlankSegment( currentDistance );
1770 }
1771
1772 private:
1773
1774 const QgsBlankSegmentUtils::BlankSegments &mBlankSegments;
1775 const QPolygonF &mPoints;
1776 QList<double> mDistances;
1777 QgsBlankSegmentUtils::BlankSegments::const_iterator mItBlankSegment;
1778};
1779
1780
1782
1783
1784void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
1785{
1786 if ( points.isEmpty() )
1787 return;
1788
1789 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1790 double lengthLeft = 0; // how much is left until next marker
1791
1792 QgsRenderContext &rc = context.renderContext();
1793 double interval = mInterval;
1794
1795 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
1796 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1797
1799 {
1800 context.setOriginalValueVariable( mInterval );
1802 }
1803 if ( interval <= 0 )
1804 {
1805 interval = 0.1;
1806 }
1807 double offsetAlongLine = mOffsetAlongLine;
1809 {
1810 context.setOriginalValueVariable( mOffsetAlongLine );
1812 }
1813
1814 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1816 {
1817 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1818 // and clamp it to a reasonable range. It's the best we can do in this situation!
1819 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1820 }
1821
1822 constexpr double EPSILON = 1e-5;
1823 if ( painterUnitInterval < EPSILON )
1824 return;
1825
1826 double painterUnitOffsetAlongLine = 0;
1827
1828 // only calculated if we need it!
1829 double totalLength = -1;
1830
1831 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1832 {
1833 switch ( offsetAlongLineUnit() )
1834 {
1843 break;
1845 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1846 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1847 break;
1848 }
1849
1850 if ( points.isClosed() )
1851 {
1852 if ( painterUnitOffsetAlongLine > 0 )
1853 {
1854 if ( totalLength < 0 )
1855 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1856 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1857 }
1858 else if ( painterUnitOffsetAlongLine < 0 )
1859 {
1860 if ( totalLength < 0 )
1861 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1862 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1863 }
1864 }
1865 }
1866
1868 {
1869 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1870 // and clamp it to a reasonable range. It's the best we can do in this situation!
1871 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1872 }
1873
1874 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1875
1876 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1877 {
1878 QVector< QPointF > angleStartPoints;
1879 QVector< QPointF > symbolPoints;
1880 QVector< QPointF > angleEndPoints;
1881
1882 // we collect 3 arrays of points. These correspond to
1883 // 1. the actual point at which to render the symbol
1884 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1885 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1886 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1887 // (or trace past the final point)
1888
1889 QList<int> pointIndices; // keep a track on original pointIndices so we can decide later whether or not symbol points belong to a blank segment
1890 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft, blankSegments.isEmpty() ? nullptr : &pointIndices );
1891
1892 if ( symbolPoints.empty() )
1893 {
1894 // no symbols to draw, shortcut out early
1895 return;
1896 }
1897
1898 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1899 {
1900 // avoid duplicate points at start and end of closed rings
1901 symbolPoints.pop_back();
1902 }
1903
1904 angleEndPoints.reserve( symbolPoints.size() );
1905 angleStartPoints.reserve( symbolPoints.size() );
1906 if ( averageOver <= painterUnitOffsetAlongLine )
1907 {
1908 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, nullptr, 0, symbolPoints.size() );
1909 }
1910 else
1911 {
1912 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, nullptr, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1913 }
1914 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, nullptr, 0, symbolPoints.size() );
1915
1916 int pointNum = 0;
1917 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
1918 for ( int i = 0; i < symbolPoints.size(); ++ i )
1919 {
1920 if ( context.renderContext().renderingStopped() )
1921 break;
1922
1923 const QPointF pt = symbolPoints[i];
1924 if ( i < pointIndices.count() && blankSegmentsWalker.insideBlankSegment( pt, pointIndices.at( i ) ) )
1925 // skip the rendering
1926 continue;
1927
1928 const QPointF startPt = angleStartPoints[i];
1929 const QPointF endPt = angleEndPoints[i];
1930
1931 MyLine l( startPt, endPt );
1932 // rotate marker (if desired)
1933 if ( rotateSymbols() )
1934 {
1935 setSymbolLineAngle( l.angle() * 180 / M_PI );
1936 }
1937
1938 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1939 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1940 }
1941 }
1942 else
1943 {
1944
1945 // not averaging line angle -- always use exact section angle
1946 int pointNum = 0;
1947 QPointF lastPt = points[0];
1948 BlankSegmentsWalker itBlankSegment( points, blankSegments );
1949 for ( int i = 1; i < points.count(); ++i )
1950 {
1951 if ( context.renderContext().renderingStopped() )
1952 break;
1953
1954 const QPointF &pt = points[i];
1955
1956 if ( lastPt == pt ) // must not be equal!
1957 continue;
1958
1959 // for each line, find out dx and dy, and length
1960 MyLine l( lastPt, pt );
1961 QPointF diff = l.diffForInterval( painterUnitInterval );
1962
1963 // if there's some length left from previous line
1964 // use only the rest for the first point in new line segment
1965 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1966 double c = 1 - lengthLeft / painterUnitInterval;
1967
1968 lengthLeft += l.length();
1969
1970 // rotate marker (if desired)
1971 if ( rotateSymbols() )
1972 {
1973 setSymbolLineAngle( l.angle() * 180 / M_PI );
1974 }
1975
1976 // while we're not at the end of line segment, draw!
1977 while ( lengthLeft > painterUnitInterval )
1978 {
1979 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1980 lastPt += c * diff;
1981 c = 1; // reset c (if wasn't 1 already)
1982
1983 // we draw
1984 if ( !itBlankSegment.insideBlankSegment( lastPt, i - 1 ) )
1985 {
1986 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
1987 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1988 }
1989
1990 lengthLeft -= painterUnitInterval;
1991 }
1992
1993 lastPt = pt;
1994 }
1995
1996 }
1997}
1998
1999static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
2000{
2001 // calc average angle between the previous and next point
2002 double a1 = MyLine( prevPt, pt ).angle();
2003 double a2 = MyLine( pt, nextPt ).angle();
2004 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
2005
2006 return std::atan2( unitY, unitX );
2007}
2008
2009void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
2010{
2011 if ( points.isEmpty() )
2012 return;
2013
2014 QgsRenderContext &rc = context.renderContext();
2015
2016 int i = -1, maxCount = 0;
2017 bool isRing = false;
2018
2019 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
2020 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
2021 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size(), true ) );
2022
2023 double offsetAlongLine = mOffsetAlongLine;
2025 {
2026 context.setOriginalValueVariable( mOffsetAlongLine );
2028 }
2029
2030 // only calculated if we need it!!
2031 double totalLength = -1;
2032 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
2033 {
2034 //scale offset along line
2035 switch ( offsetAlongLineUnit() )
2036 {
2045 break;
2047 totalLength = QgsSymbolLayerUtils::polylineLength( points );
2048 offsetAlongLine = offsetAlongLine / 100 * totalLength;
2049 break;
2050 }
2051 if ( points.isClosed() )
2052 {
2053 if ( offsetAlongLine > 0 )
2054 {
2055 if ( totalLength < 0 )
2056 totalLength = QgsSymbolLayerUtils::polylineLength( points );
2057 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
2058 }
2059 else if ( offsetAlongLine < 0 )
2060 {
2061 if ( totalLength < 0 )
2062 totalLength = QgsSymbolLayerUtils::polylineLength( points );
2063 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
2064 }
2065 }
2066 }
2067
2068 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2069 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
2073 {
2074 QgsCoordinateTransform ct = context.renderContext().coordinateTransform();
2075 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
2076
2077 QgsVertexId vId;
2078 QgsPoint vPoint;
2079 double x, y, z;
2080 QPointF mapPoint;
2081 int pointNum = 0;
2082 const int numPoints = context.renderContext().geometry()->nCoordinates();
2083 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
2084 {
2085 if ( context.renderContext().renderingStopped() )
2086 break;
2087
2088 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2089
2090 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
2091 continue;
2092
2093 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
2094 continue;
2095
2098 {
2099 //transform
2100 x = vPoint.x();
2101 y = vPoint.y();
2102 z = 0.0;
2103 if ( ct.isValid() )
2104 {
2105 ct.transformInPlace( x, y, z );
2106 }
2107 mapPoint.setX( x );
2108 mapPoint.setY( y );
2109 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
2110 if ( rotateSymbols() )
2111 {
2112 double angle = context.renderContext().geometry()->vertexAngle( vId );
2113 setSymbolLineAngle( angle * 180 / M_PI );
2114 }
2115 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
2116 }
2117 }
2118
2119 return;
2120 }
2121
2122 int pointNum = 0;
2123
2124 switch ( placement )
2125 {
2127 {
2128 i = 0;
2129 maxCount = 1;
2130 break;
2131 }
2132
2134 {
2135 i = points.count() - 1;
2136 pointNum = i;
2137 maxCount = points.count();
2138 break;
2139 }
2140
2142 {
2143 i = 1;
2144 pointNum = 1;
2145 maxCount = points.count() - 1;
2146 break;
2147 }
2148
2151 {
2153 maxCount = points.count();
2154 if ( points.first() == points.last() )
2155 isRing = true;
2156 break;
2157 }
2158
2162 {
2163 return;
2164 }
2165 }
2166
2168 {
2169 double distance;
2171 renderOffsetVertexAlongLine( points, i, distance, context, placement, blankSegments );
2172
2173 return;
2174 }
2175
2176 QPointF prevPoint;
2177 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2178 prevPoint = points.at( 0 );
2179
2180 QPointF symbolPoint;
2181 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2182 for ( ; i < maxCount; ++i )
2183 {
2184 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
2185
2186 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2187 {
2188 continue; // don't draw the last marker - it has been drawn already
2189 }
2190
2192 {
2193 QPointF currentPoint = points.at( i );
2194 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2195 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2196 if ( rotateSymbols() )
2197 {
2198 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2199 currentPoint.x() - prevPoint.x() );
2200 setSymbolLineAngle( angle * 180 / M_PI );
2201 }
2202 prevPoint = currentPoint;
2203 }
2204 else
2205 {
2206 symbolPoint = points.at( i );
2207 // rotate marker (if desired)
2208 if ( rotateSymbols() )
2209 {
2210 double angle = markerAngle( points, isRing, i );
2211 setSymbolLineAngle( angle * 180 / M_PI );
2212 }
2213 }
2214
2215 mFinalVertex = symbolPoint;
2216 if ( ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2217 && !blankSegmentsWalker.insideBlankSegment( symbolPoint, i ) )
2218 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2219 }
2220}
2221
2222double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2223{
2224 double angle = 0;
2225 const QPointF &pt = points[vertex];
2226
2227 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2228 {
2229 int prevIndex = vertex - 1;
2230 int nextIndex = vertex + 1;
2231
2232 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2233 {
2234 prevIndex = points.count() - 2;
2235 nextIndex = 1;
2236 }
2237
2238 QPointF prevPoint, nextPoint;
2239 while ( prevIndex >= 0 )
2240 {
2241 prevPoint = points[ prevIndex ];
2242 if ( prevPoint != pt )
2243 {
2244 break;
2245 }
2246 --prevIndex;
2247 }
2248
2249 while ( nextIndex < points.count() )
2250 {
2251 nextPoint = points[ nextIndex ];
2252 if ( nextPoint != pt )
2253 {
2254 break;
2255 }
2256 ++nextIndex;
2257 }
2258
2259 if ( prevIndex >= 0 && nextIndex < points.count() )
2260 {
2261 angle = _averageAngle( prevPoint, pt, nextPoint );
2262 }
2263 }
2264 else //no ring and vertex is at start / at end
2265 {
2266 if ( vertex == 0 )
2267 {
2268 while ( vertex < points.size() - 1 )
2269 {
2270 const QPointF &nextPt = points[vertex + 1];
2271 if ( pt != nextPt )
2272 {
2273 angle = MyLine( pt, nextPt ).angle();
2274 return angle;
2275 }
2276 ++vertex;
2277 }
2278 }
2279 else
2280 {
2281 // use last segment's angle
2282 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2283 {
2284 const QPointF &prevPt = points[vertex - 1];
2285 if ( pt != prevPt )
2286 {
2287 angle = MyLine( prevPt, pt ).angle();
2288 return angle;
2289 }
2290 --vertex;
2291 }
2292 }
2293 }
2294 return angle;
2295}
2296
2297void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
2298{
2299 if ( points.isEmpty() )
2300 return;
2301
2302 QgsRenderContext &rc = context.renderContext();
2303 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2304 if ( qgsDoubleNear( distance, 0.0 ) )
2305 {
2306 // rotate marker (if desired)
2307 if ( rotateSymbols() )
2308 {
2309 bool isRing = false;
2310 if ( points.first() == points.last() )
2311 isRing = true;
2312 double angle = markerAngle( points, isRing, vertex );
2313 setSymbolLineAngle( angle * 180 / M_PI );
2314 }
2315 mFinalVertex = points[vertex];
2316 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2317 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2318 return;
2319 }
2320
2321 int pointIncrement = distance > 0 ? 1 : -1;
2322 QPointF previousPoint = points[vertex];
2323 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2324 int endPoint = distance > 0 ? points.count() - 1 : 0;
2325 double distanceLeft = std::fabs( distance );
2326 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2327
2328 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2329 {
2330 const QPointF &pt = points[i];
2331
2332 if ( previousPoint == pt ) // must not be equal!
2333 continue;
2334
2335 // create line segment
2336 MyLine l( previousPoint, pt );
2337
2338 if ( distanceLeft < l.length() )
2339 {
2340 //destination point is in current segment
2341 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2342 // rotate marker (if desired)
2343 if ( rotateSymbols() )
2344 {
2345 setSymbolLineAngle( l.angle() * 180 / M_PI );
2346 }
2347 mFinalVertex = markerPoint;
2348 if ( ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2349 && !blankSegmentsWalker.insideBlankSegment( markerPoint, i - 1 ) )
2350 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2351 return;
2352 }
2353
2354 distanceLeft -= l.length();
2355 previousPoint = pt;
2356 }
2357
2358 //didn't find point
2359}
2360
2361void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset,
2362 QList<int> *pointIndices,
2363 double initialLag, int numberPointsRequired )
2364{
2365 if ( p.empty() )
2366 return;
2367
2368 QVector< QPointF > points = p;
2369 const bool closedRing = points.first() == points.last();
2370
2371 double lengthLeft = initialOffset;
2372
2373 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2374 if ( initialLagLeft < 0 && closedRing )
2375 {
2376 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2377 QPointF lastPt = points.constLast();
2378 QVector< QPointF > pseudoPoints;
2379 for ( int i = points.count() - 2; i > 0; --i )
2380 {
2381 if ( initialLagLeft >= 0 )
2382 {
2383 break;
2384 }
2385
2386 const QPointF &pt = points[i];
2387
2388 if ( lastPt == pt ) // must not be equal!
2389 continue;
2390
2391 MyLine l( lastPt, pt );
2392 initialLagLeft += l.length();
2393 lastPt = pt;
2394
2395 pseudoPoints << pt;
2396 }
2397 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2398
2399 points = pseudoPoints;
2400 points.append( p );
2401 }
2402 else
2403 {
2404 while ( initialLagLeft < 0 )
2405 {
2406 dest << points.constFirst();
2407 initialLagLeft += intervalPainterUnits;
2408 }
2409 }
2410 if ( initialLag > 0 )
2411 {
2412 lengthLeft += intervalPainterUnits - initialLagLeft;
2413 }
2414
2415 QPointF lastPt = points[0];
2416 for ( int i = 1; i < points.count(); ++i )
2417 {
2418 const QPointF &pt = points[i];
2419
2420 if ( lastPt == pt ) // must not be equal!
2421 {
2422 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2423 {
2424 lastPt = points[0];
2425 i = 0;
2426 }
2427 continue;
2428 }
2429
2430 // for each line, find out dx and dy, and length
2431 MyLine l( lastPt, pt );
2432 QPointF diff = l.diffForInterval( intervalPainterUnits );
2433
2434 // if there's some length left from previous line
2435 // use only the rest for the first point in new line segment
2436 double c = 1 - lengthLeft / intervalPainterUnits;
2437
2438 lengthLeft += l.length();
2439
2440
2441 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2442 {
2443 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2444 lastPt += c * diff;
2445 lengthLeft -= intervalPainterUnits;
2446 dest << lastPt;
2447 if ( pointIndices )
2448 *pointIndices << i - 1;
2449 c = 1; // reset c (if wasn't 1 already)
2450
2451 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2452 break;
2453 }
2454 lastPt = pt;
2455
2456 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2457 break;
2458
2459 // if a closed ring, we keep looping around the ring until we hit the required number of points
2460 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2461 {
2462 lastPt = points[0];
2463 i = 0;
2464 }
2465 }
2466
2467 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2468 {
2469 // pad with repeating last point to match desired size
2470 while ( dest.size() < numberPointsRequired )
2471 dest << points.constLast();
2472 }
2473}
2474
2475void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver, const QgsBlankSegmentUtils::BlankSegments &blankSegments )
2476{
2477 if ( !points.isEmpty() )
2478 {
2479 // calc length
2480 qreal length = 0;
2481 QPolygonF::const_iterator it = points.constBegin();
2482 QPointF last = *it;
2483 for ( ++it; it != points.constEnd(); ++it )
2484 {
2485 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2486 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2487 last = *it;
2488 }
2489 if ( qgsDoubleNear( length, 0.0 ) )
2490 return;
2491
2492 const double midPoint = length / 2;
2493
2494 BlankSegmentsWalker blankSegmentsWalker( points, blankSegments );
2495 if ( blankSegmentsWalker.insideBlankSegment( midPoint ) )
2496 return;
2497
2498 QPointF pt;
2499 double thisSymbolAngle = 0;
2500
2501 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2502 {
2503 QVector< QPointF > angleStartPoints;
2504 QVector< QPointF > symbolPoints;
2505 QVector< QPointF > angleEndPoints;
2506
2507 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2508 // already dealt with blank segment before, no need to make them check again
2509 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, nullptr, 0.0, 2 );
2510 collectOffsetPoints( points, angleStartPoints, midPoint, 0, nullptr, averageAngleOver, 2 );
2511 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, nullptr, 0, 2 );
2512
2513 pt = symbolPoints.at( 1 );
2514 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2515 thisSymbolAngle = l.angle();
2516 }
2517 else
2518 {
2519 // find the segment where the central point lies
2520 it = points.constBegin();
2521 last = *it;
2522 qreal last_at = 0, next_at = 0;
2523 QPointF next;
2524 for ( ++it; it != points.constEnd(); ++it )
2525 {
2526 next = *it;
2527 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2528 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2529 if ( next_at >= midPoint )
2530 break; // we have reached the center
2531 last = *it;
2532 last_at = next_at;
2533 }
2534
2535 // find out the central point on segment
2536 MyLine l( last, next ); // for line angle
2537 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2538 pt = last + ( next - last ) * k;
2539 thisSymbolAngle = l.angle();
2540 }
2541
2542 // draw the marker
2543 // rotate marker (if desired)
2544 if ( rotateSymbols() )
2545 {
2546 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2547 }
2548
2549 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2550 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2551 }
2552}
2553
2558
2560{
2561 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2562 {
2563 delete symbol;
2564 return false;
2565 }
2566
2567 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2568 mColor = mMarker->color();
2569 return true;
2570}
2571
2572
2573
2574//
2575// QgsMarkerLineSymbolLayer
2576//
2577
2583
2585
2587{
2588 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2590
2591 if ( props.contains( QStringLiteral( "interval" ) ) )
2592 interval = props[QStringLiteral( "interval" )].toDouble();
2593 if ( props.contains( QStringLiteral( "rotate" ) ) )
2594 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2595
2596 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2597 setCommonProperties( x.get(), props );
2598 return x.release();
2599}
2600
2602{
2603 return QStringLiteral( "MarkerLine" );
2604}
2605
2607{
2608 mMarker->setColor( color );
2609 mColor = color;
2610}
2611
2613{
2614 return mMarker ? mMarker->color() : mColor;
2615}
2616
2618{
2619 // if being rotated, it gets initialized with every line segment
2621 if ( rotateSymbols() )
2623 mMarker->setRenderHints( hints );
2624
2625 mMarker->startRender( context.renderContext(), context.fields() );
2626}
2627
2629{
2630 mMarker->stopRender( context.renderContext() );
2631}
2632
2633
2635{
2636 auto x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2638 return x.release();
2639}
2640
2641void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2642{
2643 QgsSldExportContext context;
2644 context.setExtraProperties( props );
2645 toSld( doc, element, context );
2646}
2647
2648bool QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
2649{
2650 const QVariantMap props = context.extraProperties();
2651 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2652 {
2653 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2654 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2655 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2656 element.appendChild( symbolizerElem );
2657
2658 // <Geometry>
2659 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString(), context );
2660
2661 QString gap;
2663 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2665 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2667 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2669 // no way to get line/polygon's vertices, use a VendorOption
2670 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2671
2673 {
2675 gap = qgsDoubleToString( interval );
2676 }
2677
2678 if ( !rotateSymbols() )
2679 {
2680 // markers in LineSymbolizer must be drawn following the line orientation,
2681 // use a VendorOption when no marker rotation
2682 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2683 }
2684
2685 // <Stroke>
2686 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2687 symbolizerElem.appendChild( strokeElem );
2688
2689 // <GraphicStroke>
2690 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2691 strokeElem.appendChild( graphicStrokeElem );
2692
2693 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2694 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2695 {
2696 markerLayer->writeSldMarker( doc, graphicStrokeElem, context );
2697 }
2698 else if ( layer )
2699 {
2700 QgsDebugError( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) );
2701 }
2702 else
2703 {
2704 QgsDebugError( QStringLiteral( "Missing marker line symbol layer. Skip it." ) );
2705 }
2706
2707 if ( !gap.isEmpty() )
2708 {
2709 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2710 QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap, context );
2711 graphicStrokeElem.appendChild( gapElem );
2712 }
2713
2714 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2715 {
2716 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2718 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2719 symbolizerElem.appendChild( perpOffsetElem );
2720 }
2721 }
2722 return true;
2723}
2724
2726{
2727 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2728
2729 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2730 if ( strokeElem.isNull() )
2731 return nullptr;
2732
2733 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2734 if ( graphicStrokeElem.isNull() )
2735 return nullptr;
2736
2737 // retrieve vendor options
2738 bool rotateMarker = true;
2740
2741 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2742 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2743 {
2744 if ( it.key() == QLatin1String( "placement" ) )
2745 {
2746 if ( it.value() == QLatin1String( "points" ) )
2748 else if ( it.value() == QLatin1String( "firstPoint" ) )
2750 else if ( it.value() == QLatin1String( "lastPoint" ) )
2752 else if ( it.value() == QLatin1String( "centralPoint" ) )
2754 }
2755 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2756 {
2757 rotateMarker = it.value() == QLatin1String( "0" );
2758 }
2759 }
2760
2761 std::unique_ptr< QgsMarkerSymbol > marker;
2762
2763 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicStrokeElem );
2764 if ( l )
2765 {
2766 QgsSymbolLayerList layers;
2767 layers.append( l.release() );
2768 marker = std::make_unique<QgsMarkerSymbol>( layers );
2769 }
2770
2771 if ( !marker )
2772 return nullptr;
2773
2774 double interval = 0.0;
2775 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2776 if ( !gapElem.isNull() )
2777 {
2778 bool ok;
2779 double d = gapElem.firstChild().firstChild().nodeValue().toDouble( &ok );
2780 if ( ok )
2781 interval = d;
2782 }
2783
2784 double offset = 0.0;
2785 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2786 if ( !perpOffsetElem.isNull() )
2787 {
2788 bool ok;
2789 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2790 if ( ok )
2791 offset = d;
2792 }
2793
2794 double scaleFactor = 1.0;
2795 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2796 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2797 interval = interval * scaleFactor;
2798 offset = offset * scaleFactor;
2799
2801 x->setOutputUnit( sldUnitSize );
2803 x->setInterval( interval );
2804 x->setSubSymbol( marker.release() );
2805 x->setOffset( offset );
2806 return x;
2807}
2808
2810{
2811 mMarker->setSize( width );
2812}
2813
2815{
2816 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2817 {
2818 mMarker->setDataDefinedSize( property );
2819 }
2821}
2822
2824{
2825 const double prevOpacity = mMarker->opacity();
2826 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2828 mMarker->setOpacity( prevOpacity );
2829}
2830
2832{
2833 mMarker->setLineAngle( angle );
2834}
2835
2837{
2838 return mMarker->angle();
2839}
2840
2842{
2843 mMarker->setAngle( angle );
2844}
2845
2846void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2847{
2848 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2850
2851 mMarker->renderPoint( point, feature, context, layer, selected );
2852
2853 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2854}
2855
2857{
2858 return mMarker->size();
2859}
2860
2862{
2863 return mMarker->size( context );
2864}
2865
2871
2882
2884{
2885 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2886 if ( mMarker )
2887 attr.unite( mMarker->usedAttributes( context ) );
2888 return attr;
2889}
2890
2892{
2894 return true;
2895 if ( mMarker && mMarker->hasDataDefinedProperties() )
2896 return true;
2897 return false;
2898}
2899
2901{
2902 return ( mMarker->size( context ) / 2.0 ) +
2904}
2905
2906
2907//
2908// QgsHashedLineSymbolLayer
2909//
2910
2912 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2913{
2914 setSubSymbol( new QgsLineSymbol() );
2915}
2916
2918
2920{
2921 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2923
2924 if ( props.contains( QStringLiteral( "interval" ) ) )
2925 interval = props[QStringLiteral( "interval" )].toDouble();
2926 if ( props.contains( QStringLiteral( "rotate" ) ) )
2927 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2928
2929 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2930 setCommonProperties( x.get(), props );
2931 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2932 {
2933 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2934 }
2935
2936 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2937 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2938
2939 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2940 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2941
2942 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2943 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2944
2945 return x.release();
2946}
2947
2949{
2950 return QStringLiteral( "HashLine" );
2951}
2952
2954{
2955 // if being rotated, it gets initialized with every line segment
2957 if ( rotateSymbols() )
2959 mHashSymbol->setRenderHints( hints );
2960
2961 mHashSymbol->startRender( context.renderContext(), context.fields() );
2962}
2963
2965{
2966 mHashSymbol->stopRender( context.renderContext() );
2967}
2968
2970{
2972 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2973
2974 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2975 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2976 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2977
2978 return map;
2979}
2980
2982{
2983 auto x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2985 x->setHashAngle( mHashAngle );
2986 x->setHashLength( mHashLength );
2987 x->setHashLengthUnit( mHashLengthUnit );
2988 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2989 return x.release();
2990}
2991
2993{
2994 mHashSymbol->setColor( color );
2995 mColor = color;
2996}
2997
2999{
3000 return mHashSymbol ? mHashSymbol->color() : mColor;
3001}
3002
3004{
3005 return mHashSymbol.get();
3006}
3007
3009{
3010 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
3011 {
3012 delete symbol;
3013 return false;
3014 }
3015
3016 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
3017 mColor = mHashSymbol->color();
3018 return true;
3019}
3020
3022{
3023 mHashLength = width;
3024}
3025
3027{
3028 return mHashLength;
3029}
3030
3032{
3033 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
3034}
3035
3037{
3038 return ( mHashSymbol->width( context ) / 2.0 )
3039 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
3041}
3042
3044{
3046 mHashSymbol->setOutputUnit( unit );
3047}
3048
3050{
3051 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
3052 if ( mHashSymbol )
3053 attr.unite( mHashSymbol->usedAttributes( context ) );
3054 return attr;
3055}
3056
3058{
3060 return true;
3061 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
3062 return true;
3063 return false;
3064}
3065
3067{
3068 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
3069 {
3070 mHashSymbol->setDataDefinedWidth( property );
3071 }
3073}
3074
3086
3088{
3089 mSymbolLineAngle = angle;
3090}
3091
3093{
3094 return mSymbolAngle;
3095}
3096
3098{
3099 mSymbolAngle = angle;
3100}
3101
3102void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
3103{
3104 double lineLength = mHashLength;
3106 {
3107 context.expressionContext().setOriginalValueVariable( mHashLength );
3108 lineLength = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::LineDistance, context.expressionContext(), lineLength );
3109 }
3110 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
3111
3112 double hashAngle = mHashAngle;
3114 {
3115 context.expressionContext().setOriginalValueVariable( mHashAngle );
3117 }
3118
3119 QgsPointXY center( point );
3120 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3121 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
3122
3123 QPolygonF points;
3124 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
3125
3126 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3128
3129 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
3130
3131 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
3132}
3133
3135{
3136 return mHashAngle;
3137}
3138
3140{
3141 mHashAngle = angle;
3142}
3143
3145{
3146 const double prevOpacity = mHashSymbol->opacity();
3147 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
3149 mHashSymbol->setOpacity( prevOpacity );
3150}
3151
3152//
3153// QgsAbstractBrushedLineSymbolLayer
3154//
3155
3156void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
3157{
3158 if ( !context.renderContext().painter() )
3159 return;
3160
3161 double offset = mOffset;
3163 {
3166 }
3167
3168 QPolygonF offsetPoints;
3169 if ( qgsDoubleNear( offset, 0 ) )
3170 {
3171 renderLine( points, context, patternThickness, patternLength, brush );
3172 }
3173 else
3174 {
3175 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3176
3177 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3178 for ( const QPolygonF &part : offsetLine )
3179 {
3180 renderLine( part, context, patternThickness, patternLength, brush );
3181 }
3182 }
3183}
3184
3185void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3186 const double patternLength, const QBrush &sourceBrush )
3187{
3188 QPainter *p = context.renderContext().painter();
3189 if ( !p )
3190 return;
3191
3192 QBrush brush = sourceBrush;
3193
3194 // duplicate points mess up the calculations, we need to remove them first
3195 // we'll calculate the min/max coordinate at the same time, since we're already looping
3196 // through the points
3197 QPolygonF inputPoints;
3198 inputPoints.reserve( points.size() );
3199 QPointF prev;
3200 double minX = std::numeric_limits< double >::max();
3201 double minY = std::numeric_limits< double >::max();
3202 double maxX = std::numeric_limits< double >::lowest();
3203 double maxY = std::numeric_limits< double >::lowest();
3204
3205 for ( const QPointF &pt : std::as_const( points ) )
3206 {
3207 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3208 continue;
3209
3210 inputPoints << pt;
3211 prev = pt;
3212 minX = std::min( minX, pt.x() );
3213 minY = std::min( minY, pt.y() );
3214 maxX = std::max( maxX, pt.x() );
3215 maxY = std::max( maxY, pt.y() );
3216 }
3217
3218 if ( inputPoints.size() < 2 ) // nothing to render
3219 return;
3220
3221 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3222 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3223 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3224 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3225
3226 // 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
3227 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3228 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3229
3230 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3231 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3232
3233 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3234 if ( temporaryImage.isNull() )
3235 {
3236 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3237 return;
3238 }
3239
3240 // clear temporary image contents
3241 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3242 return;
3243 temporaryImage.fill( Qt::transparent );
3244 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3245 return;
3246
3247 Qt::PenJoinStyle join = mPenJoinStyle;
3249 {
3252 if ( !QgsVariantUtils::isNull( exprVal ) )
3253 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3254 }
3255
3256 Qt::PenCapStyle cap = mPenCapStyle;
3258 {
3261 if ( !QgsVariantUtils::isNull( exprVal ) )
3262 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3263 }
3264
3265 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3266 QPainterPathStroker stroker;
3267 stroker.setWidth( lineThickness );
3268 stroker.setCapStyle( cap );
3269 stroker.setJoinStyle( join );
3270
3271 QPainterPath path;
3272 path.addPolygon( inputPoints );
3273 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3274
3275 // prepare temporary image
3276 QPainter imagePainter;
3277 imagePainter.begin( &temporaryImage );
3278 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3279 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3280
3281 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3282 imagePainter.setPen( Qt::NoPen );
3283
3284 QPointF segmentStartPoint = inputPoints.at( 0 );
3285
3286 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3287 double progressThroughImage = 0;
3288
3289 QgsPoint prevSegmentPolygonEndLeft;
3290 QgsPoint prevSegmentPolygonEndRight;
3291
3292 // for closed rings this will store the left/right polygon points of the start/end of the line
3293 QgsPoint startLinePolygonLeft;
3294 QgsPoint startLinePolygonRight;
3295
3296 for ( int i = 1; i < inputPoints.size(); ++i )
3297 {
3298 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3299 break;
3300
3301 const QPointF segmentEndPoint = inputPoints.at( i );
3302 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3303 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3304
3305 // left/right end points of the current segment polygon
3306 QgsPoint thisSegmentPolygonEndLeft;
3307 QgsPoint thisSegmentPolygonEndRight;
3308 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3309 QgsPoint thisSegmentPolygonEndLeftForPainter;
3310 QgsPoint thisSegmentPolygonEndRightForPainter;
3311 if ( i == 1 )
3312 {
3313 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3314 // (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.)
3315 if ( isClosedLine )
3316 {
3317 // project the current segment out by half the image thickness to either side of the line
3318 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3319 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3320 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3321 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3322
3323 // 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
3324 // what angle the current segment polygon should START on.
3325 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3326 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3327
3328 // project out the LAST segment in the line by half the image thickness to either side of the line
3329 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3330 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3331 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3332 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3333
3334 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3335 // 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
3336 // join)
3337 QgsPoint intersectionPoint;
3338 bool isIntersection = false;
3339 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3340 if ( !isIntersection )
3341 prevSegmentPolygonEndLeft = startPointLeft;
3342 isIntersection = false;
3343 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3344 if ( !isIntersection )
3345 prevSegmentPolygonEndRight = startPointRight;
3346
3347 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3348 startLinePolygonRight = prevSegmentPolygonEndRight;
3349 }
3350 else
3351 {
3352 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3353 if ( cap != Qt::PenCapStyle::FlatCap )
3354 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3355 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3356 if ( cap != Qt::PenCapStyle::FlatCap )
3357 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3358 }
3359 }
3360
3361 if ( i < inputPoints.size() - 1 )
3362 {
3363 // for all other segments except the last
3364
3365 // project the current segment out by half the image thickness to either side of the line
3366 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3367 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3368 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3369 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3370
3371 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3372 // what angle the current segment polygon should end on
3373 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3374 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3375
3376 // project out the next segment by half the image thickness to either side of the line
3377 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3378 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3379 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3380 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3381
3382 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3383 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3384 // join)
3385 QgsPoint intersectionPoint;
3386 bool isIntersection = false;
3387 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3388 if ( !isIntersection )
3389 thisSegmentPolygonEndLeft = endPointLeft;
3390 isIntersection = false;
3391 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3392 if ( !isIntersection )
3393 thisSegmentPolygonEndRight = endPointRight;
3394
3395 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3396 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3397 }
3398 else
3399 {
3400 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3401 // unless it's a closed line
3402 if ( isClosedLine )
3403 {
3404 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3405 thisSegmentPolygonEndRight = startLinePolygonRight;
3406
3407 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3408 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3409 }
3410 else
3411 {
3412 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3413 if ( cap != Qt::PenCapStyle::FlatCap )
3414 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3415 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3416 if ( cap != Qt::PenCapStyle::FlatCap )
3417 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3418
3419 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3420 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3421 }
3422 }
3423
3424 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3425 // where we got with the previous segment), at the correct angle
3426 QTransform brushTransform;
3427 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3428 brushTransform.rotate( -segmentAngleDegrees );
3429 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3430 {
3431 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3432 // we need to also do the same for the brush transform
3433 brushTransform.translate( -( lineThickness / 2 ), 0 );
3434 }
3435 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3436
3437 brush.setTransform( brushTransform );
3438 imagePainter.setBrush( brush );
3439
3440 // now draw the segment polygon
3441 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3442 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3443 << thisSegmentPolygonEndRightForPainter.toQPointF()
3444 << prevSegmentPolygonEndRight.toQPointF()
3445 << prevSegmentPolygonEndLeft.toQPointF() );
3446
3447#if 0 // for debugging, will draw the segment polygons
3448 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3449 imagePainter.setBrush( Qt::NoBrush );
3450 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3451 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3452 << thisSegmentPolygonEndRightForPainter.toQPointF()
3453 << prevSegmentPolygonEndRight.toQPointF()
3454 << prevSegmentPolygonEndLeft.toQPointF() );
3455 imagePainter.setPen( Qt::NoPen );
3456#endif
3457
3458 // calculate the new progress horizontal through the source image to account for the length
3459 // of the segment we've just drawn
3460 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3461 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3462 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3463 progressThroughImage = fmod( progressThroughImage, patternLength );
3464
3465 // shuffle buffered variables for next loop
3466 segmentStartPoint = segmentEndPoint;
3467 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3468 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3469 }
3470 imagePainter.end();
3471
3472 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3473 return;
3474
3475 // lastly, draw the temporary image onto the destination painter at the correct place
3476 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3477 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3478}
3479
3480
3481//
3482// QgsRasterLineSymbolLayer
3483//
3484
3489
3491
3493{
3494 auto res = std::make_unique<QgsRasterLineSymbolLayer>();
3495
3496 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3497 {
3498 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3499 }
3500 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3501 {
3502 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3503 }
3504 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3505 {
3506 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3507 }
3508
3509 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3510 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3511
3512 if ( properties.contains( QStringLiteral( "offset" ) ) )
3513 {
3514 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3515 }
3516 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3517 {
3518 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3519 }
3520 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3521 {
3522 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3523 }
3524
3525 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3526 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3527 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3528 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3529
3530 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3531 {
3532 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3533 }
3534
3535 return res.release();
3536}
3537
3538
3540{
3541 QVariantMap map;
3542 map[QStringLiteral( "imageFile" )] = mPath;
3543
3544 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3545 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3546 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3547
3548 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3549 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3550
3551 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3552 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3553 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3554
3555 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3556
3557 return map;
3558}
3559
3561{
3562 auto res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3563 res->setWidth( mWidth );
3564 res->setWidthUnit( mWidthUnit );
3565 res->setWidthMapUnitScale( mWidthMapUnitScale );
3566 res->setPenJoinStyle( mPenJoinStyle );
3567 res->setPenCapStyle( mPenCapStyle );
3568 res->setOffsetUnit( mOffsetUnit );
3569 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3570 res->setOffset( mOffset );
3571 res->setOpacity( mOpacity );
3572 copyDataDefinedProperties( res.get() );
3573 copyPaintEffect( res.get() );
3574 return res.release();
3575}
3576
3577void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3578{
3579 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3580 if ( it != properties.end() && it.value().userType() == QMetaType::Type::QString )
3581 {
3582 if ( saving )
3583 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3584 else
3585 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3586 }
3587}
3588
3590{
3591 mPath = path;
3592}
3593
3595{
3596 return QStringLiteral( "RasterLine" );
3597}
3598
3603
3605{
3606 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3607
3609
3610 double opacity = mOpacity * context.opacity();
3611 bool cached = false;
3613 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3614 static_cast< int >( std::ceil( scaledHeight ) ) ),
3615 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3616}
3617
3621
3623{
3624 if ( !context.renderContext().painter() )
3625 return;
3626
3627 QImage sourceImage = mLineImage;
3631 {
3632 QString path = mPath;
3634 {
3635 context.setOriginalValueVariable( path );
3637 }
3638
3639 double strokeWidth = mWidth;
3641 {
3642 context.setOriginalValueVariable( strokeWidth );
3643 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3644 }
3645 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3646
3648 double opacity = mOpacity;
3650 {
3653 }
3654 opacity *= context.opacity();
3655
3656 bool cached = false;
3658 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3659 static_cast< int >( std::ceil( scaledHeight ) ) ),
3660 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3661 }
3662
3663 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3664 if ( useSelectedColor )
3665 {
3667 }
3668
3669 const QBrush brush( sourceImage );
3670
3671 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3672}
3673
3680
3682{
3684 if ( mWidthUnit != unit || mOffsetUnit != unit )
3685 {
3687 }
3688 return unit;
3689}
3690
3696
3702
3712
3714{
3715 return ( mWidth / 2.0 ) + mOffset;
3716}
3717
3719{
3720 return QColor();
3721}
3722
3723
3724//
3725// QgsLineburstSymbolLayer
3726//
3727
3734
3736
3738{
3739 auto res = std::make_unique<QgsLineburstSymbolLayer>();
3740
3741 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3742 {
3743 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3744 }
3745 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3746 {
3747 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3748 }
3749 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3750 {
3751 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3752 }
3753
3754 if ( properties.contains( QStringLiteral( "offset" ) ) )
3755 {
3756 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3757 }
3758 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3759 {
3760 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3761 }
3762 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3763 {
3764 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3765 }
3766
3767 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3768 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3769 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3770 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3771
3772 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3773 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3774
3775 if ( properties.contains( QStringLiteral( "color" ) ) )
3776 {
3777 res->setColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() ) );
3778 }
3779 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3780 {
3781 res->setColor2( QgsColorUtils::colorFromString( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3782 }
3783
3784 //attempt to create color ramp from props
3785 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3786 {
3787 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3788 }
3789 else
3790 {
3791 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3792 }
3793
3794 return res.release();
3795}
3796
3798{
3799 QVariantMap map;
3800
3801 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3802 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3803 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3804
3805 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3806 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3807
3808 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3809 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3810 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3811
3812 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
3813 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
3814 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3815 if ( mGradientRamp )
3816 {
3817 map.insert( mGradientRamp->properties() );
3818 }
3819
3820 return map;
3821}
3822
3824{
3825 auto res = std::make_unique< QgsLineburstSymbolLayer >();
3826 res->setWidth( mWidth );
3827 res->setWidthUnit( mWidthUnit );
3828 res->setWidthMapUnitScale( mWidthMapUnitScale );
3829 res->setPenJoinStyle( mPenJoinStyle );
3830 res->setPenCapStyle( mPenCapStyle );
3831 res->setOffsetUnit( mOffsetUnit );
3832 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3833 res->setOffset( mOffset );
3834 res->setColor( mColor );
3835 res->setColor2( mColor2 );
3836 res->setGradientColorType( mGradientColorType );
3837 if ( mGradientRamp )
3838 res->setColorRamp( mGradientRamp->clone() );
3839 copyDataDefinedProperties( res.get() );
3840 copyPaintEffect( res.get() );
3841 return res.release();
3842}
3843
3845{
3846 return QStringLiteral( "Lineburst" );
3847}
3848
3853
3857
3861
3863{
3864 if ( !context.renderContext().painter() )
3865 return;
3866
3867 double strokeWidth = mWidth;
3869 {
3870 context.setOriginalValueVariable( strokeWidth );
3871 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), strokeWidth );
3872 }
3873 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3874
3875 //update alpha of gradient colors
3876 QColor color1 = mColor;
3878 {
3881 }
3882 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3883 if ( useSelectedColor )
3884 {
3885 color1 = context.renderContext().selectionColor();
3886 }
3887 color1.setAlphaF( context.opacity() * color1.alphaF() );
3888
3889 //second gradient color
3890 QColor color2 = mColor2;
3892 {
3895 }
3896
3897 //create a QGradient with the desired properties
3898 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3899 //add stops to gradient
3902 {
3903 //color ramp gradient
3904 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3905 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3906 }
3907 else
3908 {
3909 //two color gradient
3910 gradient.setColorAt( 0.0, color1 );
3911 gradient.setColorAt( 1.0, color2 );
3912 }
3913 const QBrush brush( gradient );
3914
3915 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3916}
3917
3924
3926{
3928 if ( mWidthUnit != unit || mOffsetUnit != unit )
3929 {
3931 }
3932 return unit;
3933}
3934
3940
3946
3956
3958{
3959 return ( mWidth / 2.0 ) + mOffset;
3960}
3961
3966
3968{
3969 mGradientRamp.reset( ramp );
3970}
3971
3972//
3973// QgsFilledLineSymbolLayer
3974//
3975
3978{
3979 mWidth = width;
3980 mFill = fillSymbol ? std::unique_ptr< QgsFillSymbol >( fillSymbol ) : QgsFillSymbol::createSimple( QVariantMap() );
3981}
3982
3984
3986{
3988
3989 // throughout the history of QGIS and different layer types, we've used
3990 // a huge range of different strings for the same property. The logic here
3991 // is designed to be forgiving to this and accept a range of string keys:
3992 if ( props.contains( QStringLiteral( "line_width" ) ) )
3993 {
3994 width = props[QStringLiteral( "line_width" )].toDouble();
3995 }
3996 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
3997 {
3998 width = props[QStringLiteral( "outline_width" )].toDouble();
3999 }
4000 else if ( props.contains( QStringLiteral( "width" ) ) )
4001 {
4002 width = props[QStringLiteral( "width" )].toDouble();
4003 }
4004
4005 auto l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ).release() );
4006
4007 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
4008 {
4009 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
4010 }
4011 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
4012 {
4013 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
4014 }
4015 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
4016 {
4017 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
4018 }
4019
4020 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
4021 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
4022 if ( props.contains( QStringLiteral( "offset" ) ) )
4023 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
4024 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
4025 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
4026 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
4027 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
4028 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
4029 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
4030 if ( props.contains( QStringLiteral( "capstyle" ) ) )
4031 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
4032
4033 l->restoreOldDataDefinedProperties( props );
4034
4035 return l.release();
4036}
4037
4039{
4040 return QStringLiteral( "FilledLine" );
4041}
4042
4044{
4045 if ( mFill )
4046 {
4047 mFill->setRenderHints( mFill->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
4048 mFill->startRender( context.renderContext(), context.fields() );
4049 }
4050}
4051
4053{
4054 if ( mFill )
4055 {
4056 mFill->stopRender( context.renderContext() );
4057 }
4058}
4059
4061{
4062 installMasks( context, true );
4063
4064 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4065}
4066
4068{
4069 removeMasks( context, true );
4070
4071 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4072}
4073
4075{
4076 QPainter *p = context.renderContext().painter();
4077 if ( !p || !mFill )
4078 return;
4079
4080 double width = mWidth;
4082 {
4085 }
4086
4087 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
4088
4089 Qt::PenJoinStyle join = mPenJoinStyle;
4091 {
4094 if ( !QgsVariantUtils::isNull( exprVal ) )
4095 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
4096 }
4097
4098 Qt::PenCapStyle cap = mPenCapStyle;
4100 {
4103 if ( !QgsVariantUtils::isNull( exprVal ) )
4104 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
4105 }
4106
4107 double offset = mOffset;
4109 {
4112 }
4113
4114 const double prevOpacity = mFill->opacity();
4115 mFill->setOpacity( mFill->opacity() * context.opacity() );
4116
4117 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4119
4120 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4121
4122 if ( points.count() >= 2 )
4123 {
4124 std::unique_ptr< QgsAbstractGeometry > ls = QgsLineString::fromQPolygonF( points );
4125 geos::unique_ptr lineGeom;
4126
4127 if ( !qgsDoubleNear( offset, 0 ) )
4128 {
4129 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
4131 {
4132 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
4133 // and clamp it to a reasonable range. It's the best we can do in this situation!
4134 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
4135 }
4136
4138 if ( geometryType == Qgis::GeometryType::Polygon )
4139 {
4140 auto inputPoly = std::make_unique< QgsPolygon >( static_cast< QgsLineString * >( ls.release() ) );
4141 geos::unique_ptr g( QgsGeos::asGeos( inputPoly.get() ) );
4142 lineGeom = QgsGeos::buffer( g.get(), -scaledOffset, 0, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Miter, 2 );
4143 // the result is a polygon => extract line work
4144 QgsGeometry polygon( QgsGeos::fromGeos( lineGeom.get() ) );
4145 QVector<QgsGeometry> parts = polygon.coerceToType( Qgis::WkbType::MultiLineString );
4146 if ( !parts.empty() )
4147 {
4148 lineGeom = QgsGeos::asGeos( parts.at( 0 ).constGet() );
4149 }
4150 else
4151 {
4152 lineGeom.reset();
4153 }
4154 }
4155 else
4156 {
4157 geos::unique_ptr g( QgsGeos::asGeos( ls.get() ) );
4158 lineGeom = QgsGeos::offsetCurve( g.get(), scaledOffset, 0, Qgis::JoinStyle::Miter, 8.0 );
4159 }
4160 }
4161 else
4162 {
4163 lineGeom = QgsGeos::asGeos( ls.get() );
4164 }
4165
4166 if ( lineGeom )
4167 {
4168 geos::unique_ptr buffered = QgsGeos::buffer( lineGeom.get(), scaledWidth / 2, 8,
4171 if ( buffered )
4172 {
4173 // convert to rings
4174 std::unique_ptr< QgsAbstractGeometry > bufferedGeom = QgsGeos::fromGeos( buffered.get() );
4175 const QList< QList< QPolygonF > > parts = QgsSymbolLayerUtils::toQPolygonF( bufferedGeom.get(), Qgis::SymbolType::Fill );
4176 for ( const QList< QPolygonF > &polygon : parts )
4177 {
4178 QVector< QPolygonF > rings;
4179 for ( int i = 1; i < polygon.size(); ++i )
4180 rings << polygon.at( i );
4181 mFill->renderPolygon( polygon.value( 0 ), &rings, context.feature(), context.renderContext(), -1, useSelectedColor );
4182 }
4183 }
4184 }
4185 }
4186
4188
4189 mFill->setOpacity( prevOpacity );
4190}
4191
4193{
4194 QVariantMap map;
4195
4196 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
4197 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
4198 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4199 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
4200 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
4201 map[QStringLiteral( "offset" )] = QString::number( mOffset );
4202 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4203 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4204 if ( mFill )
4205 {
4206 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mFill->color() );
4207 }
4208 return map;
4209}
4210
4212{
4213 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4214 copyPaintEffect( res.get() );
4215 copyDataDefinedProperties( res.get() );
4216 res->setSubSymbol( mFill->clone() );
4217 return res.release();
4218}
4219
4221{
4222 return mFill.get();
4223}
4224
4226{
4227 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4228 {
4229 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4230 return true;
4231 }
4232 else
4233 {
4234 delete symbol;
4235 return false;
4236 }
4237}
4238
4240{
4241 if ( mFill )
4242 {
4243 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4244 }
4245 return 0;
4246}
4247
4249{
4250 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4251 if ( mFill )
4252 attr.unite( mFill->usedAttributes( context ) );
4253 return attr;
4254}
4255
4257{
4259 return true;
4260 if ( mFill && mFill->hasDataDefinedProperties() )
4261 return true;
4262 return false;
4263}
4264
4266{
4267 mColor = c;
4268 if ( mFill )
4269 mFill->setColor( c );
4270}
4271
4273{
4274 return mFill ? mFill->color() : mColor;
4275}
4276
4283
4285{
4287 if ( mFill )
4288 mFill->setMapUnitScale( scale );
4289}
4290
4292{
4293 if ( mFill )
4294 {
4295 return mFill->mapUnitScale();
4296 }
4297 return QgsMapUnitScale();
4298}
4299
4301{
4303 if ( mFill )
4304 mFill->setOutputUnit( unit );
4305}
4306
4308{
4309 if ( mFill )
4310 {
4311 return mFill->outputUnit();
4312 }
4314}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:3129
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
Definition qgis.h:3135
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex).
Definition qgis.h:3137
@ LastVertex
Place symbols on the last vertex in the line.
Definition qgis.h:3132
@ CentralPoint
Place symbols at the mid point of the line.
Definition qgis.h:3134
@ SegmentCenter
Place symbols at the center of every line segment.
Definition qgis.h:3136
@ Vertex
Place symbols on every vertex in the line.
Definition qgis.h:3131
@ Interval
Place symbols at regular intervals.
Definition qgis.h:3130
@ FirstVertex
Place symbols on the first vertex in the line.
Definition qgis.h:3133
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
Definition qgis.h:770
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:771
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
Definition qgis.h:3032
GradientColorSource
Gradient color sources.
Definition qgis.h:3178
@ ColorRamp
Gradient color ramp.
Definition qgis.h:3180
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
Definition qgis.h:882
@ Curve
An intermediate point on a segment defining the curvature of the segment.
Definition qgis.h:3068
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3067
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:887
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:358
@ Line
Lines.
Definition qgis.h:360
@ Polygon
Polygons.
Definition qgis.h:361
@ Unknown
Unknown types.
Definition qgis.h:362
@ Miter
Use mitered joins.
Definition qgis.h:2123
RenderUnit
Rendering size units.
Definition qgis.h:5183
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5187
@ Millimeters
Millimeters.
Definition qgis.h:5184
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5188
@ Unknown
Mixed or unknown units.
Definition qgis.h:5190
@ MapUnits
Map units.
Definition qgis.h:5185
@ Pixels
Pixels.
Definition qgis.h:5186
@ Inches
Inches.
Definition qgis.h:5189
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5191
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2110
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2765
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2760
@ RenderLayerTree
The render is for a layer tree display where map based properties are not available and where avoidan...
Definition qgis.h:2771
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2759
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:777
@ Marker
Marker symbol.
Definition qgis.h:611
@ Line
Line symbol.
Definition qgis.h:612
@ Fill
Fill symbol.
Definition qgis.h:613
@ MultiLineString
MultiLineString.
Definition qgis.h:284
QFlags< MarkerLinePlacement > MarkerLinePlacements
Definition qgis.h:3140
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 The blank segments are expected to be exp...
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:58
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
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:2757
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2093
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:260
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition qgsgeos.cpp:1574
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
double hashAngle() const
Returns the angle to use when drawing the hashed lines sections, in degrees clockwise.
QgsHashedLineSymbolLayer(bool rotateSymbol=true, double interval=3)
Constructor for QgsHashedLineSymbolLayer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setWidth(double width) override
Sets the width of the line symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
double width() const override
Returns the estimated width for the line symbol layer.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsHashedLineSymbolLayer, using the settings serialized in the properties map (correspo...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsHashedLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
~QgsHashedLineSymbolLayer() override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QColor color() const override
Returns the "representative" color of the symbol layer.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setHashAngle(double angle)
Sets the angle to use when drawing the hashed lines sections, in degrees clockwise.
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Line string geometry type, with support for z-dimension and m-values.
static std::unique_ptr< QgsLineString > fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
Qgis::RenderUnit mOffsetUnit
RenderRingFilter
Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
@ ExteriorRingOnly
Render the exterior ring only.
@ InteriorRingsOnly
Render the interior rings only.
@ AllRings
Render both exterior and interior rings.
QgsMapUnitScale mWidthMapUnitScale
QgsLineSymbolLayer(const QgsLineSymbolLayer &other)=delete
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setWidthMapUnitScale(const QgsMapUnitScale &scale)
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
Qgis::RenderUnit mWidthUnit
void setOffset(double offset)
Sets the line's offset.
RenderRingFilter mRingFilter
void setOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the line's offset.
void setWidthUnit(Qgis::RenderUnit unit)
Sets the units for the line's width.
virtual double width() const
Returns the estimated width for the line symbol layer.
QgsMapUnitScale mOffsetMapUnitScale
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the line's offset.
QgsMapUnitScale mapUnitScale() const override
void setRingFilter(QgsLineSymbolLayer::RenderRingFilter filter)
Sets the line symbol layer's ring filter, which controls which rings are rendered when the line symbo...
void setMapUnitScale(const QgsMapUnitScale &scale) override
double offset() const
Returns the line's offset.
Qgis::RenderUnit offsetUnit() const
Returns the units for the line's offset.
Qgis::RenderUnit widthUnit() const
Returns the units for the line's width.
A line symbol type, for rendering LineString and MultiLineString geometries.
std::unique_ptr< QgsColorRamp > mGradientRamp
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient line.
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
Qgis::GradientColorSource mGradientColorType
QString layerType() const override
Returns a string that represents this layer type.
~QgsLineburstSymbolLayer() override
QgsLineburstSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLineburstSymbolLayer, using the settings serialized in the properties map (correspon...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QgsLineburstSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, const QColor &color2=Qt::white)
Constructor for QgsLineburstSymbolLayer, with the specified start and end gradient colors.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsColorRamp * colorRamp()
Returns the color ramp used for the gradient line.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
void transformInPlace(double &x, double &y) const
Transforms map coordinates to device coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
Q_DECL_DEPRECATED bool rotateMarker() const
Shall the marker be rotated.
std::unique_ptr< QgsMarkerSymbol > mMarker
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsMarkerLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
double width() const override
Returns the estimated width for the line symbol layer.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsMarkerLineSymbolLayer, using the settings serialized in the properties map (correspo...
QgsMarkerLineSymbolLayer(bool rotateMarker=DEFAULT_MARKERLINE_ROTATE, double interval=DEFAULT_MARKERLINE_INTERVAL)
Constructor for QgsMarkerLineSymbolLayer.
void setWidth(double width) override
Sets the width of the line symbol layer.
QColor color() const override
Returns the "representative" color of the symbol layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
~QgsMarkerLineSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QString layerType() const override
Returns a string that represents this layer type.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM element.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
Represents a 2D point.
Definition qgspointxy.h:60
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:64
double x
Definition qgspointxy.h:63
QPointF toQPointF() const
Returns the point as a QPointF.
Definition qgspoint.h:376
double x
Definition qgspoint.h:52
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:707
double y
Definition qgspoint.h:53
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
A store for object properties.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsRasterLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double opacity() const
Returns the line opacity.
QString path() const
Returns the raster image path.
void setPath(const QString &path)
Set the raster image path.
QString layerType() const override
Returns a string that represents this layer type.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QgsRasterLineSymbolLayer(const QString &path=QString())
Constructor for QgsRasterLineSymbolLayer, with the specified raster image path.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRasterLineSymbolLayer, using the settings serialized in the properties map (correspo...
QColor color() const override
Returns the "representative" color of the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
~QgsRasterLineSymbolLayer() override
void setMapUnitScale(const QgsMapUnitScale &scale) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsMapUnitScale mapUnitScale() const override
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QgsVectorSimplifyMethod & vectorSimplifyMethod()
Returns the simplification settings to use when rendering vector layers.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QColor selectionColor() const
Returns the color to use when rendering selected features.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsAbstractGeometry * geometry() const
Returns pointer to the unsegmentized geometry.
Scoped object for saving and restoring a QPainter object's state.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setDrawInsidePolygon(bool drawInsidePolygon)
Sets whether the line should only be drawn inside polygons, and any portion of the line which falls o...
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsMapUnitScale mapUnitScale() const override
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleLineSymbolLayer, using the settings serialized in the properties map (correspo...
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QVector< qreal > customDashVector() const
Returns the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ...
Qt::PenJoinStyle penJoinStyle() const
Returns the pen join style used to render the line (e.g.
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the outline of polygon, using the given render context.
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
void setCustomDashPatternMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for lengths used in the custom dash pattern.
void setTrimDistanceEndMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the end of the line.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void setTrimDistanceEnd(double distance)
Sets the trim distance for the end of the line, which dictates a length from the end of the line at w...
double dxfOffset(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets offset.
QgsSimpleLineSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, double width=DEFAULT_SIMPLELINE_WIDTH, Qt::PenStyle penStyle=DEFAULT_SIMPLELINE_PENSTYLE)
Constructor for QgsSimpleLineSymbolLayer.
~QgsSimpleLineSymbolLayer() override
void setUseCustomDashPattern(bool b)
Sets whether the line uses a custom dash pattern.
void setTweakDashPatternOnCorners(bool enabled)
Sets whether dash patterns tweaks should be applied on sharp corners, to ensure that a double-length ...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setCustomDashVector(const QVector< qreal > &vector)
Sets the custom dash vector, which is the pattern of alternating drawn/skipped lengths used while ren...
void setDashPatternOffset(double offset)
Sets the dash pattern offset, which dictates how far along the dash pattern the pattern should start ...
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
QString layerType() const override
Returns a string that represents this layer type.
void setDashPatternOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the dash pattern offset.
QgsSimpleLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qt::PenStyle penStyle() const
Returns the pen style used to render the line (e.g.
void setPenJoinStyle(Qt::PenJoinStyle style)
Sets the pen join style used to render the line (e.g.
void setAlignDashPattern(bool enabled)
Sets whether dash patterns should be aligned to the start and end of lines, by applying subtle tweaks...
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsSimpleLineSymbolLayer from an SLD XML DOM element.
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
void setTrimDistanceStartMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the trim distance for the start of the line.
QVector< qreal > dxfCustomDashPattern(Qgis::RenderUnit &unit) const override
Gets dash pattern.
Qt::PenCapStyle penCapStyle() const
Returns the pen cap style used to render the line (e.g.
void setTrimDistanceEndUnit(Qgis::RenderUnit unit)
Sets the unit for the trim distance for the end of the line.
void setDashPatternOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the dash pattern offset.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
void setMapUnitScale(const QgsMapUnitScale &scale) override
void setTrimDistanceStart(double distance)
Sets the trim distance for the start of the line, which dictates a length from the start of the line ...
QString ogrFeatureStyle(double mmScaleFactor, double mapUnitScaleFactor) const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setTrimDistanceStartUnit(Qgis::RenderUnit unit)
Sets the unit for the trim distance for the start of the line.
bool alignDashPattern() const
Returns true if dash patterns should be aligned to the start and end of lines, by applying subtle twe...
void setCustomDashPatternUnit(Qgis::RenderUnit unit)
Sets the unit for lengths used in the custom dash pattern.
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
QVariantMap extraProperties() const
Returns the open ended set of properties that can drive/inform the SLD encoding.
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static Q_DECL_DEPRECATED bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static double rescaleUom(double size, Qgis::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static std::unique_ptr< QgsSymbolLayer > createMarkerLayerFromSld(QDomElement &element)
Creates a new marker layer from a SLD DOM element.
static bool isSharpCorner(QPointF p1, QPointF p2, QPointF p3)
Returns true if the angle formed by the line p1 - p2 - p3 forms a "sharp" corner.
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static Qt::PenCapStyle decodePenCapStyle(const QString &str)
static QList< QList< QPolygonF > > toQPolygonF(const QgsGeometry &geometry, Qgis::SymbolType type)
Converts a geometry to a set of QPolygonF objects representing how the geometry should be drawn for a...
static QVector< qreal > decodeRealVector(const QString &s)
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QString encodePenCapStyle(Qt::PenCapStyle style)
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static void appendPolyline(QPolygonF &target, const QPolygonF &line)
Appends a polyline line to an existing target polyline.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static Qgis::EndCapStyle penCapStyleToEndCapStyle(Qt::PenCapStyle style)
Converts a Qt pen cap style to a QGIS end cap style.
static double polylineLength(const QPolygonF &polyline)
Returns the total length of a polyline.
static Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static Q_DECL_DEPRECATED void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
Creates an SLD geometry element.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static Qt::PenStyle decodePenStyle(const QString &str)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static Qgis::JoinStyle penJoinStyleToJoinStyle(Qt::PenJoinStyle style)
Converts a Qt pen joinstyle to a QGIS join style.
static QPolygonF polylineSubstring(const QPolygonF &polyline, double startOffset, double endOffset)
Returns the substring of a polyline which starts at startOffset from the beginning of the line and en...
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, QgsSldExportContext &context, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QString encodeRealVector(const QVector< qreal > &v)
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
Property
Data definable properties.
@ SecondaryColor
Secondary color (eg for gradient fills).
@ File
Filename, eg for svg files.
@ BlankSegments
String list of distance to define blank segments along line for templated line symbol layers.
@ DashPatternOffset
Dash pattern offset,.
@ OffsetAlongLine
Offset along line.
@ CustomDash
Custom dash pattern.
@ StrokeStyle
Stroke style (eg solid, dashed).
@ TrimStart
Trim distance from start of line.
@ CapStyle
Line cap style.
@ Placement
Line marker placement.
@ LineAngle
Line angle, or angle of hash lines for hash line symbols.
@ JoinStyle
Line join style.
@ AverageAngleLength
Length to average symbol angles over.
@ Interval
Line marker interval.
@ LineDistance
Distance between lines, or length of lines for hash line symbols.
@ TrimEnd
Trim distance from end of line.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
virtual void setColor(const QColor &color)
Sets the "representative" color for the symbol layer.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
QgsPropertyCollection mDataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsSymbolLayer(const QgsSymbolLayer &other)
Encapsulates the context in which a symbol is being rendered.
const QgsFeature * feature() const
Returns the current feature being rendered.
Qgis::GeometryType originalGeometryType() const
Returns the geometry type for the original feature geometry being rendered.
QgsFields fields() const
Fields of the layer.
int geometryPartNum() const
Part number of current geometry.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:659
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition qgssymbol.h:666
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
bool rotateSymbols() const
Returns true if the repeating symbols be rotated to match their line segment orientation.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static void setCommonProperties(QgsTemplatedLineSymbolLayerBase *destLayer, const QVariantMap &properties)
Sets all common symbol properties in the destLayer, using the settings serialized in the properties m...
void setMapUnitScale(const QgsMapUnitScale &scale) final
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) final
Renders the line symbol layer along the outline of polygon, using the given render context.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setIntervalUnit(Qgis::RenderUnit unit)
Sets the units for the interval between symbols.
void setAverageAngleUnit(Qgis::RenderUnit unit)
Sets the unit for the length over which the line's direction is averaged when calculating individual ...
double interval() const
Returns the interval between individual symbols.
void setOffsetAlongLineUnit(Qgis::RenderUnit unit)
Sets the unit used for calculating the offset along line for symbols.
void setAverageAngleMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the length over which the line's direction is averaged when calculating i...
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
double offsetAlongLine() const
Returns the offset along the line for the symbol placement.
void copyTemplateSymbolProperties(QgsTemplatedLineSymbolLayerBase *destLayer) const
Copies all common properties of this layer to another templated symbol layer.
Qgis::MarkerLinePlacements placements() const
Returns the placement of the symbols.
void setOffsetAlongLine(double offsetAlongLine)
Sets the the offset along the line for the symbol placement.
Qgis::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
Qgis::RenderUnit outputUnit() const final
Returns the units to use for sizes and widths within the symbol layer.
QgsTemplatedLineSymbolLayerBase(bool rotateSymbol=true, double interval=3)
Constructor for QgsTemplatedLineSymbolLayerBase.
virtual void setSymbolLineAngle(double angle)=0
Sets the line angle modification for the symbol's angle.
QgsMapUnitScale mapUnitScale() const final
void setInterval(double interval)
Sets the interval between individual symbols.
Qgis::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
Q_DECL_DEPRECATED Qgis::MarkerLinePlacement placement() const
Returns the placement of the symbols.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setPlaceOnEveryPart(bool respect)
Sets whether the placement applies for every part of multi-part feature geometries.
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qgis::RenderUnit averageAngleUnit() const
Returns the unit for the length over which the line's direction is averaged when calculating individu...
Q_DECL_DEPRECATED void setPlacement(Qgis::MarkerLinePlacement placement)
Sets the placement of the symbols.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
Qgis::RenderUnit blankSegmentsUnit() const
Returns the unit for for blank segments start and end distances.
void setBlankSegmentsUnit(Qgis::RenderUnit unit)
Sets the unit for blank segments start and end distances.
const QgsMapUnitScale & offsetAlongLineMapUnitScale() const
Returns the map unit scale used for calculating the offset in map units along line for symbols.
virtual void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false)=0
Renders the templated symbol at the specified point, using the given render context.
void setIntervalMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the interval between symbols.
void setOffsetAlongLineMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for calculating the offset in map units along line for symbols.
void setAverageAngleLength(double length)
Sets the length of line over which the line's direction is averaged when calculating individual symbo...
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored).
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6524
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:6856
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:6878
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
QMap< QString, QString > QgsStringMap
Definition qgis.h:7132
#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:61
#define QgsDebugError(str)
Definition qgslogger.h:57
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.
Definition qgsvertexid.h:97
#define EPSILON
Definition util.h:82