QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 int segment = 0;
2357 for ( ++it; it != points.constEnd(); ++it )
2358 {
2359 next = *it;
2360 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2361 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2362 if ( next_at >= midPoint )
2363 break; // we have reached the center
2364 last = *it;
2365 last_at = next_at;
2366 segment++;
2367 }
2368
2369 // find out the central point on segment
2370 MyLine l( last, next ); // for line angle
2371 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2372 pt = last + ( next - last ) * k;
2373 thisSymbolAngle = l.angle();
2374 }
2375
2376 // draw the marker
2377 // rotate marker (if desired)
2378 if ( rotateSymbols() )
2379 {
2380 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2381 }
2382
2383 renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2384
2385 }
2386}
2387
2389{
2390 return mMarker.get();
2391}
2392
2394{
2395 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2396 {
2397 delete symbol;
2398 return false;
2399 }
2400
2401 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2402 mColor = mMarker->color();
2403 return true;
2404}
2405
2406
2407
2408//
2409// QgsMarkerLineSymbolLayer
2410//
2411
2412QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2413 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2414{
2416}
2417
2419
2421{
2422 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2424
2425 if ( props.contains( QStringLiteral( "interval" ) ) )
2426 interval = props[QStringLiteral( "interval" )].toDouble();
2427 if ( props.contains( QStringLiteral( "rotate" ) ) )
2428 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2429
2430 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2431 setCommonProperties( x.get(), props );
2432 return x.release();
2433}
2434
2436{
2437 return QStringLiteral( "MarkerLine" );
2438}
2439
2440void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2441{
2442 mMarker->setColor( color );
2443 mColor = color;
2444}
2445
2447{
2448 return mMarker ? mMarker->color() : mColor;
2449}
2450
2452{
2453 // if being rotated, it gets initialized with every line segment
2454 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2455 if ( rotateSymbols() )
2457 mMarker->setRenderHints( hints );
2458
2459 mMarker->startRender( context.renderContext(), context.fields() );
2460}
2461
2463{
2464 mMarker->stopRender( context.renderContext() );
2465}
2466
2467
2469{
2470 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2472 return x.release();
2473}
2474
2475void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2476{
2477 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2478 {
2479 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2480 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2481 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2482 element.appendChild( symbolizerElem );
2483
2484 // <Geometry>
2485 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2486
2487 QString gap;
2489 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2491 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2493 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2495 // no way to get line/polygon's vertices, use a VendorOption
2496 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2497
2499 {
2501 gap = qgsDoubleToString( interval );
2502 }
2503
2504 if ( !rotateSymbols() )
2505 {
2506 // markers in LineSymbolizer must be drawn following the line orientation,
2507 // use a VendorOption when no marker rotation
2508 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2509 }
2510
2511 // <Stroke>
2512 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2513 symbolizerElem.appendChild( strokeElem );
2514
2515 // <GraphicStroke>
2516 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2517 strokeElem.appendChild( graphicStrokeElem );
2518
2519 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2520 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2521 {
2522 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2523 }
2524 else if ( layer )
2525 {
2526 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2527 }
2528 else
2529 {
2530 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2531 }
2532
2533 if ( !gap.isEmpty() )
2534 {
2535 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2537 graphicStrokeElem.appendChild( gapElem );
2538 }
2539
2540 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2541 {
2542 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2544 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2545 symbolizerElem.appendChild( perpOffsetElem );
2546 }
2547 }
2548}
2549
2551{
2552 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2553
2554 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2555 if ( strokeElem.isNull() )
2556 return nullptr;
2557
2558 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2559 if ( graphicStrokeElem.isNull() )
2560 return nullptr;
2561
2562 // retrieve vendor options
2563 bool rotateMarker = true;
2565
2566 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2567 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2568 {
2569 if ( it.key() == QLatin1String( "placement" ) )
2570 {
2571 if ( it.value() == QLatin1String( "points" ) )
2573 else if ( it.value() == QLatin1String( "firstPoint" ) )
2575 else if ( it.value() == QLatin1String( "lastPoint" ) )
2577 else if ( it.value() == QLatin1String( "centralPoint" ) )
2579 }
2580 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2581 {
2582 rotateMarker = it.value() == QLatin1String( "0" );
2583 }
2584 }
2585
2586 std::unique_ptr< QgsMarkerSymbol > marker;
2587
2589 if ( l )
2590 {
2591 QgsSymbolLayerList layers;
2592 layers.append( l );
2593 marker.reset( new QgsMarkerSymbol( layers ) );
2594 }
2595
2596 if ( !marker )
2597 return nullptr;
2598
2599 double interval = 0.0;
2600 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2601 if ( !gapElem.isNull() )
2602 {
2603 bool ok;
2604 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2605 if ( ok )
2606 interval = d;
2607 }
2608
2609 double offset = 0.0;
2610 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2611 if ( !perpOffsetElem.isNull() )
2612 {
2613 bool ok;
2614 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2615 if ( ok )
2616 offset = d;
2617 }
2618
2619 double scaleFactor = 1.0;
2620 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2621 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2622 interval = interval * scaleFactor;
2623 offset = offset * scaleFactor;
2624
2626 x->setOutputUnit( sldUnitSize );
2628 x->setInterval( interval );
2629 x->setSubSymbol( marker.release() );
2630 x->setOffset( offset );
2631 return x;
2632}
2633
2635{
2636 mMarker->setSize( width );
2637}
2638
2640{
2641 if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2642 {
2643 mMarker->setDataDefinedSize( property );
2644 }
2646}
2647
2649{
2650 const double prevOpacity = mMarker->opacity();
2651 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2653 mMarker->setOpacity( prevOpacity );
2654}
2655
2657{
2658 mMarker->setLineAngle( angle );
2659}
2660
2662{
2663 return mMarker->angle();
2664}
2665
2667{
2668 mMarker->setAngle( angle );
2669}
2670
2671void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2672{
2673 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2675
2676 mMarker->renderPoint( point, feature, context, layer, selected );
2677
2678 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2679}
2680
2682{
2683 return mMarker->size();
2684}
2685
2687{
2688 return mMarker->size( context );
2689}
2690
2692{
2694 mMarker->setOutputUnit( unit );
2695}
2696
2698{
2699 return intervalUnit() == Qgis::RenderUnit::MapUnits || intervalUnit() == Qgis::RenderUnit::MetersInMapUnits
2700 || offsetAlongLineUnit() == Qgis::RenderUnit::MapUnits || offsetAlongLineUnit() == Qgis::RenderUnit::MetersInMapUnits
2701 || averageAngleUnit() == Qgis::RenderUnit::MapUnits || averageAngleUnit() == Qgis::RenderUnit::MetersInMapUnits
2702 || mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2703 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits
2704 || ( mMarker && mMarker->usesMapUnits() );
2705}
2706
2708{
2709 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2710 if ( mMarker )
2711 attr.unite( mMarker->usedAttributes( context ) );
2712 return attr;
2713}
2714
2716{
2718 return true;
2719 if ( mMarker && mMarker->hasDataDefinedProperties() )
2720 return true;
2721 return false;
2722}
2723
2725{
2726 return ( mMarker->size( context ) / 2.0 ) +
2728}
2729
2730
2731//
2732// QgsHashedLineSymbolLayer
2733//
2734
2735QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2736 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2737{
2738 setSubSymbol( new QgsLineSymbol() );
2739}
2740
2742
2744{
2745 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2747
2748 if ( props.contains( QStringLiteral( "interval" ) ) )
2749 interval = props[QStringLiteral( "interval" )].toDouble();
2750 if ( props.contains( QStringLiteral( "rotate" ) ) )
2751 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2752
2753 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2754 setCommonProperties( x.get(), props );
2755 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2756 {
2757 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2758 }
2759
2760 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2761 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2762
2763 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2764 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2765
2766 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2767 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2768
2769 return x.release();
2770}
2771
2773{
2774 return QStringLiteral( "HashLine" );
2775}
2776
2778{
2779 // if being rotated, it gets initialized with every line segment
2780 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2781 if ( rotateSymbols() )
2783 mHashSymbol->setRenderHints( hints );
2784
2785 mHashSymbol->startRender( context.renderContext(), context.fields() );
2786}
2787
2789{
2790 mHashSymbol->stopRender( context.renderContext() );
2791}
2792
2794{
2796 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2797
2798 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2799 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2800 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2801
2802 return map;
2803}
2804
2806{
2807 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2809 x->setHashAngle( mHashAngle );
2810 x->setHashLength( mHashLength );
2811 x->setHashLengthUnit( mHashLengthUnit );
2812 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2813 return x.release();
2814}
2815
2816void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2817{
2818 mHashSymbol->setColor( color );
2819 mColor = color;
2820}
2821
2823{
2824 return mHashSymbol ? mHashSymbol->color() : mColor;
2825}
2826
2828{
2829 return mHashSymbol.get();
2830}
2831
2833{
2834 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2835 {
2836 delete symbol;
2837 return false;
2838 }
2839
2840 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2841 mColor = mHashSymbol->color();
2842 return true;
2843}
2844
2845void QgsHashedLineSymbolLayer::setWidth( const double width )
2846{
2847 mHashLength = width;
2848}
2849
2851{
2852 return mHashLength;
2853}
2854
2856{
2857 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2858}
2859
2861{
2862 return ( mHashSymbol->width( context ) / 2.0 )
2863 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2865}
2866
2868{
2870 mHashSymbol->setOutputUnit( unit );
2871}
2872
2874{
2875 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2876 if ( mHashSymbol )
2877 attr.unite( mHashSymbol->usedAttributes( context ) );
2878 return attr;
2879}
2880
2882{
2884 return true;
2885 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2886 return true;
2887 return false;
2888}
2889
2891{
2892 if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2893 {
2894 mHashSymbol->setDataDefinedWidth( property );
2895 }
2897}
2898
2900{
2901 return mHashLengthUnit == Qgis::RenderUnit::MapUnits || mHashLengthUnit == Qgis::RenderUnit::MetersInMapUnits
2902 || intervalUnit() == Qgis::RenderUnit::MapUnits || intervalUnit() == Qgis::RenderUnit::MetersInMapUnits
2903 || offsetAlongLineUnit() == Qgis::RenderUnit::MapUnits || offsetAlongLineUnit() == Qgis::RenderUnit::MetersInMapUnits
2904 || averageAngleUnit() == Qgis::RenderUnit::MapUnits || averageAngleUnit() == Qgis::RenderUnit::MetersInMapUnits
2905 || mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2906 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits
2907 || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2908}
2909
2911{
2912 mSymbolLineAngle = angle;
2913}
2914
2916{
2917 return mSymbolAngle;
2918}
2919
2921{
2922 mSymbolAngle = angle;
2923}
2924
2925void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2926{
2927 double lineLength = mHashLength;
2929 {
2930 context.expressionContext().setOriginalValueVariable( mHashLength );
2932 }
2933 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2934
2935 double hashAngle = mHashAngle;
2937 {
2938 context.expressionContext().setOriginalValueVariable( mHashAngle );
2940 }
2941
2942 QgsPointXY center( point );
2943 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2944 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2945
2946 QPolygonF points;
2947 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2948
2949 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2951
2952 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2953
2954 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2955}
2956
2958{
2959 return mHashAngle;
2960}
2961
2963{
2964 mHashAngle = angle;
2965}
2966
2968{
2969 const double prevOpacity = mHashSymbol->opacity();
2970 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2972 mHashSymbol->setOpacity( prevOpacity );
2973}
2974
2975//
2976// QgsAbstractBrushedLineSymbolLayer
2977//
2978
2979void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2980{
2981 if ( !context.renderContext().painter() )
2982 return;
2983
2984 double offset = mOffset;
2986 {
2989 }
2990
2991 QPolygonF offsetPoints;
2992 if ( qgsDoubleNear( offset, 0 ) )
2993 {
2994 renderLine( points, context, patternThickness, patternLength, brush );
2995 }
2996 else
2997 {
2998 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
2999
3000 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3001 for ( const QPolygonF &part : offsetLine )
3002 {
3003 renderLine( part, context, patternThickness, patternLength, brush );
3004 }
3005 }
3006}
3007
3008void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3009 const double patternLength, const QBrush &sourceBrush )
3010{
3011 QPainter *p = context.renderContext().painter();
3012 if ( !p )
3013 return;
3014
3015 QBrush brush = sourceBrush;
3016
3017 // duplicate points mess up the calculations, we need to remove them first
3018 // we'll calculate the min/max coordinate at the same time, since we're already looping
3019 // through the points
3020 QPolygonF inputPoints;
3021 inputPoints.reserve( points.size() );
3022 QPointF prev;
3023 double minX = std::numeric_limits< double >::max();
3024 double minY = std::numeric_limits< double >::max();
3025 double maxX = std::numeric_limits< double >::lowest();
3026 double maxY = std::numeric_limits< double >::lowest();
3027
3028 for ( const QPointF &pt : std::as_const( points ) )
3029 {
3030 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3031 continue;
3032
3033 inputPoints << pt;
3034 prev = pt;
3035 minX = std::min( minX, pt.x() );
3036 minY = std::min( minY, pt.y() );
3037 maxX = std::max( maxX, pt.x() );
3038 maxY = std::max( maxY, pt.y() );
3039 }
3040
3041 if ( inputPoints.size() < 2 ) // nothing to render
3042 return;
3043
3044 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3045 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3046 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3047 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3048
3049 // 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
3050 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3051 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3052
3053 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3054 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3055
3056 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3057 if ( temporaryImage.isNull() )
3058 {
3059 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3060 return;
3061 }
3062
3063 // clear temporary image contents
3064 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3065 return;
3066 temporaryImage.fill( Qt::transparent );
3067 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3068 return;
3069
3070 Qt::PenJoinStyle join = mPenJoinStyle;
3072 {
3075 if ( !QgsVariantUtils::isNull( exprVal ) )
3076 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3077 }
3078
3079 Qt::PenCapStyle cap = mPenCapStyle;
3081 {
3084 if ( !QgsVariantUtils::isNull( exprVal ) )
3085 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3086 }
3087
3088 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3089 QPainterPathStroker stroker;
3090 stroker.setWidth( lineThickness );
3091 stroker.setCapStyle( cap );
3092 stroker.setJoinStyle( join );
3093
3094 QPainterPath path;
3095 path.addPolygon( inputPoints );
3096 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3097
3098 // prepare temporary image
3099 QPainter imagePainter;
3100 imagePainter.begin( &temporaryImage );
3101 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3102 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3103
3104 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3105 imagePainter.setPen( Qt::NoPen );
3106
3107 QPointF segmentStartPoint = inputPoints.at( 0 );
3108
3109 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3110 double progressThroughImage = 0;
3111
3112 QgsPoint prevSegmentPolygonEndLeft;
3113 QgsPoint prevSegmentPolygonEndRight;
3114
3115 // for closed rings this will store the left/right polygon points of the start/end of the line
3116 QgsPoint startLinePolygonLeft;
3117 QgsPoint startLinePolygonRight;
3118
3119 for ( int i = 1; i < inputPoints.size(); ++i )
3120 {
3121 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3122 break;
3123
3124 const QPointF segmentEndPoint = inputPoints.at( i );
3125 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3126 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3127
3128 // left/right end points of the current segment polygon
3129 QgsPoint thisSegmentPolygonEndLeft;
3130 QgsPoint thisSegmentPolygonEndRight;
3131 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3132 QgsPoint thisSegmentPolygonEndLeftForPainter;
3133 QgsPoint thisSegmentPolygonEndRightForPainter;
3134 if ( i == 1 )
3135 {
3136 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3137 // (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.)
3138 if ( isClosedLine )
3139 {
3140 // project the current segment out by half the image thickness to either side of the line
3141 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3142 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3143 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3144 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3145
3146 // 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
3147 // what angle the current segment polygon should START on.
3148 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3149 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3150
3151 // project out the LAST segment in the line by half the image thickness to either side of the line
3152 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3153 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3154 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3155 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3156
3157 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3158 // 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
3159 // join)
3160 QgsPoint intersectionPoint;
3161 bool isIntersection = false;
3162 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3163 if ( !isIntersection )
3164 prevSegmentPolygonEndLeft = startPointLeft;
3165 isIntersection = false;
3166 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3167 if ( !isIntersection )
3168 prevSegmentPolygonEndRight = startPointRight;
3169
3170 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3171 startLinePolygonRight = prevSegmentPolygonEndRight;
3172 }
3173 else
3174 {
3175 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3176 if ( cap != Qt::PenCapStyle::FlatCap )
3177 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3178 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3179 if ( cap != Qt::PenCapStyle::FlatCap )
3180 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3181 }
3182 }
3183
3184 if ( i < inputPoints.size() - 1 )
3185 {
3186 // for all other segments except the last
3187
3188 // project the current segment out by half the image thickness to either side of the line
3189 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3190 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3191 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3192 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3193
3194 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3195 // what angle the current segment polygon should end on
3196 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3197 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3198
3199 // project out the next segment by half the image thickness to either side of the line
3200 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3201 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3202 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3203 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3204
3205 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3206 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3207 // join)
3208 QgsPoint intersectionPoint;
3209 bool isIntersection = false;
3210 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3211 if ( !isIntersection )
3212 thisSegmentPolygonEndLeft = endPointLeft;
3213 isIntersection = false;
3214 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3215 if ( !isIntersection )
3216 thisSegmentPolygonEndRight = endPointRight;
3217
3218 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3219 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3220 }
3221 else
3222 {
3223 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3224 // unless it's a closed line
3225 if ( isClosedLine )
3226 {
3227 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3228 thisSegmentPolygonEndRight = startLinePolygonRight;
3229
3230 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3231 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3232 }
3233 else
3234 {
3235 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3236 if ( cap != Qt::PenCapStyle::FlatCap )
3237 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3238 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3239 if ( cap != Qt::PenCapStyle::FlatCap )
3240 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3241
3242 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3243 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3244 }
3245 }
3246
3247 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3248 // where we got with the previous segment), at the correct angle
3249 QTransform brushTransform;
3250 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3251 brushTransform.rotate( -segmentAngleDegrees );
3252 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3253 {
3254 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3255 // we need to also do the same for the brush transform
3256 brushTransform.translate( -( lineThickness / 2 ), 0 );
3257 }
3258 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3259
3260 brush.setTransform( brushTransform );
3261 imagePainter.setBrush( brush );
3262
3263 // now draw the segment polygon
3264 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3265 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3266 << thisSegmentPolygonEndRightForPainter.toQPointF()
3267 << prevSegmentPolygonEndRight.toQPointF()
3268 << prevSegmentPolygonEndLeft.toQPointF() );
3269
3270#if 0 // for debugging, will draw the segment polygons
3271 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3272 imagePainter.setBrush( Qt::NoBrush );
3273 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3274 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3275 << thisSegmentPolygonEndRightForPainter.toQPointF()
3276 << prevSegmentPolygonEndRight.toQPointF()
3277 << prevSegmentPolygonEndLeft.toQPointF() );
3278 imagePainter.setPen( Qt::NoPen );
3279#endif
3280
3281 // calculate the new progress horizontal through the source image to account for the length
3282 // of the segment we've just drawn
3283 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3284 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3285 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3286 progressThroughImage = fmod( progressThroughImage, patternLength );
3287
3288 // shuffle buffered variables for next loop
3289 segmentStartPoint = segmentEndPoint;
3290 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3291 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3292 }
3293 imagePainter.end();
3294
3295 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3296 return;
3297
3298 // lastly, draw the temporary image onto the destination painter at the correct place
3299 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3300 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3301}
3302
3303
3304//
3305// QgsRasterLineSymbolLayer
3306//
3307
3309 : mPath( path )
3310{
3311}
3312
3314
3315QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3316{
3317 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3318
3319 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3320 {
3321 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3322 }
3323 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3324 {
3325 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3326 }
3327 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3328 {
3329 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3330 }
3331
3332 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3333 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3334
3335 if ( properties.contains( QStringLiteral( "offset" ) ) )
3336 {
3337 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3338 }
3339 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3340 {
3341 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3342 }
3343 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3344 {
3345 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3346 }
3347
3348 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3349 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3350 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3351 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3352
3353 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3354 {
3355 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3356 }
3357
3358 return res.release();
3359}
3360
3361
3363{
3364 QVariantMap map;
3365 map[QStringLiteral( "imageFile" )] = mPath;
3366
3367 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3368 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3369 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3370
3371 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3372 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3373
3374 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3375 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3376 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3377
3378 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3379
3380 return map;
3381}
3382
3384{
3385 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3386 res->setWidth( mWidth );
3387 res->setWidthUnit( mWidthUnit );
3388 res->setWidthMapUnitScale( mWidthMapUnitScale );
3389 res->setPenJoinStyle( mPenJoinStyle );
3390 res->setPenCapStyle( mPenCapStyle );
3391 res->setOffsetUnit( mOffsetUnit );
3392 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3393 res->setOffset( mOffset );
3394 res->setOpacity( mOpacity );
3395 copyDataDefinedProperties( res.get() );
3396 copyPaintEffect( res.get() );
3397 return res.release();
3398}
3399
3400void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3401{
3402 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3403 if ( it != properties.end() && it.value().type() == QVariant::String )
3404 {
3405 if ( saving )
3406 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3407 else
3408 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3409 }
3410}
3411
3412void QgsRasterLineSymbolLayer::setPath( const QString &path )
3413{
3414 mPath = path;
3415}
3416
3418{
3419 return QStringLiteral( "RasterLine" );
3420}
3421
3423{
3424 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3425
3427
3428 double opacity = mOpacity * context.opacity();
3429 bool cached = false;
3431 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3432 static_cast< int >( std::ceil( scaledHeight ) ) ),
3433 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3434}
3435
3437{
3438}
3439
3441{
3442 if ( !context.renderContext().painter() )
3443 return;
3444
3445 QImage sourceImage = mLineImage;
3449 {
3450 QString path = mPath;
3452 {
3453 context.setOriginalValueVariable( path );
3455 }
3456
3457 double strokeWidth = mWidth;
3459 {
3460 context.setOriginalValueVariable( strokeWidth );
3462 }
3463 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3464
3466 double opacity = mOpacity;
3468 {
3471 }
3472 opacity *= context.opacity();
3473
3474 bool cached = false;
3476 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3477 static_cast< int >( std::ceil( scaledHeight ) ) ),
3478 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3479 }
3480
3481 if ( context.selected() )
3482 {
3484 }
3485
3486 const QBrush brush( sourceImage );
3487
3488 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3489}
3490
3492{
3494 mWidthUnit = unit;
3495 mOffsetUnit = unit;
3496}
3497
3499{
3501 if ( mWidthUnit != unit || mOffsetUnit != unit )
3502 {
3503 return Qgis::RenderUnit::Unknown;
3504 }
3505 return unit;
3506}
3507
3509{
3510 return mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
3511 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
3512}
3513
3515{
3517 mOffsetMapUnitScale = scale;
3518}
3519
3521{
3524 {
3525 return mWidthMapUnitScale;
3526 }
3527 return QgsMapUnitScale();
3528}
3529
3531{
3532 return ( mWidth / 2.0 ) + mOffset;
3533}
3534
3536{
3537 return QColor();
3538}
3539
3540
3541//
3542// QgsLineburstSymbolLayer
3543//
3544
3545QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3547 , mColor2( color2 )
3548{
3549 setColor( color );
3550}
3551
3553
3554QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3555{
3556 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3557
3558 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3559 {
3560 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3561 }
3562 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3563 {
3564 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3565 }
3566 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3567 {
3568 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3569 }
3570
3571 if ( properties.contains( QStringLiteral( "offset" ) ) )
3572 {
3573 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3574 }
3575 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3576 {
3577 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3578 }
3579 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3580 {
3581 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3582 }
3583
3584 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3585 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3586 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3587 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3588
3589 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3590 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3591
3592 if ( properties.contains( QStringLiteral( "color" ) ) )
3593 {
3594 res->setColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
3595 }
3596 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3597 {
3598 res->setColor2( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3599 }
3600
3601 //attempt to create color ramp from props
3602 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3603 {
3604 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3605 }
3606 else
3607 {
3608 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3609 }
3610
3611 return res.release();
3612}
3613
3615{
3616 QVariantMap map;
3617
3618 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3619 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3620 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3621
3622 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3623 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3624
3625 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3626 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3627 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3628
3629 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3630 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
3631 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3632 if ( mGradientRamp )
3633 {
3634#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3635 map.unite( mGradientRamp->properties() );
3636#else
3637 map.insert( mGradientRamp->properties() );
3638#endif
3639 }
3640
3641 return map;
3642}
3643
3645{
3646 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3647 res->setWidth( mWidth );
3648 res->setWidthUnit( mWidthUnit );
3649 res->setWidthMapUnitScale( mWidthMapUnitScale );
3650 res->setPenJoinStyle( mPenJoinStyle );
3651 res->setPenCapStyle( mPenCapStyle );
3652 res->setOffsetUnit( mOffsetUnit );
3653 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3654 res->setOffset( mOffset );
3655 res->setColor( mColor );
3656 res->setColor2( mColor2 );
3657 res->setGradientColorType( mGradientColorType );
3658 if ( mGradientRamp )
3659 res->setColorRamp( mGradientRamp->clone() );
3660 copyDataDefinedProperties( res.get() );
3661 copyPaintEffect( res.get() );
3662 return res.release();
3663}
3664
3666{
3667 return QStringLiteral( "Lineburst" );
3668}
3669
3671{
3672}
3673
3675{
3676}
3677
3679{
3680 if ( !context.renderContext().painter() )
3681 return;
3682
3683 double strokeWidth = mWidth;
3685 {
3686 context.setOriginalValueVariable( strokeWidth );
3688 }
3689 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3690
3691 //update alpha of gradient colors
3692 QColor color1 = mColor;
3694 {
3697 }
3698 if ( context.selected() )
3699 {
3700 color1 = context.renderContext().selectionColor();
3701 }
3702 color1.setAlphaF( context.opacity() * color1.alphaF() );
3703
3704 //second gradient color
3705 QColor color2 = mColor2;
3707 {
3710 }
3711
3712 //create a QGradient with the desired properties
3713 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3714 //add stops to gradient
3717 {
3718 //color ramp gradient
3719 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3720 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3721 }
3722 else
3723 {
3724 //two color gradient
3725 gradient.setColorAt( 0.0, color1 );
3726 gradient.setColorAt( 1.0, color2 );
3727 }
3728 const QBrush brush( gradient );
3729
3730 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3731}
3732
3734{
3736 mWidthUnit = unit;
3737 mOffsetUnit = unit;
3738}
3739
3741{
3743 if ( mWidthUnit != unit || mOffsetUnit != unit )
3744 {
3745 return Qgis::RenderUnit::Unknown;
3746 }
3747 return unit;
3748}
3749
3751{
3752 return mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
3753 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
3754}
3755
3757{
3759 mOffsetMapUnitScale = scale;
3760}
3761
3763{
3766 {
3767 return mWidthMapUnitScale;
3768 }
3769 return QgsMapUnitScale();
3770}
3771
3773{
3774 return ( mWidth / 2.0 ) + mOffset;
3775}
3776
3778{
3779 return mGradientRamp.get();
3780}
3781
3783{
3784 mGradientRamp.reset( ramp );
3785}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition: qgis.h:1958
@ 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:1980
@ ColorRamp
Gradient color ramp.
RenderUnit
Rendering size units.
Definition: qgis.h:3176
@ 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:229
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:93
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:495
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition: qgssymbol.h:502
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:152
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:3448
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition: qgis.h:3771
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:3793
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509
QMap< QString, QString > QgsStringMap
Definition: qgis.h:4054
#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
QLineF segment(int index, QRectF rect, double radius)
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:29
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