QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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#include "qgscurve.h"
18#include "qgscurvepolygon.h"
19#include "qgsdxfexport.h"
20#include "qgssymbollayerutils.h"
21#include "qgsrendercontext.h"
22#include "qgslogger.h"
24#include "qgsunittypes.h"
25#include "qgsproperty.h"
27#include "qgsmarkersymbol.h"
28#include "qgslinesymbol.h"
29#include "qgsapplication.h"
30#include "qgsimagecache.h"
31#include "qgsfeedback.h"
32#include "qgsimageoperation.h"
33#include "qgscolorrampimpl.h"
34
35#include <algorithm>
36#include <QPainter>
37#include <QDomDocument>
38#include <QDomElement>
39
40#include <cmath>
41
42QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
43 : mPenStyle( penStyle )
44{
45 mColor = color;
46 mWidth = width;
47 mCustomDashVector << 5 << 2;
48}
49
51
53{
55 mWidthUnit = unit;
56 mOffsetUnit = unit;
57 mCustomDashPatternUnit = unit;
58 mDashPatternOffsetUnit = unit;
59 mTrimDistanceStartUnit = unit;
60 mTrimDistanceEndUnit = unit;
61}
62
64{
66 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
67 {
68 return Qgis::RenderUnit::Unknown;
69 }
70 return unit;
71}
72
74{
75 return mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
76 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
77}
78
80{
82 mWidthMapUnitScale = scale;
83 mOffsetMapUnitScale = scale;
84 mCustomDashPatternMapUnitScale = scale;
85}
86
88{
91 mOffsetMapUnitScale == mCustomDashPatternMapUnitScale )
92 {
93 return mWidthMapUnitScale;
94 }
95 return QgsMapUnitScale();
96}
97
99{
103
104 if ( props.contains( QStringLiteral( "line_color" ) ) )
105 {
106 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
107 }
108 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
109 {
110 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
111 }
112 else if ( props.contains( QStringLiteral( "color" ) ) )
113 {
114 //pre 2.5 projects used "color"
115 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
116 }
117 if ( props.contains( QStringLiteral( "line_width" ) ) )
118 {
119 width = props[QStringLiteral( "line_width" )].toDouble();
120 }
121 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
122 {
123 width = props[QStringLiteral( "outline_width" )].toDouble();
124 }
125 else if ( props.contains( QStringLiteral( "width" ) ) )
126 {
127 //pre 2.5 projects used "width"
128 width = props[QStringLiteral( "width" )].toDouble();
129 }
130 if ( props.contains( QStringLiteral( "line_style" ) ) )
131 {
132 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
133 }
134 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
135 {
136 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
137 }
138 else if ( props.contains( QStringLiteral( "penstyle" ) ) )
139 {
140 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
141 }
142
144 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
145 {
146 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
147 }
148 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
149 {
150 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
151 }
152 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
153 {
154 //pre 2.5 projects used "width_unit"
155 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
156 }
157 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
158 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
159 if ( props.contains( QStringLiteral( "offset" ) ) )
160 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
161 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
162 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
163 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
164 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
165 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
166 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
167 if ( props.contains( QStringLiteral( "capstyle" ) ) )
168 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
169
170 if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
171 {
172 l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
173 }
174 if ( props.contains( QStringLiteral( "customdash" ) ) )
175 {
176 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
177 }
178 if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
179 {
180 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
181 }
182 if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
183 {
184 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
185 }
186
187 if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
188 {
189 l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
190 }
191
192 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
193 {
194 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
195 }
196
197 if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
198 l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
199 if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
200 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
201 if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
202 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
203
204 if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
205 l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
206 if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
207 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
208 if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
209 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
210 if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
211 l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
212 if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
213 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
214 if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
215 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
216
217 if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
218 l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
219
220 if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
221 l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
222
224
225 return l;
226}
227
229{
230 return QStringLiteral( "SimpleLine" );
231}
232
234{
235 QColor penColor = mColor;
236 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
237 mPen.setColor( penColor );
238 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
239 mPen.setWidthF( scaledWidth );
240
241 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
242 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
243 const double dashWidthDiv = std::max( 1.0, scaledWidth );
244 if ( mUseCustomDashPattern )
245 {
246 mPen.setStyle( Qt::CustomDashLine );
247
248 //scale pattern vector
249
250 QVector<qreal> scaledVector;
251 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
252 for ( ; it != mCustomDashVector.constEnd(); ++it )
253 {
254 //the dash is specified in terms of pen widths, therefore the division
255 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
256 }
257 mPen.setDashPattern( scaledVector );
258 }
259 else
260 {
261 mPen.setStyle( mPenStyle );
262 }
263
264 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
265 {
266 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
267 }
268
269 mPen.setJoinStyle( mPenJoinStyle );
270 mPen.setCapStyle( mPenCapStyle );
271
272 mSelPen = mPen;
273 QColor selColor = context.renderContext().selectionColor();
274 if ( ! SELECTION_IS_OPAQUE )
275 selColor.setAlphaF( context.opacity() );
276 mSelPen.setColor( selColor );
277}
278
280{
281 Q_UNUSED( context )
282}
283
284void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
285{
286 QPainter *p = context.renderContext().painter();
287 if ( !p )
288 {
289 return;
290 }
291
292 QgsExpressionContextScope *scope = nullptr;
293 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
295 {
296 scope = new QgsExpressionContextScope();
297 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
298 }
299
300 if ( mDrawInsidePolygon )
301 p->save();
302
303 switch ( mRingFilter )
304 {
305 case AllRings:
306 case ExteriorRingOnly:
307 {
308 if ( mDrawInsidePolygon )
309 {
310 //only drawing the line on the interior of the polygon, so set clip path for painter
311 QPainterPath clipPath;
312 clipPath.addPolygon( points );
313
314 if ( rings )
315 {
316 //add polygon rings
317 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
318 {
319 QPolygonF ring = *it;
320 clipPath.addPolygon( ring );
321 }
322 }
323
324 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
325 p->setClipPath( clipPath, Qt::IntersectClip );
326 }
327
328 if ( scope )
330
331 renderPolyline( points, context );
332 }
333 break;
334
336 break;
337 }
338
339 if ( rings )
340 {
341 switch ( mRingFilter )
342 {
343 case AllRings:
345 {
346 mOffset = -mOffset; // invert the offset for rings!
347 int ringIndex = 1;
348 for ( const QPolygonF &ring : std::as_const( *rings ) )
349 {
350 if ( scope )
352
353 renderPolyline( ring, context );
354 ringIndex++;
355 }
356 mOffset = -mOffset;
357 }
358 break;
359 case ExteriorRingOnly:
360 break;
361 }
362 }
363
364 if ( mDrawInsidePolygon )
365 {
366 //restore painter to reset clip path
367 p->restore();
368 }
369
370}
371
373{
374 QPainter *p = context.renderContext().painter();
375 if ( !p )
376 {
377 return;
378 }
379
380 QPolygonF points = pts;
381
382 double startTrim = mTrimDistanceStart;
384 {
385 context.setOriginalValueVariable( startTrim );
387 }
388 double endTrim = mTrimDistanceEnd;
390 {
391 context.setOriginalValueVariable( endTrim );
393 }
394
395 double totalLength = -1;
396 if ( mTrimDistanceStartUnit == Qgis::RenderUnit::Percentage )
397 {
398 totalLength = QgsSymbolLayerUtils::polylineLength( points );
399 startTrim = startTrim * 0.01 * totalLength;
400 }
401 else
402 {
403 startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
404 }
405 if ( mTrimDistanceEndUnit == Qgis::RenderUnit::Percentage )
406 {
407 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
408 totalLength = QgsSymbolLayerUtils::polylineLength( points );
409 endTrim = endTrim * 0.01 * totalLength;
410 }
411 else
412 {
413 endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
414 }
415 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
416 {
417 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
418 }
419
420 QColor penColor = mColor;
421 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
422 mPen.setColor( penColor );
423
424 double offset = mOffset;
425 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
426
427 const QPen pen = context.selected() ? mSelPen : mPen;
428
429 if ( !pen.dashPattern().isEmpty() )
430 {
431 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
432 const QVector<double> pattern = pen.dashPattern();
433 bool foundNonNull = false;
434 for ( int i = 0; i < pattern.size(); ++i )
435 {
436 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
437 {
438 foundNonNull = true;
439 break;
440 }
441 }
442 if ( !foundNonNull )
443 return;
444 }
445
446 p->setBrush( Qt::NoBrush );
447
448 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
449 std::unique_ptr< QgsScopedQPainterState > painterState;
450 if ( points.size() <= 2 &&
453 ( p->renderHints() & QPainter::Antialiasing ) )
454 {
455 painterState = std::make_unique< QgsScopedQPainterState >( p );
456 p->setRenderHint( QPainter::Antialiasing, false );
457 }
458
459 const bool applyPatternTweaks = mAlignDashPattern
460 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
461 && pen.dashOffset() == 0;
462
463 if ( qgsDoubleNear( offset, 0 ) )
464 {
465 if ( applyPatternTweaks )
466 {
467 drawPathWithDashPatternTweaks( p, points, pen );
468 }
469 else
470 {
471 p->setPen( pen );
472 QPainterPath path;
473 path.addPolygon( points );
474 p->drawPath( path );
475 }
476 }
477 else
478 {
479 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
480 if ( mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits && context.renderContext().flags() & Qgis::RenderContextFlag::RenderSymbolPreview )
481 {
482 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
483 // and clamp it to a reasonable range. It's the best we can do in this situation!
484 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
485 }
486
487 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
488 for ( const QPolygonF &part : mline )
489 {
490 if ( applyPatternTweaks )
491 {
492 drawPathWithDashPatternTweaks( p, part, pen );
493 }
494 else
495 {
496 p->setPen( pen );
497 QPainterPath path;
498 path.addPolygon( part );
499 p->drawPath( path );
500 }
501 }
502 }
503}
504
506{
507 QVariantMap map;
508 map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
509 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
510 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
511 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
512 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
513 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
514 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
515 map[QStringLiteral( "offset" )] = QString::number( mOffset );
516 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
517 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
518 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
519 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
520 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
521 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
522 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
523 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
524 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
525 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
526 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
527 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
528 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
529 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
530 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
531 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
532 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
533 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
534 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
535 return map;
536}
537
539{
545 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
546 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
547 l->setOffset( mOffset );
548 l->setPenJoinStyle( mPenJoinStyle );
549 l->setPenCapStyle( mPenCapStyle );
550 l->setUseCustomDashPattern( mUseCustomDashPattern );
551 l->setCustomDashVector( mCustomDashVector );
552 l->setDrawInsidePolygon( mDrawInsidePolygon );
554 l->setDashPatternOffset( mDashPatternOffset );
555 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
556 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
557 l->setTrimDistanceStart( mTrimDistanceStart );
558 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
559 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
560 l->setTrimDistanceEnd( mTrimDistanceEnd );
561 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
562 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
563 l->setAlignDashPattern( mAlignDashPattern );
564 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
565
567 copyPaintEffect( l );
568 return l;
569}
570
571void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
572{
573 if ( mPenStyle == Qt::NoPen )
574 return;
575
576 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
577 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
578 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
579 element.appendChild( symbolizerElem );
580
581 // <Geometry>
582 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
583
584 // <Stroke>
585 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
586 symbolizerElem.appendChild( strokeElem );
587
588 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
590 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
592 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
593
594 // <se:PerpendicularOffset>
595 if ( !qgsDoubleNear( mOffset, 0.0 ) )
596 {
597 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
599 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
600 symbolizerElem.appendChild( perpOffsetElem );
601 }
602}
603
604QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
605{
606 if ( mUseCustomDashPattern )
607 {
608 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
609 mPen.color(), mPenJoinStyle,
610 mPenCapStyle, mOffset, &mCustomDashVector );
611 }
612 else
613 {
614 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
615 mPenCapStyle, mOffset );
616 }
617}
618
620{
621 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
622
623 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
624 if ( strokeElem.isNull() )
625 return nullptr;
626
627 Qt::PenStyle penStyle;
628 QColor color;
629 double width;
630 Qt::PenJoinStyle penJoinStyle;
631 Qt::PenCapStyle penCapStyle;
632 QVector<qreal> customDashVector;
633
635 color, width,
638 return nullptr;
639
640 double offset = 0.0;
641 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
642 if ( !perpOffsetElem.isNull() )
643 {
644 bool ok;
645 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
646 if ( ok )
647 offset = d;
648 }
649
650 double scaleFactor = 1.0;
651 const QString uom = element.attribute( QStringLiteral( "uom" ) );
652 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
653 width = width * scaleFactor;
654 offset = offset * scaleFactor;
655
657 l->setOutputUnit( sldUnitSize );
658 l->setOffset( offset );
661 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
663 return l;
664}
665
666void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
667{
668 if ( !dataDefinedProperties().hasActiveProperties() )
669 return; // shortcut
670
671 //data defined properties
672 bool hasStrokeWidthExpression = false;
674 {
676 double scaledWidth = context.renderContext().convertToPainterUnits(
679 pen.setWidthF( scaledWidth );
680 selPen.setWidthF( scaledWidth );
681 hasStrokeWidthExpression = true;
682 }
683
684 //color
686 {
688
690 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
691 pen.setColor( penColor );
692 }
693
694 //offset
696 {
699 }
700
701 //dash dot vector
702
703 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
704 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
705 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
706
708 {
709 QVector<qreal> dashVector;
711 if ( !QgsVariantUtils::isNull( exprVal ) )
712 {
713 QStringList dashList = exprVal.toString().split( ';' );
714 QStringList::const_iterator dashIt = dashList.constBegin();
715 for ( ; dashIt != dashList.constEnd(); ++dashIt )
716 {
717 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
718 }
719 pen.setDashPattern( dashVector );
720 }
721 }
722 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
723 {
724 //re-scale pattern vector after data defined pen width was applied
725
726 QVector<qreal> scaledVector;
727 for ( double v : std::as_const( mCustomDashVector ) )
728 {
729 //the dash is specified in terms of pen widths, therefore the division
730 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
731 }
732 mPen.setDashPattern( scaledVector );
733 }
734
735 // dash pattern offset
736 double patternOffset = mDashPatternOffset;
737 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
738 {
739 context.setOriginalValueVariable( patternOffset );
741 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
742 }
743
744 //line style
746 {
749 if ( !QgsVariantUtils::isNull( exprVal ) )
750 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
751 }
752
753 //join style
755 {
758 if ( !QgsVariantUtils::isNull( exprVal ) )
759 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
760 }
761
762 //cap style
764 {
767 if ( !QgsVariantUtils::isNull( exprVal ) )
768 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
769 }
770}
771
772void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
773{
774 if ( pen.dashPattern().empty() || points.size() < 2 )
775 return;
776
777 QVector< qreal > sourcePattern = pen.dashPattern();
778 const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
779 // back to painter units
780 for ( int i = 0; i < sourcePattern.size(); ++ i )
781 sourcePattern[i] *= pen.widthF();
782
783 if ( pen.widthF() <= 1.0 )
784 pen.setWidthF( 1.0001 );
785
786 QVector< qreal > buffer;
787 QPolygonF bufferedPoints;
788 QPolygonF previousSegmentBuffer;
789 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
790 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
791 // and then append the buffer to the output pattern.
792
793 auto ptIt = points.constBegin();
794 double totalBufferLength = 0;
795 int patternIndex = 0;
796 double currentRemainingDashLength = 0;
797 double currentRemainingGapLength = 0;
798
799 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
800 {
801 QVector< qreal > result;
802 result.reserve( buffer.size() );
803 for ( auto it = buffer.begin(); it != buffer.end(); )
804 {
805 qreal dash = *it++;
806 qreal gap = *it++;
807 while ( dash == 0 && !result.empty() )
808 {
809 result.last() += gap;
810
811 if ( it == buffer.end() )
812 return result;
813 dash = *it++;
814 gap = *it++;
815 }
816 while ( gap == 0 && it != buffer.end() )
817 {
818 dash += *it++;
819 gap = *it++;
820 }
821 result << dash << gap;
822 }
823 return result;
824 };
825
826 double currentBufferLineLength = 0;
827 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
828 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
829 {
830 if ( buffer.empty() || bufferedPoints.size() < 2 )
831 {
832 return;
833 }
834
835 if ( currentRemainingDashLength )
836 {
837 // ended midway through a dash -- we want to finish this off
838 buffer << currentRemainingDashLength << 0.0;
839 totalBufferLength += currentRemainingDashLength;
840 }
841 QVector< qreal > compressed = compressPattern( buffer );
842 if ( !currentRemainingDashLength )
843 {
844 // ended midway through a gap -- we don't want this, we want to end at previous dash
845 totalBufferLength -= compressed.last();
846 compressed.last() = 0;
847 }
848
849 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
850 const double scaleFactor = currentBufferLineLength / totalBufferLength;
851
852 bool shouldFlushPreviousSegmentBuffer = false;
853
854 if ( !previousSegmentBuffer.empty() )
855 {
856 // add first dash from current buffer
857 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
858 if ( !firstDashSubstring.empty() )
859 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
860
861 // then we skip over the first dash and gap for this segment
862 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
863
864 compressed = compressed.mid( 2 );
865 shouldFlushPreviousSegmentBuffer = !compressed.empty();
866 }
867
868 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
869 {
870 QPen adjustedPen = pen;
871 adjustedPen.setStyle( Qt::SolidLine );
872 painter->setPen( adjustedPen );
873 QPainterPath path;
874 path.addPolygon( previousSegmentBuffer );
875 painter->drawPath( path );
876 previousSegmentBuffer.clear();
877 }
878
879 double finalDash = 0;
880 if ( nextPoint )
881 {
882 // sharp bend:
883 // 1. rewind buffered points line by final dash and gap length
884 // (later) 2. draw the bend with a solid line of length 2 * final dash size
885
886 if ( !compressed.empty() )
887 {
888 finalDash = compressed.at( compressed.size() - 2 );
889 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
890
891 const QPolygonF thisPoints = bufferedPoints;
892 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
893 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
894 }
895 else
896 {
897 previousSegmentBuffer << bufferedPoints;
898 }
899 }
900
901 currentBufferLineLength = 0;
902 currentRemainingDashLength = 0;
903 currentRemainingGapLength = 0;
904 totalBufferLength = 0;
905 buffer.clear();
906
907 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
908 {
909 QPen adjustedPen = pen;
910 if ( !compressed.empty() )
911 {
912 // maximum size of dash pattern is 32 elements
913 compressed = compressed.mid( 0, 32 );
914 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
915 adjustedPen.setDashPattern( compressed );
916 }
917 else
918 {
919 adjustedPen.setStyle( Qt::SolidLine );
920 }
921
922 painter->setPen( adjustedPen );
923 QPainterPath path;
924 path.addPolygon( bufferedPoints );
925 painter->drawPath( path );
926 }
927
928 bufferedPoints.clear();
929 };
930
931 QPointF p1;
932 QPointF p2 = *ptIt;
933 ptIt++;
934 bufferedPoints << p2;
935 for ( ; ptIt != points.constEnd(); ++ptIt )
936 {
937 p1 = *ptIt;
938 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
939 {
940 continue;
941 }
942
943 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
944 currentBufferLineLength += remainingSegmentDistance;
945 while ( true )
946 {
947 // handle currentRemainingDashLength/currentRemainingGapLength
948 if ( currentRemainingDashLength > 0 )
949 {
950 // bit more of dash to insert
951 if ( remainingSegmentDistance >= currentRemainingDashLength )
952 {
953 // all of dash fits in
954 buffer << currentRemainingDashLength << 0.0;
955 totalBufferLength += currentRemainingDashLength;
956 remainingSegmentDistance -= currentRemainingDashLength;
957 patternIndex++;
958 currentRemainingDashLength = 0.0;
959 currentRemainingGapLength = sourcePattern.at( patternIndex );
960 }
961 else
962 {
963 // only part of remaining dash fits in
964 buffer << remainingSegmentDistance << 0.0;
965 totalBufferLength += remainingSegmentDistance;
966 currentRemainingDashLength -= remainingSegmentDistance;
967 break;
968 }
969 }
970 if ( currentRemainingGapLength > 0 )
971 {
972 // bit more of gap to insert
973 if ( remainingSegmentDistance >= currentRemainingGapLength )
974 {
975 // all of gap fits in
976 buffer << 0.0 << currentRemainingGapLength;
977 totalBufferLength += currentRemainingGapLength;
978 remainingSegmentDistance -= currentRemainingGapLength;
979 currentRemainingGapLength = 0.0;
980 patternIndex++;
981 }
982 else
983 {
984 // only part of remaining gap fits in
985 buffer << 0.0 << remainingSegmentDistance;
986 totalBufferLength += remainingSegmentDistance;
987 currentRemainingGapLength -= remainingSegmentDistance;
988 break;
989 }
990 }
991
992 if ( patternIndex >= sourcePattern.size() )
993 patternIndex = 0;
994
995 const double nextPatternDashLength = sourcePattern.at( patternIndex );
996 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
997 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
998 {
999 buffer << nextPatternDashLength << nextPatternGapLength;
1000 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1001 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1002 patternIndex += 2;
1003 }
1004 else if ( nextPatternDashLength <= remainingSegmentDistance )
1005 {
1006 // can fit in "dash", but not "gap"
1007 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1008 totalBufferLength += remainingSegmentDistance;
1009 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1010 currentRemainingDashLength = 0;
1011 patternIndex++;
1012 break;
1013 }
1014 else
1015 {
1016 // can't fit in "dash"
1017 buffer << remainingSegmentDistance << 0.0;
1018 totalBufferLength += remainingSegmentDistance;
1019 currentRemainingGapLength = 0;
1020 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1021 break;
1022 }
1023 }
1024
1025 bufferedPoints << p1;
1026 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1027 {
1028 QPointF nextPoint = *( ptIt + 1 );
1029
1030 // extreme angles form more than 45 degree angle at a node
1031 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1032 {
1033 // extreme angle. Rescale buffer and flush
1034 flushBuffer( &nextPoint );
1035 bufferedPoints << p1;
1036 // restart the line with the full length of the most recent dash element -- see
1037 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1038 if ( patternIndex % 2 == 1 )
1039 {
1040 patternIndex--;
1041 }
1042 currentRemainingDashLength = sourcePattern.at( patternIndex );
1043 }
1044 }
1045
1046 p2 = p1;
1047 }
1048
1049 flushBuffer( nullptr );
1050 if ( !previousSegmentBuffer.empty() )
1051 {
1052 QPen adjustedPen = pen;
1053 adjustedPen.setStyle( Qt::SolidLine );
1054 painter->setPen( adjustedPen );
1055 QPainterPath path;
1056 path.addPolygon( previousSegmentBuffer );
1057 painter->drawPath( path );
1058 previousSegmentBuffer.clear();
1059 }
1060}
1061
1063{
1064 if ( mDrawInsidePolygon )
1065 {
1066 //set to clip line to the interior of polygon, so we expect no bleed
1067 return 0;
1068 }
1069 else
1070 {
1071 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1073 }
1074}
1075
1077{
1078 unit = mCustomDashPatternUnit;
1079 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1080}
1081
1083{
1084 return mPenStyle;
1085}
1086
1088{
1089 double width = mWidth;
1091 {
1094 }
1095
1097 if ( mWidthUnit == Qgis::RenderUnit::MapUnits )
1098 {
1100 }
1101 return width;
1102}
1103
1105{
1107 {
1110 }
1111 return mColor;
1112}
1113
1115{
1116 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1117}
1118
1120{
1121 return mAlignDashPattern;
1122}
1123
1125{
1126 mAlignDashPattern = enabled;
1127}
1128
1130{
1131 return mPatternCartographicTweakOnSharpCorners;
1132}
1133
1135{
1136 mPatternCartographicTweakOnSharpCorners = enabled;
1137}
1138
1140{
1141 double offset = mOffset;
1142
1144 {
1147 }
1148
1150 if ( mOffsetUnit == Qgis::RenderUnit::MapUnits )
1151 {
1153 }
1154 return -offset; //direction seems to be inverse to symbology offset
1155}
1156
1158
1160
1161class MyLine
1162{
1163 public:
1164 MyLine( QPointF p1, QPointF p2 )
1165 : mVertical( false )
1166 , mIncreasing( false )
1167 , mT( 0.0 )
1168 , mLength( 0.0 )
1169 {
1170 if ( p1 == p2 )
1171 return; // invalid
1172
1173 // tangent and direction
1174 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1175 {
1176 // vertical line - tangent undefined
1177 mVertical = true;
1178 mIncreasing = ( p2.y() > p1.y() );
1179 }
1180 else
1181 {
1182 mVertical = false;
1183 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1184 mIncreasing = ( p2.x() > p1.x() );
1185 }
1186
1187 // length
1188 double x = ( p2.x() - p1.x() );
1189 double y = ( p2.y() - p1.y() );
1190 mLength = std::sqrt( x * x + y * y );
1191 }
1192
1193 // return angle in radians
1194 double angle()
1195 {
1196 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1197
1198 if ( !mIncreasing )
1199 a += M_PI;
1200 return a;
1201 }
1202
1203 // return difference for x,y when going along the line with specified interval
1204 QPointF diffForInterval( double interval )
1205 {
1206 if ( mVertical )
1207 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1208
1209 double alpha = std::atan( mT );
1210 double dx = std::cos( alpha ) * interval;
1211 double dy = std::sin( alpha ) * interval;
1212 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1213 }
1214
1215 double length() const { return mLength; }
1216
1217 protected:
1218 bool mVertical;
1219 bool mIncreasing;
1220 double mT;
1221 double mLength;
1222};
1223
1225
1226//
1227// QgsTemplatedLineSymbolLayerBase
1228//
1230 : mRotateSymbols( rotateSymbol )
1231 , mInterval( interval )
1232{
1233
1234}
1235
1237{
1238 if ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1240 else if ( mPlacements & Qgis::MarkerLinePlacement::Vertex )
1242 else if ( ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1243 && ( mPlacements & Qgis::MarkerLinePlacement::InnerVertices )
1244 && ( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1245 return Qgis::MarkerLinePlacement::Vertex; // retain round trip for deprecated old API
1246 else if ( mPlacements & Qgis::MarkerLinePlacement::LastVertex )
1248 else if ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1250 else if ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1252 else if ( mPlacements & Qgis::MarkerLinePlacement::CurvePoint )
1254 else if ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter )
1256 else
1258}
1259
1261{
1262 mPlacements = placement;
1263}
1264
1266
1268{
1269 if ( mRenderingFeature )
1270 {
1271 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1272 // until after we've received the final part
1273 mFeatureSymbolOpacity = context.opacity();
1274 mCurrentFeatureIsSelected = context.selected();
1275 }
1276
1277 double offset = mOffset;
1278
1280 {
1283 }
1284
1285 Qgis::MarkerLinePlacements placements = QgsTemplatedLineSymbolLayerBase::placements();
1286
1288 {
1290 if ( !QgsVariantUtils::isNull( exprVal ) )
1291 {
1292 QString placementString = exprVal.toString();
1293 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1294 {
1296 }
1297 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1298 {
1300 }
1301 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1302 {
1304 }
1305 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1306 {
1308 }
1309 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1310 {
1312 }
1313 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1314 {
1316 }
1317 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1318 {
1320 }
1321 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1322 {
1324 }
1325 else
1326 {
1328 }
1329 }
1330 }
1331
1332 QgsScopedQPainterState painterState( context.renderContext().painter() );
1333
1334 double averageOver = mAverageAngleLength;
1336 {
1337 context.setOriginalValueVariable( mAverageAngleLength );
1339 }
1340 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1341
1342 if ( qgsDoubleNear( offset, 0.0 ) )
1343 {
1345 renderPolylineInterval( points, context, averageOver );
1347 renderPolylineCentral( points, context, averageOver );
1349 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1351 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1352 {
1353 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1354 mHasRenderedFirstPart = mRenderingFeature;
1355 }
1357 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1359 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1361 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1363 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1364 }
1365 else
1366 {
1367 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1368 QList<QPolygonF> mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
1369
1370 for ( int part = 0; part < mline.count(); ++part )
1371 {
1372 const QPolygonF &points2 = mline[ part ];
1373
1375 renderPolylineInterval( points2, context, averageOver );
1377 renderPolylineCentral( points2, context, averageOver );
1379 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1381 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1383 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1385 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1386 {
1387 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1388 mHasRenderedFirstPart = mRenderingFeature;
1389 }
1391 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1393 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1394 }
1395 }
1396}
1397
1398void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1399{
1400 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1401
1402 if ( curvePolygon )
1403 {
1404 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1405 }
1406
1407 QgsExpressionContextScope *scope = nullptr;
1408 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1410 {
1411 scope = new QgsExpressionContextScope();
1412 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1413 }
1414
1415 switch ( mRingFilter )
1416 {
1417 case AllRings:
1418 case ExteriorRingOnly:
1419 {
1420 if ( scope )
1422
1423 renderPolyline( points, context );
1424 break;
1425 }
1426 case InteriorRingsOnly:
1427 break;
1428 }
1429
1430 if ( rings )
1431 {
1432 switch ( mRingFilter )
1433 {
1434 case AllRings:
1435 case InteriorRingsOnly:
1436 {
1437 mOffset = -mOffset; // invert the offset for rings!
1438 for ( int i = 0; i < rings->size(); ++i )
1439 {
1440 if ( curvePolygon )
1441 {
1442 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1443 }
1444 if ( scope )
1446
1447 renderPolyline( rings->at( i ), context );
1448 }
1449 mOffset = -mOffset;
1450 }
1451 break;
1452 case ExteriorRingOnly:
1453 break;
1454 }
1455 }
1456}
1457
1459{
1461 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1462 {
1463 return Qgis::RenderUnit::Unknown;
1464 }
1465 return unit;
1466}
1467
1469{
1471 mIntervalUnit = unit;
1472 mOffsetAlongLineUnit = unit;
1473 mAverageAngleLengthUnit = unit;
1474}
1475
1477{
1479 setIntervalMapUnitScale( scale );
1480 mOffsetMapUnitScale = scale;
1482}
1483
1485{
1489 {
1490 return mOffsetMapUnitScale;
1491 }
1492 return QgsMapUnitScale();
1493}
1494
1496{
1497 QVariantMap map;
1498 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1499 map[QStringLiteral( "interval" )] = QString::number( interval() );
1500 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1501 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1502 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1503 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1504 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1505 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1506 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1507 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1508 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1509 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1510 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1511
1512 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1513
1514 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1515 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1516 return map;
1517}
1518
1520{
1521 return mPlaceOnEveryPart
1522 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1523 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1524 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1525}
1526
1528{
1529 mRenderingFeature = true;
1530 mHasRenderedFirstPart = false;
1531}
1532
1534{
1535 mRenderingFeature = false;
1536 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1537 return;
1538
1539 const double prevOpacity = subSymbol()->opacity();
1540 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1541
1542 // render final point
1543 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1544 mFeatureSymbolOpacity = 1;
1545 subSymbol()->setOpacity( prevOpacity );
1546}
1547
1549{
1550 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1551 destLayer->setOffset( mOffset );
1552 destLayer->setPlacements( placements() );
1553 destLayer->setOffsetUnit( mOffsetUnit );
1555 destLayer->setIntervalUnit( intervalUnit() );
1557 destLayer->setOffsetAlongLine( offsetAlongLine() );
1560 destLayer->setAverageAngleLength( mAverageAngleLength );
1561 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1562 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1563 destLayer->setRingFilter( mRingFilter );
1564 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1565 copyDataDefinedProperties( destLayer );
1566 copyPaintEffect( destLayer );
1567}
1568
1570{
1571 if ( properties.contains( QStringLiteral( "offset" ) ) )
1572 {
1573 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1574 }
1575 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1576 {
1577 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1578 }
1579 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1580 {
1581 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1582 }
1583 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1584 {
1585 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1586 }
1587 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1588 {
1589 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1590 }
1591 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1592 {
1593 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1594 }
1595
1596 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1597 {
1598 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1599 }
1600 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1601 {
1602 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1603 }
1604
1605 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1606 {
1607 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1608 }
1609 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1610 {
1611 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1612 }
1613 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1614 {
1615 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1616 }
1617
1618 if ( properties.contains( QStringLiteral( "placement" ) ) )
1619 {
1620 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1622 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1624 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1626 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1628 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1630 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1632 else
1634 }
1635 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1636 {
1637 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1638 destLayer->setPlacements( placements );
1639 }
1640
1641 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1642 {
1643 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1644 }
1645
1646 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1647
1649}
1650
1651void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1652{
1653 if ( points.isEmpty() )
1654 return;
1655
1656 double lengthLeft = 0; // how much is left until next marker
1657
1658 QgsRenderContext &rc = context.renderContext();
1659 double interval = mInterval;
1660
1662 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1663
1665 {
1666 context.setOriginalValueVariable( mInterval );
1668 }
1669 if ( interval <= 0 )
1670 {
1671 interval = 0.1;
1672 }
1673 double offsetAlongLine = mOffsetAlongLine;
1675 {
1676 context.setOriginalValueVariable( mOffsetAlongLine );
1678 }
1679
1680 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1681 if ( intervalUnit() == Qgis::RenderUnit::MetersInMapUnits && rc.flags() & Qgis::RenderContextFlag::RenderSymbolPreview )
1682 {
1683 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1684 // and clamp it to a reasonable range. It's the best we can do in this situation!
1685 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1686 }
1687
1688 if ( painterUnitInterval < 0 )
1689 return;
1690
1691 double painterUnitOffsetAlongLine = 0;
1692
1693 // only calculated if we need it!
1694 double totalLength = -1;
1695
1696 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1697 {
1698 switch ( offsetAlongLineUnit() )
1699 {
1700 case Qgis::RenderUnit::Millimeters:
1701 case Qgis::RenderUnit::MapUnits:
1702 case Qgis::RenderUnit::Pixels:
1703 case Qgis::RenderUnit::Points:
1704 case Qgis::RenderUnit::Inches:
1705 case Qgis::RenderUnit::Unknown:
1706 case Qgis::RenderUnit::MetersInMapUnits:
1708 break;
1709 case Qgis::RenderUnit::Percentage:
1710 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1711 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1712 break;
1713 }
1714
1715 if ( points.isClosed() )
1716 {
1717 if ( painterUnitOffsetAlongLine > 0 )
1718 {
1719 if ( totalLength < 0 )
1720 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1721 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1722 }
1723 else if ( painterUnitOffsetAlongLine < 0 )
1724 {
1725 if ( totalLength < 0 )
1726 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1727 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1728 }
1729 }
1730 }
1731
1732 if ( offsetAlongLineUnit() == Qgis::RenderUnit::MetersInMapUnits && rc.flags() & Qgis::RenderContextFlag::RenderSymbolPreview )
1733 {
1734 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1735 // and clamp it to a reasonable range. It's the best we can do in this situation!
1736 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1737 }
1738
1739 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1740
1741 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1742 {
1743 QVector< QPointF > angleStartPoints;
1744 QVector< QPointF > symbolPoints;
1745 QVector< QPointF > angleEndPoints;
1746
1747 // we collect 3 arrays of points. These correspond to
1748 // 1. the actual point at which to render the symbol
1749 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1750 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1751 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1752 // (or trace past the final point)
1753 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1754
1755 if ( symbolPoints.empty() )
1756 {
1757 // no symbols to draw, shortcut out early
1758 return;
1759 }
1760
1761 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1762 {
1763 // avoid duplicate points at start and end of closed rings
1764 symbolPoints.pop_back();
1765 }
1766
1767 angleEndPoints.reserve( symbolPoints.size() );
1768 angleStartPoints.reserve( symbolPoints.size() );
1769 if ( averageOver <= painterUnitOffsetAlongLine )
1770 {
1771 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1772 }
1773 else
1774 {
1775 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1776 }
1777 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1778
1779 int pointNum = 0;
1780 for ( int i = 0; i < symbolPoints.size(); ++ i )
1781 {
1782 if ( context.renderContext().renderingStopped() )
1783 break;
1784
1785 const QPointF pt = symbolPoints[i];
1786 const QPointF startPt = angleStartPoints[i];
1787 const QPointF endPt = angleEndPoints[i];
1788
1789 MyLine l( startPt, endPt );
1790 // rotate marker (if desired)
1791 if ( rotateSymbols() )
1792 {
1793 setSymbolLineAngle( l.angle() * 180 / M_PI );
1794 }
1795
1797 renderSymbol( pt, context.feature(), rc, -1, context.selected() );
1798 }
1799 }
1800 else
1801 {
1802 // not averaging line angle -- always use exact section angle
1803 int pointNum = 0;
1804 QPointF lastPt = points[0];
1805 for ( int i = 1; i < points.count(); ++i )
1806 {
1807 if ( context.renderContext().renderingStopped() )
1808 break;
1809
1810 const QPointF &pt = points[i];
1811
1812 if ( lastPt == pt ) // must not be equal!
1813 continue;
1814
1815 // for each line, find out dx and dy, and length
1816 MyLine l( lastPt, pt );
1817 QPointF diff = l.diffForInterval( painterUnitInterval );
1818
1819 // if there's some length left from previous line
1820 // use only the rest for the first point in new line segment
1821 double c = 1 - lengthLeft / painterUnitInterval;
1822
1823 lengthLeft += l.length();
1824
1825 // rotate marker (if desired)
1826 if ( rotateSymbols() )
1827 {
1828 setSymbolLineAngle( l.angle() * 180 / M_PI );
1829 }
1830
1831 // while we're not at the end of line segment, draw!
1832 while ( lengthLeft > painterUnitInterval )
1833 {
1834 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1835 lastPt += c * diff;
1836 lengthLeft -= painterUnitInterval;
1838 renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
1839 c = 1; // reset c (if wasn't 1 already)
1840 }
1841
1842 lastPt = pt;
1843 }
1844
1845 }
1846}
1847
1848static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1849{
1850 // calc average angle between the previous and next point
1851 double a1 = MyLine( prevPt, pt ).angle();
1852 double a2 = MyLine( pt, nextPt ).angle();
1853 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1854
1855 return std::atan2( unitY, unitX );
1856}
1857
1858void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1859{
1860 if ( points.isEmpty() )
1861 return;
1862
1863 QgsRenderContext &rc = context.renderContext();
1864
1865 int i = -1, maxCount = 0;
1866 bool isRing = false;
1867
1869 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1871
1872 double offsetAlongLine = mOffsetAlongLine;
1874 {
1875 context.setOriginalValueVariable( mOffsetAlongLine );
1877 }
1878
1879 // only calculated if we need it!!
1880 double totalLength = -1;
1881 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1882 {
1883 //scale offset along line
1884 switch ( offsetAlongLineUnit() )
1885 {
1886 case Qgis::RenderUnit::Millimeters:
1887 case Qgis::RenderUnit::MapUnits:
1888 case Qgis::RenderUnit::Pixels:
1889 case Qgis::RenderUnit::Points:
1890 case Qgis::RenderUnit::Inches:
1891 case Qgis::RenderUnit::Unknown:
1892 case Qgis::RenderUnit::MetersInMapUnits:
1894 break;
1895 case Qgis::RenderUnit::Percentage:
1896 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1897 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1898 break;
1899 }
1900 if ( points.isClosed() )
1901 {
1902 if ( offsetAlongLine > 0 )
1903 {
1904 if ( totalLength < 0 )
1905 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1906 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1907 }
1908 else if ( offsetAlongLine < 0 )
1909 {
1910 if ( totalLength < 0 )
1911 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1912 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1913 }
1914 }
1915 }
1916
1917 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1921 {
1923 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1924
1925 QgsVertexId vId;
1926 QgsPoint vPoint;
1927 double x, y, z;
1928 QPointF mapPoint;
1929 int pointNum = 0;
1930 const int numPoints = context.renderContext().geometry()->nCoordinates();
1931 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1932 {
1933 if ( context.renderContext().renderingStopped() )
1934 break;
1935
1937
1938 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1939 continue;
1940
1941 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1942 continue;
1943
1944 if ( ( ( placement == Qgis::MarkerLinePlacement::Vertex || placement == Qgis::MarkerLinePlacement::InnerVertices ) && vId.type == Qgis::VertexType::Segment )
1945 || ( placement == Qgis::MarkerLinePlacement::CurvePoint && vId.type == Qgis::VertexType::Curve ) )
1946 {
1947 //transform
1948 x = vPoint.x();
1949 y = vPoint.y();
1950 z = 0.0;
1951 if ( ct.isValid() )
1952 {
1953 ct.transformInPlace( x, y, z );
1954 }
1955 mapPoint.setX( x );
1956 mapPoint.setY( y );
1957 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1958 if ( rotateSymbols() )
1959 {
1960 double angle = context.renderContext().geometry()->vertexAngle( vId );
1961 setSymbolLineAngle( angle * 180 / M_PI );
1962 }
1963 renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() );
1964 }
1965 }
1966
1967 return;
1968 }
1969
1970 int pointNum = 0;
1971
1972 switch ( placement )
1973 {
1975 {
1976 i = 0;
1977 maxCount = 1;
1978 break;
1979 }
1980
1982 {
1983 i = points.count() - 1;
1984 pointNum = i;
1985 maxCount = points.count();
1986 break;
1987 }
1988
1990 {
1991 i = 1;
1992 pointNum = 1;
1993 maxCount = points.count() - 1;
1994 break;
1995 }
1996
1999 {
2001 maxCount = points.count();
2002 if ( points.first() == points.last() )
2003 isRing = true;
2004 break;
2005 }
2006
2010 {
2011 return;
2012 }
2013 }
2014
2016 {
2017 double distance;
2019 renderOffsetVertexAlongLine( points, i, distance, context, placement );
2020
2021 return;
2022 }
2023
2024 QPointF prevPoint;
2025 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2026 prevPoint = points.at( 0 );
2027
2028 QPointF symbolPoint;
2029 for ( ; i < maxCount; ++i )
2030 {
2032
2033 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2034 {
2035 continue; // don't draw the last marker - it has been drawn already
2036 }
2037
2039 {
2040 QPointF currentPoint = points.at( i );
2041 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2042 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2043 if ( rotateSymbols() )
2044 {
2045 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2046 currentPoint.x() - prevPoint.x() );
2047 setSymbolLineAngle( angle * 180 / M_PI );
2048 }
2049 prevPoint = currentPoint;
2050 }
2051 else
2052 {
2053 symbolPoint = points.at( i );
2054 // rotate marker (if desired)
2055 if ( rotateSymbols() )
2056 {
2057 double angle = markerAngle( points, isRing, i );
2058 setSymbolLineAngle( angle * 180 / M_PI );
2059 }
2060 }
2061
2062 mFinalVertex = symbolPoint;
2063 if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2064 renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
2065 }
2066}
2067
2068double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2069{
2070 double angle = 0;
2071 const QPointF &pt = points[vertex];
2072
2073 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2074 {
2075 int prevIndex = vertex - 1;
2076 int nextIndex = vertex + 1;
2077
2078 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2079 {
2080 prevIndex = points.count() - 2;
2081 nextIndex = 1;
2082 }
2083
2084 QPointF prevPoint, nextPoint;
2085 while ( prevIndex >= 0 )
2086 {
2087 prevPoint = points[ prevIndex ];
2088 if ( prevPoint != pt )
2089 {
2090 break;
2091 }
2092 --prevIndex;
2093 }
2094
2095 while ( nextIndex < points.count() )
2096 {
2097 nextPoint = points[ nextIndex ];
2098 if ( nextPoint != pt )
2099 {
2100 break;
2101 }
2102 ++nextIndex;
2103 }
2104
2105 if ( prevIndex >= 0 && nextIndex < points.count() )
2106 {
2107 angle = _averageAngle( prevPoint, pt, nextPoint );
2108 }
2109 }
2110 else //no ring and vertex is at start / at end
2111 {
2112 if ( vertex == 0 )
2113 {
2114 while ( vertex < points.size() - 1 )
2115 {
2116 const QPointF &nextPt = points[vertex + 1];
2117 if ( pt != nextPt )
2118 {
2119 angle = MyLine( pt, nextPt ).angle();
2120 return angle;
2121 }
2122 ++vertex;
2123 }
2124 }
2125 else
2126 {
2127 // use last segment's angle
2128 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2129 {
2130 const QPointF &prevPt = points[vertex - 1];
2131 if ( pt != prevPt )
2132 {
2133 angle = MyLine( prevPt, pt ).angle();
2134 return angle;
2135 }
2136 --vertex;
2137 }
2138 }
2139 }
2140 return angle;
2141}
2142
2143void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2144{
2145 if ( points.isEmpty() )
2146 return;
2147
2148 QgsRenderContext &rc = context.renderContext();
2149 if ( qgsDoubleNear( distance, 0.0 ) )
2150 {
2151 // rotate marker (if desired)
2152 if ( rotateSymbols() )
2153 {
2154 bool isRing = false;
2155 if ( points.first() == points.last() )
2156 isRing = true;
2157 double angle = markerAngle( points, isRing, vertex );
2158 setSymbolLineAngle( angle * 180 / M_PI );
2159 }
2160 mFinalVertex = points[vertex];
2161 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2162 renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
2163 return;
2164 }
2165
2166 int pointIncrement = distance > 0 ? 1 : -1;
2167 QPointF previousPoint = points[vertex];
2168 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2169 int endPoint = distance > 0 ? points.count() - 1 : 0;
2170 double distanceLeft = std::fabs( distance );
2171
2172 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2173 {
2174 const QPointF &pt = points[i];
2175
2176 if ( previousPoint == pt ) // must not be equal!
2177 continue;
2178
2179 // create line segment
2180 MyLine l( previousPoint, pt );
2181
2182 if ( distanceLeft < l.length() )
2183 {
2184 //destination point is in current segment
2185 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2186 // rotate marker (if desired)
2187 if ( rotateSymbols() )
2188 {
2189 setSymbolLineAngle( l.angle() * 180 / M_PI );
2190 }
2191 mFinalVertex = markerPoint;
2192 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2193 renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
2194 return;
2195 }
2196
2197 distanceLeft -= l.length();
2198 previousPoint = pt;
2199 }
2200
2201 //didn't find point
2202}
2203
2204void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2205{
2206 if ( p.empty() )
2207 return;
2208
2209 QVector< QPointF > points = p;
2210 const bool closedRing = points.first() == points.last();
2211
2212 double lengthLeft = initialOffset;
2213
2214 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2215 if ( initialLagLeft < 0 && closedRing )
2216 {
2217 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2218 QPointF lastPt = points.constLast();
2219 QVector< QPointF > pseudoPoints;
2220 for ( int i = points.count() - 2; i > 0; --i )
2221 {
2222 if ( initialLagLeft >= 0 )
2223 {
2224 break;
2225 }
2226
2227 const QPointF &pt = points[i];
2228
2229 if ( lastPt == pt ) // must not be equal!
2230 continue;
2231
2232 MyLine l( lastPt, pt );
2233 initialLagLeft += l.length();
2234 lastPt = pt;
2235
2236 pseudoPoints << pt;
2237 }
2238 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2239
2240 points = pseudoPoints;
2241 points.append( p );
2242 }
2243 else
2244 {
2245 while ( initialLagLeft < 0 )
2246 {
2247 dest << points.constFirst();
2248 initialLagLeft += intervalPainterUnits;
2249 }
2250 }
2251 if ( initialLag > 0 )
2252 {
2253 lengthLeft += intervalPainterUnits - initialLagLeft;
2254 }
2255
2256 QPointF lastPt = points[0];
2257 for ( int i = 1; i < points.count(); ++i )
2258 {
2259 const QPointF &pt = points[i];
2260
2261 if ( lastPt == pt ) // must not be equal!
2262 {
2263 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2264 {
2265 lastPt = points[0];
2266 i = 0;
2267 }
2268 continue;
2269 }
2270
2271 // for each line, find out dx and dy, and length
2272 MyLine l( lastPt, pt );
2273 QPointF diff = l.diffForInterval( intervalPainterUnits );
2274
2275 // if there's some length left from previous line
2276 // use only the rest for the first point in new line segment
2277 double c = 1 - lengthLeft / intervalPainterUnits;
2278
2279 lengthLeft += l.length();
2280
2281
2282 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2283 {
2284 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2285 lastPt += c * diff;
2286 lengthLeft -= intervalPainterUnits;
2287 dest << lastPt;
2288 c = 1; // reset c (if wasn't 1 already)
2289 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2290 break;
2291 }
2292 lastPt = pt;
2293
2294 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2295 break;
2296
2297 // if a closed ring, we keep looping around the ring until we hit the required number of points
2298 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2299 {
2300 lastPt = points[0];
2301 i = 0;
2302 }
2303 }
2304
2305 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2306 {
2307 // pad with repeating last point to match desired size
2308 while ( dest.size() < numberPointsRequired )
2309 dest << points.constLast();
2310 }
2311}
2312
2313void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2314{
2315 if ( !points.isEmpty() )
2316 {
2317 // calc length
2318 qreal length = 0;
2319 QPolygonF::const_iterator it = points.constBegin();
2320 QPointF last = *it;
2321 for ( ++it; it != points.constEnd(); ++it )
2322 {
2323 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2324 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2325 last = *it;
2326 }
2327 if ( qgsDoubleNear( length, 0.0 ) )
2328 return;
2329
2330 const double midPoint = length / 2;
2331
2332 QPointF pt;
2333 double thisSymbolAngle = 0;
2334
2335 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2336 {
2337 QVector< QPointF > angleStartPoints;
2338 QVector< QPointF > symbolPoints;
2339 QVector< QPointF > angleEndPoints;
2340 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2341 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2342 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2343 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2344
2345 pt = symbolPoints.at( 1 );
2346 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2347 thisSymbolAngle = l.angle();
2348 }
2349 else
2350 {
2351 // find the segment where the central point lies
2352 it = points.constBegin();
2353 last = *it;
2354 qreal last_at = 0, next_at = 0;
2355 QPointF next;
2356 for ( ++it; it != points.constEnd(); ++it )
2357 {
2358 next = *it;
2359 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2360 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2361 if ( next_at >= midPoint )
2362 break; // we have reached the center
2363 last = *it;
2364 last_at = next_at;
2365 }
2366
2367 // find out the central point on segment
2368 MyLine l( last, next ); // for line angle
2369 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2370 pt = last + ( next - last ) * k;
2371 thisSymbolAngle = l.angle();
2372 }
2373
2374 // draw the marker
2375 // rotate marker (if desired)
2376 if ( rotateSymbols() )
2377 {
2378 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2379 }
2380
2381 renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2382
2383 }
2384}
2385
2387{
2388 return mMarker.get();
2389}
2390
2392{
2393 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2394 {
2395 delete symbol;
2396 return false;
2397 }
2398
2399 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2400 mColor = mMarker->color();
2401 return true;
2402}
2403
2404
2405
2406//
2407// QgsMarkerLineSymbolLayer
2408//
2409
2410QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2411 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2412{
2414}
2415
2417
2419{
2420 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2422
2423 if ( props.contains( QStringLiteral( "interval" ) ) )
2424 interval = props[QStringLiteral( "interval" )].toDouble();
2425 if ( props.contains( QStringLiteral( "rotate" ) ) )
2426 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2427
2428 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2429 setCommonProperties( x.get(), props );
2430 return x.release();
2431}
2432
2434{
2435 return QStringLiteral( "MarkerLine" );
2436}
2437
2438void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2439{
2440 mMarker->setColor( color );
2441 mColor = color;
2442}
2443
2445{
2446 return mMarker ? mMarker->color() : mColor;
2447}
2448
2450{
2451 // if being rotated, it gets initialized with every line segment
2452 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2453 if ( rotateSymbols() )
2455 mMarker->setRenderHints( hints );
2456
2457 mMarker->startRender( context.renderContext(), context.fields() );
2458}
2459
2461{
2462 mMarker->stopRender( context.renderContext() );
2463}
2464
2465
2467{
2468 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2470 return x.release();
2471}
2472
2473void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2474{
2475 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2476 {
2477 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2478 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2479 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2480 element.appendChild( symbolizerElem );
2481
2482 // <Geometry>
2483 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2484
2485 QString gap;
2487 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2489 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2491 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2493 // no way to get line/polygon's vertices, use a VendorOption
2494 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2495
2497 {
2499 gap = qgsDoubleToString( interval );
2500 }
2501
2502 if ( !rotateSymbols() )
2503 {
2504 // markers in LineSymbolizer must be drawn following the line orientation,
2505 // use a VendorOption when no marker rotation
2506 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2507 }
2508
2509 // <Stroke>
2510 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2511 symbolizerElem.appendChild( strokeElem );
2512
2513 // <GraphicStroke>
2514 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2515 strokeElem.appendChild( graphicStrokeElem );
2516
2517 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2518 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2519 {
2520 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2521 }
2522 else if ( layer )
2523 {
2524 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2525 }
2526 else
2527 {
2528 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2529 }
2530
2531 if ( !gap.isEmpty() )
2532 {
2533 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2535 graphicStrokeElem.appendChild( gapElem );
2536 }
2537
2538 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2539 {
2540 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2542 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2543 symbolizerElem.appendChild( perpOffsetElem );
2544 }
2545 }
2546}
2547
2549{
2550 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2551
2552 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2553 if ( strokeElem.isNull() )
2554 return nullptr;
2555
2556 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2557 if ( graphicStrokeElem.isNull() )
2558 return nullptr;
2559
2560 // retrieve vendor options
2561 bool rotateMarker = true;
2563
2564 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2565 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2566 {
2567 if ( it.key() == QLatin1String( "placement" ) )
2568 {
2569 if ( it.value() == QLatin1String( "points" ) )
2571 else if ( it.value() == QLatin1String( "firstPoint" ) )
2573 else if ( it.value() == QLatin1String( "lastPoint" ) )
2575 else if ( it.value() == QLatin1String( "centralPoint" ) )
2577 }
2578 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2579 {
2580 rotateMarker = it.value() == QLatin1String( "0" );
2581 }
2582 }
2583
2584 std::unique_ptr< QgsMarkerSymbol > marker;
2585
2587 if ( l )
2588 {
2589 QgsSymbolLayerList layers;
2590 layers.append( l );
2591 marker.reset( new QgsMarkerSymbol( layers ) );
2592 }
2593
2594 if ( !marker )
2595 return nullptr;
2596
2597 double interval = 0.0;
2598 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2599 if ( !gapElem.isNull() )
2600 {
2601 bool ok;
2602 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2603 if ( ok )
2604 interval = d;
2605 }
2606
2607 double offset = 0.0;
2608 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2609 if ( !perpOffsetElem.isNull() )
2610 {
2611 bool ok;
2612 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2613 if ( ok )
2614 offset = d;
2615 }
2616
2617 double scaleFactor = 1.0;
2618 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2619 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2620 interval = interval * scaleFactor;
2621 offset = offset * scaleFactor;
2622
2624 x->setOutputUnit( sldUnitSize );
2626 x->setInterval( interval );
2627 x->setSubSymbol( marker.release() );
2628 x->setOffset( offset );
2629 return x;
2630}
2631
2633{
2634 mMarker->setSize( width );
2635}
2636
2638{
2639 if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2640 {
2641 mMarker->setDataDefinedSize( property );
2642 }
2644}
2645
2647{
2648 const double prevOpacity = mMarker->opacity();
2649 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2651 mMarker->setOpacity( prevOpacity );
2652}
2653
2655{
2656 mMarker->setLineAngle( angle );
2657}
2658
2660{
2661 return mMarker->angle();
2662}
2663
2665{
2666 mMarker->setAngle( angle );
2667}
2668
2669void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2670{
2671 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2673
2674 mMarker->renderPoint( point, feature, context, layer, selected );
2675
2676 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2677}
2678
2680{
2681 return mMarker->size();
2682}
2683
2685{
2686 return mMarker->size( context );
2687}
2688
2690{
2692 mMarker->setOutputUnit( unit );
2693}
2694
2696{
2697 return intervalUnit() == Qgis::RenderUnit::MapUnits || intervalUnit() == Qgis::RenderUnit::MetersInMapUnits
2698 || offsetAlongLineUnit() == Qgis::RenderUnit::MapUnits || offsetAlongLineUnit() == Qgis::RenderUnit::MetersInMapUnits
2699 || averageAngleUnit() == Qgis::RenderUnit::MapUnits || averageAngleUnit() == Qgis::RenderUnit::MetersInMapUnits
2700 || mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2701 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits
2702 || ( mMarker && mMarker->usesMapUnits() );
2703}
2704
2706{
2707 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2708 if ( mMarker )
2709 attr.unite( mMarker->usedAttributes( context ) );
2710 return attr;
2711}
2712
2714{
2716 return true;
2717 if ( mMarker && mMarker->hasDataDefinedProperties() )
2718 return true;
2719 return false;
2720}
2721
2723{
2724 return ( mMarker->size( context ) / 2.0 ) +
2726}
2727
2728
2729//
2730// QgsHashedLineSymbolLayer
2731//
2732
2733QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2734 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2735{
2736 setSubSymbol( new QgsLineSymbol() );
2737}
2738
2740
2742{
2743 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2745
2746 if ( props.contains( QStringLiteral( "interval" ) ) )
2747 interval = props[QStringLiteral( "interval" )].toDouble();
2748 if ( props.contains( QStringLiteral( "rotate" ) ) )
2749 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2750
2751 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2752 setCommonProperties( x.get(), props );
2753 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2754 {
2755 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2756 }
2757
2758 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2759 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2760
2761 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2762 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2763
2764 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2765 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2766
2767 return x.release();
2768}
2769
2771{
2772 return QStringLiteral( "HashLine" );
2773}
2774
2776{
2777 // if being rotated, it gets initialized with every line segment
2778 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2779 if ( rotateSymbols() )
2781 mHashSymbol->setRenderHints( hints );
2782
2783 mHashSymbol->startRender( context.renderContext(), context.fields() );
2784}
2785
2787{
2788 mHashSymbol->stopRender( context.renderContext() );
2789}
2790
2792{
2794 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2795
2796 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2797 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2798 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2799
2800 return map;
2801}
2802
2804{
2805 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2807 x->setHashAngle( mHashAngle );
2808 x->setHashLength( mHashLength );
2809 x->setHashLengthUnit( mHashLengthUnit );
2810 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2811 return x.release();
2812}
2813
2814void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2815{
2816 mHashSymbol->setColor( color );
2817 mColor = color;
2818}
2819
2821{
2822 return mHashSymbol ? mHashSymbol->color() : mColor;
2823}
2824
2826{
2827 return mHashSymbol.get();
2828}
2829
2831{
2832 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2833 {
2834 delete symbol;
2835 return false;
2836 }
2837
2838 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2839 mColor = mHashSymbol->color();
2840 return true;
2841}
2842
2843void QgsHashedLineSymbolLayer::setWidth( const double width )
2844{
2845 mHashLength = width;
2846}
2847
2849{
2850 return mHashLength;
2851}
2852
2854{
2855 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2856}
2857
2859{
2860 return ( mHashSymbol->width( context ) / 2.0 )
2861 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2863}
2864
2866{
2868 mHashSymbol->setOutputUnit( unit );
2869}
2870
2872{
2873 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2874 if ( mHashSymbol )
2875 attr.unite( mHashSymbol->usedAttributes( context ) );
2876 return attr;
2877}
2878
2880{
2882 return true;
2883 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2884 return true;
2885 return false;
2886}
2887
2889{
2890 if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2891 {
2892 mHashSymbol->setDataDefinedWidth( property );
2893 }
2895}
2896
2898{
2899 return mHashLengthUnit == Qgis::RenderUnit::MapUnits || mHashLengthUnit == Qgis::RenderUnit::MetersInMapUnits
2900 || intervalUnit() == Qgis::RenderUnit::MapUnits || intervalUnit() == Qgis::RenderUnit::MetersInMapUnits
2901 || offsetAlongLineUnit() == Qgis::RenderUnit::MapUnits || offsetAlongLineUnit() == Qgis::RenderUnit::MetersInMapUnits
2902 || averageAngleUnit() == Qgis::RenderUnit::MapUnits || averageAngleUnit() == Qgis::RenderUnit::MetersInMapUnits
2903 || mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2904 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits
2905 || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2906}
2907
2909{
2910 mSymbolLineAngle = angle;
2911}
2912
2914{
2915 return mSymbolAngle;
2916}
2917
2919{
2920 mSymbolAngle = angle;
2921}
2922
2923void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2924{
2925 double lineLength = mHashLength;
2927 {
2928 context.expressionContext().setOriginalValueVariable( mHashLength );
2930 }
2931 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2932
2933 double hashAngle = mHashAngle;
2935 {
2936 context.expressionContext().setOriginalValueVariable( mHashAngle );
2938 }
2939
2940 QgsPointXY center( point );
2941 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2942 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2943
2944 QPolygonF points;
2945 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2946
2947 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2949
2950 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2951
2952 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2953}
2954
2956{
2957 return mHashAngle;
2958}
2959
2961{
2962 mHashAngle = angle;
2963}
2964
2966{
2967 const double prevOpacity = mHashSymbol->opacity();
2968 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2970 mHashSymbol->setOpacity( prevOpacity );
2971}
2972
2973//
2974// QgsAbstractBrushedLineSymbolLayer
2975//
2976
2977void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2978{
2979 if ( !context.renderContext().painter() )
2980 return;
2981
2982 double offset = mOffset;
2984 {
2987 }
2988
2989 QPolygonF offsetPoints;
2990 if ( qgsDoubleNear( offset, 0 ) )
2991 {
2992 renderLine( points, context, patternThickness, patternLength, brush );
2993 }
2994 else
2995 {
2996 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
2997
2998 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
2999 for ( const QPolygonF &part : offsetLine )
3000 {
3001 renderLine( part, context, patternThickness, patternLength, brush );
3002 }
3003 }
3004}
3005
3006void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3007 const double patternLength, const QBrush &sourceBrush )
3008{
3009 QPainter *p = context.renderContext().painter();
3010 if ( !p )
3011 return;
3012
3013 QBrush brush = sourceBrush;
3014
3015 // duplicate points mess up the calculations, we need to remove them first
3016 // we'll calculate the min/max coordinate at the same time, since we're already looping
3017 // through the points
3018 QPolygonF inputPoints;
3019 inputPoints.reserve( points.size() );
3020 QPointF prev;
3021 double minX = std::numeric_limits< double >::max();
3022 double minY = std::numeric_limits< double >::max();
3023 double maxX = std::numeric_limits< double >::lowest();
3024 double maxY = std::numeric_limits< double >::lowest();
3025
3026 for ( const QPointF &pt : std::as_const( points ) )
3027 {
3028 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3029 continue;
3030
3031 inputPoints << pt;
3032 prev = pt;
3033 minX = std::min( minX, pt.x() );
3034 minY = std::min( minY, pt.y() );
3035 maxX = std::max( maxX, pt.x() );
3036 maxY = std::max( maxY, pt.y() );
3037 }
3038
3039 if ( inputPoints.size() < 2 ) // nothing to render
3040 return;
3041
3042 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3043 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3044 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3045 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3046
3047 // 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
3048 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3049 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3050
3051 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3052 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3053
3054 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3055 if ( temporaryImage.isNull() )
3056 {
3057 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3058 return;
3059 }
3060
3061 // clear temporary image contents
3062 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3063 return;
3064 temporaryImage.fill( Qt::transparent );
3065 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3066 return;
3067
3068 Qt::PenJoinStyle join = mPenJoinStyle;
3070 {
3073 if ( !QgsVariantUtils::isNull( exprVal ) )
3074 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3075 }
3076
3077 Qt::PenCapStyle cap = mPenCapStyle;
3079 {
3082 if ( !QgsVariantUtils::isNull( exprVal ) )
3083 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3084 }
3085
3086 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3087 QPainterPathStroker stroker;
3088 stroker.setWidth( lineThickness );
3089 stroker.setCapStyle( cap );
3090 stroker.setJoinStyle( join );
3091
3092 QPainterPath path;
3093 path.addPolygon( inputPoints );
3094 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3095
3096 // prepare temporary image
3097 QPainter imagePainter;
3098 imagePainter.begin( &temporaryImage );
3099 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3100 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3101
3102 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3103 imagePainter.setPen( Qt::NoPen );
3104
3105 QPointF segmentStartPoint = inputPoints.at( 0 );
3106
3107 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3108 double progressThroughImage = 0;
3109
3110 QgsPoint prevSegmentPolygonEndLeft;
3111 QgsPoint prevSegmentPolygonEndRight;
3112
3113 // for closed rings this will store the left/right polygon points of the start/end of the line
3114 QgsPoint startLinePolygonLeft;
3115 QgsPoint startLinePolygonRight;
3116
3117 for ( int i = 1; i < inputPoints.size(); ++i )
3118 {
3119 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3120 break;
3121
3122 const QPointF segmentEndPoint = inputPoints.at( i );
3123 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3124 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3125
3126 // left/right end points of the current segment polygon
3127 QgsPoint thisSegmentPolygonEndLeft;
3128 QgsPoint thisSegmentPolygonEndRight;
3129 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3130 QgsPoint thisSegmentPolygonEndLeftForPainter;
3131 QgsPoint thisSegmentPolygonEndRightForPainter;
3132 if ( i == 1 )
3133 {
3134 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3135 // (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.)
3136 if ( isClosedLine )
3137 {
3138 // project the current segment out by half the image thickness to either side of the line
3139 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3140 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3141 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3142 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3143
3144 // 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
3145 // what angle the current segment polygon should START on.
3146 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3147 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3148
3149 // project out the LAST segment in the line by half the image thickness to either side of the line
3150 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3151 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3152 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3153 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3154
3155 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3156 // 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
3157 // join)
3158 QgsPoint intersectionPoint;
3159 bool isIntersection = false;
3160 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3161 if ( !isIntersection )
3162 prevSegmentPolygonEndLeft = startPointLeft;
3163 isIntersection = false;
3164 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3165 if ( !isIntersection )
3166 prevSegmentPolygonEndRight = startPointRight;
3167
3168 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3169 startLinePolygonRight = prevSegmentPolygonEndRight;
3170 }
3171 else
3172 {
3173 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3174 if ( cap != Qt::PenCapStyle::FlatCap )
3175 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3176 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3177 if ( cap != Qt::PenCapStyle::FlatCap )
3178 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3179 }
3180 }
3181
3182 if ( i < inputPoints.size() - 1 )
3183 {
3184 // for all other segments except the last
3185
3186 // project the current segment out by half the image thickness to either side of the line
3187 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3188 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3189 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3190 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3191
3192 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3193 // what angle the current segment polygon should end on
3194 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3195 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3196
3197 // project out the next segment by half the image thickness to either side of the line
3198 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3199 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3200 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3201 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3202
3203 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3204 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3205 // join)
3206 QgsPoint intersectionPoint;
3207 bool isIntersection = false;
3208 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3209 if ( !isIntersection )
3210 thisSegmentPolygonEndLeft = endPointLeft;
3211 isIntersection = false;
3212 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3213 if ( !isIntersection )
3214 thisSegmentPolygonEndRight = endPointRight;
3215
3216 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3217 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3218 }
3219 else
3220 {
3221 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3222 // unless it's a closed line
3223 if ( isClosedLine )
3224 {
3225 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3226 thisSegmentPolygonEndRight = startLinePolygonRight;
3227
3228 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3229 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3230 }
3231 else
3232 {
3233 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3234 if ( cap != Qt::PenCapStyle::FlatCap )
3235 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3236 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3237 if ( cap != Qt::PenCapStyle::FlatCap )
3238 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3239
3240 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3241 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3242 }
3243 }
3244
3245 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3246 // where we got with the previous segment), at the correct angle
3247 QTransform brushTransform;
3248 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3249 brushTransform.rotate( -segmentAngleDegrees );
3250 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3251 {
3252 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3253 // we need to also do the same for the brush transform
3254 brushTransform.translate( -( lineThickness / 2 ), 0 );
3255 }
3256 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3257
3258 brush.setTransform( brushTransform );
3259 imagePainter.setBrush( brush );
3260
3261 // now draw the segment polygon
3262 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3263 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3264 << thisSegmentPolygonEndRightForPainter.toQPointF()
3265 << prevSegmentPolygonEndRight.toQPointF()
3266 << prevSegmentPolygonEndLeft.toQPointF() );
3267
3268#if 0 // for debugging, will draw the segment polygons
3269 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3270 imagePainter.setBrush( Qt::NoBrush );
3271 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3272 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3273 << thisSegmentPolygonEndRightForPainter.toQPointF()
3274 << prevSegmentPolygonEndRight.toQPointF()
3275 << prevSegmentPolygonEndLeft.toQPointF() );
3276 imagePainter.setPen( Qt::NoPen );
3277#endif
3278
3279 // calculate the new progress horizontal through the source image to account for the length
3280 // of the segment we've just drawn
3281 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3282 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3283 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3284 progressThroughImage = fmod( progressThroughImage, patternLength );
3285
3286 // shuffle buffered variables for next loop
3287 segmentStartPoint = segmentEndPoint;
3288 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3289 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3290 }
3291 imagePainter.end();
3292
3293 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3294 return;
3295
3296 // lastly, draw the temporary image onto the destination painter at the correct place
3297 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3298 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3299}
3300
3301
3302//
3303// QgsRasterLineSymbolLayer
3304//
3305
3307 : mPath( path )
3308{
3309}
3310
3312
3313QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3314{
3315 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3316
3317 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3318 {
3319 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3320 }
3321 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3322 {
3323 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3324 }
3325 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3326 {
3327 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3328 }
3329
3330 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3331 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3332
3333 if ( properties.contains( QStringLiteral( "offset" ) ) )
3334 {
3335 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3336 }
3337 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3338 {
3339 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3340 }
3341 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3342 {
3343 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3344 }
3345
3346 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3347 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3348 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3349 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3350
3351 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3352 {
3353 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3354 }
3355
3356 return res.release();
3357}
3358
3359
3361{
3362 QVariantMap map;
3363 map[QStringLiteral( "imageFile" )] = mPath;
3364
3365 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3366 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3367 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3368
3369 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3370 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3371
3372 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3373 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3374 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3375
3376 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3377
3378 return map;
3379}
3380
3382{
3383 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3384 res->setWidth( mWidth );
3385 res->setWidthUnit( mWidthUnit );
3386 res->setWidthMapUnitScale( mWidthMapUnitScale );
3387 res->setPenJoinStyle( mPenJoinStyle );
3388 res->setPenCapStyle( mPenCapStyle );
3389 res->setOffsetUnit( mOffsetUnit );
3390 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3391 res->setOffset( mOffset );
3392 res->setOpacity( mOpacity );
3393 copyDataDefinedProperties( res.get() );
3394 copyPaintEffect( res.get() );
3395 return res.release();
3396}
3397
3398void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3399{
3400 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3401 if ( it != properties.end() && it.value().type() == QVariant::String )
3402 {
3403 if ( saving )
3404 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3405 else
3406 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3407 }
3408}
3409
3410void QgsRasterLineSymbolLayer::setPath( const QString &path )
3411{
3412 mPath = path;
3413}
3414
3416{
3417 return QStringLiteral( "RasterLine" );
3418}
3419
3421{
3422 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3423
3425
3426 double opacity = mOpacity * context.opacity();
3427 bool cached = false;
3429 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3430 static_cast< int >( std::ceil( scaledHeight ) ) ),
3431 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3432}
3433
3435{
3436}
3437
3439{
3440 if ( !context.renderContext().painter() )
3441 return;
3442
3443 QImage sourceImage = mLineImage;
3447 {
3448 QString path = mPath;
3450 {
3451 context.setOriginalValueVariable( path );
3453 }
3454
3455 double strokeWidth = mWidth;
3457 {
3458 context.setOriginalValueVariable( strokeWidth );
3460 }
3461 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3462
3464 double opacity = mOpacity;
3466 {
3469 }
3470 opacity *= context.opacity();
3471
3472 bool cached = false;
3474 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3475 static_cast< int >( std::ceil( scaledHeight ) ) ),
3476 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3477 }
3478
3479 if ( context.selected() )
3480 {
3482 }
3483
3484 const QBrush brush( sourceImage );
3485
3486 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3487}
3488
3490{
3492 mWidthUnit = unit;
3493 mOffsetUnit = unit;
3494}
3495
3497{
3499 if ( mWidthUnit != unit || mOffsetUnit != unit )
3500 {
3501 return Qgis::RenderUnit::Unknown;
3502 }
3503 return unit;
3504}
3505
3507{
3508 return mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
3509 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
3510}
3511
3513{
3515 mOffsetMapUnitScale = scale;
3516}
3517
3519{
3522 {
3523 return mWidthMapUnitScale;
3524 }
3525 return QgsMapUnitScale();
3526}
3527
3529{
3530 return ( mWidth / 2.0 ) + mOffset;
3531}
3532
3534{
3535 return QColor();
3536}
3537
3538
3539//
3540// QgsLineburstSymbolLayer
3541//
3542
3543QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3545 , mColor2( color2 )
3546{
3547 setColor( color );
3548}
3549
3551
3552QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3553{
3554 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3555
3556 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3557 {
3558 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3559 }
3560 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3561 {
3562 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3563 }
3564 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3565 {
3566 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3567 }
3568
3569 if ( properties.contains( QStringLiteral( "offset" ) ) )
3570 {
3571 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3572 }
3573 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3574 {
3575 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3576 }
3577 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3578 {
3579 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3580 }
3581
3582 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3583 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3584 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3585 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3586
3587 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3588 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3589
3590 if ( properties.contains( QStringLiteral( "color" ) ) )
3591 {
3592 res->setColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
3593 }
3594 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3595 {
3596 res->setColor2( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3597 }
3598
3599 //attempt to create color ramp from props
3600 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3601 {
3602 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3603 }
3604 else
3605 {
3606 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3607 }
3608
3609 return res.release();
3610}
3611
3613{
3614 QVariantMap map;
3615
3616 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3617 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3618 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3619
3620 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3621 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3622
3623 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3624 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3625 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3626
3627 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3628 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
3629 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3630 if ( mGradientRamp )
3631 {
3632#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3633 map.unite( mGradientRamp->properties() );
3634#else
3635 map.insert( mGradientRamp->properties() );
3636#endif
3637 }
3638
3639 return map;
3640}
3641
3643{
3644 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3645 res->setWidth( mWidth );
3646 res->setWidthUnit( mWidthUnit );
3647 res->setWidthMapUnitScale( mWidthMapUnitScale );
3648 res->setPenJoinStyle( mPenJoinStyle );
3649 res->setPenCapStyle( mPenCapStyle );
3650 res->setOffsetUnit( mOffsetUnit );
3651 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3652 res->setOffset( mOffset );
3653 res->setColor( mColor );
3654 res->setColor2( mColor2 );
3655 res->setGradientColorType( mGradientColorType );
3656 if ( mGradientRamp )
3657 res->setColorRamp( mGradientRamp->clone() );
3658 copyDataDefinedProperties( res.get() );
3659 copyPaintEffect( res.get() );
3660 return res.release();
3661}
3662
3664{
3665 return QStringLiteral( "Lineburst" );
3666}
3667
3669{
3670}
3671
3673{
3674}
3675
3677{
3678 if ( !context.renderContext().painter() )
3679 return;
3680
3681 double strokeWidth = mWidth;
3683 {
3684 context.setOriginalValueVariable( strokeWidth );
3686 }
3687 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3688
3689 //update alpha of gradient colors
3690 QColor color1 = mColor;
3692 {
3695 }
3696 if ( context.selected() )
3697 {
3698 color1 = context.renderContext().selectionColor();
3699 }
3700 color1.setAlphaF( context.opacity() * color1.alphaF() );
3701
3702 //second gradient color
3703 QColor color2 = mColor2;
3705 {
3708 }
3709
3710 //create a QGradient with the desired properties
3711 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3712 //add stops to gradient
3715 {
3716 //color ramp gradient
3717 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3718 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3719 }
3720 else
3721 {
3722 //two color gradient
3723 gradient.setColorAt( 0.0, color1 );
3724 gradient.setColorAt( 1.0, color2 );
3725 }
3726 const QBrush brush( gradient );
3727
3728 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3729}
3730
3732{
3734 mWidthUnit = unit;
3735 mOffsetUnit = unit;
3736}
3737
3739{
3741 if ( mWidthUnit != unit || mOffsetUnit != unit )
3742 {
3743 return Qgis::RenderUnit::Unknown;
3744 }
3745 return unit;
3746}
3747
3749{
3750 return mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
3751 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
3752}
3753
3755{
3757 mOffsetMapUnitScale = scale;
3758}
3759
3761{
3764 {
3765 return mWidthMapUnitScale;
3766 }
3767 return QgsMapUnitScale();
3768}
3769
3771{
3772 return ( mWidth / 2.0 ) + mOffset;
3773}
3774
3776{
3777 return mGradientRamp.get();
3778}
3779
3781{
3782 mGradientRamp.reset( ramp );
3783}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition: qgis.h:2116
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex) (since QGIS 3....
@ LastVertex
Place symbols on the last vertex in the line.
@ CentralPoint
Place symbols at the mid point of the line.
@ SegmentCenter
Place symbols at the center of every line segment.
@ Vertex
Place symbols on every vertex in the line.
@ Interval
Place symbols at regular intervals.
@ FirstVertex
Place symbols on the first vertex in the line.
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
GradientColorSource
Gradient color sources.
Definition: qgis.h:2138
@ ColorRamp
Gradient color ramp.
RenderUnit
Rendering size units.
Definition: qgis.h:3441
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ Marker
Marker symbol.
@ Line
Line symbol.
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.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
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.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
Class for doing transforms between two map coordinate systems.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
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 * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:65
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.
Definition: qgsdxfexport.h:222
void clipValueToMapUnitScale(double &value, const QgsMapUnitScale &scale, double pixelToMMFactor) const
Clips value to scale minimum/maximum.
RAII class to pop scope from an expression context on destruction.
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:56
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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) SIP_HOLDGIL
Compute the intersection between two segments.
static double lineAngle(double x1, double y1, double x2, double y2) SIP_HOLDGIL
Calculates the direction of line joining two points in radians, clockwise from the north direction.
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.
Line symbol layer type which draws repeating line sections along a line feature.
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.
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
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.
Definition: qgslinesymbol.h:30
Line symbol layer type which draws a gradient pattern perpendicularly along a line.
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...
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.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
Line symbol layer type which draws repeating marker symbols along a line feature.
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.
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)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
A class to represent a 2D point.
Definition: qgspointxy.h:59
QgsPointXY project(double distance, double bearing) const SIP_HOLDGIL
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
Definition: qgspointxy.cpp:87
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
QPointF toQPointF() const SIP_HOLDGIL
Returns the point as a QPointF.
Definition: qgspoint.h:331
QgsPoint project(double distance, double azimuth, double inclination=90.0) const SIP_HOLDGIL
Returns a new point which corresponds to this point projected by a specified distance with specified ...
Definition: qgspoint.cpp:735
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
A store for object properties.
Definition: qgsproperty.h:230
Line symbol layer type which draws line sections using a raster image file.
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.
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.
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.
virtual ~QgsRasterLineSymbolLayer()
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.
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 QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
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.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
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...
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.
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.
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static QColor decodeColor(const QString &str)
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 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 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 void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
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 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 void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static Qt::PenStyle decodePenStyle(const QString &str)
static QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
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 QString encodeRealVector(const QVector< qreal > &v)
Property
Data definable properties.
@ PropertyStrokeStyle
Stroke style (eg solid, dashed)
@ PropertyPlacement
Line marker placement.
@ PropertyFile
Filename, eg for svg files.
@ PropertyCapStyle
Line cap style.
@ PropertyLineDistance
Distance between lines, or length of lines for hash line symbols.
@ PropertyOffsetAlongLine
Offset along line.
@ PropertyCustomDash
Custom dash pattern.
@ PropertyJoinStyle
Line join style.
@ PropertyTrimEnd
Trim distance from end of line (since QGIS 3.20)
@ PropertyOpacity
Opacity.
@ PropertySecondaryColor
Secondary color (eg for gradient fills)
@ PropertyLineAngle
Line angle, or angle of hash lines for hash line symbols.
@ PropertyTrimStart
Trim distance from start of line (since QGIS 3.20)
@ PropertyOffset
Symbol offset.
@ PropertyStrokeWidth
Stroke width.
@ PropertyDashPatternOffset
Dash pattern offset,.
@ PropertyAverageAngleLength
Length to average symbol angles over.
@ PropertyInterval
Line marker interval.
@ PropertyStrokeColor
Stroke color.
@ PropertyWidth
Symbol width.
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.
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
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.
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.
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.
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.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
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:94
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:497
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition: qgssymbol.h:504
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:153
Base class for templated line symbols, e.g.
bool rotateSymbols() const
Returns true if the repeating symbols be rotated to match their line segment orientation.
Qgis::RenderUnit outputUnit() const FINAL
Returns the units to use for sizes and widths within the symbol layer.
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...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QgsMapUnitScale mapUnitScale() const FINAL
void setMapUnitScale(const QgsMapUnitScale &scale) FINAL
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.
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.
Qgis::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
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.
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.
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)
Returns true if the specified variant should be considered a NULL value.
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
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)
Definition: MathUtils.cpp:786
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:3927
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition: qgis.h:4250
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:4272
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3988
QMap< QString, QString > QgsStringMap
Definition: qgis.h:4533
#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:39
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.
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:31
Qgis::VertexType type
Vertex type.
Definition: qgsvertexid.h:98