QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
Loading...
Searching...
No Matches
qgslinesymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinesymbollayer.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslinesymbollayer.h"
17#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 {
69 }
70 return unit;
71}
72
78
80{
82 mWidthMapUnitScale = scale;
83 mOffsetMapUnitScale = scale;
84 mCustomDashPatternMapUnitScale = scale;
85}
86
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 bool useSelectedColor = shouldRenderUsingSelectionColor( context );
428 const QPen pen = useSelectedColor ? mSelPen : mPen;
429
430 if ( !pen.dashPattern().isEmpty() )
431 {
432 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
433 const QVector<double> pattern = pen.dashPattern();
434 bool foundNonNull = false;
435 for ( int i = 0; i < pattern.size(); ++i )
436 {
437 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
438 {
439 foundNonNull = true;
440 break;
441 }
442 }
443 if ( !foundNonNull )
444 return;
445 }
446
447 p->setBrush( Qt::NoBrush );
448
449 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
450 std::unique_ptr< QgsScopedQPainterState > painterState;
451 if ( points.size() <= 2 &&
454 ( p->renderHints() & QPainter::Antialiasing ) )
455 {
456 painterState = std::make_unique< QgsScopedQPainterState >( p );
457 p->setRenderHint( QPainter::Antialiasing, false );
458 }
459
460 const bool applyPatternTweaks = mAlignDashPattern
461 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
462 && pen.dashOffset() == 0;
463
464 if ( qgsDoubleNear( offset, 0 ) )
465 {
466 if ( applyPatternTweaks )
467 {
468 drawPathWithDashPatternTweaks( p, points, pen );
469 }
470 else
471 {
472 p->setPen( pen );
473 QPainterPath path;
474 path.addPolygon( points );
475 p->drawPath( path );
476 }
477 }
478 else
479 {
480 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
482 {
483 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
484 // and clamp it to a reasonable range. It's the best we can do in this situation!
485 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
486 }
487
488 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
489 for ( const QPolygonF &part : mline )
490 {
491 if ( applyPatternTweaks )
492 {
493 drawPathWithDashPatternTweaks( p, part, pen );
494 }
495 else
496 {
497 p->setPen( pen );
498 QPainterPath path;
499 path.addPolygon( part );
500 p->drawPath( path );
501 }
502 }
503 }
504}
505
507{
508 QVariantMap map;
509 map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
510 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
511 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
512 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
513 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
514 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
515 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
516 map[QStringLiteral( "offset" )] = QString::number( mOffset );
517 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
518 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
519 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
520 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
521 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
522 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
523 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
524 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
525 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
526 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
527 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
528 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
529 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
530 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
531 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
532 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
533 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
534 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
535 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
536 return map;
537}
538
540{
546 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
547 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
548 l->setOffset( mOffset );
549 l->setPenJoinStyle( mPenJoinStyle );
550 l->setPenCapStyle( mPenCapStyle );
551 l->setUseCustomDashPattern( mUseCustomDashPattern );
552 l->setCustomDashVector( mCustomDashVector );
553 l->setDrawInsidePolygon( mDrawInsidePolygon );
555 l->setDashPatternOffset( mDashPatternOffset );
556 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
557 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
558 l->setTrimDistanceStart( mTrimDistanceStart );
559 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
560 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
561 l->setTrimDistanceEnd( mTrimDistanceEnd );
562 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
563 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
564 l->setAlignDashPattern( mAlignDashPattern );
565 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
566
568 copyPaintEffect( l );
569 return l;
570}
571
572void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
573{
574 if ( mPenStyle == Qt::NoPen )
575 return;
576
577 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
578 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
579 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
580 element.appendChild( symbolizerElem );
581
582 // <Geometry>
583 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
584
585 // <Stroke>
586 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
587 symbolizerElem.appendChild( strokeElem );
588
589 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
591 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
593 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
594
595 // <se:PerpendicularOffset>
596 if ( !qgsDoubleNear( mOffset, 0.0 ) )
597 {
598 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
600 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
601 symbolizerElem.appendChild( perpOffsetElem );
602 }
603}
604
605QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
606{
607 if ( mUseCustomDashPattern )
608 {
609 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
610 mPen.color(), mPenJoinStyle,
611 mPenCapStyle, mOffset, &mCustomDashVector );
612 }
613 else
614 {
615 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
616 mPenCapStyle, mOffset );
617 }
618}
619
621{
622 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
623
624 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
625 if ( strokeElem.isNull() )
626 return nullptr;
627
628 Qt::PenStyle penStyle;
629 QColor color;
630 double width;
631 Qt::PenJoinStyle penJoinStyle;
632 Qt::PenCapStyle penCapStyle;
633 QVector<qreal> customDashVector;
634
636 color, width,
639 return nullptr;
640
641 double offset = 0.0;
642 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
643 if ( !perpOffsetElem.isNull() )
644 {
645 bool ok;
646 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
647 if ( ok )
648 offset = d;
649 }
650
651 double scaleFactor = 1.0;
652 const QString uom = element.attribute( QStringLiteral( "uom" ) );
653 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
654 width = width * scaleFactor;
655 offset = offset * scaleFactor;
656
658 l->setOutputUnit( sldUnitSize );
659 l->setOffset( offset );
662 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
664 return l;
665}
666
667void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
668{
669 if ( !dataDefinedProperties().hasActiveProperties() )
670 return; // shortcut
671
672 //data defined properties
673 bool hasStrokeWidthExpression = false;
675 {
677 double scaledWidth = context.renderContext().convertToPainterUnits(
680 pen.setWidthF( scaledWidth );
681 selPen.setWidthF( scaledWidth );
682 hasStrokeWidthExpression = true;
683 }
684
685 //color
687 {
689
691 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
692 pen.setColor( penColor );
693 }
694
695 //offset
697 {
700 }
701
702 //dash dot vector
703
704 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
705 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
706 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
707
709 {
710 QVector<qreal> dashVector;
712 if ( !QgsVariantUtils::isNull( exprVal ) )
713 {
714 QStringList dashList = exprVal.toString().split( ';' );
715 QStringList::const_iterator dashIt = dashList.constBegin();
716 for ( ; dashIt != dashList.constEnd(); ++dashIt )
717 {
718 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
719 }
720 pen.setDashPattern( dashVector );
721 }
722 }
723 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
724 {
725 //re-scale pattern vector after data defined pen width was applied
726
727 QVector<qreal> scaledVector;
728 for ( double v : std::as_const( mCustomDashVector ) )
729 {
730 //the dash is specified in terms of pen widths, therefore the division
731 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
732 }
733 mPen.setDashPattern( scaledVector );
734 }
735
736 // dash pattern offset
737 double patternOffset = mDashPatternOffset;
738 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
739 {
740 context.setOriginalValueVariable( patternOffset );
742 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
743 }
744
745 //line style
747 {
750 if ( !QgsVariantUtils::isNull( exprVal ) )
751 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
752 }
753
754 //join style
756 {
759 if ( !QgsVariantUtils::isNull( exprVal ) )
760 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
761 }
762
763 //cap style
765 {
768 if ( !QgsVariantUtils::isNull( exprVal ) )
769 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
770 }
771}
772
773void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
774{
775 if ( pen.dashPattern().empty() || points.size() < 2 )
776 return;
777
778 QVector< qreal > sourcePattern = pen.dashPattern();
779 const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
780 // back to painter units
781 for ( int i = 0; i < sourcePattern.size(); ++ i )
782 sourcePattern[i] *= pen.widthF();
783
784 if ( pen.widthF() <= 1.0 )
785 pen.setWidthF( 1.0001 );
786
787 QVector< qreal > buffer;
788 QPolygonF bufferedPoints;
789 QPolygonF previousSegmentBuffer;
790 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
791 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
792 // and then append the buffer to the output pattern.
793
794 auto ptIt = points.constBegin();
795 double totalBufferLength = 0;
796 int patternIndex = 0;
797 double currentRemainingDashLength = 0;
798 double currentRemainingGapLength = 0;
799
800 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
801 {
802 QVector< qreal > result;
803 result.reserve( buffer.size() );
804 for ( auto it = buffer.begin(); it != buffer.end(); )
805 {
806 qreal dash = *it++;
807 qreal gap = *it++;
808 while ( dash == 0 && !result.empty() )
809 {
810 result.last() += gap;
811
812 if ( it == buffer.end() )
813 return result;
814 dash = *it++;
815 gap = *it++;
816 }
817 while ( gap == 0 && it != buffer.end() )
818 {
819 dash += *it++;
820 gap = *it++;
821 }
822 result << dash << gap;
823 }
824 return result;
825 };
826
827 double currentBufferLineLength = 0;
828 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
829 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
830 {
831 if ( buffer.empty() || bufferedPoints.size() < 2 )
832 {
833 return;
834 }
835
836 if ( currentRemainingDashLength )
837 {
838 // ended midway through a dash -- we want to finish this off
839 buffer << currentRemainingDashLength << 0.0;
840 totalBufferLength += currentRemainingDashLength;
841 }
842 QVector< qreal > compressed = compressPattern( buffer );
843 if ( !currentRemainingDashLength )
844 {
845 // ended midway through a gap -- we don't want this, we want to end at previous dash
846 totalBufferLength -= compressed.last();
847 compressed.last() = 0;
848 }
849
850 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
851 const double scaleFactor = currentBufferLineLength / totalBufferLength;
852
853 bool shouldFlushPreviousSegmentBuffer = false;
854
855 if ( !previousSegmentBuffer.empty() )
856 {
857 // add first dash from current buffer
858 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
859 if ( !firstDashSubstring.empty() )
860 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
861
862 // then we skip over the first dash and gap for this segment
863 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
864
865 compressed = compressed.mid( 2 );
866 shouldFlushPreviousSegmentBuffer = !compressed.empty();
867 }
868
869 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
870 {
871 QPen adjustedPen = pen;
872 adjustedPen.setStyle( Qt::SolidLine );
873 painter->setPen( adjustedPen );
874 QPainterPath path;
875 path.addPolygon( previousSegmentBuffer );
876 painter->drawPath( path );
877 previousSegmentBuffer.clear();
878 }
879
880 double finalDash = 0;
881 if ( nextPoint )
882 {
883 // sharp bend:
884 // 1. rewind buffered points line by final dash and gap length
885 // (later) 2. draw the bend with a solid line of length 2 * final dash size
886
887 if ( !compressed.empty() )
888 {
889 finalDash = compressed.at( compressed.size() - 2 );
890 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
891
892 const QPolygonF thisPoints = bufferedPoints;
893 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
894 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
895 }
896 else
897 {
898 previousSegmentBuffer << bufferedPoints;
899 }
900 }
901
902 currentBufferLineLength = 0;
903 currentRemainingDashLength = 0;
904 currentRemainingGapLength = 0;
905 totalBufferLength = 0;
906 buffer.clear();
907
908 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
909 {
910 QPen adjustedPen = pen;
911 if ( !compressed.empty() )
912 {
913 // maximum size of dash pattern is 32 elements
914 compressed = compressed.mid( 0, 32 );
915 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
916 adjustedPen.setDashPattern( compressed );
917 }
918 else
919 {
920 adjustedPen.setStyle( Qt::SolidLine );
921 }
922
923 painter->setPen( adjustedPen );
924 QPainterPath path;
925 path.addPolygon( bufferedPoints );
926 painter->drawPath( path );
927 }
928
929 bufferedPoints.clear();
930 };
931
932 QPointF p1;
933 QPointF p2 = *ptIt;
934 ptIt++;
935 bufferedPoints << p2;
936 for ( ; ptIt != points.constEnd(); ++ptIt )
937 {
938 p1 = *ptIt;
939 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
940 {
941 continue;
942 }
943
944 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
945 currentBufferLineLength += remainingSegmentDistance;
946 while ( true )
947 {
948 // handle currentRemainingDashLength/currentRemainingGapLength
949 if ( currentRemainingDashLength > 0 )
950 {
951 // bit more of dash to insert
952 if ( remainingSegmentDistance >= currentRemainingDashLength )
953 {
954 // all of dash fits in
955 buffer << currentRemainingDashLength << 0.0;
956 totalBufferLength += currentRemainingDashLength;
957 remainingSegmentDistance -= currentRemainingDashLength;
958 patternIndex++;
959 currentRemainingDashLength = 0.0;
960 currentRemainingGapLength = sourcePattern.at( patternIndex );
961 }
962 else
963 {
964 // only part of remaining dash fits in
965 buffer << remainingSegmentDistance << 0.0;
966 totalBufferLength += remainingSegmentDistance;
967 currentRemainingDashLength -= remainingSegmentDistance;
968 break;
969 }
970 }
971 if ( currentRemainingGapLength > 0 )
972 {
973 // bit more of gap to insert
974 if ( remainingSegmentDistance >= currentRemainingGapLength )
975 {
976 // all of gap fits in
977 buffer << 0.0 << currentRemainingGapLength;
978 totalBufferLength += currentRemainingGapLength;
979 remainingSegmentDistance -= currentRemainingGapLength;
980 currentRemainingGapLength = 0.0;
981 patternIndex++;
982 }
983 else
984 {
985 // only part of remaining gap fits in
986 buffer << 0.0 << remainingSegmentDistance;
987 totalBufferLength += remainingSegmentDistance;
988 currentRemainingGapLength -= remainingSegmentDistance;
989 break;
990 }
991 }
992
993 if ( patternIndex >= sourcePattern.size() )
994 patternIndex = 0;
995
996 const double nextPatternDashLength = sourcePattern.at( patternIndex );
997 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
998 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
999 {
1000 buffer << nextPatternDashLength << nextPatternGapLength;
1001 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1002 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1003 patternIndex += 2;
1004 }
1005 else if ( nextPatternDashLength <= remainingSegmentDistance )
1006 {
1007 // can fit in "dash", but not "gap"
1008 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1009 totalBufferLength += remainingSegmentDistance;
1010 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1011 currentRemainingDashLength = 0;
1012 patternIndex++;
1013 break;
1014 }
1015 else
1016 {
1017 // can't fit in "dash"
1018 buffer << remainingSegmentDistance << 0.0;
1019 totalBufferLength += remainingSegmentDistance;
1020 currentRemainingGapLength = 0;
1021 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1022 break;
1023 }
1024 }
1025
1026 bufferedPoints << p1;
1027 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1028 {
1029 QPointF nextPoint = *( ptIt + 1 );
1030
1031 // extreme angles form more than 45 degree angle at a node
1032 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1033 {
1034 // extreme angle. Rescale buffer and flush
1035 flushBuffer( &nextPoint );
1036 bufferedPoints << p1;
1037 // restart the line with the full length of the most recent dash element -- see
1038 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1039 if ( patternIndex % 2 == 1 )
1040 {
1041 patternIndex--;
1042 }
1043 currentRemainingDashLength = sourcePattern.at( patternIndex );
1044 }
1045 }
1046
1047 p2 = p1;
1048 }
1049
1050 flushBuffer( nullptr );
1051 if ( !previousSegmentBuffer.empty() )
1052 {
1053 QPen adjustedPen = pen;
1054 adjustedPen.setStyle( Qt::SolidLine );
1055 painter->setPen( adjustedPen );
1056 QPainterPath path;
1057 path.addPolygon( previousSegmentBuffer );
1058 painter->drawPath( path );
1059 previousSegmentBuffer.clear();
1060 }
1061}
1062
1064{
1065 if ( mDrawInsidePolygon )
1066 {
1067 //set to clip line to the interior of polygon, so we expect no bleed
1068 return 0;
1069 }
1070 else
1071 {
1072 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1074 }
1075}
1076
1078{
1079 unit = mCustomDashPatternUnit;
1080 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1081}
1082
1084{
1085 return mPenStyle;
1086}
1087
1104
1114
1116{
1117 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1118}
1119
1121{
1122 return mAlignDashPattern;
1123}
1124
1126{
1127 mAlignDashPattern = enabled;
1128}
1129
1131{
1132 return mPatternCartographicTweakOnSharpCorners;
1133}
1134
1136{
1137 mPatternCartographicTweakOnSharpCorners = enabled;
1138}
1139
1157
1159
1161
1162class MyLine
1163{
1164 public:
1165 MyLine( QPointF p1, QPointF p2 )
1166 : mVertical( false )
1167 , mIncreasing( false )
1168 , mT( 0.0 )
1169 , mLength( 0.0 )
1170 {
1171 if ( p1 == p2 )
1172 return; // invalid
1173
1174 // tangent and direction
1175 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1176 {
1177 // vertical line - tangent undefined
1178 mVertical = true;
1179 mIncreasing = ( p2.y() > p1.y() );
1180 }
1181 else
1182 {
1183 mVertical = false;
1184 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1185 mIncreasing = ( p2.x() > p1.x() );
1186 }
1187
1188 // length
1189 double x = ( p2.x() - p1.x() );
1190 double y = ( p2.y() - p1.y() );
1191 mLength = std::sqrt( x * x + y * y );
1192 }
1193
1194 // return angle in radians
1195 double angle()
1196 {
1197 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1198
1199 if ( !mIncreasing )
1200 a += M_PI;
1201 return a;
1202 }
1203
1204 // return difference for x,y when going along the line with specified interval
1205 QPointF diffForInterval( double interval )
1206 {
1207 if ( mVertical )
1208 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1209
1210 double alpha = std::atan( mT );
1211 double dx = std::cos( alpha ) * interval;
1212 double dy = std::sin( alpha ) * interval;
1213 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1214 }
1215
1216 double length() const { return mLength; }
1217
1218 protected:
1219 bool mVertical;
1220 bool mIncreasing;
1221 double mT;
1222 double mLength;
1223};
1224
1226
1227//
1228// QgsTemplatedLineSymbolLayerBase
1229//
1231 : mRotateSymbols( rotateSymbol )
1232 , mInterval( interval )
1233{
1234
1235}
1236
1260
1265
1267
1269{
1270 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1271 if ( mRenderingFeature )
1272 {
1273 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1274 // until after we've received the final part
1275 mFeatureSymbolOpacity = context.opacity();
1276 mCurrentFeatureIsSelected = useSelectedColor;
1277 }
1278
1279 double offset = mOffset;
1280
1282 {
1285 }
1286
1287 Qgis::MarkerLinePlacements placements = QgsTemplatedLineSymbolLayerBase::placements();
1288
1290 {
1292 if ( !QgsVariantUtils::isNull( exprVal ) )
1293 {
1294 QString placementString = exprVal.toString();
1295 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1296 {
1298 }
1299 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1300 {
1302 }
1303 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1304 {
1306 }
1307 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1308 {
1310 }
1311 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1312 {
1314 }
1315 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1316 {
1318 }
1319 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1320 {
1322 }
1323 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1324 {
1326 }
1327 else
1328 {
1330 }
1331 }
1332 }
1333
1334 QgsScopedQPainterState painterState( context.renderContext().painter() );
1335
1336 double averageOver = mAverageAngleLength;
1338 {
1339 context.setOriginalValueVariable( mAverageAngleLength );
1341 }
1342 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1343
1344 if ( qgsDoubleNear( offset, 0.0 ) )
1345 {
1347 renderPolylineInterval( points, context, averageOver );
1349 renderPolylineCentral( points, context, averageOver );
1351 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1353 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1354 {
1355 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1356 mHasRenderedFirstPart = mRenderingFeature;
1357 }
1359 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1361 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1363 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1365 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1366 }
1367 else
1368 {
1369 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1371
1372 for ( int part = 0; part < mline.count(); ++part )
1373 {
1374 const QPolygonF &points2 = mline[ part ];
1375
1377 renderPolylineInterval( points2, context, averageOver );
1379 renderPolylineCentral( points2, context, averageOver );
1381 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1383 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1385 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1387 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1388 {
1389 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1390 mHasRenderedFirstPart = mRenderingFeature;
1391 }
1393 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1395 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1396 }
1397 }
1398}
1399
1400void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1401{
1402 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1403
1404 if ( curvePolygon )
1405 {
1406 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1407 }
1408
1409 QgsExpressionContextScope *scope = nullptr;
1410 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1412 {
1413 scope = new QgsExpressionContextScope();
1414 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1415 }
1416
1417 switch ( mRingFilter )
1418 {
1419 case AllRings:
1420 case ExteriorRingOnly:
1421 {
1422 if ( scope )
1424
1425 renderPolyline( points, context );
1426 break;
1427 }
1428 case InteriorRingsOnly:
1429 break;
1430 }
1431
1432 if ( rings )
1433 {
1434 switch ( mRingFilter )
1435 {
1436 case AllRings:
1437 case InteriorRingsOnly:
1438 {
1439 mOffset = -mOffset; // invert the offset for rings!
1440 for ( int i = 0; i < rings->size(); ++i )
1441 {
1442 if ( curvePolygon )
1443 {
1444 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1445 }
1446 if ( scope )
1448
1449 renderPolyline( rings->at( i ), context );
1450 }
1451 mOffset = -mOffset;
1452 }
1453 break;
1454 case ExteriorRingOnly:
1455 break;
1456 }
1457 }
1458}
1459
1461{
1463 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1464 {
1466 }
1467 return unit;
1468}
1469
1471{
1473 mIntervalUnit = unit;
1474 mOffsetAlongLineUnit = unit;
1475 mAverageAngleLengthUnit = unit;
1476}
1477
1485
1496
1498{
1499 QVariantMap map;
1500 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1501 map[QStringLiteral( "interval" )] = QString::number( interval() );
1502 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1503 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1504 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1505 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1506 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1507 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1508 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1509 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1510 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1511 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1512 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1513
1514 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1515
1516 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1517 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1518 return map;
1519}
1520
1522{
1523 return mPlaceOnEveryPart
1524 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1525 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1526 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1527}
1528
1530{
1531 mRenderingFeature = true;
1532 mHasRenderedFirstPart = false;
1533}
1534
1536{
1537 mRenderingFeature = false;
1538 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1539 return;
1540
1541 const double prevOpacity = subSymbol()->opacity();
1542 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1543
1544 // render final point
1545 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1546 mFeatureSymbolOpacity = 1;
1547 subSymbol()->setOpacity( prevOpacity );
1548}
1549
1551{
1552 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1553 destLayer->setOffset( mOffset );
1554 destLayer->setPlacements( placements() );
1555 destLayer->setOffsetUnit( mOffsetUnit );
1557 destLayer->setIntervalUnit( intervalUnit() );
1559 destLayer->setOffsetAlongLine( offsetAlongLine() );
1562 destLayer->setAverageAngleLength( mAverageAngleLength );
1563 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1564 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1565 destLayer->setRingFilter( mRingFilter );
1566 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1567 copyDataDefinedProperties( destLayer );
1568 copyPaintEffect( destLayer );
1569}
1570
1572{
1573 if ( properties.contains( QStringLiteral( "offset" ) ) )
1574 {
1575 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1576 }
1577 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1578 {
1579 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1580 }
1581 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1582 {
1583 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1584 }
1585 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1586 {
1587 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1588 }
1589 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1590 {
1591 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1592 }
1593 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1594 {
1595 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1596 }
1597
1598 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1599 {
1600 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1601 }
1602 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1603 {
1604 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1605 }
1606
1607 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1608 {
1609 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1610 }
1611 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1612 {
1613 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1614 }
1615 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1616 {
1617 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1618 }
1619
1620 if ( properties.contains( QStringLiteral( "placement" ) ) )
1621 {
1622 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1624 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1626 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1628 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1630 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1632 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1634 else
1636 }
1637 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1638 {
1639 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1640 destLayer->setPlacements( placements );
1641 }
1642
1643 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1644 {
1645 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1646 }
1647
1648 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1649
1651}
1652
1653void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1654{
1655 if ( points.isEmpty() )
1656 return;
1657
1658 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1659 double lengthLeft = 0; // how much is left until next marker
1660
1661 QgsRenderContext &rc = context.renderContext();
1662 double interval = mInterval;
1663
1665 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1666
1668 {
1669 context.setOriginalValueVariable( mInterval );
1671 }
1672 if ( interval <= 0 )
1673 {
1674 interval = 0.1;
1675 }
1676 double offsetAlongLine = mOffsetAlongLine;
1678 {
1679 context.setOriginalValueVariable( mOffsetAlongLine );
1681 }
1682
1683 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1685 {
1686 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1687 // and clamp it to a reasonable range. It's the best we can do in this situation!
1688 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1689 }
1690
1691 if ( painterUnitInterval < 0 )
1692 return;
1693
1694 double painterUnitOffsetAlongLine = 0;
1695
1696 // only calculated if we need it!
1697 double totalLength = -1;
1698
1699 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1700 {
1701 switch ( offsetAlongLineUnit() )
1702 {
1711 break;
1713 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1714 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1715 break;
1716 }
1717
1718 if ( points.isClosed() )
1719 {
1720 if ( painterUnitOffsetAlongLine > 0 )
1721 {
1722 if ( totalLength < 0 )
1723 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1724 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1725 }
1726 else if ( painterUnitOffsetAlongLine < 0 )
1727 {
1728 if ( totalLength < 0 )
1729 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1730 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1731 }
1732 }
1733 }
1734
1736 {
1737 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1738 // and clamp it to a reasonable range. It's the best we can do in this situation!
1739 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1740 }
1741
1742 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1743
1744 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1745 {
1746 QVector< QPointF > angleStartPoints;
1747 QVector< QPointF > symbolPoints;
1748 QVector< QPointF > angleEndPoints;
1749
1750 // we collect 3 arrays of points. These correspond to
1751 // 1. the actual point at which to render the symbol
1752 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1753 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1754 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1755 // (or trace past the final point)
1756 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1757
1758 if ( symbolPoints.empty() )
1759 {
1760 // no symbols to draw, shortcut out early
1761 return;
1762 }
1763
1764 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1765 {
1766 // avoid duplicate points at start and end of closed rings
1767 symbolPoints.pop_back();
1768 }
1769
1770 angleEndPoints.reserve( symbolPoints.size() );
1771 angleStartPoints.reserve( symbolPoints.size() );
1772 if ( averageOver <= painterUnitOffsetAlongLine )
1773 {
1774 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1775 }
1776 else
1777 {
1778 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1779 }
1780 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1781
1782 int pointNum = 0;
1783 for ( int i = 0; i < symbolPoints.size(); ++ i )
1784 {
1785 if ( context.renderContext().renderingStopped() )
1786 break;
1787
1788 const QPointF pt = symbolPoints[i];
1789 const QPointF startPt = angleStartPoints[i];
1790 const QPointF endPt = angleEndPoints[i];
1791
1792 MyLine l( startPt, endPt );
1793 // rotate marker (if desired)
1794 if ( rotateSymbols() )
1795 {
1796 setSymbolLineAngle( l.angle() * 180 / M_PI );
1797 }
1798
1800 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1801 }
1802 }
1803 else
1804 {
1805 // not averaging line angle -- always use exact section angle
1806 int pointNum = 0;
1807 QPointF lastPt = points[0];
1808 for ( int i = 1; i < points.count(); ++i )
1809 {
1810 if ( context.renderContext().renderingStopped() )
1811 break;
1812
1813 const QPointF &pt = points[i];
1814
1815 if ( lastPt == pt ) // must not be equal!
1816 continue;
1817
1818 // for each line, find out dx and dy, and length
1819 MyLine l( lastPt, pt );
1820 QPointF diff = l.diffForInterval( painterUnitInterval );
1821
1822 // if there's some length left from previous line
1823 // use only the rest for the first point in new line segment
1824 double c = 1 - lengthLeft / painterUnitInterval;
1825
1826 lengthLeft += l.length();
1827
1828 // rotate marker (if desired)
1829 if ( rotateSymbols() )
1830 {
1831 setSymbolLineAngle( l.angle() * 180 / M_PI );
1832 }
1833
1834 // while we're not at the end of line segment, draw!
1835 while ( lengthLeft > painterUnitInterval )
1836 {
1837 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1838 lastPt += c * diff;
1839 lengthLeft -= painterUnitInterval;
1841 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1842 c = 1; // reset c (if wasn't 1 already)
1843 }
1844
1845 lastPt = pt;
1846 }
1847
1848 }
1849}
1850
1851static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1852{
1853 // calc average angle between the previous and next point
1854 double a1 = MyLine( prevPt, pt ).angle();
1855 double a2 = MyLine( pt, nextPt ).angle();
1856 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1857
1858 return std::atan2( unitY, unitX );
1859}
1860
1861void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1862{
1863 if ( points.isEmpty() )
1864 return;
1865
1866 QgsRenderContext &rc = context.renderContext();
1867
1868 int i = -1, maxCount = 0;
1869 bool isRing = false;
1870
1872 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1874
1875 double offsetAlongLine = mOffsetAlongLine;
1877 {
1878 context.setOriginalValueVariable( mOffsetAlongLine );
1880 }
1881
1882 // only calculated if we need it!!
1883 double totalLength = -1;
1884 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1885 {
1886 //scale offset along line
1887 switch ( offsetAlongLineUnit() )
1888 {
1897 break;
1899 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1900 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1901 break;
1902 }
1903 if ( points.isClosed() )
1904 {
1905 if ( offsetAlongLine > 0 )
1906 {
1907 if ( totalLength < 0 )
1908 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1909 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1910 }
1911 else if ( offsetAlongLine < 0 )
1912 {
1913 if ( totalLength < 0 )
1914 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1915 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1916 }
1917 }
1918 }
1919
1920 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1921 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1925 {
1927 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1928
1929 QgsVertexId vId;
1930 QgsPoint vPoint;
1931 double x, y, z;
1932 QPointF mapPoint;
1933 int pointNum = 0;
1934 const int numPoints = context.renderContext().geometry()->nCoordinates();
1935 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1936 {
1937 if ( context.renderContext().renderingStopped() )
1938 break;
1939
1941
1942 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1943 continue;
1944
1945 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1946 continue;
1947
1950 {
1951 //transform
1952 x = vPoint.x();
1953 y = vPoint.y();
1954 z = 0.0;
1955 if ( ct.isValid() )
1956 {
1957 ct.transformInPlace( x, y, z );
1958 }
1959 mapPoint.setX( x );
1960 mapPoint.setY( y );
1961 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1962 if ( rotateSymbols() )
1963 {
1964 double angle = context.renderContext().geometry()->vertexAngle( vId );
1965 setSymbolLineAngle( angle * 180 / M_PI );
1966 }
1967 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
1968 }
1969 }
1970
1971 return;
1972 }
1973
1974 int pointNum = 0;
1975
1976 switch ( placement )
1977 {
1979 {
1980 i = 0;
1981 maxCount = 1;
1982 break;
1983 }
1984
1986 {
1987 i = points.count() - 1;
1988 pointNum = i;
1989 maxCount = points.count();
1990 break;
1991 }
1992
1994 {
1995 i = 1;
1996 pointNum = 1;
1997 maxCount = points.count() - 1;
1998 break;
1999 }
2000
2003 {
2005 maxCount = points.count();
2006 if ( points.first() == points.last() )
2007 isRing = true;
2008 break;
2009 }
2010
2014 {
2015 return;
2016 }
2017 }
2018
2020 {
2021 double distance;
2023 renderOffsetVertexAlongLine( points, i, distance, context, placement );
2024
2025 return;
2026 }
2027
2028 QPointF prevPoint;
2029 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2030 prevPoint = points.at( 0 );
2031
2032 QPointF symbolPoint;
2033 for ( ; i < maxCount; ++i )
2034 {
2036
2037 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2038 {
2039 continue; // don't draw the last marker - it has been drawn already
2040 }
2041
2043 {
2044 QPointF currentPoint = points.at( i );
2045 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2046 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2047 if ( rotateSymbols() )
2048 {
2049 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2050 currentPoint.x() - prevPoint.x() );
2051 setSymbolLineAngle( angle * 180 / M_PI );
2052 }
2053 prevPoint = currentPoint;
2054 }
2055 else
2056 {
2057 symbolPoint = points.at( i );
2058 // rotate marker (if desired)
2059 if ( rotateSymbols() )
2060 {
2061 double angle = markerAngle( points, isRing, i );
2062 setSymbolLineAngle( angle * 180 / M_PI );
2063 }
2064 }
2065
2066 mFinalVertex = symbolPoint;
2067 if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2068 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2069 }
2070}
2071
2072double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2073{
2074 double angle = 0;
2075 const QPointF &pt = points[vertex];
2076
2077 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2078 {
2079 int prevIndex = vertex - 1;
2080 int nextIndex = vertex + 1;
2081
2082 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2083 {
2084 prevIndex = points.count() - 2;
2085 nextIndex = 1;
2086 }
2087
2088 QPointF prevPoint, nextPoint;
2089 while ( prevIndex >= 0 )
2090 {
2091 prevPoint = points[ prevIndex ];
2092 if ( prevPoint != pt )
2093 {
2094 break;
2095 }
2096 --prevIndex;
2097 }
2098
2099 while ( nextIndex < points.count() )
2100 {
2101 nextPoint = points[ nextIndex ];
2102 if ( nextPoint != pt )
2103 {
2104 break;
2105 }
2106 ++nextIndex;
2107 }
2108
2109 if ( prevIndex >= 0 && nextIndex < points.count() )
2110 {
2111 angle = _averageAngle( prevPoint, pt, nextPoint );
2112 }
2113 }
2114 else //no ring and vertex is at start / at end
2115 {
2116 if ( vertex == 0 )
2117 {
2118 while ( vertex < points.size() - 1 )
2119 {
2120 const QPointF &nextPt = points[vertex + 1];
2121 if ( pt != nextPt )
2122 {
2123 angle = MyLine( pt, nextPt ).angle();
2124 return angle;
2125 }
2126 ++vertex;
2127 }
2128 }
2129 else
2130 {
2131 // use last segment's angle
2132 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2133 {
2134 const QPointF &prevPt = points[vertex - 1];
2135 if ( pt != prevPt )
2136 {
2137 angle = MyLine( prevPt, pt ).angle();
2138 return angle;
2139 }
2140 --vertex;
2141 }
2142 }
2143 }
2144 return angle;
2145}
2146
2147void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2148{
2149 if ( points.isEmpty() )
2150 return;
2151
2152 QgsRenderContext &rc = context.renderContext();
2153 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2154 if ( qgsDoubleNear( distance, 0.0 ) )
2155 {
2156 // rotate marker (if desired)
2157 if ( rotateSymbols() )
2158 {
2159 bool isRing = false;
2160 if ( points.first() == points.last() )
2161 isRing = true;
2162 double angle = markerAngle( points, isRing, vertex );
2163 setSymbolLineAngle( angle * 180 / M_PI );
2164 }
2165 mFinalVertex = points[vertex];
2166 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2167 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2168 return;
2169 }
2170
2171 int pointIncrement = distance > 0 ? 1 : -1;
2172 QPointF previousPoint = points[vertex];
2173 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2174 int endPoint = distance > 0 ? points.count() - 1 : 0;
2175 double distanceLeft = std::fabs( distance );
2176
2177 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2178 {
2179 const QPointF &pt = points[i];
2180
2181 if ( previousPoint == pt ) // must not be equal!
2182 continue;
2183
2184 // create line segment
2185 MyLine l( previousPoint, pt );
2186
2187 if ( distanceLeft < l.length() )
2188 {
2189 //destination point is in current segment
2190 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2191 // rotate marker (if desired)
2192 if ( rotateSymbols() )
2193 {
2194 setSymbolLineAngle( l.angle() * 180 / M_PI );
2195 }
2196 mFinalVertex = markerPoint;
2197 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2198 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2199 return;
2200 }
2201
2202 distanceLeft -= l.length();
2203 previousPoint = pt;
2204 }
2205
2206 //didn't find point
2207}
2208
2209void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2210{
2211 if ( p.empty() )
2212 return;
2213
2214 QVector< QPointF > points = p;
2215 const bool closedRing = points.first() == points.last();
2216
2217 double lengthLeft = initialOffset;
2218
2219 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2220 if ( initialLagLeft < 0 && closedRing )
2221 {
2222 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2223 QPointF lastPt = points.constLast();
2224 QVector< QPointF > pseudoPoints;
2225 for ( int i = points.count() - 2; i > 0; --i )
2226 {
2227 if ( initialLagLeft >= 0 )
2228 {
2229 break;
2230 }
2231
2232 const QPointF &pt = points[i];
2233
2234 if ( lastPt == pt ) // must not be equal!
2235 continue;
2236
2237 MyLine l( lastPt, pt );
2238 initialLagLeft += l.length();
2239 lastPt = pt;
2240
2241 pseudoPoints << pt;
2242 }
2243 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2244
2245 points = pseudoPoints;
2246 points.append( p );
2247 }
2248 else
2249 {
2250 while ( initialLagLeft < 0 )
2251 {
2252 dest << points.constFirst();
2253 initialLagLeft += intervalPainterUnits;
2254 }
2255 }
2256 if ( initialLag > 0 )
2257 {
2258 lengthLeft += intervalPainterUnits - initialLagLeft;
2259 }
2260
2261 QPointF lastPt = points[0];
2262 for ( int i = 1; i < points.count(); ++i )
2263 {
2264 const QPointF &pt = points[i];
2265
2266 if ( lastPt == pt ) // must not be equal!
2267 {
2268 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2269 {
2270 lastPt = points[0];
2271 i = 0;
2272 }
2273 continue;
2274 }
2275
2276 // for each line, find out dx and dy, and length
2277 MyLine l( lastPt, pt );
2278 QPointF diff = l.diffForInterval( intervalPainterUnits );
2279
2280 // if there's some length left from previous line
2281 // use only the rest for the first point in new line segment
2282 double c = 1 - lengthLeft / intervalPainterUnits;
2283
2284 lengthLeft += l.length();
2285
2286
2287 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2288 {
2289 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2290 lastPt += c * diff;
2291 lengthLeft -= intervalPainterUnits;
2292 dest << lastPt;
2293 c = 1; // reset c (if wasn't 1 already)
2294 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2295 break;
2296 }
2297 lastPt = pt;
2298
2299 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2300 break;
2301
2302 // if a closed ring, we keep looping around the ring until we hit the required number of points
2303 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2304 {
2305 lastPt = points[0];
2306 i = 0;
2307 }
2308 }
2309
2310 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2311 {
2312 // pad with repeating last point to match desired size
2313 while ( dest.size() < numberPointsRequired )
2314 dest << points.constLast();
2315 }
2316}
2317
2318void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2319{
2320 if ( !points.isEmpty() )
2321 {
2322 // calc length
2323 qreal length = 0;
2324 QPolygonF::const_iterator it = points.constBegin();
2325 QPointF last = *it;
2326 for ( ++it; it != points.constEnd(); ++it )
2327 {
2328 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2329 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2330 last = *it;
2331 }
2332 if ( qgsDoubleNear( length, 0.0 ) )
2333 return;
2334
2335 const double midPoint = length / 2;
2336
2337 QPointF pt;
2338 double thisSymbolAngle = 0;
2339
2340 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2341 {
2342 QVector< QPointF > angleStartPoints;
2343 QVector< QPointF > symbolPoints;
2344 QVector< QPointF > angleEndPoints;
2345 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2346 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2347 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2348 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2349
2350 pt = symbolPoints.at( 1 );
2351 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2352 thisSymbolAngle = l.angle();
2353 }
2354 else
2355 {
2356 // find the segment where the central point lies
2357 it = points.constBegin();
2358 last = *it;
2359 qreal last_at = 0, next_at = 0;
2360 QPointF next;
2361 for ( ++it; it != points.constEnd(); ++it )
2362 {
2363 next = *it;
2364 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2365 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2366 if ( next_at >= midPoint )
2367 break; // we have reached the center
2368 last = *it;
2369 last_at = next_at;
2370 }
2371
2372 // find out the central point on segment
2373 MyLine l( last, next ); // for line angle
2374 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2375 pt = last + ( next - last ) * k;
2376 thisSymbolAngle = l.angle();
2377 }
2378
2379 // draw the marker
2380 // rotate marker (if desired)
2381 if ( rotateSymbols() )
2382 {
2383 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2384 }
2385
2386 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2387 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2388 }
2389}
2390
2395
2397{
2398 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2399 {
2400 delete symbol;
2401 return false;
2402 }
2403
2404 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2405 mColor = mMarker->color();
2406 return true;
2407}
2408
2409
2410
2411//
2412// QgsMarkerLineSymbolLayer
2413//
2414
2415QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2416 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2417{
2419}
2420
2422
2424{
2425 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2427
2428 if ( props.contains( QStringLiteral( "interval" ) ) )
2429 interval = props[QStringLiteral( "interval" )].toDouble();
2430 if ( props.contains( QStringLiteral( "rotate" ) ) )
2431 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2432
2433 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2434 setCommonProperties( x.get(), props );
2435 return x.release();
2436}
2437
2439{
2440 return QStringLiteral( "MarkerLine" );
2441}
2442
2443void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2444{
2445 mMarker->setColor( color );
2446 mColor = color;
2447}
2448
2450{
2451 return mMarker ? mMarker->color() : mColor;
2452}
2453
2455{
2456 // if being rotated, it gets initialized with every line segment
2457 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2458 if ( rotateSymbols() )
2460 mMarker->setRenderHints( hints );
2461
2462 mMarker->startRender( context.renderContext(), context.fields() );
2463}
2464
2466{
2467 mMarker->stopRender( context.renderContext() );
2468}
2469
2470
2472{
2473 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2475 return x.release();
2476}
2477
2478void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2479{
2480 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2481 {
2482 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2483 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2484 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2485 element.appendChild( symbolizerElem );
2486
2487 // <Geometry>
2488 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2489
2490 QString gap;
2492 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2494 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2496 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2498 // no way to get line/polygon's vertices, use a VendorOption
2499 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2500
2502 {
2504 gap = qgsDoubleToString( interval );
2505 }
2506
2507 if ( !rotateSymbols() )
2508 {
2509 // markers in LineSymbolizer must be drawn following the line orientation,
2510 // use a VendorOption when no marker rotation
2511 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2512 }
2513
2514 // <Stroke>
2515 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2516 symbolizerElem.appendChild( strokeElem );
2517
2518 // <GraphicStroke>
2519 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2520 strokeElem.appendChild( graphicStrokeElem );
2521
2522 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2523 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2524 {
2525 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2526 }
2527 else if ( layer )
2528 {
2529 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2530 }
2531 else
2532 {
2533 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2534 }
2535
2536 if ( !gap.isEmpty() )
2537 {
2538 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2540 graphicStrokeElem.appendChild( gapElem );
2541 }
2542
2543 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2544 {
2545 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2547 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2548 symbolizerElem.appendChild( perpOffsetElem );
2549 }
2550 }
2551}
2552
2554{
2555 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2556
2557 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2558 if ( strokeElem.isNull() )
2559 return nullptr;
2560
2561 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2562 if ( graphicStrokeElem.isNull() )
2563 return nullptr;
2564
2565 // retrieve vendor options
2566 bool rotateMarker = true;
2568
2569 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2570 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2571 {
2572 if ( it.key() == QLatin1String( "placement" ) )
2573 {
2574 if ( it.value() == QLatin1String( "points" ) )
2576 else if ( it.value() == QLatin1String( "firstPoint" ) )
2578 else if ( it.value() == QLatin1String( "lastPoint" ) )
2580 else if ( it.value() == QLatin1String( "centralPoint" ) )
2582 }
2583 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2584 {
2585 rotateMarker = it.value() == QLatin1String( "0" );
2586 }
2587 }
2588
2589 std::unique_ptr< QgsMarkerSymbol > marker;
2590
2592 if ( l )
2593 {
2594 QgsSymbolLayerList layers;
2595 layers.append( l );
2596 marker.reset( new QgsMarkerSymbol( layers ) );
2597 }
2598
2599 if ( !marker )
2600 return nullptr;
2601
2602 double interval = 0.0;
2603 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2604 if ( !gapElem.isNull() )
2605 {
2606 bool ok;
2607 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2608 if ( ok )
2609 interval = d;
2610 }
2611
2612 double offset = 0.0;
2613 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2614 if ( !perpOffsetElem.isNull() )
2615 {
2616 bool ok;
2617 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2618 if ( ok )
2619 offset = d;
2620 }
2621
2622 double scaleFactor = 1.0;
2623 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2624 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2625 interval = interval * scaleFactor;
2626 offset = offset * scaleFactor;
2627
2629 x->setOutputUnit( sldUnitSize );
2631 x->setInterval( interval );
2632 x->setSubSymbol( marker.release() );
2633 x->setOffset( offset );
2634 return x;
2635}
2636
2638{
2639 mMarker->setSize( width );
2640}
2641
2643{
2644 if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2645 {
2646 mMarker->setDataDefinedSize( property );
2647 }
2649}
2650
2652{
2653 const double prevOpacity = mMarker->opacity();
2654 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2656 mMarker->setOpacity( prevOpacity );
2657}
2658
2660{
2661 mMarker->setLineAngle( angle );
2662}
2663
2665{
2666 return mMarker->angle();
2667}
2668
2670{
2671 mMarker->setAngle( angle );
2672}
2673
2674void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2675{
2676 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2678
2679 mMarker->renderPoint( point, feature, context, layer, selected );
2680
2681 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2682}
2683
2685{
2686 return mMarker->size();
2687}
2688
2690{
2691 return mMarker->size( context );
2692}
2693
2699
2709
2711{
2712 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2713 if ( mMarker )
2714 attr.unite( mMarker->usedAttributes( context ) );
2715 return attr;
2716}
2717
2719{
2721 return true;
2722 if ( mMarker && mMarker->hasDataDefinedProperties() )
2723 return true;
2724 return false;
2725}
2726
2728{
2729 return ( mMarker->size( context ) / 2.0 ) +
2731}
2732
2733
2734//
2735// QgsHashedLineSymbolLayer
2736//
2737
2738QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2739 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2740{
2741 setSubSymbol( new QgsLineSymbol() );
2742}
2743
2745
2747{
2748 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2750
2751 if ( props.contains( QStringLiteral( "interval" ) ) )
2752 interval = props[QStringLiteral( "interval" )].toDouble();
2753 if ( props.contains( QStringLiteral( "rotate" ) ) )
2754 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2755
2756 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2757 setCommonProperties( x.get(), props );
2758 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2759 {
2760 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2761 }
2762
2763 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2764 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2765
2766 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2767 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2768
2769 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2770 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2771
2772 return x.release();
2773}
2774
2776{
2777 return QStringLiteral( "HashLine" );
2778}
2779
2781{
2782 // if being rotated, it gets initialized with every line segment
2783 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2784 if ( rotateSymbols() )
2786 mHashSymbol->setRenderHints( hints );
2787
2788 mHashSymbol->startRender( context.renderContext(), context.fields() );
2789}
2790
2792{
2793 mHashSymbol->stopRender( context.renderContext() );
2794}
2795
2797{
2799 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2800
2801 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2802 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2803 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2804
2805 return map;
2806}
2807
2809{
2810 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2812 x->setHashAngle( mHashAngle );
2813 x->setHashLength( mHashLength );
2814 x->setHashLengthUnit( mHashLengthUnit );
2815 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2816 return x.release();
2817}
2818
2819void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2820{
2821 mHashSymbol->setColor( color );
2822 mColor = color;
2823}
2824
2826{
2827 return mHashSymbol ? mHashSymbol->color() : mColor;
2828}
2829
2831{
2832 return mHashSymbol.get();
2833}
2834
2836{
2837 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2838 {
2839 delete symbol;
2840 return false;
2841 }
2842
2843 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2844 mColor = mHashSymbol->color();
2845 return true;
2846}
2847
2848void QgsHashedLineSymbolLayer::setWidth( const double width )
2849{
2850 mHashLength = width;
2851}
2852
2854{
2855 return mHashLength;
2856}
2857
2859{
2860 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2861}
2862
2864{
2865 return ( mHashSymbol->width( context ) / 2.0 )
2866 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2868}
2869
2871{
2873 mHashSymbol->setOutputUnit( unit );
2874}
2875
2877{
2878 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2879 if ( mHashSymbol )
2880 attr.unite( mHashSymbol->usedAttributes( context ) );
2881 return attr;
2882}
2883
2885{
2887 return true;
2888 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2889 return true;
2890 return false;
2891}
2892
2894{
2895 if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2896 {
2897 mHashSymbol->setDataDefinedWidth( property );
2898 }
2900}
2901
2912
2914{
2915 mSymbolLineAngle = angle;
2916}
2917
2919{
2920 return mSymbolAngle;
2921}
2922
2924{
2925 mSymbolAngle = angle;
2926}
2927
2928void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2929{
2930 double lineLength = mHashLength;
2932 {
2933 context.expressionContext().setOriginalValueVariable( mHashLength );
2935 }
2936 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2937
2938 double hashAngle = mHashAngle;
2940 {
2941 context.expressionContext().setOriginalValueVariable( mHashAngle );
2943 }
2944
2945 QgsPointXY center( point );
2946 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2947 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2948
2949 QPolygonF points;
2950 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2951
2952 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2954
2955 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2956
2957 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2958}
2959
2961{
2962 return mHashAngle;
2963}
2964
2966{
2967 mHashAngle = angle;
2968}
2969
2971{
2972 const double prevOpacity = mHashSymbol->opacity();
2973 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2975 mHashSymbol->setOpacity( prevOpacity );
2976}
2977
2978//
2979// QgsAbstractBrushedLineSymbolLayer
2980//
2981
2982void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2983{
2984 if ( !context.renderContext().painter() )
2985 return;
2986
2987 double offset = mOffset;
2989 {
2992 }
2993
2994 QPolygonF offsetPoints;
2995 if ( qgsDoubleNear( offset, 0 ) )
2996 {
2997 renderLine( points, context, patternThickness, patternLength, brush );
2998 }
2999 else
3000 {
3001 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3002
3003 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3004 for ( const QPolygonF &part : offsetLine )
3005 {
3006 renderLine( part, context, patternThickness, patternLength, brush );
3007 }
3008 }
3009}
3010
3011void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3012 const double patternLength, const QBrush &sourceBrush )
3013{
3014 QPainter *p = context.renderContext().painter();
3015 if ( !p )
3016 return;
3017
3018 QBrush brush = sourceBrush;
3019
3020 // duplicate points mess up the calculations, we need to remove them first
3021 // we'll calculate the min/max coordinate at the same time, since we're already looping
3022 // through the points
3023 QPolygonF inputPoints;
3024 inputPoints.reserve( points.size() );
3025 QPointF prev;
3026 double minX = std::numeric_limits< double >::max();
3027 double minY = std::numeric_limits< double >::max();
3028 double maxX = std::numeric_limits< double >::lowest();
3029 double maxY = std::numeric_limits< double >::lowest();
3030
3031 for ( const QPointF &pt : std::as_const( points ) )
3032 {
3033 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3034 continue;
3035
3036 inputPoints << pt;
3037 prev = pt;
3038 minX = std::min( minX, pt.x() );
3039 minY = std::min( minY, pt.y() );
3040 maxX = std::max( maxX, pt.x() );
3041 maxY = std::max( maxY, pt.y() );
3042 }
3043
3044 if ( inputPoints.size() < 2 ) // nothing to render
3045 return;
3046
3047 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3048 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3049 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3050 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3051
3052 // 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
3053 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3054 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3055
3056 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3057 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3058
3059 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3060 if ( temporaryImage.isNull() )
3061 {
3062 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3063 return;
3064 }
3065
3066 // clear temporary image contents
3067 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3068 return;
3069 temporaryImage.fill( Qt::transparent );
3070 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3071 return;
3072
3073 Qt::PenJoinStyle join = mPenJoinStyle;
3075 {
3078 if ( !QgsVariantUtils::isNull( exprVal ) )
3079 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3080 }
3081
3082 Qt::PenCapStyle cap = mPenCapStyle;
3084 {
3087 if ( !QgsVariantUtils::isNull( exprVal ) )
3088 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3089 }
3090
3091 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3092 QPainterPathStroker stroker;
3093 stroker.setWidth( lineThickness );
3094 stroker.setCapStyle( cap );
3095 stroker.setJoinStyle( join );
3096
3097 QPainterPath path;
3098 path.addPolygon( inputPoints );
3099 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3100
3101 // prepare temporary image
3102 QPainter imagePainter;
3103 imagePainter.begin( &temporaryImage );
3104 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3105 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3106
3107 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3108 imagePainter.setPen( Qt::NoPen );
3109
3110 QPointF segmentStartPoint = inputPoints.at( 0 );
3111
3112 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3113 double progressThroughImage = 0;
3114
3115 QgsPoint prevSegmentPolygonEndLeft;
3116 QgsPoint prevSegmentPolygonEndRight;
3117
3118 // for closed rings this will store the left/right polygon points of the start/end of the line
3119 QgsPoint startLinePolygonLeft;
3120 QgsPoint startLinePolygonRight;
3121
3122 for ( int i = 1; i < inputPoints.size(); ++i )
3123 {
3124 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3125 break;
3126
3127 const QPointF segmentEndPoint = inputPoints.at( i );
3128 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3129 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3130
3131 // left/right end points of the current segment polygon
3132 QgsPoint thisSegmentPolygonEndLeft;
3133 QgsPoint thisSegmentPolygonEndRight;
3134 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3135 QgsPoint thisSegmentPolygonEndLeftForPainter;
3136 QgsPoint thisSegmentPolygonEndRightForPainter;
3137 if ( i == 1 )
3138 {
3139 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3140 // (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.)
3141 if ( isClosedLine )
3142 {
3143 // project the current segment out by half the image thickness to either side of the line
3144 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3145 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3146 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3147 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3148
3149 // 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
3150 // what angle the current segment polygon should START on.
3151 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3152 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3153
3154 // project out the LAST segment in the line by half the image thickness to either side of the line
3155 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3156 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3157 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3158 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3159
3160 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3161 // 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
3162 // join)
3163 QgsPoint intersectionPoint;
3164 bool isIntersection = false;
3165 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3166 if ( !isIntersection )
3167 prevSegmentPolygonEndLeft = startPointLeft;
3168 isIntersection = false;
3169 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3170 if ( !isIntersection )
3171 prevSegmentPolygonEndRight = startPointRight;
3172
3173 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3174 startLinePolygonRight = prevSegmentPolygonEndRight;
3175 }
3176 else
3177 {
3178 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3179 if ( cap != Qt::PenCapStyle::FlatCap )
3180 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3181 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3182 if ( cap != Qt::PenCapStyle::FlatCap )
3183 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3184 }
3185 }
3186
3187 if ( i < inputPoints.size() - 1 )
3188 {
3189 // for all other segments except the last
3190
3191 // project the current segment out by half the image thickness to either side of the line
3192 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3193 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3194 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3195 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3196
3197 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3198 // what angle the current segment polygon should end on
3199 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3200 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3201
3202 // project out the next segment by half the image thickness to either side of the line
3203 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3204 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3205 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3206 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3207
3208 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3209 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3210 // join)
3211 QgsPoint intersectionPoint;
3212 bool isIntersection = false;
3213 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3214 if ( !isIntersection )
3215 thisSegmentPolygonEndLeft = endPointLeft;
3216 isIntersection = false;
3217 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3218 if ( !isIntersection )
3219 thisSegmentPolygonEndRight = endPointRight;
3220
3221 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3222 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3223 }
3224 else
3225 {
3226 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3227 // unless it's a closed line
3228 if ( isClosedLine )
3229 {
3230 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3231 thisSegmentPolygonEndRight = startLinePolygonRight;
3232
3233 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3234 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3235 }
3236 else
3237 {
3238 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3239 if ( cap != Qt::PenCapStyle::FlatCap )
3240 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3241 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3242 if ( cap != Qt::PenCapStyle::FlatCap )
3243 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3244
3245 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3246 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3247 }
3248 }
3249
3250 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3251 // where we got with the previous segment), at the correct angle
3252 QTransform brushTransform;
3253 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3254 brushTransform.rotate( -segmentAngleDegrees );
3255 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3256 {
3257 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3258 // we need to also do the same for the brush transform
3259 brushTransform.translate( -( lineThickness / 2 ), 0 );
3260 }
3261 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3262
3263 brush.setTransform( brushTransform );
3264 imagePainter.setBrush( brush );
3265
3266 // now draw the segment polygon
3267 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3268 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3269 << thisSegmentPolygonEndRightForPainter.toQPointF()
3270 << prevSegmentPolygonEndRight.toQPointF()
3271 << prevSegmentPolygonEndLeft.toQPointF() );
3272
3273#if 0 // for debugging, will draw the segment polygons
3274 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3275 imagePainter.setBrush( Qt::NoBrush );
3276 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3277 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3278 << thisSegmentPolygonEndRightForPainter.toQPointF()
3279 << prevSegmentPolygonEndRight.toQPointF()
3280 << prevSegmentPolygonEndLeft.toQPointF() );
3281 imagePainter.setPen( Qt::NoPen );
3282#endif
3283
3284 // calculate the new progress horizontal through the source image to account for the length
3285 // of the segment we've just drawn
3286 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3287 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3288 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3289 progressThroughImage = fmod( progressThroughImage, patternLength );
3290
3291 // shuffle buffered variables for next loop
3292 segmentStartPoint = segmentEndPoint;
3293 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3294 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3295 }
3296 imagePainter.end();
3297
3298 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3299 return;
3300
3301 // lastly, draw the temporary image onto the destination painter at the correct place
3302 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3303 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3304}
3305
3306
3307//
3308// QgsRasterLineSymbolLayer
3309//
3310
3312 : mPath( path )
3313{
3314}
3315
3317
3318QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3319{
3320 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3321
3322 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3323 {
3324 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3325 }
3326 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3327 {
3328 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3329 }
3330 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3331 {
3332 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3333 }
3334
3335 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3336 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3337
3338 if ( properties.contains( QStringLiteral( "offset" ) ) )
3339 {
3340 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3341 }
3342 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3343 {
3344 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3345 }
3346 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3347 {
3348 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3349 }
3350
3351 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3352 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3353 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3354 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3355
3356 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3357 {
3358 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3359 }
3360
3361 return res.release();
3362}
3363
3364
3366{
3367 QVariantMap map;
3368 map[QStringLiteral( "imageFile" )] = mPath;
3369
3370 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3371 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3372 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3373
3374 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3375 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3376
3377 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3378 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3379 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3380
3381 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3382
3383 return map;
3384}
3385
3387{
3388 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3389 res->setWidth( mWidth );
3390 res->setWidthUnit( mWidthUnit );
3391 res->setWidthMapUnitScale( mWidthMapUnitScale );
3392 res->setPenJoinStyle( mPenJoinStyle );
3393 res->setPenCapStyle( mPenCapStyle );
3394 res->setOffsetUnit( mOffsetUnit );
3395 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3396 res->setOffset( mOffset );
3397 res->setOpacity( mOpacity );
3398 copyDataDefinedProperties( res.get() );
3399 copyPaintEffect( res.get() );
3400 return res.release();
3401}
3402
3403void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3404{
3405 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3406 if ( it != properties.end() && it.value().type() == QVariant::String )
3407 {
3408 if ( saving )
3409 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3410 else
3411 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3412 }
3413}
3414
3415void QgsRasterLineSymbolLayer::setPath( const QString &path )
3416{
3417 mPath = path;
3418}
3419
3421{
3422 return QStringLiteral( "RasterLine" );
3423}
3424
3426{
3427 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3428
3430
3431 double opacity = mOpacity * context.opacity();
3432 bool cached = false;
3434 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3435 static_cast< int >( std::ceil( scaledHeight ) ) ),
3436 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3437}
3438
3442
3444{
3445 if ( !context.renderContext().painter() )
3446 return;
3447
3448 QImage sourceImage = mLineImage;
3452 {
3453 QString path = mPath;
3455 {
3456 context.setOriginalValueVariable( path );
3458 }
3459
3460 double strokeWidth = mWidth;
3462 {
3463 context.setOriginalValueVariable( strokeWidth );
3465 }
3466 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3467
3469 double opacity = mOpacity;
3471 {
3474 }
3475 opacity *= context.opacity();
3476
3477 bool cached = false;
3479 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3480 static_cast< int >( std::ceil( scaledHeight ) ) ),
3481 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3482 }
3483
3484 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3485 if ( useSelectedColor )
3486 {
3488 }
3489
3490 const QBrush brush( sourceImage );
3491
3492 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3493}
3494
3501
3503{
3505 if ( mWidthUnit != unit || mOffsetUnit != unit )
3506 {
3508 }
3509 return unit;
3510}
3511
3517
3523
3533
3535{
3536 return ( mWidth / 2.0 ) + mOffset;
3537}
3538
3540{
3541 return QColor();
3542}
3543
3544
3545//
3546// QgsLineburstSymbolLayer
3547//
3548
3549QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3551 , mColor2( color2 )
3552{
3553 setColor( color );
3554}
3555
3557
3558QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3559{
3560 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3561
3562 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3563 {
3564 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3565 }
3566 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3567 {
3568 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3569 }
3570 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3571 {
3572 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3573 }
3574
3575 if ( properties.contains( QStringLiteral( "offset" ) ) )
3576 {
3577 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3578 }
3579 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3580 {
3581 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3582 }
3583 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3584 {
3585 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3586 }
3587
3588 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3589 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3590 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3591 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3592
3593 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3594 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3595
3596 if ( properties.contains( QStringLiteral( "color" ) ) )
3597 {
3598 res->setColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
3599 }
3600 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3601 {
3602 res->setColor2( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3603 }
3604
3605 //attempt to create color ramp from props
3606 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3607 {
3608 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3609 }
3610 else
3611 {
3612 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3613 }
3614
3615 return res.release();
3616}
3617
3619{
3620 QVariantMap map;
3621
3622 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3623 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3624 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3625
3626 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3627 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3628
3629 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3630 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3631 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3632
3633 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3634 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
3635 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3636 if ( mGradientRamp )
3637 {
3638#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3639 map.unite( mGradientRamp->properties() );
3640#else
3641 map.insert( mGradientRamp->properties() );
3642#endif
3643 }
3644
3645 return map;
3646}
3647
3649{
3650 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3651 res->setWidth( mWidth );
3652 res->setWidthUnit( mWidthUnit );
3653 res->setWidthMapUnitScale( mWidthMapUnitScale );
3654 res->setPenJoinStyle( mPenJoinStyle );
3655 res->setPenCapStyle( mPenCapStyle );
3656 res->setOffsetUnit( mOffsetUnit );
3657 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3658 res->setOffset( mOffset );
3659 res->setColor( mColor );
3660 res->setColor2( mColor2 );
3661 res->setGradientColorType( mGradientColorType );
3662 if ( mGradientRamp )
3663 res->setColorRamp( mGradientRamp->clone() );
3664 copyDataDefinedProperties( res.get() );
3665 copyPaintEffect( res.get() );
3666 return res.release();
3667}
3668
3670{
3671 return QStringLiteral( "Lineburst" );
3672}
3673
3677
3681
3683{
3684 if ( !context.renderContext().painter() )
3685 return;
3686
3687 double strokeWidth = mWidth;
3689 {
3690 context.setOriginalValueVariable( strokeWidth );
3692 }
3693 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3694
3695 //update alpha of gradient colors
3696 QColor color1 = mColor;
3698 {
3701 }
3702 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3703 if ( useSelectedColor )
3704 {
3705 color1 = context.renderContext().selectionColor();
3706 }
3707 color1.setAlphaF( context.opacity() * color1.alphaF() );
3708
3709 //second gradient color
3710 QColor color2 = mColor2;
3712 {
3715 }
3716
3717 //create a QGradient with the desired properties
3718 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3719 //add stops to gradient
3722 {
3723 //color ramp gradient
3724 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3725 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3726 }
3727 else
3728 {
3729 //two color gradient
3730 gradient.setColorAt( 0.0, color1 );
3731 gradient.setColorAt( 1.0, color2 );
3732 }
3733 const QBrush brush( gradient );
3734
3735 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3736}
3737
3744
3746{
3748 if ( mWidthUnit != unit || mOffsetUnit != unit )
3749 {
3751 }
3752 return unit;
3753}
3754
3760
3766
3776
3778{
3779 return ( mWidth / 2.0 ) + mOffset;
3780}
3781
3786
3788{
3789 mGradientRamp.reset( ramp );
3790}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:2286
@ 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:2308
@ ColorRamp
Gradient color ramp.
@ Curve
An intermediate point on a segment defining the curvature of the segment.
@ Segment
The actual start or end point of a segment.
@ Unknown
Unknown types.
RenderUnit
Rendering size units.
Definition qgis.h:3627
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ 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.
Class for doing transforms between two map coordinate systems.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
Curve polygon geometry type.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Exports QGIS layers to the DXF format.
static double mapUnitScaleFactor(double scale, Qgis::RenderUnit symbolUnits, Qgis::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
Qgis::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
void clipValueToMapUnitScale(double &value, const QgsMapUnitScale &scale, double pixelToMMFactor) const
Clips value to scale minimum/maximum.
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
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)
Compute the intersection between two segments.
static double lineAngle(double x1, double y1, double x2, double y2)
Calculates the direction of line joining two points in radians, clockwise from the north direction.
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.
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.
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
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
double y
Definition qgspointxy.h:63
double x
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
QPointF toQPointF() const
Returns the point as a QPointF.
Definition qgspoint.h:331
double x
Definition qgspoint.h:52
QgsPoint project(double distance, double azimuth, double inclination=90.0) const
Returns a new point which corresponds to this point projected by a specified distance with specified ...
Definition qgspoint.cpp:741
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.
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.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
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.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:94
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:497
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition qgssymbol.h:504
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:153
Base class for templated line symbols, e.g.
bool rotateSymbols() const
Returns true if the repeating symbols be rotated to match their line segment orientation.
Qgis::RenderUnit outputUnit() const FINAL
Returns the units to use for sizes and widths within the symbol layer.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static void setCommonProperties(QgsTemplatedLineSymbolLayerBase *destLayer, const QVariantMap &properties)
Sets all common symbol properties in the destLayer, using the settings serialized in the properties m...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QgsMapUnitScale mapUnitScale() const FINAL
void setMapUnitScale(const QgsMapUnitScale &scale) FINAL
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setIntervalUnit(Qgis::RenderUnit unit)
Sets the units for the interval between symbols.
void setAverageAngleUnit(Qgis::RenderUnit unit)
Sets the unit for the length over which the line's direction is averaged when calculating individual ...
double interval() const
Returns the interval between individual symbols.
void setOffsetAlongLineUnit(Qgis::RenderUnit unit)
Sets the unit used for calculating the offset along line for symbols.
void setAverageAngleMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the length over which the line's direction is averaged when calculating i...
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
double offsetAlongLine() const
Returns the offset along the line for the symbol placement.
void copyTemplateSymbolProperties(QgsTemplatedLineSymbolLayerBase *destLayer) const
Copies all common properties of this layer to another templated symbol layer.
Qgis::MarkerLinePlacements placements() const
Returns the placement of the symbols.
void setOffsetAlongLine(double offsetAlongLine)
Sets the the offset along the line for the symbol placement.
void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) FINAL
Renders the line symbol layer along the outline of polygon, using the given render context.
Qgis::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
QgsTemplatedLineSymbolLayerBase(bool rotateSymbol=true, double interval=3)
Constructor for QgsTemplatedLineSymbolLayerBase.
virtual void setSymbolLineAngle(double angle)=0
Sets the line angle modification for the symbol's angle.
void setInterval(double interval)
Sets the interval between individual symbols.
Qgis::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
Q_DECL_DEPRECATED Qgis::MarkerLinePlacement placement() const
Returns the placement of the symbols.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setPlaceOnEveryPart(bool respect)
Sets whether the placement applies for every part of multi-part feature geometries.
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qgis::RenderUnit averageAngleUnit() const
Returns the unit for the length over which the line's direction is averaged when calculating individu...
Q_DECL_DEPRECATED void setPlacement(Qgis::MarkerLinePlacement placement)
Sets the placement of the symbols.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
const QgsMapUnitScale & offsetAlongLineMapUnitScale() const
Returns the map unit scale used for calculating the offset in map units along line for symbols.
virtual void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false)=0
Renders the templated symbol at the specified point, using the given render context.
void setIntervalMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the interval between symbols.
void setOffsetAlongLineMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale used for calculating the offset in map units along line for symbols.
void setAverageAngleLength(double length)
Sets the length of line over which the line's direction is averaged when calculating individual symbo...
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
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:4271
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:4594
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:4616
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:4332
QMap< QString, QString > QgsStringMap
Definition qgis.h:4877
#define DEFAULT_MARKERLINE_INTERVAL
#define DEFAULT_SIMPLELINE_WIDTH
#define DEFAULT_MARKERLINE_ROTATE
#define DEFAULT_SIMPLELINE_PENSTYLE
#define DEFAULT_SIMPLELINE_COLOR
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
QList< QPolygonF > offsetLine(QPolygonF polyline, double dist, Qgis::GeometryType geometryType)
calculate geometry shifted by a specified distance
Single variable definition for use within a QgsExpressionContextScope.
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:31
Qgis::VertexType type
Vertex type.
Definition qgsvertexid.h:98