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