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