QGIS API Documentation 3.38.0-Grenoble (exported)
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 "qgscurvepolygon.h"
18#include "qgsdxfexport.h"
19#include "qgssymbollayerutils.h"
20#include "qgsrendercontext.h"
21#include "qgslogger.h"
23#include "qgsunittypes.h"
24#include "qgsproperty.h"
26#include "qgsmarkersymbol.h"
27#include "qgslinesymbol.h"
28#include "qgsapplication.h"
29#include "qgsimagecache.h"
30#include "qgsfeedback.h"
31#include "qgsimageoperation.h"
32#include "qgscolorrampimpl.h"
33#include "qgsfillsymbol.h"
34#include "qgscolorutils.h"
35
36#include <algorithm>
37#include <QPainter>
38#include <QDomDocument>
39#include <QDomElement>
40
41#include <cmath>
42
43QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
44 : mPenStyle( penStyle )
45{
46 mColor = color;
47 mWidth = width;
48 mCustomDashVector << 5 << 2;
49}
50
52
54{
56 mWidthUnit = unit;
57 mOffsetUnit = unit;
58 mCustomDashPatternUnit = unit;
59 mDashPatternOffsetUnit = unit;
60 mTrimDistanceStartUnit = unit;
61 mTrimDistanceEndUnit = unit;
62}
63
65{
67 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
68 {
70 }
71 return unit;
72}
73
79
81{
83 mWidthMapUnitScale = scale;
84 mOffsetMapUnitScale = scale;
85 mCustomDashPatternMapUnitScale = scale;
86}
87
98
100{
104
105 if ( props.contains( QStringLiteral( "line_color" ) ) )
106 {
107 color = QgsColorUtils::colorFromString( props[QStringLiteral( "line_color" )].toString() );
108 }
109 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
110 {
111 color = QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() );
112 }
113 else if ( props.contains( QStringLiteral( "color" ) ) )
114 {
115 //pre 2.5 projects used "color"
116 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
117 }
118 if ( props.contains( QStringLiteral( "line_width" ) ) )
119 {
120 width = props[QStringLiteral( "line_width" )].toDouble();
121 }
122 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
123 {
124 width = props[QStringLiteral( "outline_width" )].toDouble();
125 }
126 else if ( props.contains( QStringLiteral( "width" ) ) )
127 {
128 //pre 2.5 projects used "width"
129 width = props[QStringLiteral( "width" )].toDouble();
130 }
131 if ( props.contains( QStringLiteral( "line_style" ) ) )
132 {
133 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
134 }
135 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
136 {
137 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
138 }
139 else if ( props.contains( QStringLiteral( "penstyle" ) ) )
140 {
141 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
142 }
143
145 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
146 {
147 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
148 }
149 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
150 {
151 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
152 }
153 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
154 {
155 //pre 2.5 projects used "width_unit"
156 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
157 }
158 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
159 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
160 if ( props.contains( QStringLiteral( "offset" ) ) )
161 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
162 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
163 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
164 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
165 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
166 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
167 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
168 if ( props.contains( QStringLiteral( "capstyle" ) ) )
169 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
170
171 if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
172 {
173 l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
174 }
175 if ( props.contains( QStringLiteral( "customdash" ) ) )
176 {
177 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
178 }
179 if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
180 {
181 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
182 }
183 if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
184 {
185 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
186 }
187
188 if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
189 {
190 l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
191 }
192
193 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
194 {
195 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
196 }
197
198 if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
199 l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
200 if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
201 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
202 if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
203 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
204
205 if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
206 l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
207 if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
208 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
209 if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
210 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
211 if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
212 l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
213 if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
214 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
215 if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
216 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
217
218 if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
219 l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
220
221 if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
222 l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
223
225
226 return l;
227}
228
230{
231 return QStringLiteral( "SimpleLine" );
232}
233
235{
236 QColor penColor = mColor;
237 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
238 mPen.setColor( penColor );
239 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
240 mPen.setWidthF( scaledWidth );
241
242 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
243 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
244 const double dashWidthDiv = std::max( 1.0, scaledWidth );
245 if ( mUseCustomDashPattern )
246 {
247 mPen.setStyle( Qt::CustomDashLine );
248
249 //scale pattern vector
250
251 QVector<qreal> scaledVector;
252 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
253 for ( ; it != mCustomDashVector.constEnd(); ++it )
254 {
255 //the dash is specified in terms of pen widths, therefore the division
256 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
257 }
258 mPen.setDashPattern( scaledVector );
259 }
260 else
261 {
262 mPen.setStyle( mPenStyle );
263 }
264
265 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
266 {
267 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
268 }
269
270 mPen.setJoinStyle( mPenJoinStyle );
271 mPen.setCapStyle( mPenCapStyle );
272
273 mSelPen = mPen;
274 QColor selColor = context.renderContext().selectionColor();
275 if ( ! SELECTION_IS_OPAQUE )
276 selColor.setAlphaF( context.opacity() );
277 mSelPen.setColor( selColor );
278}
279
281{
282 Q_UNUSED( context )
283}
284
285void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
286{
287 QPainter *p = context.renderContext().painter();
288 if ( !p )
289 {
290 return;
291 }
292
293 QgsExpressionContextScope *scope = nullptr;
294 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
296 {
297 scope = new QgsExpressionContextScope();
298 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
299 }
300
301 if ( mDrawInsidePolygon )
302 p->save();
303
304 switch ( mRingFilter )
305 {
306 case AllRings:
307 case ExteriorRingOnly:
308 {
309 if ( mDrawInsidePolygon )
310 {
311 //only drawing the line on the interior of the polygon, so set clip path for painter
312 QPainterPath clipPath;
313 clipPath.addPolygon( points );
314
315 if ( rings )
316 {
317 //add polygon rings
318 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
319 {
320 QPolygonF ring = *it;
321 clipPath.addPolygon( ring );
322 }
323 }
324
325 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
326 p->setClipPath( clipPath, Qt::IntersectClip );
327 }
328
329 if ( scope )
331
332 renderPolyline( points, context );
333 }
334 break;
335
337 break;
338 }
339
340 if ( rings )
341 {
342 switch ( mRingFilter )
343 {
344 case AllRings:
346 {
347 mOffset = -mOffset; // invert the offset for rings!
348 int ringIndex = 1;
349 for ( const QPolygonF &ring : std::as_const( *rings ) )
350 {
351 if ( scope )
353
354 renderPolyline( ring, context );
355 ringIndex++;
356 }
357 mOffset = -mOffset;
358 }
359 break;
360 case ExteriorRingOnly:
361 break;
362 }
363 }
364
365 if ( mDrawInsidePolygon )
366 {
367 //restore painter to reset clip path
368 p->restore();
369 }
370
371}
372
374{
375 QPainter *p = context.renderContext().painter();
376 if ( !p )
377 {
378 return;
379 }
380
381 QPolygonF points = pts;
382
383 double startTrim = mTrimDistanceStart;
385 {
386 context.setOriginalValueVariable( startTrim );
388 }
389 double endTrim = mTrimDistanceEnd;
391 {
392 context.setOriginalValueVariable( endTrim );
394 }
395
396 double totalLength = -1;
397 if ( mTrimDistanceStartUnit == Qgis::RenderUnit::Percentage )
398 {
399 totalLength = QgsSymbolLayerUtils::polylineLength( points );
400 startTrim = startTrim * 0.01 * totalLength;
401 }
402 else
403 {
404 startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
405 }
406 if ( mTrimDistanceEndUnit == Qgis::RenderUnit::Percentage )
407 {
408 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
409 totalLength = QgsSymbolLayerUtils::polylineLength( points );
410 endTrim = endTrim * 0.01 * totalLength;
411 }
412 else
413 {
414 endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
415 }
416 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
417 {
418 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
419 }
420
421 QColor penColor = mColor;
422 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
423 mPen.setColor( penColor );
424
425 double offset = mOffset;
426 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
427
428 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
429 const QPen pen = useSelectedColor ? mSelPen : mPen;
430
431 if ( !pen.dashPattern().isEmpty() )
432 {
433 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
434 const QVector<double> pattern = pen.dashPattern();
435 bool foundNonNull = false;
436 for ( int i = 0; i < pattern.size(); ++i )
437 {
438 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
439 {
440 foundNonNull = true;
441 break;
442 }
443 }
444 if ( !foundNonNull )
445 return;
446 }
447
448 p->setBrush( Qt::NoBrush );
449
450 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
451 std::unique_ptr< QgsScopedQPainterState > painterState;
452 if ( points.size() <= 2 &&
455 ( p->renderHints() & QPainter::Antialiasing ) )
456 {
457 painterState = std::make_unique< QgsScopedQPainterState >( p );
458 p->setRenderHint( QPainter::Antialiasing, false );
459 }
460
461 const bool applyPatternTweaks = mAlignDashPattern
462 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
463 && pen.dashOffset() == 0;
464
465 if ( qgsDoubleNear( offset, 0 ) )
466 {
467 if ( applyPatternTweaks )
468 {
469 drawPathWithDashPatternTweaks( p, points, pen );
470 }
471 else
472 {
473 p->setPen( pen );
474 QPainterPath path;
475 path.addPolygon( points );
476 p->drawPath( path );
477 }
478 }
479 else
480 {
481 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
483 {
484 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
485 // and clamp it to a reasonable range. It's the best we can do in this situation!
486 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
487 }
488
489 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
490 for ( const QPolygonF &part : mline )
491 {
492 if ( applyPatternTweaks )
493 {
494 drawPathWithDashPatternTweaks( p, part, pen );
495 }
496 else
497 {
498 p->setPen( pen );
499 QPainterPath path;
500 path.addPolygon( part );
501 p->drawPath( path );
502 }
503 }
504 }
505}
506
508{
509 QVariantMap map;
510 map[QStringLiteral( "line_color" )] = QgsColorUtils::colorToString( mColor );
511 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
512 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
513 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
514 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
515 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
516 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
517 map[QStringLiteral( "offset" )] = QString::number( mOffset );
518 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
519 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
520 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
521 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
522 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
523 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
524 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
525 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
526 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
527 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
528 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
529 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
530 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
531 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
532 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
533 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
534 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
535 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
536 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
537 return map;
538}
539
541{
547 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
548 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
549 l->setOffset( mOffset );
550 l->setPenJoinStyle( mPenJoinStyle );
551 l->setPenCapStyle( mPenCapStyle );
552 l->setUseCustomDashPattern( mUseCustomDashPattern );
553 l->setCustomDashVector( mCustomDashVector );
554 l->setDrawInsidePolygon( mDrawInsidePolygon );
556 l->setDashPatternOffset( mDashPatternOffset );
557 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
558 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
559 l->setTrimDistanceStart( mTrimDistanceStart );
560 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
561 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
562 l->setTrimDistanceEnd( mTrimDistanceEnd );
563 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
564 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
565 l->setAlignDashPattern( mAlignDashPattern );
566 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
567
569 copyPaintEffect( l );
570 return l;
571}
572
573void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
574{
575 if ( mPenStyle == Qt::NoPen )
576 return;
577
578 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
579 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
580 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
581 element.appendChild( symbolizerElem );
582
583 // <Geometry>
584 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
585
586 // <Stroke>
587 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
588 symbolizerElem.appendChild( strokeElem );
589
590 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
592 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
594 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
595
596 // <se:PerpendicularOffset>
597 if ( !qgsDoubleNear( mOffset, 0.0 ) )
598 {
599 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
601 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
602 symbolizerElem.appendChild( perpOffsetElem );
603 }
604}
605
606QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
607{
608 if ( mUseCustomDashPattern )
609 {
610 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
611 mPen.color(), mPenJoinStyle,
612 mPenCapStyle, mOffset, &mCustomDashVector );
613 }
614 else
615 {
616 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
617 mPenCapStyle, mOffset );
618 }
619}
620
622{
623 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
624
625 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
626 if ( strokeElem.isNull() )
627 return nullptr;
628
629 Qt::PenStyle penStyle;
630 QColor color;
631 double width;
632 Qt::PenJoinStyle penJoinStyle;
633 Qt::PenCapStyle penCapStyle;
634 QVector<qreal> customDashVector;
635
637 color, width,
640 return nullptr;
641
642 double offset = 0.0;
643 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
644 if ( !perpOffsetElem.isNull() )
645 {
646 bool ok;
647 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
648 if ( ok )
649 offset = d;
650 }
651
652 double scaleFactor = 1.0;
653 const QString uom = element.attribute( QStringLiteral( "uom" ) );
654 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
655 width = width * scaleFactor;
656 offset = offset * scaleFactor;
657
659 l->setOutputUnit( sldUnitSize );
660 l->setOffset( offset );
663 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
665 return l;
666}
667
668void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
669{
670 if ( !dataDefinedProperties().hasActiveProperties() )
671 return; // shortcut
672
673 //data defined properties
674 bool hasStrokeWidthExpression = false;
676 {
678 double scaledWidth = context.renderContext().convertToPainterUnits(
681 pen.setWidthF( scaledWidth );
682 selPen.setWidthF( scaledWidth );
683 hasStrokeWidthExpression = true;
684 }
685
686 //color
688 {
690
692 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
693 pen.setColor( penColor );
694 }
695
696 //offset
698 {
701 }
702
703 //dash dot vector
704
705 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
706 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
707 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
708
710 {
711 QVector<qreal> dashVector;
713 if ( !QgsVariantUtils::isNull( exprVal ) )
714 {
715 QStringList dashList = exprVal.toString().split( ';' );
716 QStringList::const_iterator dashIt = dashList.constBegin();
717 for ( ; dashIt != dashList.constEnd(); ++dashIt )
718 {
719 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
720 }
721 pen.setDashPattern( dashVector );
722 }
723 }
724 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::StrokeWidth ) && mUseCustomDashPattern )
725 {
726 //re-scale pattern vector after data defined pen width was applied
727
728 QVector<qreal> scaledVector;
729 for ( double v : std::as_const( mCustomDashVector ) )
730 {
731 //the dash is specified in terms of pen widths, therefore the division
732 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
733 }
734 mPen.setDashPattern( scaledVector );
735 }
736
737 // dash pattern offset
738 double patternOffset = mDashPatternOffset;
739 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::DashPatternOffset ) && pen.style() != Qt::SolidLine )
740 {
741 context.setOriginalValueVariable( patternOffset );
743 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
744 }
745
746 //line style
748 {
751 if ( !QgsVariantUtils::isNull( exprVal ) )
752 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
753 }
754
755 //join style
757 {
760 if ( !QgsVariantUtils::isNull( exprVal ) )
761 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
762 }
763
764 //cap style
766 {
769 if ( !QgsVariantUtils::isNull( exprVal ) )
770 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
771 }
772}
773
774void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
775{
776 if ( pen.dashPattern().empty() || points.size() < 2 )
777 return;
778
779 if ( pen.widthF() <= 1.0 )
780 {
781 pen.setWidthF( 1.0001 );
782 }
783
784 QVector< qreal > sourcePattern = pen.dashPattern();
785 const double dashWidthDiv = pen.widthF();
786 // back to painter units
787 for ( int i = 0; i < sourcePattern.size(); ++ i )
788 sourcePattern[i] *= pen.widthF();
789
790 QVector< qreal > buffer;
791 QPolygonF bufferedPoints;
792 QPolygonF previousSegmentBuffer;
793 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
794 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
795 // and then append the buffer to the output pattern.
796
797 auto ptIt = points.constBegin();
798 double totalBufferLength = 0;
799 int patternIndex = 0;
800 double currentRemainingDashLength = 0;
801 double currentRemainingGapLength = 0;
802
803 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
804 {
805 QVector< qreal > result;
806 result.reserve( buffer.size() );
807 for ( auto it = buffer.begin(); it != buffer.end(); )
808 {
809 qreal dash = *it++;
810 qreal gap = *it++;
811 while ( dash == 0 && !result.empty() )
812 {
813 result.last() += gap;
814
815 if ( it == buffer.end() )
816 return result;
817 dash = *it++;
818 gap = *it++;
819 }
820 while ( gap == 0 && it != buffer.end() )
821 {
822 dash += *it++;
823 gap = *it++;
824 }
825 result << dash << gap;
826 }
827 return result;
828 };
829
830 double currentBufferLineLength = 0;
831 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
832 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
833 {
834 if ( buffer.empty() || bufferedPoints.size() < 2 )
835 {
836 return;
837 }
838
839 if ( currentRemainingDashLength )
840 {
841 // ended midway through a dash -- we want to finish this off
842 buffer << currentRemainingDashLength << 0.0;
843 totalBufferLength += currentRemainingDashLength;
844 }
845 QVector< qreal > compressed = compressPattern( buffer );
846 if ( !currentRemainingDashLength )
847 {
848 // ended midway through a gap -- we don't want this, we want to end at previous dash
849 totalBufferLength -= compressed.last();
850 compressed.last() = 0;
851 }
852
853 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
854 const double scaleFactor = currentBufferLineLength / totalBufferLength;
855
856 bool shouldFlushPreviousSegmentBuffer = false;
857
858 if ( !previousSegmentBuffer.empty() )
859 {
860 // add first dash from current buffer
861 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
862 if ( !firstDashSubstring.empty() )
863 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
864
865 // then we skip over the first dash and gap for this segment
866 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
867
868 compressed = compressed.mid( 2 );
869 shouldFlushPreviousSegmentBuffer = !compressed.empty();
870 }
871
872 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
873 {
874 QPen adjustedPen = pen;
875 adjustedPen.setStyle( Qt::SolidLine );
876 painter->setPen( adjustedPen );
877 QPainterPath path;
878 path.addPolygon( previousSegmentBuffer );
879 painter->drawPath( path );
880 previousSegmentBuffer.clear();
881 }
882
883 double finalDash = 0;
884 if ( nextPoint )
885 {
886 // sharp bend:
887 // 1. rewind buffered points line by final dash and gap length
888 // (later) 2. draw the bend with a solid line of length 2 * final dash size
889
890 if ( !compressed.empty() )
891 {
892 finalDash = compressed.at( compressed.size() - 2 );
893 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
894
895 const QPolygonF thisPoints = bufferedPoints;
896 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
897 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
898 }
899 else
900 {
901 previousSegmentBuffer << bufferedPoints;
902 }
903 }
904
905 currentBufferLineLength = 0;
906 currentRemainingDashLength = 0;
907 currentRemainingGapLength = 0;
908 totalBufferLength = 0;
909 buffer.clear();
910
911 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
912 {
913 QPen adjustedPen = pen;
914 if ( !compressed.empty() )
915 {
916 // maximum size of dash pattern is 32 elements
917 compressed = compressed.mid( 0, 32 );
918 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
919 adjustedPen.setDashPattern( compressed );
920 }
921 else
922 {
923 adjustedPen.setStyle( Qt::SolidLine );
924 }
925
926 painter->setPen( adjustedPen );
927 QPainterPath path;
928 path.addPolygon( bufferedPoints );
929 painter->drawPath( path );
930 }
931
932 bufferedPoints.clear();
933 };
934
935 QPointF p1;
936 QPointF p2 = *ptIt;
937 ptIt++;
938 bufferedPoints << p2;
939 for ( ; ptIt != points.constEnd(); ++ptIt )
940 {
941 p1 = *ptIt;
942 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
943 {
944 continue;
945 }
946
947 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
948 currentBufferLineLength += remainingSegmentDistance;
949 while ( true )
950 {
951 // handle currentRemainingDashLength/currentRemainingGapLength
952 if ( currentRemainingDashLength > 0 )
953 {
954 // bit more of dash to insert
955 if ( remainingSegmentDistance >= currentRemainingDashLength )
956 {
957 // all of dash fits in
958 buffer << currentRemainingDashLength << 0.0;
959 totalBufferLength += currentRemainingDashLength;
960 remainingSegmentDistance -= currentRemainingDashLength;
961 patternIndex++;
962 currentRemainingDashLength = 0.0;
963 currentRemainingGapLength = sourcePattern.at( patternIndex );
964 if ( currentRemainingGapLength == 0.0 )
965 {
966 patternIndex++;
967 }
968 }
969 else
970 {
971 // only part of remaining dash fits in
972 buffer << remainingSegmentDistance << 0.0;
973 totalBufferLength += remainingSegmentDistance;
974 currentRemainingDashLength -= remainingSegmentDistance;
975 break;
976 }
977 }
978 if ( currentRemainingGapLength > 0 )
979 {
980 // bit more of gap to insert
981 if ( remainingSegmentDistance >= currentRemainingGapLength )
982 {
983 // all of gap fits in
984 buffer << 0.0 << currentRemainingGapLength;
985 totalBufferLength += currentRemainingGapLength;
986 remainingSegmentDistance -= currentRemainingGapLength;
987 currentRemainingGapLength = 0.0;
988 patternIndex++;
989 }
990 else
991 {
992 // only part of remaining gap fits in
993 buffer << 0.0 << remainingSegmentDistance;
994 totalBufferLength += remainingSegmentDistance;
995 currentRemainingGapLength -= remainingSegmentDistance;
996 break;
997 }
998 }
999
1000 if ( patternIndex + 1 >= sourcePattern.size() )
1001 {
1002 patternIndex = 0;
1003 }
1004
1005 const double nextPatternDashLength = sourcePattern.at( patternIndex );
1006 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
1007 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1008 {
1009 buffer << nextPatternDashLength << nextPatternGapLength;
1010 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1011 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1012 patternIndex += 2;
1013 }
1014 else if ( nextPatternDashLength <= remainingSegmentDistance )
1015 {
1016 // can fit in "dash", but not "gap"
1017 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1018 totalBufferLength += remainingSegmentDistance;
1019 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1020 currentRemainingDashLength = 0;
1021 patternIndex++;
1022 break;
1023 }
1024 else
1025 {
1026 // can't fit in "dash"
1027 buffer << remainingSegmentDistance << 0.0;
1028 totalBufferLength += remainingSegmentDistance;
1029 currentRemainingGapLength = 0;
1030 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1031 break;
1032 }
1033 }
1034
1035 bufferedPoints << p1;
1036 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1037 {
1038 QPointF nextPoint = *( ptIt + 1 );
1039
1040 // extreme angles form more than 45 degree angle at a node
1041 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1042 {
1043 // extreme angle. Rescale buffer and flush
1044 flushBuffer( &nextPoint );
1045 bufferedPoints << p1;
1046 // restart the line with the full length of the most recent dash element -- see
1047 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1048 if ( patternIndex % 2 == 1 )
1049 {
1050 patternIndex--;
1051 }
1052 currentRemainingDashLength = sourcePattern.at( patternIndex );
1053 }
1054 }
1055
1056 p2 = p1;
1057 }
1058
1059 flushBuffer( nullptr );
1060 if ( !previousSegmentBuffer.empty() )
1061 {
1062 QPen adjustedPen = pen;
1063 adjustedPen.setStyle( Qt::SolidLine );
1064 painter->setPen( adjustedPen );
1065 QPainterPath path;
1066 path.addPolygon( previousSegmentBuffer );
1067 painter->drawPath( path );
1068 previousSegmentBuffer.clear();
1069 }
1070}
1071
1073{
1074 if ( mDrawInsidePolygon )
1075 {
1076 //set to clip line to the interior of polygon, so we expect no bleed
1077 return 0;
1078 }
1079 else
1080 {
1081 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1083 }
1084}
1085
1087{
1088 unit = mCustomDashPatternUnit;
1089 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1090}
1091
1093{
1094 return mPenStyle;
1095}
1096
1113
1123
1125{
1126 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1127}
1128
1130{
1131 return mAlignDashPattern;
1132}
1133
1135{
1136 mAlignDashPattern = enabled;
1137}
1138
1140{
1141 return mPatternCartographicTweakOnSharpCorners;
1142}
1143
1145{
1146 mPatternCartographicTweakOnSharpCorners = enabled;
1147}
1148
1166
1168
1170
1171class MyLine
1172{
1173 public:
1174 MyLine( QPointF p1, QPointF p2 )
1175 : mVertical( false )
1176 , mIncreasing( false )
1177 , mT( 0.0 )
1178 , mLength( 0.0 )
1179 {
1180 if ( p1 == p2 )
1181 return; // invalid
1182
1183 // tangent and direction
1184 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1185 {
1186 // vertical line - tangent undefined
1187 mVertical = true;
1188 mIncreasing = ( p2.y() > p1.y() );
1189 }
1190 else
1191 {
1192 mVertical = false;
1193 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1194 mIncreasing = ( p2.x() > p1.x() );
1195 }
1196
1197 // length
1198 double x = ( p2.x() - p1.x() );
1199 double y = ( p2.y() - p1.y() );
1200 mLength = std::sqrt( x * x + y * y );
1201 }
1202
1203 // return angle in radians
1204 double angle()
1205 {
1206 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1207
1208 if ( !mIncreasing )
1209 a += M_PI;
1210 return a;
1211 }
1212
1213 // return difference for x,y when going along the line with specified interval
1214 QPointF diffForInterval( double interval )
1215 {
1216 if ( mVertical )
1217 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1218
1219 double alpha = std::atan( mT );
1220 double dx = std::cos( alpha ) * interval;
1221 double dy = std::sin( alpha ) * interval;
1222 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1223 }
1224
1225 double length() const { return mLength; }
1226
1227 protected:
1228 bool mVertical;
1229 bool mIncreasing;
1230 double mT;
1231 double mLength;
1232};
1233
1235
1236//
1237// QgsTemplatedLineSymbolLayerBase
1238//
1240 : mRotateSymbols( rotateSymbol )
1241 , mInterval( interval )
1242{
1243
1244}
1245
1269
1274
1276
1278{
1279 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1280 if ( mRenderingFeature )
1281 {
1282 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1283 // until after we've received the final part
1284 mFeatureSymbolOpacity = context.opacity();
1285 mCurrentFeatureIsSelected = useSelectedColor;
1286 }
1287
1288 double offset = mOffset;
1289
1291 {
1294 }
1295
1297
1299 {
1301 if ( !QgsVariantUtils::isNull( exprVal ) )
1302 {
1303 QString placementString = exprVal.toString();
1304 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1305 {
1307 }
1308 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1309 {
1311 }
1312 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1313 {
1315 }
1316 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1317 {
1319 }
1320 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1321 {
1323 }
1324 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1325 {
1327 }
1328 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1329 {
1331 }
1332 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1333 {
1335 }
1336 else
1337 {
1339 }
1340 }
1341 }
1342
1343 QgsScopedQPainterState painterState( context.renderContext().painter() );
1344
1345 double averageOver = mAverageAngleLength;
1347 {
1348 context.setOriginalValueVariable( mAverageAngleLength );
1350 }
1351 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1352
1353 if ( qgsDoubleNear( offset, 0.0 ) )
1354 {
1356 renderPolylineInterval( points, context, averageOver );
1358 renderPolylineCentral( points, context, averageOver );
1360 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1362 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1363 {
1364 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1365 mHasRenderedFirstPart = mRenderingFeature;
1366 }
1368 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1370 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1372 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1374 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1375 }
1376 else
1377 {
1378 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1380
1381 for ( int part = 0; part < mline.count(); ++part )
1382 {
1383 const QPolygonF &points2 = mline[ part ];
1384
1386 renderPolylineInterval( points2, context, averageOver );
1388 renderPolylineCentral( points2, context, averageOver );
1390 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1392 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1394 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1396 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1397 {
1398 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1399 mHasRenderedFirstPart = mRenderingFeature;
1400 }
1402 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1404 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1405 }
1406 }
1407}
1408
1409void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1410{
1411 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1412
1413 if ( curvePolygon )
1414 {
1415 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1416 }
1417
1418 QgsExpressionContextScope *scope = nullptr;
1419 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1421 {
1422 scope = new QgsExpressionContextScope();
1423 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1424 }
1425
1426 switch ( mRingFilter )
1427 {
1428 case AllRings:
1429 case ExteriorRingOnly:
1430 {
1431 if ( scope )
1433
1434 renderPolyline( points, context );
1435 break;
1436 }
1437 case InteriorRingsOnly:
1438 break;
1439 }
1440
1441 if ( rings )
1442 {
1443 switch ( mRingFilter )
1444 {
1445 case AllRings:
1446 case InteriorRingsOnly:
1447 {
1448 mOffset = -mOffset; // invert the offset for rings!
1449 for ( int i = 0; i < rings->size(); ++i )
1450 {
1451 if ( curvePolygon )
1452 {
1453 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1454 }
1455 if ( scope )
1457
1458 renderPolyline( rings->at( i ), context );
1459 }
1460 mOffset = -mOffset;
1461 }
1462 break;
1463 case ExteriorRingOnly:
1464 break;
1465 }
1466 }
1467}
1468
1470{
1472 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1473 {
1475 }
1476 return unit;
1477}
1478
1480{
1482 mIntervalUnit = unit;
1483 mOffsetAlongLineUnit = unit;
1484 mAverageAngleLengthUnit = unit;
1485}
1486
1494
1505
1507{
1508 QVariantMap map;
1509 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1510 map[QStringLiteral( "interval" )] = QString::number( interval() );
1511 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1512 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1513 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1514 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1515 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1516 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1517 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1518 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1519 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1520 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1521 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1522
1523 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1524
1525 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1526 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1527 return map;
1528}
1529
1531{
1532 return mPlaceOnEveryPart
1533 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1534 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1535 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1536}
1537
1539{
1540 installMasks( context, true );
1541
1542 mRenderingFeature = true;
1543 mHasRenderedFirstPart = false;
1544}
1545
1547{
1548 mRenderingFeature = false;
1549 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1550 {
1551 removeMasks( context, true );
1552 return;
1553 }
1554
1555 const double prevOpacity = subSymbol()->opacity();
1556 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1557
1558 // render final point
1559 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1560 mFeatureSymbolOpacity = 1;
1561 subSymbol()->setOpacity( prevOpacity );
1562
1563 removeMasks( context, true );
1564}
1565
1567{
1568 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1569 destLayer->setOffset( mOffset );
1570 destLayer->setPlacements( placements() );
1571 destLayer->setOffsetUnit( mOffsetUnit );
1573 destLayer->setIntervalUnit( intervalUnit() );
1575 destLayer->setOffsetAlongLine( offsetAlongLine() );
1578 destLayer->setAverageAngleLength( mAverageAngleLength );
1579 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1580 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1581 destLayer->setRingFilter( mRingFilter );
1582 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1583 copyDataDefinedProperties( destLayer );
1584 copyPaintEffect( destLayer );
1585}
1586
1588{
1589 if ( properties.contains( QStringLiteral( "offset" ) ) )
1590 {
1591 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1592 }
1593 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1594 {
1595 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1596 }
1597 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1598 {
1599 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1600 }
1601 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1602 {
1603 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1604 }
1605 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1606 {
1607 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1608 }
1609 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1610 {
1611 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1612 }
1613
1614 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1615 {
1616 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1617 }
1618 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1619 {
1620 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1621 }
1622
1623 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1624 {
1625 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1626 }
1627 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1628 {
1629 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1630 }
1631 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1632 {
1633 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1634 }
1635
1636 if ( properties.contains( QStringLiteral( "placement" ) ) )
1637 {
1638 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1640 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1642 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1644 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1646 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1648 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1650 else
1652 }
1653 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1654 {
1655 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1656 destLayer->setPlacements( placements );
1657 }
1658
1659 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1660 {
1661 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1662 }
1663
1664 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1665
1667}
1668
1669void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1670{
1671 if ( points.isEmpty() )
1672 return;
1673
1674 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1675 double lengthLeft = 0; // how much is left until next marker
1676
1677 QgsRenderContext &rc = context.renderContext();
1678 double interval = mInterval;
1679
1681 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1682
1684 {
1685 context.setOriginalValueVariable( mInterval );
1687 }
1688 if ( interval <= 0 )
1689 {
1690 interval = 0.1;
1691 }
1692 double offsetAlongLine = mOffsetAlongLine;
1694 {
1695 context.setOriginalValueVariable( mOffsetAlongLine );
1697 }
1698
1699 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1701 {
1702 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1703 // and clamp it to a reasonable range. It's the best we can do in this situation!
1704 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, Qgis::RenderUnit::Millimeters ), 10.0 ), 100.0 );
1705 }
1706
1707 if ( painterUnitInterval < 0 )
1708 return;
1709
1710 double painterUnitOffsetAlongLine = 0;
1711
1712 // only calculated if we need it!
1713 double totalLength = -1;
1714
1715 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1716 {
1717 switch ( offsetAlongLineUnit() )
1718 {
1727 break;
1729 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1730 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1731 break;
1732 }
1733
1734 if ( points.isClosed() )
1735 {
1736 if ( painterUnitOffsetAlongLine > 0 )
1737 {
1738 if ( totalLength < 0 )
1739 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1740 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1741 }
1742 else if ( painterUnitOffsetAlongLine < 0 )
1743 {
1744 if ( totalLength < 0 )
1745 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1746 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1747 }
1748 }
1749 }
1750
1752 {
1753 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1754 // and clamp it to a reasonable range. It's the best we can do in this situation!
1755 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1756 }
1757
1758 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1759
1760 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1761 {
1762 QVector< QPointF > angleStartPoints;
1763 QVector< QPointF > symbolPoints;
1764 QVector< QPointF > angleEndPoints;
1765
1766 // we collect 3 arrays of points. These correspond to
1767 // 1. the actual point at which to render the symbol
1768 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1769 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1770 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1771 // (or trace past the final point)
1772 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1773
1774 if ( symbolPoints.empty() )
1775 {
1776 // no symbols to draw, shortcut out early
1777 return;
1778 }
1779
1780 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1781 {
1782 // avoid duplicate points at start and end of closed rings
1783 symbolPoints.pop_back();
1784 }
1785
1786 angleEndPoints.reserve( symbolPoints.size() );
1787 angleStartPoints.reserve( symbolPoints.size() );
1788 if ( averageOver <= painterUnitOffsetAlongLine )
1789 {
1790 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1791 }
1792 else
1793 {
1794 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1795 }
1796 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1797
1798 int pointNum = 0;
1799 for ( int i = 0; i < symbolPoints.size(); ++ i )
1800 {
1801 if ( context.renderContext().renderingStopped() )
1802 break;
1803
1804 const QPointF pt = symbolPoints[i];
1805 const QPointF startPt = angleStartPoints[i];
1806 const QPointF endPt = angleEndPoints[i];
1807
1808 MyLine l( startPt, endPt );
1809 // rotate marker (if desired)
1810 if ( rotateSymbols() )
1811 {
1812 setSymbolLineAngle( l.angle() * 180 / M_PI );
1813 }
1814
1816 renderSymbol( pt, context.feature(), rc, -1, useSelectedColor );
1817 }
1818 }
1819 else
1820 {
1821 // not averaging line angle -- always use exact section angle
1822 int pointNum = 0;
1823 QPointF lastPt = points[0];
1824 for ( int i = 1; i < points.count(); ++i )
1825 {
1826 if ( context.renderContext().renderingStopped() )
1827 break;
1828
1829 const QPointF &pt = points[i];
1830
1831 if ( lastPt == pt ) // must not be equal!
1832 continue;
1833
1834 // for each line, find out dx and dy, and length
1835 MyLine l( lastPt, pt );
1836 QPointF diff = l.diffForInterval( painterUnitInterval );
1837
1838 // if there's some length left from previous line
1839 // use only the rest for the first point in new line segment
1840 double c = 1 - lengthLeft / painterUnitInterval;
1841
1842 lengthLeft += l.length();
1843
1844 // rotate marker (if desired)
1845 if ( rotateSymbols() )
1846 {
1847 setSymbolLineAngle( l.angle() * 180 / M_PI );
1848 }
1849
1850 // while we're not at the end of line segment, draw!
1851 while ( lengthLeft > painterUnitInterval )
1852 {
1853 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1854 lastPt += c * diff;
1855 lengthLeft -= painterUnitInterval;
1857 renderSymbol( lastPt, context.feature(), rc, -1, useSelectedColor );
1858 c = 1; // reset c (if wasn't 1 already)
1859 }
1860
1861 lastPt = pt;
1862 }
1863
1864 }
1865}
1866
1867static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1868{
1869 // calc average angle between the previous and next point
1870 double a1 = MyLine( prevPt, pt ).angle();
1871 double a2 = MyLine( pt, nextPt ).angle();
1872 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1873
1874 return std::atan2( unitY, unitX );
1875}
1876
1877void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1878{
1879 if ( points.isEmpty() )
1880 return;
1881
1882 QgsRenderContext &rc = context.renderContext();
1883
1884 int i = -1, maxCount = 0;
1885 bool isRing = false;
1886
1888 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1890
1891 double offsetAlongLine = mOffsetAlongLine;
1893 {
1894 context.setOriginalValueVariable( mOffsetAlongLine );
1896 }
1897
1898 // only calculated if we need it!!
1899 double totalLength = -1;
1900 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1901 {
1902 //scale offset along line
1903 switch ( offsetAlongLineUnit() )
1904 {
1913 break;
1915 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1916 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1917 break;
1918 }
1919 if ( points.isClosed() )
1920 {
1921 if ( offsetAlongLine > 0 )
1922 {
1923 if ( totalLength < 0 )
1924 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1925 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1926 }
1927 else if ( offsetAlongLine < 0 )
1928 {
1929 if ( totalLength < 0 )
1930 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1931 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1932 }
1933 }
1934 }
1935
1936 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1937 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1941 {
1943 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1944
1945 QgsVertexId vId;
1946 QgsPoint vPoint;
1947 double x, y, z;
1948 QPointF mapPoint;
1949 int pointNum = 0;
1950 const int numPoints = context.renderContext().geometry()->nCoordinates();
1951 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1952 {
1953 if ( context.renderContext().renderingStopped() )
1954 break;
1955
1957
1958 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1959 continue;
1960
1961 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1962 continue;
1963
1966 {
1967 //transform
1968 x = vPoint.x();
1969 y = vPoint.y();
1970 z = 0.0;
1971 if ( ct.isValid() )
1972 {
1973 ct.transformInPlace( x, y, z );
1974 }
1975 mapPoint.setX( x );
1976 mapPoint.setY( y );
1977 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1978 if ( rotateSymbols() )
1979 {
1980 double angle = context.renderContext().geometry()->vertexAngle( vId );
1981 setSymbolLineAngle( angle * 180 / M_PI );
1982 }
1983 renderSymbol( mapPoint, context.feature(), rc, -1, useSelectedColor );
1984 }
1985 }
1986
1987 return;
1988 }
1989
1990 int pointNum = 0;
1991
1992 switch ( placement )
1993 {
1995 {
1996 i = 0;
1997 maxCount = 1;
1998 break;
1999 }
2000
2002 {
2003 i = points.count() - 1;
2004 pointNum = i;
2005 maxCount = points.count();
2006 break;
2007 }
2008
2010 {
2011 i = 1;
2012 pointNum = 1;
2013 maxCount = points.count() - 1;
2014 break;
2015 }
2016
2019 {
2021 maxCount = points.count();
2022 if ( points.first() == points.last() )
2023 isRing = true;
2024 break;
2025 }
2026
2030 {
2031 return;
2032 }
2033 }
2034
2036 {
2037 double distance;
2039 renderOffsetVertexAlongLine( points, i, distance, context, placement );
2040
2041 return;
2042 }
2043
2044 QPointF prevPoint;
2045 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2046 prevPoint = points.at( 0 );
2047
2048 QPointF symbolPoint;
2049 for ( ; i < maxCount; ++i )
2050 {
2052
2053 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2054 {
2055 continue; // don't draw the last marker - it has been drawn already
2056 }
2057
2059 {
2060 QPointF currentPoint = points.at( i );
2061 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2062 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2063 if ( rotateSymbols() )
2064 {
2065 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2066 currentPoint.x() - prevPoint.x() );
2067 setSymbolLineAngle( angle * 180 / M_PI );
2068 }
2069 prevPoint = currentPoint;
2070 }
2071 else
2072 {
2073 symbolPoint = points.at( i );
2074 // rotate marker (if desired)
2075 if ( rotateSymbols() )
2076 {
2077 double angle = markerAngle( points, isRing, i );
2078 setSymbolLineAngle( angle * 180 / M_PI );
2079 }
2080 }
2081
2082 mFinalVertex = symbolPoint;
2083 if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2084 renderSymbol( symbolPoint, context.feature(), rc, -1, useSelectedColor );
2085 }
2086}
2087
2088double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2089{
2090 double angle = 0;
2091 const QPointF &pt = points[vertex];
2092
2093 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2094 {
2095 int prevIndex = vertex - 1;
2096 int nextIndex = vertex + 1;
2097
2098 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2099 {
2100 prevIndex = points.count() - 2;
2101 nextIndex = 1;
2102 }
2103
2104 QPointF prevPoint, nextPoint;
2105 while ( prevIndex >= 0 )
2106 {
2107 prevPoint = points[ prevIndex ];
2108 if ( prevPoint != pt )
2109 {
2110 break;
2111 }
2112 --prevIndex;
2113 }
2114
2115 while ( nextIndex < points.count() )
2116 {
2117 nextPoint = points[ nextIndex ];
2118 if ( nextPoint != pt )
2119 {
2120 break;
2121 }
2122 ++nextIndex;
2123 }
2124
2125 if ( prevIndex >= 0 && nextIndex < points.count() )
2126 {
2127 angle = _averageAngle( prevPoint, pt, nextPoint );
2128 }
2129 }
2130 else //no ring and vertex is at start / at end
2131 {
2132 if ( vertex == 0 )
2133 {
2134 while ( vertex < points.size() - 1 )
2135 {
2136 const QPointF &nextPt = points[vertex + 1];
2137 if ( pt != nextPt )
2138 {
2139 angle = MyLine( pt, nextPt ).angle();
2140 return angle;
2141 }
2142 ++vertex;
2143 }
2144 }
2145 else
2146 {
2147 // use last segment's angle
2148 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2149 {
2150 const QPointF &prevPt = points[vertex - 1];
2151 if ( pt != prevPt )
2152 {
2153 angle = MyLine( prevPt, pt ).angle();
2154 return angle;
2155 }
2156 --vertex;
2157 }
2158 }
2159 }
2160 return angle;
2161}
2162
2163void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2164{
2165 if ( points.isEmpty() )
2166 return;
2167
2168 QgsRenderContext &rc = context.renderContext();
2169 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2170 if ( qgsDoubleNear( distance, 0.0 ) )
2171 {
2172 // rotate marker (if desired)
2173 if ( rotateSymbols() )
2174 {
2175 bool isRing = false;
2176 if ( points.first() == points.last() )
2177 isRing = true;
2178 double angle = markerAngle( points, isRing, vertex );
2179 setSymbolLineAngle( angle * 180 / M_PI );
2180 }
2181 mFinalVertex = points[vertex];
2182 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2183 renderSymbol( points[vertex], context.feature(), rc, -1, useSelectedColor );
2184 return;
2185 }
2186
2187 int pointIncrement = distance > 0 ? 1 : -1;
2188 QPointF previousPoint = points[vertex];
2189 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2190 int endPoint = distance > 0 ? points.count() - 1 : 0;
2191 double distanceLeft = std::fabs( distance );
2192
2193 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2194 {
2195 const QPointF &pt = points[i];
2196
2197 if ( previousPoint == pt ) // must not be equal!
2198 continue;
2199
2200 // create line segment
2201 MyLine l( previousPoint, pt );
2202
2203 if ( distanceLeft < l.length() )
2204 {
2205 //destination point is in current segment
2206 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2207 // rotate marker (if desired)
2208 if ( rotateSymbols() )
2209 {
2210 setSymbolLineAngle( l.angle() * 180 / M_PI );
2211 }
2212 mFinalVertex = markerPoint;
2213 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2214 renderSymbol( markerPoint, context.feature(), rc, -1, useSelectedColor );
2215 return;
2216 }
2217
2218 distanceLeft -= l.length();
2219 previousPoint = pt;
2220 }
2221
2222 //didn't find point
2223}
2224
2225void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2226{
2227 if ( p.empty() )
2228 return;
2229
2230 QVector< QPointF > points = p;
2231 const bool closedRing = points.first() == points.last();
2232
2233 double lengthLeft = initialOffset;
2234
2235 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2236 if ( initialLagLeft < 0 && closedRing )
2237 {
2238 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2239 QPointF lastPt = points.constLast();
2240 QVector< QPointF > pseudoPoints;
2241 for ( int i = points.count() - 2; i > 0; --i )
2242 {
2243 if ( initialLagLeft >= 0 )
2244 {
2245 break;
2246 }
2247
2248 const QPointF &pt = points[i];
2249
2250 if ( lastPt == pt ) // must not be equal!
2251 continue;
2252
2253 MyLine l( lastPt, pt );
2254 initialLagLeft += l.length();
2255 lastPt = pt;
2256
2257 pseudoPoints << pt;
2258 }
2259 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2260
2261 points = pseudoPoints;
2262 points.append( p );
2263 }
2264 else
2265 {
2266 while ( initialLagLeft < 0 )
2267 {
2268 dest << points.constFirst();
2269 initialLagLeft += intervalPainterUnits;
2270 }
2271 }
2272 if ( initialLag > 0 )
2273 {
2274 lengthLeft += intervalPainterUnits - initialLagLeft;
2275 }
2276
2277 QPointF lastPt = points[0];
2278 for ( int i = 1; i < points.count(); ++i )
2279 {
2280 const QPointF &pt = points[i];
2281
2282 if ( lastPt == pt ) // must not be equal!
2283 {
2284 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2285 {
2286 lastPt = points[0];
2287 i = 0;
2288 }
2289 continue;
2290 }
2291
2292 // for each line, find out dx and dy, and length
2293 MyLine l( lastPt, pt );
2294 QPointF diff = l.diffForInterval( intervalPainterUnits );
2295
2296 // if there's some length left from previous line
2297 // use only the rest for the first point in new line segment
2298 double c = 1 - lengthLeft / intervalPainterUnits;
2299
2300 lengthLeft += l.length();
2301
2302
2303 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2304 {
2305 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2306 lastPt += c * diff;
2307 lengthLeft -= intervalPainterUnits;
2308 dest << lastPt;
2309 c = 1; // reset c (if wasn't 1 already)
2310 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2311 break;
2312 }
2313 lastPt = pt;
2314
2315 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2316 break;
2317
2318 // if a closed ring, we keep looping around the ring until we hit the required number of points
2319 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2320 {
2321 lastPt = points[0];
2322 i = 0;
2323 }
2324 }
2325
2326 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2327 {
2328 // pad with repeating last point to match desired size
2329 while ( dest.size() < numberPointsRequired )
2330 dest << points.constLast();
2331 }
2332}
2333
2334void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2335{
2336 if ( !points.isEmpty() )
2337 {
2338 // calc length
2339 qreal length = 0;
2340 QPolygonF::const_iterator it = points.constBegin();
2341 QPointF last = *it;
2342 for ( ++it; it != points.constEnd(); ++it )
2343 {
2344 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2345 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2346 last = *it;
2347 }
2348 if ( qgsDoubleNear( length, 0.0 ) )
2349 return;
2350
2351 const double midPoint = length / 2;
2352
2353 QPointF pt;
2354 double thisSymbolAngle = 0;
2355
2356 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2357 {
2358 QVector< QPointF > angleStartPoints;
2359 QVector< QPointF > symbolPoints;
2360 QVector< QPointF > angleEndPoints;
2361 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2362 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2363 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2364 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2365
2366 pt = symbolPoints.at( 1 );
2367 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2368 thisSymbolAngle = l.angle();
2369 }
2370 else
2371 {
2372 // find the segment where the central point lies
2373 it = points.constBegin();
2374 last = *it;
2375 qreal last_at = 0, next_at = 0;
2376 QPointF next;
2377 for ( ++it; it != points.constEnd(); ++it )
2378 {
2379 next = *it;
2380 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2381 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2382 if ( next_at >= midPoint )
2383 break; // we have reached the center
2384 last = *it;
2385 last_at = next_at;
2386 }
2387
2388 // find out the central point on segment
2389 MyLine l( last, next ); // for line angle
2390 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2391 pt = last + ( next - last ) * k;
2392 thisSymbolAngle = l.angle();
2393 }
2394
2395 // draw the marker
2396 // rotate marker (if desired)
2397 if ( rotateSymbols() )
2398 {
2399 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2400 }
2401
2402 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2403 renderSymbol( pt, context.feature(), context.renderContext(), -1, useSelectedColor );
2404 }
2405}
2406
2411
2413{
2414 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2415 {
2416 delete symbol;
2417 return false;
2418 }
2419
2420 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2421 mColor = mMarker->color();
2422 return true;
2423}
2424
2425
2426
2427//
2428// QgsMarkerLineSymbolLayer
2429//
2430
2431QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2432 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2433{
2435}
2436
2438
2440{
2441 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2443
2444 if ( props.contains( QStringLiteral( "interval" ) ) )
2445 interval = props[QStringLiteral( "interval" )].toDouble();
2446 if ( props.contains( QStringLiteral( "rotate" ) ) )
2447 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2448
2449 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2450 setCommonProperties( x.get(), props );
2451 return x.release();
2452}
2453
2455{
2456 return QStringLiteral( "MarkerLine" );
2457}
2458
2459void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2460{
2461 mMarker->setColor( color );
2462 mColor = color;
2463}
2464
2466{
2467 return mMarker ? mMarker->color() : mColor;
2468}
2469
2471{
2472 // if being rotated, it gets initialized with every line segment
2474 if ( rotateSymbols() )
2476 mMarker->setRenderHints( hints );
2477
2478 mMarker->startRender( context.renderContext(), context.fields() );
2479}
2480
2482{
2483 mMarker->stopRender( context.renderContext() );
2484}
2485
2486
2488{
2489 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2491 return x.release();
2492}
2493
2494void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2495{
2496 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2497 {
2498 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2499 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2500 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2501 element.appendChild( symbolizerElem );
2502
2503 // <Geometry>
2504 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2505
2506 QString gap;
2508 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2510 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2512 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2514 // no way to get line/polygon's vertices, use a VendorOption
2515 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2516
2518 {
2520 gap = qgsDoubleToString( interval );
2521 }
2522
2523 if ( !rotateSymbols() )
2524 {
2525 // markers in LineSymbolizer must be drawn following the line orientation,
2526 // use a VendorOption when no marker rotation
2527 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2528 }
2529
2530 // <Stroke>
2531 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2532 symbolizerElem.appendChild( strokeElem );
2533
2534 // <GraphicStroke>
2535 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2536 strokeElem.appendChild( graphicStrokeElem );
2537
2538 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2539 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2540 {
2541 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2542 }
2543 else if ( layer )
2544 {
2545 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2546 }
2547 else
2548 {
2549 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2550 }
2551
2552 if ( !gap.isEmpty() )
2553 {
2554 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2556 graphicStrokeElem.appendChild( gapElem );
2557 }
2558
2559 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2560 {
2561 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2563 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2564 symbolizerElem.appendChild( perpOffsetElem );
2565 }
2566 }
2567}
2568
2570{
2571 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2572
2573 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2574 if ( strokeElem.isNull() )
2575 return nullptr;
2576
2577 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2578 if ( graphicStrokeElem.isNull() )
2579 return nullptr;
2580
2581 // retrieve vendor options
2582 bool rotateMarker = true;
2584
2585 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2586 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2587 {
2588 if ( it.key() == QLatin1String( "placement" ) )
2589 {
2590 if ( it.value() == QLatin1String( "points" ) )
2592 else if ( it.value() == QLatin1String( "firstPoint" ) )
2594 else if ( it.value() == QLatin1String( "lastPoint" ) )
2596 else if ( it.value() == QLatin1String( "centralPoint" ) )
2598 }
2599 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2600 {
2601 rotateMarker = it.value() == QLatin1String( "0" );
2602 }
2603 }
2604
2605 std::unique_ptr< QgsMarkerSymbol > marker;
2606
2608 if ( l )
2609 {
2610 QgsSymbolLayerList layers;
2611 layers.append( l );
2612 marker.reset( new QgsMarkerSymbol( layers ) );
2613 }
2614
2615 if ( !marker )
2616 return nullptr;
2617
2618 double interval = 0.0;
2619 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2620 if ( !gapElem.isNull() )
2621 {
2622 bool ok;
2623 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2624 if ( ok )
2625 interval = d;
2626 }
2627
2628 double offset = 0.0;
2629 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2630 if ( !perpOffsetElem.isNull() )
2631 {
2632 bool ok;
2633 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2634 if ( ok )
2635 offset = d;
2636 }
2637
2638 double scaleFactor = 1.0;
2639 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2640 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2641 interval = interval * scaleFactor;
2642 offset = offset * scaleFactor;
2643
2645 x->setOutputUnit( sldUnitSize );
2647 x->setInterval( interval );
2648 x->setSubSymbol( marker.release() );
2649 x->setOffset( offset );
2650 return x;
2651}
2652
2654{
2655 mMarker->setSize( width );
2656}
2657
2659{
2660 if ( key == QgsSymbolLayer::Property::Width && mMarker && property )
2661 {
2662 mMarker->setDataDefinedSize( property );
2663 }
2665}
2666
2668{
2669 const double prevOpacity = mMarker->opacity();
2670 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2672 mMarker->setOpacity( prevOpacity );
2673}
2674
2676{
2677 mMarker->setLineAngle( angle );
2678}
2679
2681{
2682 return mMarker->angle();
2683}
2684
2686{
2687 mMarker->setAngle( angle );
2688}
2689
2690void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2691{
2692 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2694
2695 mMarker->renderPoint( point, feature, context, layer, selected );
2696
2697 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2698}
2699
2701{
2702 return mMarker->size();
2703}
2704
2706{
2707 return mMarker->size( context );
2708}
2709
2715
2725
2727{
2728 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2729 if ( mMarker )
2730 attr.unite( mMarker->usedAttributes( context ) );
2731 return attr;
2732}
2733
2735{
2737 return true;
2738 if ( mMarker && mMarker->hasDataDefinedProperties() )
2739 return true;
2740 return false;
2741}
2742
2744{
2745 return ( mMarker->size( context ) / 2.0 ) +
2747}
2748
2749
2750//
2751// QgsHashedLineSymbolLayer
2752//
2753
2754QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2755 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2756{
2757 setSubSymbol( new QgsLineSymbol() );
2758}
2759
2761
2763{
2764 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2766
2767 if ( props.contains( QStringLiteral( "interval" ) ) )
2768 interval = props[QStringLiteral( "interval" )].toDouble();
2769 if ( props.contains( QStringLiteral( "rotate" ) ) )
2770 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2771
2772 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2773 setCommonProperties( x.get(), props );
2774 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2775 {
2776 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2777 }
2778
2779 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2780 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2781
2782 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2783 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2784
2785 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2786 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2787
2788 return x.release();
2789}
2790
2792{
2793 return QStringLiteral( "HashLine" );
2794}
2795
2797{
2798 // if being rotated, it gets initialized with every line segment
2800 if ( rotateSymbols() )
2802 mHashSymbol->setRenderHints( hints );
2803
2804 mHashSymbol->startRender( context.renderContext(), context.fields() );
2805}
2806
2808{
2809 mHashSymbol->stopRender( context.renderContext() );
2810}
2811
2813{
2815 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2816
2817 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2818 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2819 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2820
2821 return map;
2822}
2823
2825{
2826 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2828 x->setHashAngle( mHashAngle );
2829 x->setHashLength( mHashLength );
2830 x->setHashLengthUnit( mHashLengthUnit );
2831 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2832 return x.release();
2833}
2834
2835void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2836{
2837 mHashSymbol->setColor( color );
2838 mColor = color;
2839}
2840
2842{
2843 return mHashSymbol ? mHashSymbol->color() : mColor;
2844}
2845
2847{
2848 return mHashSymbol.get();
2849}
2850
2852{
2853 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2854 {
2855 delete symbol;
2856 return false;
2857 }
2858
2859 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2860 mColor = mHashSymbol->color();
2861 return true;
2862}
2863
2864void QgsHashedLineSymbolLayer::setWidth( const double width )
2865{
2866 mHashLength = width;
2867}
2868
2870{
2871 return mHashLength;
2872}
2873
2875{
2876 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2877}
2878
2880{
2881 return ( mHashSymbol->width( context ) / 2.0 )
2882 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2884}
2885
2887{
2889 mHashSymbol->setOutputUnit( unit );
2890}
2891
2893{
2894 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2895 if ( mHashSymbol )
2896 attr.unite( mHashSymbol->usedAttributes( context ) );
2897 return attr;
2898}
2899
2901{
2903 return true;
2904 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2905 return true;
2906 return false;
2907}
2908
2910{
2911 if ( key == QgsSymbolLayer::Property::Width && mHashSymbol && property )
2912 {
2913 mHashSymbol->setDataDefinedWidth( property );
2914 }
2916}
2917
2928
2930{
2931 mSymbolLineAngle = angle;
2932}
2933
2935{
2936 return mSymbolAngle;
2937}
2938
2940{
2941 mSymbolAngle = angle;
2942}
2943
2944void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2945{
2946 double lineLength = mHashLength;
2948 {
2949 context.expressionContext().setOriginalValueVariable( mHashLength );
2951 }
2952 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2953
2954 double hashAngle = mHashAngle;
2956 {
2957 context.expressionContext().setOriginalValueVariable( mHashAngle );
2959 }
2960
2961 QgsPointXY center( point );
2962 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2963 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2964
2965 QPolygonF points;
2966 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2967
2968 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2970
2971 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2972
2973 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2974}
2975
2977{
2978 return mHashAngle;
2979}
2980
2982{
2983 mHashAngle = angle;
2984}
2985
2987{
2988 const double prevOpacity = mHashSymbol->opacity();
2989 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2991 mHashSymbol->setOpacity( prevOpacity );
2992}
2993
2994//
2995// QgsAbstractBrushedLineSymbolLayer
2996//
2997
2998void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2999{
3000 if ( !context.renderContext().painter() )
3001 return;
3002
3003 double offset = mOffset;
3005 {
3008 }
3009
3010 QPolygonF offsetPoints;
3011 if ( qgsDoubleNear( offset, 0 ) )
3012 {
3013 renderLine( points, context, patternThickness, patternLength, brush );
3014 }
3015 else
3016 {
3017 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3018
3019 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3020 for ( const QPolygonF &part : offsetLine )
3021 {
3022 renderLine( part, context, patternThickness, patternLength, brush );
3023 }
3024 }
3025}
3026
3027void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3028 const double patternLength, const QBrush &sourceBrush )
3029{
3030 QPainter *p = context.renderContext().painter();
3031 if ( !p )
3032 return;
3033
3034 QBrush brush = sourceBrush;
3035
3036 // duplicate points mess up the calculations, we need to remove them first
3037 // we'll calculate the min/max coordinate at the same time, since we're already looping
3038 // through the points
3039 QPolygonF inputPoints;
3040 inputPoints.reserve( points.size() );
3041 QPointF prev;
3042 double minX = std::numeric_limits< double >::max();
3043 double minY = std::numeric_limits< double >::max();
3044 double maxX = std::numeric_limits< double >::lowest();
3045 double maxY = std::numeric_limits< double >::lowest();
3046
3047 for ( const QPointF &pt : std::as_const( points ) )
3048 {
3049 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3050 continue;
3051
3052 inputPoints << pt;
3053 prev = pt;
3054 minX = std::min( minX, pt.x() );
3055 minY = std::min( minY, pt.y() );
3056 maxX = std::max( maxX, pt.x() );
3057 maxY = std::max( maxY, pt.y() );
3058 }
3059
3060 if ( inputPoints.size() < 2 ) // nothing to render
3061 return;
3062
3063 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3064 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3065 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3066 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3067
3068 // 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
3069 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3070 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3071
3072 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3073 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3074
3075 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3076 if ( temporaryImage.isNull() )
3077 {
3078 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3079 return;
3080 }
3081
3082 // clear temporary image contents
3083 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3084 return;
3085 temporaryImage.fill( Qt::transparent );
3086 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3087 return;
3088
3089 Qt::PenJoinStyle join = mPenJoinStyle;
3091 {
3094 if ( !QgsVariantUtils::isNull( exprVal ) )
3095 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3096 }
3097
3098 Qt::PenCapStyle cap = mPenCapStyle;
3100 {
3103 if ( !QgsVariantUtils::isNull( exprVal ) )
3104 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3105 }
3106
3107 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3108 QPainterPathStroker stroker;
3109 stroker.setWidth( lineThickness );
3110 stroker.setCapStyle( cap );
3111 stroker.setJoinStyle( join );
3112
3113 QPainterPath path;
3114 path.addPolygon( inputPoints );
3115 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3116
3117 // prepare temporary image
3118 QPainter imagePainter;
3119 imagePainter.begin( &temporaryImage );
3120 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3121 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3122
3123 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3124 imagePainter.setPen( Qt::NoPen );
3125
3126 QPointF segmentStartPoint = inputPoints.at( 0 );
3127
3128 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3129 double progressThroughImage = 0;
3130
3131 QgsPoint prevSegmentPolygonEndLeft;
3132 QgsPoint prevSegmentPolygonEndRight;
3133
3134 // for closed rings this will store the left/right polygon points of the start/end of the line
3135 QgsPoint startLinePolygonLeft;
3136 QgsPoint startLinePolygonRight;
3137
3138 for ( int i = 1; i < inputPoints.size(); ++i )
3139 {
3140 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3141 break;
3142
3143 const QPointF segmentEndPoint = inputPoints.at( i );
3144 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3145 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3146
3147 // left/right end points of the current segment polygon
3148 QgsPoint thisSegmentPolygonEndLeft;
3149 QgsPoint thisSegmentPolygonEndRight;
3150 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3151 QgsPoint thisSegmentPolygonEndLeftForPainter;
3152 QgsPoint thisSegmentPolygonEndRightForPainter;
3153 if ( i == 1 )
3154 {
3155 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3156 // (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.)
3157 if ( isClosedLine )
3158 {
3159 // project the current segment out by half the image thickness to either side of the line
3160 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3161 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3162 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3163 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3164
3165 // 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
3166 // what angle the current segment polygon should START on.
3167 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3168 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3169
3170 // project out the LAST segment in the line by half the image thickness to either side of the line
3171 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3172 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3173 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3174 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3175
3176 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3177 // 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
3178 // join)
3179 QgsPoint intersectionPoint;
3180 bool isIntersection = false;
3181 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3182 if ( !isIntersection )
3183 prevSegmentPolygonEndLeft = startPointLeft;
3184 isIntersection = false;
3185 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3186 if ( !isIntersection )
3187 prevSegmentPolygonEndRight = startPointRight;
3188
3189 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3190 startLinePolygonRight = prevSegmentPolygonEndRight;
3191 }
3192 else
3193 {
3194 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3195 if ( cap != Qt::PenCapStyle::FlatCap )
3196 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3197 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3198 if ( cap != Qt::PenCapStyle::FlatCap )
3199 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3200 }
3201 }
3202
3203 if ( i < inputPoints.size() - 1 )
3204 {
3205 // for all other segments except the last
3206
3207 // project the current segment out by half the image thickness to either side of the line
3208 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3209 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3210 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3211 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3212
3213 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3214 // what angle the current segment polygon should end on
3215 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtilsBase::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3216 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3217
3218 // project out the next segment by half the image thickness to either side of the line
3219 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3220 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3221 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3222 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3223
3224 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3225 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3226 // join)
3227 QgsPoint intersectionPoint;
3228 bool isIntersection = false;
3229 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3230 if ( !isIntersection )
3231 thisSegmentPolygonEndLeft = endPointLeft;
3232 isIntersection = false;
3233 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3234 if ( !isIntersection )
3235 thisSegmentPolygonEndRight = endPointRight;
3236
3237 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3238 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3239 }
3240 else
3241 {
3242 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3243 // unless it's a closed line
3244 if ( isClosedLine )
3245 {
3246 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3247 thisSegmentPolygonEndRight = startLinePolygonRight;
3248
3249 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3250 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3251 }
3252 else
3253 {
3254 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3255 if ( cap != Qt::PenCapStyle::FlatCap )
3256 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3257 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3258 if ( cap != Qt::PenCapStyle::FlatCap )
3259 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3260
3261 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3262 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3263 }
3264 }
3265
3266 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3267 // where we got with the previous segment), at the correct angle
3268 QTransform brushTransform;
3269 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3270 brushTransform.rotate( -segmentAngleDegrees );
3271 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3272 {
3273 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3274 // we need to also do the same for the brush transform
3275 brushTransform.translate( -( lineThickness / 2 ), 0 );
3276 }
3277 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3278
3279 brush.setTransform( brushTransform );
3280 imagePainter.setBrush( brush );
3281
3282 // now draw the segment polygon
3283 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3284 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3285 << thisSegmentPolygonEndRightForPainter.toQPointF()
3286 << prevSegmentPolygonEndRight.toQPointF()
3287 << prevSegmentPolygonEndLeft.toQPointF() );
3288
3289#if 0 // for debugging, will draw the segment polygons
3290 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3291 imagePainter.setBrush( Qt::NoBrush );
3292 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3293 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3294 << thisSegmentPolygonEndRightForPainter.toQPointF()
3295 << prevSegmentPolygonEndRight.toQPointF()
3296 << prevSegmentPolygonEndLeft.toQPointF() );
3297 imagePainter.setPen( Qt::NoPen );
3298#endif
3299
3300 // calculate the new progress horizontal through the source image to account for the length
3301 // of the segment we've just drawn
3302 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3303 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3304 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3305 progressThroughImage = fmod( progressThroughImage, patternLength );
3306
3307 // shuffle buffered variables for next loop
3308 segmentStartPoint = segmentEndPoint;
3309 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3310 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3311 }
3312 imagePainter.end();
3313
3314 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3315 return;
3316
3317 // lastly, draw the temporary image onto the destination painter at the correct place
3318 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3319 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3320}
3321
3322
3323//
3324// QgsRasterLineSymbolLayer
3325//
3326
3328 : mPath( path )
3329{
3330}
3331
3333
3334QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3335{
3336 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3337
3338 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3339 {
3340 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3341 }
3342 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3343 {
3344 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3345 }
3346 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3347 {
3348 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3349 }
3350
3351 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3352 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3353
3354 if ( properties.contains( QStringLiteral( "offset" ) ) )
3355 {
3356 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3357 }
3358 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3359 {
3360 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3361 }
3362 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3363 {
3364 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3365 }
3366
3367 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3368 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3369 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3370 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3371
3372 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3373 {
3374 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3375 }
3376
3377 return res.release();
3378}
3379
3380
3382{
3383 QVariantMap map;
3384 map[QStringLiteral( "imageFile" )] = mPath;
3385
3386 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3387 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3388 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3389
3390 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3391 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3392
3393 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3394 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3395 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3396
3397 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3398
3399 return map;
3400}
3401
3403{
3404 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3405 res->setWidth( mWidth );
3406 res->setWidthUnit( mWidthUnit );
3407 res->setWidthMapUnitScale( mWidthMapUnitScale );
3408 res->setPenJoinStyle( mPenJoinStyle );
3409 res->setPenCapStyle( mPenCapStyle );
3410 res->setOffsetUnit( mOffsetUnit );
3411 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3412 res->setOffset( mOffset );
3413 res->setOpacity( mOpacity );
3414 copyDataDefinedProperties( res.get() );
3415 copyPaintEffect( res.get() );
3416 return res.release();
3417}
3418
3419void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3420{
3421 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3422 if ( it != properties.end() && it.value().userType() == QMetaType::Type::QString )
3423 {
3424 if ( saving )
3425 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3426 else
3427 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3428 }
3429}
3430
3431void QgsRasterLineSymbolLayer::setPath( const QString &path )
3432{
3433 mPath = path;
3434}
3435
3437{
3438 return QStringLiteral( "RasterLine" );
3439}
3440
3442{
3443 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3444
3446
3447 double opacity = mOpacity * context.opacity();
3448 bool cached = false;
3450 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3451 static_cast< int >( std::ceil( scaledHeight ) ) ),
3452 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3453}
3454
3458
3460{
3461 if ( !context.renderContext().painter() )
3462 return;
3463
3464 QImage sourceImage = mLineImage;
3468 {
3469 QString path = mPath;
3471 {
3472 context.setOriginalValueVariable( path );
3474 }
3475
3476 double strokeWidth = mWidth;
3478 {
3479 context.setOriginalValueVariable( strokeWidth );
3481 }
3482 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3483
3485 double opacity = mOpacity;
3487 {
3490 }
3491 opacity *= context.opacity();
3492
3493 bool cached = false;
3495 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3496 static_cast< int >( std::ceil( scaledHeight ) ) ),
3497 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3498 }
3499
3500 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3501 if ( useSelectedColor )
3502 {
3504 }
3505
3506 const QBrush brush( sourceImage );
3507
3508 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3509}
3510
3517
3519{
3521 if ( mWidthUnit != unit || mOffsetUnit != unit )
3522 {
3524 }
3525 return unit;
3526}
3527
3533
3539
3549
3551{
3552 return ( mWidth / 2.0 ) + mOffset;
3553}
3554
3556{
3557 return QColor();
3558}
3559
3560
3561//
3562// QgsLineburstSymbolLayer
3563//
3564
3565QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3567 , mColor2( color2 )
3568{
3569 setColor( color );
3570}
3571
3573
3574QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3575{
3576 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3577
3578 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3579 {
3580 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3581 }
3582 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3583 {
3584 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3585 }
3586 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3587 {
3588 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3589 }
3590
3591 if ( properties.contains( QStringLiteral( "offset" ) ) )
3592 {
3593 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3594 }
3595 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3596 {
3597 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3598 }
3599 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3600 {
3601 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3602 }
3603
3604 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3605 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3606 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3607 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3608
3609 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3610 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3611
3612 if ( properties.contains( QStringLiteral( "color" ) ) )
3613 {
3614 res->setColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() ) );
3615 }
3616 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3617 {
3618 res->setColor2( QgsColorUtils::colorFromString( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3619 }
3620
3621 //attempt to create color ramp from props
3622 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3623 {
3624 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3625 }
3626 else
3627 {
3628 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3629 }
3630
3631 return res.release();
3632}
3633
3635{
3636 QVariantMap map;
3637
3638 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3639 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3640 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3641
3642 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3643 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3644
3645 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3646 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3647 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3648
3649 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
3650 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
3651 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3652 if ( mGradientRamp )
3653 {
3654 map.insert( mGradientRamp->properties() );
3655 }
3656
3657 return map;
3658}
3659
3661{
3662 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3663 res->setWidth( mWidth );
3664 res->setWidthUnit( mWidthUnit );
3665 res->setWidthMapUnitScale( mWidthMapUnitScale );
3666 res->setPenJoinStyle( mPenJoinStyle );
3667 res->setPenCapStyle( mPenCapStyle );
3668 res->setOffsetUnit( mOffsetUnit );
3669 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3670 res->setOffset( mOffset );
3671 res->setColor( mColor );
3672 res->setColor2( mColor2 );
3673 res->setGradientColorType( mGradientColorType );
3674 if ( mGradientRamp )
3675 res->setColorRamp( mGradientRamp->clone() );
3676 copyDataDefinedProperties( res.get() );
3677 copyPaintEffect( res.get() );
3678 return res.release();
3679}
3680
3682{
3683 return QStringLiteral( "Lineburst" );
3684}
3685
3689
3693
3695{
3696 if ( !context.renderContext().painter() )
3697 return;
3698
3699 double strokeWidth = mWidth;
3701 {
3702 context.setOriginalValueVariable( strokeWidth );
3704 }
3705 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3706
3707 //update alpha of gradient colors
3708 QColor color1 = mColor;
3710 {
3713 }
3714 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3715 if ( useSelectedColor )
3716 {
3717 color1 = context.renderContext().selectionColor();
3718 }
3719 color1.setAlphaF( context.opacity() * color1.alphaF() );
3720
3721 //second gradient color
3722 QColor color2 = mColor2;
3724 {
3727 }
3728
3729 //create a QGradient with the desired properties
3730 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3731 //add stops to gradient
3734 {
3735 //color ramp gradient
3736 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3737 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3738 }
3739 else
3740 {
3741 //two color gradient
3742 gradient.setColorAt( 0.0, color1 );
3743 gradient.setColorAt( 1.0, color2 );
3744 }
3745 const QBrush brush( gradient );
3746
3747 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3748}
3749
3756
3758{
3760 if ( mWidthUnit != unit || mOffsetUnit != unit )
3761 {
3763 }
3764 return unit;
3765}
3766
3772
3778
3788
3790{
3791 return ( mWidth / 2.0 ) + mOffset;
3792}
3793
3798
3800{
3801 mGradientRamp.reset( ramp );
3802}
3803
3804//
3805// QgsFilledLineSymbolLayer
3806//
3807
3810{
3811 mWidth = width;
3812 mFill.reset( fillSymbol ? fillSymbol : static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
3813}
3814
3816
3818{
3820
3821 // throughout the history of QGIS and different layer types, we've used
3822 // a huge range of different strings for the same property. The logic here
3823 // is designed to be forgiving to this and accept a range of string keys:
3824 if ( props.contains( QStringLiteral( "line_width" ) ) )
3825 {
3826 width = props[QStringLiteral( "line_width" )].toDouble();
3827 }
3828 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
3829 {
3830 width = props[QStringLiteral( "outline_width" )].toDouble();
3831 }
3832 else if ( props.contains( QStringLiteral( "width" ) ) )
3833 {
3834 width = props[QStringLiteral( "width" )].toDouble();
3835 }
3836
3837 std::unique_ptr<QgsFilledLineSymbolLayer > l = std::make_unique< QgsFilledLineSymbolLayer >( width, QgsFillSymbol::createSimple( props ) );
3838
3839 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
3840 {
3841 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
3842 }
3843 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
3844 {
3845 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
3846 }
3847 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
3848 {
3849 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
3850 }
3851
3852 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3853 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3854 if ( props.contains( QStringLiteral( "offset" ) ) )
3855 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
3856 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
3857 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
3858 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3859 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3860 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
3861 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
3862 if ( props.contains( QStringLiteral( "capstyle" ) ) )
3863 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
3864
3865 l->restoreOldDataDefinedProperties( props );
3866
3867 return l.release();
3868}
3869
3871{
3872 return QStringLiteral( "FilledLine" );
3873}
3874
3876{
3877 if ( mFill )
3878 {
3879 mFill->startRender( context.renderContext(), context.fields() );
3880 }
3881}
3882
3884{
3885 if ( mFill )
3886 {
3887 mFill->stopRender( context.renderContext() );
3888 }
3889}
3890
3892{
3893 QPainter *p = context.renderContext().painter();
3894 if ( !p || !mFill )
3895 return;
3896
3897 double width = mWidth;
3899 {
3902 }
3903
3904 const double scaledWidth = context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale );
3905
3906 Qt::PenJoinStyle join = mPenJoinStyle;
3908 {
3911 if ( !QgsVariantUtils::isNull( exprVal ) )
3912 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3913 }
3914
3915 Qt::PenCapStyle cap = mPenCapStyle;
3917 {
3920 if ( !QgsVariantUtils::isNull( exprVal ) )
3921 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3922 }
3923
3924 double offset = mOffset;
3926 {
3929 }
3930
3931 const double prevOpacity = mFill->opacity();
3932 mFill->setOpacity( mFill->opacity() * context.opacity() );
3933
3934 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3936
3937 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3938
3939 // stroke out the path using the correct line cap/join style. We'll then use this as the fill polygon
3940 QPainterPathStroker stroker;
3941 stroker.setWidth( scaledWidth );
3942 stroker.setCapStyle( cap );
3943 stroker.setJoinStyle( join );
3944
3945 QPolygonF polygon;
3946 if ( qgsDoubleNear( offset, 0 ) )
3947 {
3948 QPainterPath path;
3949 path.addPolygon( points );
3950 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3951 const QPolygonF polygon = stroke.toFillPolygon();
3952 if ( !polygon.isEmpty() )
3953 {
3954 mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
3955 }
3956 }
3957 else
3958 {
3959 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3961 {
3962 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
3963 // and clamp it to a reasonable range. It's the best we can do in this situation!
3964 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
3965 }
3966
3967 const QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != Qgis::GeometryType::Unknown ? context.originalGeometryType() : Qgis::GeometryType::Line );
3968 for ( const QPolygonF &part : mline )
3969 {
3970 QPainterPath path;
3971 path.addPolygon( part );
3972 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3973 const QPolygonF polygon = stroke.toFillPolygon();
3974 if ( !polygon.isEmpty() )
3975 {
3976 mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
3977 }
3978 }
3979 }
3980
3982
3983 mFill->setOpacity( prevOpacity );
3984}
3985
3987{
3988 QVariantMap map;
3989
3990 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3991 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3992 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3993 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3994 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3995 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3996 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3997 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3998 if ( mFill )
3999 {
4000 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mFill->color() );
4001 }
4002 return map;
4003}
4004
4006{
4007 std::unique_ptr< QgsFilledLineSymbolLayer > res( qgis::down_cast< QgsFilledLineSymbolLayer * >( QgsFilledLineSymbolLayer::create( properties() ) ) );
4008 copyPaintEffect( res.get() );
4009 copyDataDefinedProperties( res.get() );
4010 res->setSubSymbol( mFill->clone() );
4011 return res.release();
4012}
4013
4015{
4016 return mFill.get();
4017}
4018
4020{
4021 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
4022 {
4023 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
4024 return true;
4025 }
4026 else
4027 {
4028 delete symbol;
4029 return false;
4030 }
4031}
4032
4034{
4035 if ( mFill )
4036 {
4037 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
4038 }
4039 return 0;
4040}
4041
4043{
4044 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
4045 if ( mFill )
4046 attr.unite( mFill->usedAttributes( context ) );
4047 return attr;
4048}
4049
4051{
4053 return true;
4054 if ( mFill && mFill->hasDataDefinedProperties() )
4055 return true;
4056 return false;
4057}
4058
4060{
4061 mColor = c;
4062 if ( mFill )
4063 mFill->setColor( c );
4064}
4065
4067{
4068 return mFill ? mFill->color() : mColor;
4069}
4070
4077
4079{
4081 if ( mFill )
4082 mFill->setMapUnitScale( scale );
4083}
4084
4086{
4087 if ( mFill )
4088 {
4089 return mFill->mapUnitScale();
4090 }
4091 return QgsMapUnitScale();
4092}
4093
4095{
4097 if ( mFill )
4098 mFill->setOutputUnit( unit );
4099}
4100
4102{
4103 if ( mFill )
4104 {
4105 return mFill->outputUnit();
4106 }
4108}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition qgis.h:2677
@ 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.
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
GradientColorSource
Gradient color sources.
Definition qgis.h:2699
@ 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:4489
@ 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)...
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:584
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
QFlags< MarkerLinePlacement > MarkerLinePlacements
Definition qgis.h:2688
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.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
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:58
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
A line symbol layer type which fills a stroked line with a QgsFillSymbol.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsFilledLineSymbolLayer, using the settings serialized in the properties map (correspo...
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
~QgsFilledLineSymbolLayer() override
QgsFilledLineSymbolLayer(double width=DEFAULT_SIMPLELINE_WIDTH, QgsFillSymbol *fillSymbol=nullptr)
Constructor for QgsFilledLineSymbolLayer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
QColor color() const override
Returns the "representative" color of the symbol layer.
QgsFilledLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsMapUnitScale mapUnitScale() const override
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
static double lineAngle(double x1, double y1, double x2, double y2)
Calculates the direction of line joining two points in radians, clockwise from the north direction.
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false)
Compute the intersection between two segments.
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:60
QgsPointXY project(double distance, double bearing) const
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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:382
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:705
double y
Definition qgspoint.h:53
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
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 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 double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
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)
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.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
Property
Data definable properties.
@ SecondaryColor
Secondary color (eg for gradient fills)
@ File
Filename, eg for svg files.
@ DashPatternOffset
Dash pattern offset,.
@ OffsetAlongLine
Offset along line.
@ CustomDash
Custom dash pattern.
@ StrokeStyle
Stroke style (eg solid, dashed)
@ StrokeColor
Stroke color.
@ TrimStart
Trim distance from start of line (since QGIS 3.20)
@ CapStyle
Line cap style.
@ Placement
Line marker placement.
@ LineAngle
Line angle, or angle of hash lines for hash line symbols.
@ JoinStyle
Line join style.
@ AverageAngleLength
Length to average symbol angles over.
@ Interval
Line marker interval.
@ StrokeWidth
Stroke width.
@ LineDistance
Distance between lines, or length of lines for hash line symbols.
@ Offset
Symbol offset.
@ TrimEnd
Trim distance from end of line (since QGIS 3.20)
void installMasks(QgsRenderContext &context, bool recursive)
When rendering, install masks on context painter if recursive is true masks are installed recursively...
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:495
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition qgssymbol.h:502
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:156
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, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
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:5362
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:5694
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:5716
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5445
QMap< QString, QString > QgsStringMap
Definition qgis.h:5983
#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:30
Qgis::VertexType type
Vertex type.
Definition qgsvertexid.h:97