QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgslinesymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinesymbollayer.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslinesymbollayer.h"
17#include "qgscurve.h"
18#include "qgscurvepolygon.h"
19#include "qgsdxfexport.h"
20#include "qgssymbollayerutils.h"
21#include "qgsexpression.h"
22#include "qgsrendercontext.h"
23#include "qgslogger.h"
24#include "qgsvectorlayer.h"
26#include "qgsunittypes.h"
27#include "qgsproperty.h"
29#include "qgsmarkersymbol.h"
30#include "qgslinesymbol.h"
31#include "qgsapplication.h"
32#include "qgsimagecache.h"
33#include "qgsfeedback.h"
34#include "qgsimageoperation.h"
35#include "qgscolorrampimpl.h"
36
37#include <algorithm>
38#include <QPainter>
39#include <QDomDocument>
40#include <QDomElement>
41
42#include <cmath>
43
44QgsSimpleLineSymbolLayer::QgsSimpleLineSymbolLayer( const QColor &color, double width, Qt::PenStyle penStyle )
45 : mPenStyle( penStyle )
46{
47 mColor = color;
48 mWidth = width;
49 mCustomDashVector << 5 << 2;
50}
51
53
55{
57 mWidthUnit = unit;
58 mOffsetUnit = unit;
59 mCustomDashPatternUnit = unit;
60 mDashPatternOffsetUnit = unit;
61 mTrimDistanceStartUnit = unit;
62 mTrimDistanceEndUnit = unit;
63}
64
66{
68 if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
69 {
71 }
72 return unit;
73}
74
76{
79}
80
82{
84 mWidthMapUnitScale = scale;
85 mOffsetMapUnitScale = scale;
86 mCustomDashPatternMapUnitScale = scale;
87}
88
90{
93 mOffsetMapUnitScale == mCustomDashPatternMapUnitScale )
94 {
95 return mWidthMapUnitScale;
96 }
97 return QgsMapUnitScale();
98}
99
101{
105
106 if ( props.contains( QStringLiteral( "line_color" ) ) )
107 {
108 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
109 }
110 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
111 {
112 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
113 }
114 else if ( props.contains( QStringLiteral( "color" ) ) )
115 {
116 //pre 2.5 projects used "color"
117 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
118 }
119 if ( props.contains( QStringLiteral( "line_width" ) ) )
120 {
121 width = props[QStringLiteral( "line_width" )].toDouble();
122 }
123 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
124 {
125 width = props[QStringLiteral( "outline_width" )].toDouble();
126 }
127 else if ( props.contains( QStringLiteral( "width" ) ) )
128 {
129 //pre 2.5 projects used "width"
130 width = props[QStringLiteral( "width" )].toDouble();
131 }
132 if ( props.contains( QStringLiteral( "line_style" ) ) )
133 {
134 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
135 }
136 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
137 {
138 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
139 }
140 else if ( props.contains( QStringLiteral( "penstyle" ) ) )
141 {
142 penStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "penstyle" )].toString() );
143 }
144
146 if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
147 {
148 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
149 }
150 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
151 {
152 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
153 }
154 else if ( props.contains( QStringLiteral( "width_unit" ) ) )
155 {
156 //pre 2.5 projects used "width_unit"
157 l->setWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "width_unit" )].toString() ) );
158 }
159 if ( props.contains( QStringLiteral( "width_map_unit_scale" ) ) )
160 l->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "width_map_unit_scale" )].toString() ) );
161 if ( props.contains( QStringLiteral( "offset" ) ) )
162 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
163 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
164 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
165 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
166 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
167 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
168 l->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
169 if ( props.contains( QStringLiteral( "capstyle" ) ) )
170 l->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "capstyle" )].toString() ) );
171
172 if ( props.contains( QStringLiteral( "use_custom_dash" ) ) )
173 {
174 l->setUseCustomDashPattern( props[QStringLiteral( "use_custom_dash" )].toInt() );
175 }
176 if ( props.contains( QStringLiteral( "customdash" ) ) )
177 {
178 l->setCustomDashVector( QgsSymbolLayerUtils::decodeRealVector( props[QStringLiteral( "customdash" )].toString() ) );
179 }
180 if ( props.contains( QStringLiteral( "customdash_unit" ) ) )
181 {
182 l->setCustomDashPatternUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "customdash_unit" )].toString() ) );
183 }
184 if ( props.contains( QStringLiteral( "customdash_map_unit_scale" ) ) )
185 {
186 l->setCustomDashPatternMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "customdash_map_unit_scale" )].toString() ) );
187 }
188
189 if ( props.contains( QStringLiteral( "draw_inside_polygon" ) ) )
190 {
191 l->setDrawInsidePolygon( props[QStringLiteral( "draw_inside_polygon" )].toInt() );
192 }
193
194 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
195 {
196 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
197 }
198
199 if ( props.contains( QStringLiteral( "dash_pattern_offset" ) ) )
200 l->setDashPatternOffset( props[QStringLiteral( "dash_pattern_offset" )].toDouble() );
201 if ( props.contains( QStringLiteral( "dash_pattern_offset_unit" ) ) )
202 l->setDashPatternOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "dash_pattern_offset_unit" )].toString() ) );
203 if ( props.contains( QStringLiteral( "dash_pattern_offset_map_unit_scale" ) ) )
204 l->setDashPatternOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "dash_pattern_offset_map_unit_scale" )].toString() ) );
205
206 if ( props.contains( QStringLiteral( "trim_distance_start" ) ) )
207 l->setTrimDistanceStart( props[QStringLiteral( "trim_distance_start" )].toDouble() );
208 if ( props.contains( QStringLiteral( "trim_distance_start_unit" ) ) )
209 l->setTrimDistanceStartUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_start_unit" )].toString() ) );
210 if ( props.contains( QStringLiteral( "trim_distance_start_map_unit_scale" ) ) )
211 l->setTrimDistanceStartMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_start_map_unit_scale" )].toString() ) );
212 if ( props.contains( QStringLiteral( "trim_distance_end" ) ) )
213 l->setTrimDistanceEnd( props[QStringLiteral( "trim_distance_end" )].toDouble() );
214 if ( props.contains( QStringLiteral( "trim_distance_end_unit" ) ) )
215 l->setTrimDistanceEndUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "trim_distance_end_unit" )].toString() ) );
216 if ( props.contains( QStringLiteral( "trim_distance_end_map_unit_scale" ) ) )
217 l->setTrimDistanceEndMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "trim_distance_end_map_unit_scale" )].toString() ) );
218
219 if ( props.contains( QStringLiteral( "align_dash_pattern" ) ) )
220 l->setAlignDashPattern( props[ QStringLiteral( "align_dash_pattern" )].toInt() );
221
222 if ( props.contains( QStringLiteral( "tweak_dash_pattern_on_corners" ) ) )
223 l->setTweakDashPatternOnCorners( props[ QStringLiteral( "tweak_dash_pattern_on_corners" )].toInt() );
224
226
227 return l;
228}
229
231{
232 return QStringLiteral( "SimpleLine" );
233}
234
236{
237 QColor penColor = mColor;
238 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
239 mPen.setColor( penColor );
240 double scaledWidth = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
241 mPen.setWidthF( scaledWidth );
242
243 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
244 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
245 const double dashWidthDiv = std::max( 1.0, scaledWidth );
246 if ( mUseCustomDashPattern )
247 {
248 mPen.setStyle( Qt::CustomDashLine );
249
250 //scale pattern vector
251
252 QVector<qreal> scaledVector;
253 QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
254 for ( ; it != mCustomDashVector.constEnd(); ++it )
255 {
256 //the dash is specified in terms of pen widths, therefore the division
257 scaledVector << context.renderContext().convertToPainterUnits( ( *it ), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
258 }
259 mPen.setDashPattern( scaledVector );
260 }
261 else
262 {
263 mPen.setStyle( mPenStyle );
264 }
265
266 if ( mDashPatternOffset && mPen.style() != Qt::SolidLine )
267 {
268 mPen.setDashOffset( context.renderContext().convertToPainterUnits( mDashPatternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv ) ;
269 }
270
271 mPen.setJoinStyle( mPenJoinStyle );
272 mPen.setCapStyle( mPenCapStyle );
273
274 mSelPen = mPen;
275 QColor selColor = context.renderContext().selectionColor();
276 if ( ! SELECTION_IS_OPAQUE )
277 selColor.setAlphaF( context.opacity() );
278 mSelPen.setColor( selColor );
279}
280
282{
283 Q_UNUSED( context )
284}
285
286void QgsSimpleLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
287{
288 QPainter *p = context.renderContext().painter();
289 if ( !p )
290 {
291 return;
292 }
293
294 QgsExpressionContextScope *scope = nullptr;
295 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
297 {
298 scope = new QgsExpressionContextScope();
299 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
300 }
301
302 if ( mDrawInsidePolygon )
303 p->save();
304
305 switch ( mRingFilter )
306 {
307 case AllRings:
308 case ExteriorRingOnly:
309 {
310 if ( mDrawInsidePolygon )
311 {
312 //only drawing the line on the interior of the polygon, so set clip path for painter
313 QPainterPath clipPath;
314 clipPath.addPolygon( points );
315
316 if ( rings )
317 {
318 //add polygon rings
319 for ( auto it = rings->constBegin(); it != rings->constEnd(); ++it )
320 {
321 QPolygonF ring = *it;
322 clipPath.addPolygon( ring );
323 }
324 }
325
326 //use intersect mode, as a clip path may already exist (e.g., for composer maps)
327 p->setClipPath( clipPath, Qt::IntersectClip );
328 }
329
330 if ( scope )
332
333 renderPolyline( points, context );
334 }
335 break;
336
338 break;
339 }
340
341 if ( rings )
342 {
343 switch ( mRingFilter )
344 {
345 case AllRings:
347 {
348 mOffset = -mOffset; // invert the offset for rings!
349 int ringIndex = 1;
350 for ( const QPolygonF &ring : std::as_const( *rings ) )
351 {
352 if ( scope )
354
355 renderPolyline( ring, context );
356 ringIndex++;
357 }
358 mOffset = -mOffset;
359 }
360 break;
361 case ExteriorRingOnly:
362 break;
363 }
364 }
365
366 if ( mDrawInsidePolygon )
367 {
368 //restore painter to reset clip path
369 p->restore();
370 }
371
372}
373
375{
376 QPainter *p = context.renderContext().painter();
377 if ( !p )
378 {
379 return;
380 }
381
382 QPolygonF points = pts;
383
384 double startTrim = mTrimDistanceStart;
386 {
387 context.setOriginalValueVariable( startTrim );
389 }
390 double endTrim = mTrimDistanceEnd;
392 {
393 context.setOriginalValueVariable( endTrim );
395 }
396
397 double totalLength = -1;
398 if ( mTrimDistanceStartUnit == QgsUnitTypes::RenderPercentage )
399 {
400 totalLength = QgsSymbolLayerUtils::polylineLength( points );
401 startTrim = startTrim * 0.01 * totalLength;
402 }
403 else
404 {
405 startTrim = context.renderContext().convertToPainterUnits( startTrim, mTrimDistanceStartUnit, mTrimDistanceStartMapUnitScale );
406 }
407 if ( mTrimDistanceEndUnit == QgsUnitTypes::RenderPercentage )
408 {
409 if ( totalLength < 0 ) // only recalculate if we didn't already work this out for the start distance!
410 totalLength = QgsSymbolLayerUtils::polylineLength( points );
411 endTrim = endTrim * 0.01 * totalLength;
412 }
413 else
414 {
415 endTrim = context.renderContext().convertToPainterUnits( endTrim, mTrimDistanceEndUnit, mTrimDistanceEndMapUnitScale );
416 }
417 if ( !qgsDoubleNear( startTrim, 0 ) || !qgsDoubleNear( endTrim, 0 ) )
418 {
419 points = QgsSymbolLayerUtils::polylineSubstring( points, startTrim, -endTrim );
420 }
421
422 QColor penColor = mColor;
423 penColor.setAlphaF( mColor.alphaF() * context.opacity() );
424 mPen.setColor( penColor );
425
426 double offset = mOffset;
427 applyDataDefinedSymbology( context, mPen, mSelPen, offset );
428
429 const QPen pen = context.selected() ? mSelPen : mPen;
430
431 if ( !pen.dashPattern().isEmpty() )
432 {
433 // check for a null (all 0) dash component, and shortcut out early if so -- these lines are rendered as "no pen"
434 const QVector<double> pattern = pen.dashPattern();
435 bool foundNonNull = false;
436 for ( int i = 0; i < pattern.size(); ++i )
437 {
438 if ( i % 2 == 0 && !qgsDoubleNear( pattern[i], 0 ) )
439 {
440 foundNonNull = true;
441 break;
442 }
443 }
444 if ( !foundNonNull )
445 return;
446 }
447
448 p->setBrush( Qt::NoBrush );
449
450 // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
451 std::unique_ptr< QgsScopedQPainterState > painterState;
452 if ( points.size() <= 2 &&
455 ( p->renderHints() & QPainter::Antialiasing ) )
456 {
457 painterState = std::make_unique< QgsScopedQPainterState >( p );
458 p->setRenderHint( QPainter::Antialiasing, false );
459 }
460
461 const bool applyPatternTweaks = mAlignDashPattern
462 && ( pen.style() != Qt::SolidLine || !pen.dashPattern().empty() )
463 && pen.dashOffset() == 0;
464
465 if ( qgsDoubleNear( offset, 0 ) )
466 {
467 if ( applyPatternTweaks )
468 {
469 drawPathWithDashPatternTweaks( p, points, pen );
470 }
471 else
472 {
473 p->setPen( pen );
474 QPainterPath path;
475 path.addPolygon( points );
476 p->drawPath( path );
477 }
478 }
479 else
480 {
481 double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
483 {
484 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
485 // and clamp it to a reasonable range. It's the best we can do in this situation!
486 scaledOffset = std::min( std::max( context.renderContext().convertToPainterUnits( offset, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
487 }
488
489 QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
490 for ( const QPolygonF &part : mline )
491 {
492 if ( applyPatternTweaks )
493 {
494 drawPathWithDashPatternTweaks( p, part, pen );
495 }
496 else
497 {
498 p->setPen( pen );
499 QPainterPath path;
500 path.addPolygon( part );
501 p->drawPath( path );
502 }
503 }
504 }
505}
506
508{
509 QVariantMap map;
510 map[QStringLiteral( "line_color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
511 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
512 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
513 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
514 map[QStringLiteral( "line_style" )] = QgsSymbolLayerUtils::encodePenStyle( mPenStyle );
515 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
516 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
517 map[QStringLiteral( "offset" )] = QString::number( mOffset );
518 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
519 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
520 map[QStringLiteral( "use_custom_dash" )] = ( mUseCustomDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
521 map[QStringLiteral( "customdash" )] = QgsSymbolLayerUtils::encodeRealVector( mCustomDashVector );
522 map[QStringLiteral( "customdash_unit" )] = QgsUnitTypes::encodeUnit( mCustomDashPatternUnit );
523 map[QStringLiteral( "customdash_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mCustomDashPatternMapUnitScale );
524 map[QStringLiteral( "dash_pattern_offset" )] = QString::number( mDashPatternOffset );
525 map[QStringLiteral( "dash_pattern_offset_unit" )] = QgsUnitTypes::encodeUnit( mDashPatternOffsetUnit );
526 map[QStringLiteral( "dash_pattern_offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDashPatternOffsetMapUnitScale );
527 map[QStringLiteral( "trim_distance_start" )] = QString::number( mTrimDistanceStart );
528 map[QStringLiteral( "trim_distance_start_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceStartUnit );
529 map[QStringLiteral( "trim_distance_start_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceStartMapUnitScale );
530 map[QStringLiteral( "trim_distance_end" )] = QString::number( mTrimDistanceEnd );
531 map[QStringLiteral( "trim_distance_end_unit" )] = QgsUnitTypes::encodeUnit( mTrimDistanceEndUnit );
532 map[QStringLiteral( "trim_distance_end_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mTrimDistanceEndMapUnitScale );
533 map[QStringLiteral( "draw_inside_polygon" )] = ( mDrawInsidePolygon ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
534 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
535 map[QStringLiteral( "align_dash_pattern" )] = mAlignDashPattern ? QStringLiteral( "1" ) : QStringLiteral( "0" );
536 map[QStringLiteral( "tweak_dash_pattern_on_corners" )] = mPatternCartographicTweakOnSharpCorners ? QStringLiteral( "1" ) : QStringLiteral( "0" );
537 return map;
538}
539
541{
547 l->setCustomDashPatternUnit( mCustomDashPatternUnit );
548 l->setCustomDashPatternMapUnitScale( mCustomDashPatternMapUnitScale );
549 l->setOffset( mOffset );
550 l->setPenJoinStyle( mPenJoinStyle );
551 l->setPenCapStyle( mPenCapStyle );
552 l->setUseCustomDashPattern( mUseCustomDashPattern );
553 l->setCustomDashVector( mCustomDashVector );
554 l->setDrawInsidePolygon( mDrawInsidePolygon );
556 l->setDashPatternOffset( mDashPatternOffset );
557 l->setDashPatternOffsetUnit( mDashPatternOffsetUnit );
558 l->setDashPatternOffsetMapUnitScale( mDashPatternOffsetMapUnitScale );
559 l->setTrimDistanceStart( mTrimDistanceStart );
560 l->setTrimDistanceStartUnit( mTrimDistanceStartUnit );
561 l->setTrimDistanceStartMapUnitScale( mTrimDistanceStartMapUnitScale );
562 l->setTrimDistanceEnd( mTrimDistanceEnd );
563 l->setTrimDistanceEndUnit( mTrimDistanceEndUnit );
564 l->setTrimDistanceEndMapUnitScale( mTrimDistanceEndMapUnitScale );
565 l->setAlignDashPattern( mAlignDashPattern );
566 l->setTweakDashPatternOnCorners( mPatternCartographicTweakOnSharpCorners );
567
569 copyPaintEffect( l );
570 return l;
571}
572
573void QgsSimpleLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
574{
575 if ( mPenStyle == Qt::NoPen )
576 return;
577
578 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
579 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
580 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
581 element.appendChild( symbolizerElem );
582
583 // <Geometry>
584 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
585
586 // <Stroke>
587 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
588 symbolizerElem.appendChild( strokeElem );
589
590 Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
592 QVector<qreal> customDashVector = QgsSymbolLayerUtils::rescaleUom( mCustomDashVector, mCustomDashPatternUnit, props );
594 &mPenJoinStyle, &mPenCapStyle, &customDashVector );
595
596 // <se:PerpendicularOffset>
597 if ( !qgsDoubleNear( mOffset, 0.0 ) )
598 {
599 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
601 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
602 symbolizerElem.appendChild( perpOffsetElem );
603 }
604}
605
606QString QgsSimpleLineSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
607{
608 if ( mUseCustomDashPattern )
609 {
610 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
611 mPen.color(), mPenJoinStyle,
612 mPenCapStyle, mOffset, &mCustomDashVector );
613 }
614 else
615 {
616 return QgsSymbolLayerUtils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
617 mPenCapStyle, mOffset );
618 }
619}
620
622{
623 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
624
625 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
626 if ( strokeElem.isNull() )
627 return nullptr;
628
629 Qt::PenStyle penStyle;
630 QColor color;
631 double width;
632 Qt::PenJoinStyle penJoinStyle;
633 Qt::PenCapStyle penCapStyle;
634 QVector<qreal> customDashVector;
635
637 color, width,
640 return nullptr;
641
642 double offset = 0.0;
643 QDomElement perpOffsetElem = element.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
644 if ( !perpOffsetElem.isNull() )
645 {
646 bool ok;
647 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
648 if ( ok )
649 offset = d;
650 }
651
652 double scaleFactor = 1.0;
653 const QString uom = element.attribute( QStringLiteral( "uom" ) );
654 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
655 width = width * scaleFactor;
656 offset = offset * scaleFactor;
657
659 l->setOutputUnit( sldUnitSize );
660 l->setOffset( offset );
663 l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
665 return l;
666}
667
668void QgsSimpleLineSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QPen &pen, QPen &selPen, double &offset )
669{
670 if ( !dataDefinedProperties().hasActiveProperties() )
671 return; // shortcut
672
673 //data defined properties
674 bool hasStrokeWidthExpression = false;
676 {
678 double scaledWidth = context.renderContext().convertToPainterUnits(
681 pen.setWidthF( scaledWidth );
682 selPen.setWidthF( scaledWidth );
683 hasStrokeWidthExpression = true;
684 }
685
686 //color
688 {
690
692 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
693 pen.setColor( penColor );
694 }
695
696 //offset
698 {
701 }
702
703 //dash dot vector
704
705 //note that Qt seems to have issues with scaling dash patterns with very small pen widths.
706 //treating the pen as having no less than a 1 pixel size avoids the worst of these issues
707 const double dashWidthDiv = std::max( hasStrokeWidthExpression ? pen.widthF() : mPen.widthF(), 1.0 );
708
710 {
711 QVector<qreal> dashVector;
713 if ( !QgsVariantUtils::isNull( exprVal ) )
714 {
715 QStringList dashList = exprVal.toString().split( ';' );
716 QStringList::const_iterator dashIt = dashList.constBegin();
717 for ( ; dashIt != dashList.constEnd(); ++dashIt )
718 {
719 dashVector.push_back( context.renderContext().convertToPainterUnits( dashIt->toDouble(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
720 }
721 pen.setDashPattern( dashVector );
722 }
723 }
724 else if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyStrokeWidth ) && mUseCustomDashPattern )
725 {
726 //re-scale pattern vector after data defined pen width was applied
727
728 QVector<qreal> scaledVector;
729 for ( double v : std::as_const( mCustomDashVector ) )
730 {
731 //the dash is specified in terms of pen widths, therefore the division
732 scaledVector << context.renderContext().convertToPainterUnits( v, mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv;
733 }
734 mPen.setDashPattern( scaledVector );
735 }
736
737 // dash pattern offset
738 double patternOffset = mDashPatternOffset;
739 if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDashPatternOffset ) && pen.style() != Qt::SolidLine )
740 {
741 context.setOriginalValueVariable( patternOffset );
743 pen.setDashOffset( context.renderContext().convertToPainterUnits( patternOffset, mDashPatternOffsetUnit, mDashPatternOffsetMapUnitScale ) / dashWidthDiv );
744 }
745
746 //line style
748 {
751 if ( !QgsVariantUtils::isNull( exprVal ) )
752 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( exprVal.toString() ) );
753 }
754
755 //join style
757 {
760 if ( !QgsVariantUtils::isNull( exprVal ) )
761 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() ) );
762 }
763
764 //cap style
766 {
769 if ( !QgsVariantUtils::isNull( exprVal ) )
770 pen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() ) );
771 }
772}
773
774void QgsSimpleLineSymbolLayer::drawPathWithDashPatternTweaks( QPainter *painter, const QPolygonF &points, QPen pen ) const
775{
776 if ( pen.dashPattern().empty() || points.size() < 2 )
777 return;
778
779 QVector< qreal > sourcePattern = pen.dashPattern();
780 const double dashWidthDiv = std::max( 1.0001, pen.widthF() );
781 // back to painter units
782 for ( int i = 0; i < sourcePattern.size(); ++ i )
783 sourcePattern[i] *= pen.widthF();
784
785 if ( pen.widthF() <= 1.0 )
786 pen.setWidthF( 1.0001 );
787
788 QVector< qreal > buffer;
789 QPolygonF bufferedPoints;
790 QPolygonF previousSegmentBuffer;
791 // we iterate through the line points, building a custom dash pattern and adding it to the buffer
792 // as soon as we hit a sharp bend, we scale the buffered pattern in order to nicely place a dash component over the bend
793 // and then append the buffer to the output pattern.
794
795 auto ptIt = points.constBegin();
796 double totalBufferLength = 0;
797 int patternIndex = 0;
798 double currentRemainingDashLength = 0;
799 double currentRemainingGapLength = 0;
800
801 auto compressPattern = []( const QVector< qreal > &buffer ) -> QVector< qreal >
802 {
803 QVector< qreal > result;
804 result.reserve( buffer.size() );
805 for ( auto it = buffer.begin(); it != buffer.end(); )
806 {
807 qreal dash = *it++;
808 qreal gap = *it++;
809 while ( dash == 0 && !result.empty() )
810 {
811 result.last() += gap;
812
813 if ( it == buffer.end() )
814 return result;
815 dash = *it++;
816 gap = *it++;
817 }
818 while ( gap == 0 && it != buffer.end() )
819 {
820 dash += *it++;
821 gap = *it++;
822 }
823 result << dash << gap;
824 }
825 return result;
826 };
827
828 double currentBufferLineLength = 0;
829 auto flushBuffer = [pen, painter, &buffer, &bufferedPoints, &previousSegmentBuffer, &currentRemainingDashLength, &currentRemainingGapLength, &currentBufferLineLength, &totalBufferLength,
830 dashWidthDiv, &compressPattern]( QPointF * nextPoint )
831 {
832 if ( buffer.empty() || bufferedPoints.size() < 2 )
833 {
834 return;
835 }
836
837 if ( currentRemainingDashLength )
838 {
839 // ended midway through a dash -- we want to finish this off
840 buffer << currentRemainingDashLength << 0.0;
841 totalBufferLength += currentRemainingDashLength;
842 }
843 QVector< qreal > compressed = compressPattern( buffer );
844 if ( !currentRemainingDashLength )
845 {
846 // ended midway through a gap -- we don't want this, we want to end at previous dash
847 totalBufferLength -= compressed.last();
848 compressed.last() = 0;
849 }
850
851 // rescale buffer for final bit of line -- we want to end at the end of a dash, not a gap
852 const double scaleFactor = currentBufferLineLength / totalBufferLength;
853
854 bool shouldFlushPreviousSegmentBuffer = false;
855
856 if ( !previousSegmentBuffer.empty() )
857 {
858 // add first dash from current buffer
859 QPolygonF firstDashSubstring = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, 0, compressed.first() * scaleFactor );
860 if ( !firstDashSubstring.empty() )
861 QgsSymbolLayerUtils::appendPolyline( previousSegmentBuffer, firstDashSubstring );
862
863 // then we skip over the first dash and gap for this segment
864 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( bufferedPoints, ( compressed.first() + compressed.at( 1 ) ) * scaleFactor, 0 );
865
866 compressed = compressed.mid( 2 );
867 shouldFlushPreviousSegmentBuffer = !compressed.empty();
868 }
869
870 if ( !previousSegmentBuffer.empty() && ( shouldFlushPreviousSegmentBuffer || !nextPoint ) )
871 {
872 QPen adjustedPen = pen;
873 adjustedPen.setStyle( Qt::SolidLine );
874 painter->setPen( adjustedPen );
875 QPainterPath path;
876 path.addPolygon( previousSegmentBuffer );
877 painter->drawPath( path );
878 previousSegmentBuffer.clear();
879 }
880
881 double finalDash = 0;
882 if ( nextPoint )
883 {
884 // sharp bend:
885 // 1. rewind buffered points line by final dash and gap length
886 // (later) 2. draw the bend with a solid line of length 2 * final dash size
887
888 if ( !compressed.empty() )
889 {
890 finalDash = compressed.at( compressed.size() - 2 );
891 const double finalGap = compressed.size() > 2 ? compressed.at( compressed.size() - 3 ) : 0;
892
893 const QPolygonF thisPoints = bufferedPoints;
894 bufferedPoints = QgsSymbolLayerUtils::polylineSubstring( thisPoints, 0, -( finalDash + finalGap ) * scaleFactor );
895 previousSegmentBuffer = QgsSymbolLayerUtils::polylineSubstring( thisPoints, - finalDash * scaleFactor, 0 );
896 }
897 else
898 {
899 previousSegmentBuffer << bufferedPoints;
900 }
901 }
902
903 currentBufferLineLength = 0;
904 currentRemainingDashLength = 0;
905 currentRemainingGapLength = 0;
906 totalBufferLength = 0;
907 buffer.clear();
908
909 if ( !bufferedPoints.empty() && ( !compressed.empty() || !nextPoint ) )
910 {
911 QPen adjustedPen = pen;
912 if ( !compressed.empty() )
913 {
914 // maximum size of dash pattern is 32 elements
915 compressed = compressed.mid( 0, 32 );
916 std::for_each( compressed.begin(), compressed.end(), [scaleFactor, dashWidthDiv]( qreal & element ) { element *= scaleFactor / dashWidthDiv; } );
917 adjustedPen.setDashPattern( compressed );
918 }
919 else
920 {
921 adjustedPen.setStyle( Qt::SolidLine );
922 }
923
924 painter->setPen( adjustedPen );
925 QPainterPath path;
926 path.addPolygon( bufferedPoints );
927 painter->drawPath( path );
928 }
929
930 bufferedPoints.clear();
931 };
932
933 QPointF p1;
934 QPointF p2 = *ptIt;
935 ptIt++;
936 bufferedPoints << p2;
937 for ( ; ptIt != points.constEnd(); ++ptIt )
938 {
939 p1 = *ptIt;
940 if ( qgsDoubleNear( p1.y(), p2.y() ) && qgsDoubleNear( p1.x(), p2.x() ) )
941 {
942 continue;
943 }
944
945 double remainingSegmentDistance = std::sqrt( std::pow( p2.x() - p1.x(), 2.0 ) + std::pow( p2.y() - p1.y(), 2.0 ) );
946 currentBufferLineLength += remainingSegmentDistance;
947 while ( true )
948 {
949 // handle currentRemainingDashLength/currentRemainingGapLength
950 if ( currentRemainingDashLength > 0 )
951 {
952 // bit more of dash to insert
953 if ( remainingSegmentDistance >= currentRemainingDashLength )
954 {
955 // all of dash fits in
956 buffer << currentRemainingDashLength << 0.0;
957 totalBufferLength += currentRemainingDashLength;
958 remainingSegmentDistance -= currentRemainingDashLength;
959 patternIndex++;
960 currentRemainingDashLength = 0.0;
961 currentRemainingGapLength = sourcePattern.at( patternIndex );
962 }
963 else
964 {
965 // only part of remaining dash fits in
966 buffer << remainingSegmentDistance << 0.0;
967 totalBufferLength += remainingSegmentDistance;
968 currentRemainingDashLength -= remainingSegmentDistance;
969 break;
970 }
971 }
972 if ( currentRemainingGapLength > 0 )
973 {
974 // bit more of gap to insert
975 if ( remainingSegmentDistance >= currentRemainingGapLength )
976 {
977 // all of gap fits in
978 buffer << 0.0 << currentRemainingGapLength;
979 totalBufferLength += currentRemainingGapLength;
980 remainingSegmentDistance -= currentRemainingGapLength;
981 currentRemainingGapLength = 0.0;
982 patternIndex++;
983 }
984 else
985 {
986 // only part of remaining gap fits in
987 buffer << 0.0 << remainingSegmentDistance;
988 totalBufferLength += remainingSegmentDistance;
989 currentRemainingGapLength -= remainingSegmentDistance;
990 break;
991 }
992 }
993
994 if ( patternIndex >= sourcePattern.size() )
995 patternIndex = 0;
996
997 const double nextPatternDashLength = sourcePattern.at( patternIndex );
998 const double nextPatternGapLength = sourcePattern.at( patternIndex + 1 );
999 if ( nextPatternDashLength + nextPatternGapLength <= remainingSegmentDistance )
1000 {
1001 buffer << nextPatternDashLength << nextPatternGapLength;
1002 remainingSegmentDistance -= nextPatternDashLength + nextPatternGapLength;
1003 totalBufferLength += nextPatternDashLength + nextPatternGapLength;
1004 patternIndex += 2;
1005 }
1006 else if ( nextPatternDashLength <= remainingSegmentDistance )
1007 {
1008 // can fit in "dash", but not "gap"
1009 buffer << nextPatternDashLength << remainingSegmentDistance - nextPatternDashLength;
1010 totalBufferLength += remainingSegmentDistance;
1011 currentRemainingGapLength = nextPatternGapLength - ( remainingSegmentDistance - nextPatternDashLength );
1012 currentRemainingDashLength = 0;
1013 patternIndex++;
1014 break;
1015 }
1016 else
1017 {
1018 // can't fit in "dash"
1019 buffer << remainingSegmentDistance << 0.0;
1020 totalBufferLength += remainingSegmentDistance;
1021 currentRemainingGapLength = 0;
1022 currentRemainingDashLength = nextPatternDashLength - remainingSegmentDistance;
1023 break;
1024 }
1025 }
1026
1027 bufferedPoints << p1;
1028 if ( mPatternCartographicTweakOnSharpCorners && ptIt + 1 != points.constEnd() )
1029 {
1030 QPointF nextPoint = *( ptIt + 1 );
1031
1032 // extreme angles form more than 45 degree angle at a node
1033 if ( QgsSymbolLayerUtils::isSharpCorner( p2, p1, nextPoint ) )
1034 {
1035 // extreme angle. Rescale buffer and flush
1036 flushBuffer( &nextPoint );
1037 bufferedPoints << p1;
1038 // restart the line with the full length of the most recent dash element -- see
1039 // "Cartographic Generalization" (Swiss Society of Cartography) p33, example #8
1040 if ( patternIndex % 2 == 1 )
1041 {
1042 patternIndex--;
1043 }
1044 currentRemainingDashLength = sourcePattern.at( patternIndex );
1045 }
1046 }
1047
1048 p2 = p1;
1049 }
1050
1051 flushBuffer( nullptr );
1052 if ( !previousSegmentBuffer.empty() )
1053 {
1054 QPen adjustedPen = pen;
1055 adjustedPen.setStyle( Qt::SolidLine );
1056 painter->setPen( adjustedPen );
1057 QPainterPath path;
1058 path.addPolygon( previousSegmentBuffer );
1059 painter->drawPath( path );
1060 previousSegmentBuffer.clear();
1061 }
1062}
1063
1065{
1066 if ( mDrawInsidePolygon )
1067 {
1068 //set to clip line to the interior of polygon, so we expect no bleed
1069 return 0;
1070 }
1071 else
1072 {
1073 return context.convertToPainterUnits( ( mWidth / 2.0 ), mWidthUnit, mWidthMapUnitScale ) +
1075 }
1076}
1077
1079{
1080 unit = mCustomDashPatternUnit;
1081 return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
1082}
1083
1085{
1086 return mPenStyle;
1087}
1088
1090{
1091 double width = mWidth;
1093 {
1096 }
1097
1100 {
1102 }
1103 return width;
1104}
1105
1107{
1109 {
1112 }
1113 return mColor;
1114}
1115
1117{
1118 return mPenStyle != Qt::SolidLine || mUseCustomDashPattern;
1119}
1120
1122{
1123 return mAlignDashPattern;
1124}
1125
1127{
1128 mAlignDashPattern = enabled;
1129}
1130
1132{
1133 return mPatternCartographicTweakOnSharpCorners;
1134}
1135
1137{
1138 mPatternCartographicTweakOnSharpCorners = enabled;
1139}
1140
1142{
1143 double offset = mOffset;
1144
1146 {
1149 }
1150
1153 {
1155 }
1156 return -offset; //direction seems to be inverse to symbology offset
1157}
1158
1160
1162
1163class MyLine
1164{
1165 public:
1166 MyLine( QPointF p1, QPointF p2 )
1167 : mVertical( false )
1168 , mIncreasing( false )
1169 , mT( 0.0 )
1170 , mLength( 0.0 )
1171 {
1172 if ( p1 == p2 )
1173 return; // invalid
1174
1175 // tangent and direction
1176 if ( qgsDoubleNear( p1.x(), p2.x() ) )
1177 {
1178 // vertical line - tangent undefined
1179 mVertical = true;
1180 mIncreasing = ( p2.y() > p1.y() );
1181 }
1182 else
1183 {
1184 mVertical = false;
1185 mT = ( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
1186 mIncreasing = ( p2.x() > p1.x() );
1187 }
1188
1189 // length
1190 double x = ( p2.x() - p1.x() );
1191 double y = ( p2.y() - p1.y() );
1192 mLength = std::sqrt( x * x + y * y );
1193 }
1194
1195 // return angle in radians
1196 double angle()
1197 {
1198 double a = ( mVertical ? M_PI_2 : std::atan( mT ) );
1199
1200 if ( !mIncreasing )
1201 a += M_PI;
1202 return a;
1203 }
1204
1205 // return difference for x,y when going along the line with specified interval
1206 QPointF diffForInterval( double interval )
1207 {
1208 if ( mVertical )
1209 return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
1210
1211 double alpha = std::atan( mT );
1212 double dx = std::cos( alpha ) * interval;
1213 double dy = std::sin( alpha ) * interval;
1214 return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
1215 }
1216
1217 double length() const { return mLength; }
1218
1219 protected:
1220 bool mVertical;
1221 bool mIncreasing;
1222 double mT;
1223 double mLength;
1224};
1225
1227
1228//
1229// QgsTemplatedLineSymbolLayerBase
1230//
1232 : mRotateSymbols( rotateSymbol )
1233 , mInterval( interval )
1234{
1235
1236}
1237
1239{
1240 if ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1242 else if ( mPlacements & Qgis::MarkerLinePlacement::Vertex )
1244 else if ( ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1245 && ( mPlacements & Qgis::MarkerLinePlacement::InnerVertices )
1246 && ( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1247 return Qgis::MarkerLinePlacement::Vertex; // retain round trip for deprecated old API
1248 else if ( mPlacements & Qgis::MarkerLinePlacement::LastVertex )
1250 else if ( mPlacements & Qgis::MarkerLinePlacement::FirstVertex )
1252 else if ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1254 else if ( mPlacements & Qgis::MarkerLinePlacement::CurvePoint )
1256 else if ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter )
1258 else
1260}
1261
1263{
1264 mPlacements = placement;
1265}
1266
1268
1270{
1271 if ( mRenderingFeature )
1272 {
1273 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1274 // until after we've received the final part
1275 mFeatureSymbolOpacity = context.opacity();
1276 mCurrentFeatureIsSelected = context.selected();
1277 }
1278
1279 double offset = mOffset;
1280
1282 {
1285 }
1286
1287 Qgis::MarkerLinePlacements placements = QgsTemplatedLineSymbolLayerBase::placements();
1288
1290 {
1292 if ( !QgsVariantUtils::isNull( exprVal ) )
1293 {
1294 QString placementString = exprVal.toString();
1295 if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 )
1296 {
1298 }
1299 else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 )
1300 {
1302 }
1303 else if ( placementString.compare( QLatin1String( "innervertices" ), Qt::CaseInsensitive ) == 0 )
1304 {
1306 }
1307 else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 )
1308 {
1310 }
1311 else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 )
1312 {
1314 }
1315 else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 )
1316 {
1318 }
1319 else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 )
1320 {
1322 }
1323 else if ( placementString.compare( QLatin1String( "segmentcenter" ), Qt::CaseInsensitive ) == 0 )
1324 {
1326 }
1327 else
1328 {
1330 }
1331 }
1332 }
1333
1334 QgsScopedQPainterState painterState( context.renderContext().painter() );
1335
1336 double averageOver = mAverageAngleLength;
1338 {
1339 context.setOriginalValueVariable( mAverageAngleLength );
1341 }
1342 averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
1343
1344 if ( qgsDoubleNear( offset, 0.0 ) )
1345 {
1347 renderPolylineInterval( points, context, averageOver );
1349 renderPolylineCentral( points, context, averageOver );
1351 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::Vertex );
1353 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1354 {
1355 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::FirstVertex );
1356 mHasRenderedFirstPart = mRenderingFeature;
1357 }
1359 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::InnerVertices );
1361 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::CurvePoint );
1363 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::SegmentCenter );
1365 renderPolylineVertex( points, context, Qgis::MarkerLinePlacement::LastVertex );
1366 }
1367 else
1368 {
1369 context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
1371
1372 for ( int part = 0; part < mline.count(); ++part )
1373 {
1374 const QPolygonF &points2 = mline[ part ];
1375
1377 renderPolylineInterval( points2, context, averageOver );
1379 renderPolylineCentral( points2, context, averageOver );
1381 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::Vertex );
1383 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::InnerVertices );
1385 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::LastVertex );
1387 && ( mPlaceOnEveryPart || !mHasRenderedFirstPart ) )
1388 {
1389 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::FirstVertex );
1390 mHasRenderedFirstPart = mRenderingFeature;
1391 }
1393 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::CurvePoint );
1395 renderPolylineVertex( points2, context, Qgis::MarkerLinePlacement::SegmentCenter );
1396 }
1397 }
1398}
1399
1400void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1401{
1402 const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( context.renderContext().geometry() );
1403
1404 if ( curvePolygon )
1405 {
1406 context.renderContext().setGeometry( curvePolygon->exteriorRing() );
1407 }
1408
1409 QgsExpressionContextScope *scope = nullptr;
1410 std::unique_ptr< QgsExpressionContextScopePopper > scopePopper;
1412 {
1413 scope = new QgsExpressionContextScope();
1414 scopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.renderContext().expressionContext(), scope );
1415 }
1416
1417 switch ( mRingFilter )
1418 {
1419 case AllRings:
1420 case ExteriorRingOnly:
1421 {
1422 if ( scope )
1424
1425 renderPolyline( points, context );
1426 break;
1427 }
1428 case InteriorRingsOnly:
1429 break;
1430 }
1431
1432 if ( rings )
1433 {
1434 switch ( mRingFilter )
1435 {
1436 case AllRings:
1437 case InteriorRingsOnly:
1438 {
1439 mOffset = -mOffset; // invert the offset for rings!
1440 for ( int i = 0; i < rings->size(); ++i )
1441 {
1442 if ( curvePolygon )
1443 {
1444 context.renderContext().setGeometry( curvePolygon->interiorRing( i ) );
1445 }
1446 if ( scope )
1448
1449 renderPolyline( rings->at( i ), context );
1450 }
1451 mOffset = -mOffset;
1452 }
1453 break;
1454 case ExteriorRingOnly:
1455 break;
1456 }
1457 }
1458}
1459
1461{
1463 if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit )
1464 {
1466 }
1467 return unit;
1468}
1469
1471{
1473 mIntervalUnit = unit;
1474 mOffsetAlongLineUnit = unit;
1475 mAverageAngleLengthUnit = unit;
1476}
1477
1479{
1481 setIntervalMapUnitScale( scale );
1482 mOffsetMapUnitScale = scale;
1484}
1485
1487{
1491 {
1492 return mOffsetMapUnitScale;
1493 }
1494 return QgsMapUnitScale();
1495}
1496
1498{
1499 QVariantMap map;
1500 map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1501 map[QStringLiteral( "interval" )] = QString::number( interval() );
1502 map[QStringLiteral( "offset" )] = QString::number( mOffset );
1503 map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() );
1504 map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() );
1505 map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() );
1506 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1507 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1508 map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
1509 map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
1510 map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
1511 map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
1512 map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
1513
1514 map[QStringLiteral( "placements" )] = qgsFlagValueToKeys( mPlacements );
1515
1516 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
1517 map[QStringLiteral( "place_on_every_part" )] = mPlaceOnEveryPart;
1518 return map;
1519}
1520
1522{
1523 return mPlaceOnEveryPart
1524 || ( mPlacements & Qgis::MarkerLinePlacement::Interval )
1525 || ( mPlacements & Qgis::MarkerLinePlacement::CentralPoint )
1526 || ( mPlacements & Qgis::MarkerLinePlacement::SegmentCenter );
1527}
1528
1530{
1531 mRenderingFeature = true;
1532 mHasRenderedFirstPart = false;
1533}
1534
1536{
1537 mRenderingFeature = false;
1538 if ( mPlaceOnEveryPart || !( mPlacements & Qgis::MarkerLinePlacement::LastVertex ) )
1539 return;
1540
1541 const double prevOpacity = subSymbol()->opacity();
1542 subSymbol()->setOpacity( prevOpacity * mFeatureSymbolOpacity );
1543
1544 // render final point
1545 renderSymbol( mFinalVertex, &feature, context, -1, mCurrentFeatureIsSelected );
1546 mFeatureSymbolOpacity = 1;
1547 subSymbol()->setOpacity( prevOpacity );
1548}
1549
1551{
1552 destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() );
1553 destLayer->setOffset( mOffset );
1554 destLayer->setPlacements( placements() );
1555 destLayer->setOffsetUnit( mOffsetUnit );
1557 destLayer->setIntervalUnit( intervalUnit() );
1559 destLayer->setOffsetAlongLine( offsetAlongLine() );
1562 destLayer->setAverageAngleLength( mAverageAngleLength );
1563 destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
1564 destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
1565 destLayer->setRingFilter( mRingFilter );
1566 destLayer->setPlaceOnEveryPart( mPlaceOnEveryPart );
1567 copyDataDefinedProperties( destLayer );
1568 copyPaintEffect( destLayer );
1569}
1570
1572{
1573 if ( properties.contains( QStringLiteral( "offset" ) ) )
1574 {
1575 destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
1576 }
1577 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
1578 {
1579 destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
1580 }
1581 if ( properties.contains( QStringLiteral( "interval_unit" ) ) )
1582 {
1583 destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )].toString() ) );
1584 }
1585 if ( properties.contains( QStringLiteral( "offset_along_line" ) ) )
1586 {
1587 destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() );
1588 }
1589 if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) )
1590 {
1591 destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )].toString() ) );
1592 }
1593 if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) )
1594 {
1595 destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )].toString() ) );
1596 }
1597
1598 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1599 {
1600 destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1601 }
1602 if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) )
1603 {
1604 destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )].toString() ) );
1605 }
1606
1607 if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
1608 {
1609 destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
1610 }
1611 if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
1612 {
1613 destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) );
1614 }
1615 if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
1616 {
1617 destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) );
1618 }
1619
1620 if ( properties.contains( QStringLiteral( "placement" ) ) )
1621 {
1622 if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
1624 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) )
1626 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) )
1628 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) )
1630 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) )
1632 else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "segmentcenter" ) )
1634 else
1636 }
1637 else if ( properties.contains( QStringLiteral( "placements" ) ) )
1638 {
1639 Qgis::MarkerLinePlacements placements = qgsFlagKeysToValue( properties.value( QStringLiteral( "placements" ) ).toString(), Qgis::MarkerLinePlacements() );
1640 destLayer->setPlacements( placements );
1641 }
1642
1643 if ( properties.contains( QStringLiteral( "ring_filter" ) ) )
1644 {
1645 destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) );
1646 }
1647
1648 destLayer->setPlaceOnEveryPart( properties.value( QStringLiteral( "place_on_every_part" ), true ).toBool() );
1649
1651}
1652
1653void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
1654{
1655 if ( points.isEmpty() )
1656 return;
1657
1658 double lengthLeft = 0; // how much is left until next marker
1659
1660 QgsRenderContext &rc = context.renderContext();
1661 double interval = mInterval;
1662
1664 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1665
1667 {
1668 context.setOriginalValueVariable( mInterval );
1670 }
1671 if ( interval <= 0 )
1672 {
1673 interval = 0.1;
1674 }
1675 double offsetAlongLine = mOffsetAlongLine;
1677 {
1678 context.setOriginalValueVariable( mOffsetAlongLine );
1680 }
1681
1682 double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
1684 {
1685 // rendering for symbol previews -- an interval in meters in map units can't be calculated, so treat the size as millimeters
1686 // and clamp it to a reasonable range. It's the best we can do in this situation!
1687 painterUnitInterval = std::min( std::max( rc.convertToPainterUnits( interval, QgsUnitTypes::RenderMillimeters ), 10.0 ), 100.0 );
1688 }
1689
1690 if ( painterUnitInterval < 0 )
1691 return;
1692
1693 double painterUnitOffsetAlongLine = 0;
1694
1695 // only calculated if we need it!
1696 double totalLength = -1;
1697
1698 if ( !qgsDoubleNear( offsetAlongLine, 0 ) )
1699 {
1700 switch ( offsetAlongLineUnit() )
1701 {
1710 break;
1712 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1713 painterUnitOffsetAlongLine = offsetAlongLine / 100 * totalLength;
1714 break;
1715 }
1716
1717 if ( points.isClosed() )
1718 {
1719 if ( painterUnitOffsetAlongLine > 0 )
1720 {
1721 if ( totalLength < 0 )
1722 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1723 painterUnitOffsetAlongLine = std::fmod( painterUnitOffsetAlongLine, totalLength );
1724 }
1725 else if ( painterUnitOffsetAlongLine < 0 )
1726 {
1727 if ( totalLength < 0 )
1728 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1729 painterUnitOffsetAlongLine = totalLength - std::fmod( -painterUnitOffsetAlongLine, totalLength );
1730 }
1731 }
1732 }
1733
1735 {
1736 // rendering for symbol previews -- an offset in meters in map units can't be calculated, so treat the size as millimeters
1737 // and clamp it to a reasonable range. It's the best we can do in this situation!
1738 painterUnitOffsetAlongLine = std::min( std::max( rc.convertToPainterUnits( offsetAlongLine, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
1739 }
1740
1741 lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
1742
1743 if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
1744 {
1745 QVector< QPointF > angleStartPoints;
1746 QVector< QPointF > symbolPoints;
1747 QVector< QPointF > angleEndPoints;
1748
1749 // we collect 3 arrays of points. These correspond to
1750 // 1. the actual point at which to render the symbol
1751 // 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
1752 // 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
1753 // it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
1754 // (or trace past the final point)
1755 collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
1756
1757 if ( symbolPoints.empty() )
1758 {
1759 // no symbols to draw, shortcut out early
1760 return;
1761 }
1762
1763 if ( symbolPoints.count() > 1 && symbolPoints.constFirst() == symbolPoints.constLast() )
1764 {
1765 // avoid duplicate points at start and end of closed rings
1766 symbolPoints.pop_back();
1767 }
1768
1769 angleEndPoints.reserve( symbolPoints.size() );
1770 angleStartPoints.reserve( symbolPoints.size() );
1771 if ( averageOver <= painterUnitOffsetAlongLine )
1772 {
1773 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
1774 }
1775 else
1776 {
1777 collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
1778 }
1779 collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
1780
1781 int pointNum = 0;
1782 for ( int i = 0; i < symbolPoints.size(); ++ i )
1783 {
1784 if ( context.renderContext().renderingStopped() )
1785 break;
1786
1787 const QPointF pt = symbolPoints[i];
1788 const QPointF startPt = angleStartPoints[i];
1789 const QPointF endPt = angleEndPoints[i];
1790
1791 MyLine l( startPt, endPt );
1792 // rotate marker (if desired)
1793 if ( rotateSymbols() )
1794 {
1795 setSymbolLineAngle( l.angle() * 180 / M_PI );
1796 }
1797
1799 renderSymbol( pt, context.feature(), rc, -1, context.selected() );
1800 }
1801 }
1802 else
1803 {
1804 // not averaging line angle -- always use exact section angle
1805 int pointNum = 0;
1806 QPointF lastPt = points[0];
1807 for ( int i = 1; i < points.count(); ++i )
1808 {
1809 if ( context.renderContext().renderingStopped() )
1810 break;
1811
1812 const QPointF &pt = points[i];
1813
1814 if ( lastPt == pt ) // must not be equal!
1815 continue;
1816
1817 // for each line, find out dx and dy, and length
1818 MyLine l( lastPt, pt );
1819 QPointF diff = l.diffForInterval( painterUnitInterval );
1820
1821 // if there's some length left from previous line
1822 // use only the rest for the first point in new line segment
1823 double c = 1 - lengthLeft / painterUnitInterval;
1824
1825 lengthLeft += l.length();
1826
1827 // rotate marker (if desired)
1828 if ( rotateSymbols() )
1829 {
1830 setSymbolLineAngle( l.angle() * 180 / M_PI );
1831 }
1832
1833 // while we're not at the end of line segment, draw!
1834 while ( lengthLeft > painterUnitInterval )
1835 {
1836 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
1837 lastPt += c * diff;
1838 lengthLeft -= painterUnitInterval;
1840 renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
1841 c = 1; // reset c (if wasn't 1 already)
1842 }
1843
1844 lastPt = pt;
1845 }
1846
1847 }
1848}
1849
1850static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt )
1851{
1852 // calc average angle between the previous and next point
1853 double a1 = MyLine( prevPt, pt ).angle();
1854 double a2 = MyLine( pt, nextPt ).angle();
1855 double unitX = std::cos( a1 ) + std::cos( a2 ), unitY = std::sin( a1 ) + std::sin( a2 );
1856
1857 return std::atan2( unitY, unitX );
1858}
1859
1860void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
1861{
1862 if ( points.isEmpty() )
1863 return;
1864
1865 QgsRenderContext &rc = context.renderContext();
1866
1867 int i = -1, maxCount = 0;
1868 bool isRing = false;
1869
1871 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
1873
1874 double offsetAlongLine = mOffsetAlongLine;
1876 {
1877 context.setOriginalValueVariable( mOffsetAlongLine );
1879 }
1880
1881 // only calculated if we need it!!
1882 double totalLength = -1;
1883 if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) )
1884 {
1885 //scale offset along line
1886 switch ( offsetAlongLineUnit() )
1887 {
1896 break;
1898 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1899 offsetAlongLine = offsetAlongLine / 100 * totalLength;
1900 break;
1901 }
1902 if ( points.isClosed() )
1903 {
1904 if ( offsetAlongLine > 0 )
1905 {
1906 if ( totalLength < 0 )
1907 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1908 offsetAlongLine = std::fmod( offsetAlongLine, totalLength );
1909 }
1910 else if ( offsetAlongLine < 0 )
1911 {
1912 if ( totalLength < 0 )
1913 totalLength = QgsSymbolLayerUtils::polylineLength( points );
1914 offsetAlongLine = totalLength - std::fmod( -offsetAlongLine, totalLength );
1915 }
1916 }
1917 }
1918
1919 if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry()
1923 {
1925 const QgsMapToPixel &mtp = context.renderContext().mapToPixel();
1926
1927 QgsVertexId vId;
1928 QgsPoint vPoint;
1929 double x, y, z;
1930 QPointF mapPoint;
1931 int pointNum = 0;
1932 const int numPoints = context.renderContext().geometry()->nCoordinates();
1933 while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
1934 {
1935 if ( context.renderContext().renderingStopped() )
1936 break;
1937
1939
1940 if ( pointNum == 1 && placement == Qgis::MarkerLinePlacement::InnerVertices )
1941 continue;
1942
1943 if ( pointNum == numPoints && placement == Qgis::MarkerLinePlacement::InnerVertices )
1944 continue;
1945
1946 if ( ( ( placement == Qgis::MarkerLinePlacement::Vertex || placement == Qgis::MarkerLinePlacement::InnerVertices ) && vId.type == Qgis::VertexType::Segment )
1947 || ( placement == Qgis::MarkerLinePlacement::CurvePoint && vId.type == Qgis::VertexType::Curve ) )
1948 {
1949 //transform
1950 x = vPoint.x();
1951 y = vPoint.y();
1952 z = 0.0;
1953 if ( ct.isValid() )
1954 {
1955 ct.transformInPlace( x, y, z );
1956 }
1957 mapPoint.setX( x );
1958 mapPoint.setY( y );
1959 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1960 if ( rotateSymbols() )
1961 {
1962 double angle = context.renderContext().geometry()->vertexAngle( vId );
1963 setSymbolLineAngle( angle * 180 / M_PI );
1964 }
1965 renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() );
1966 }
1967 }
1968
1969 return;
1970 }
1971
1972 int pointNum = 0;
1973
1974 switch ( placement )
1975 {
1977 {
1978 i = 0;
1979 maxCount = 1;
1980 break;
1981 }
1982
1984 {
1985 i = points.count() - 1;
1986 pointNum = i;
1987 maxCount = points.count();
1988 break;
1989 }
1990
1992 {
1993 i = 1;
1994 pointNum = 1;
1995 maxCount = points.count() - 1;
1996 break;
1997 }
1998
2001 {
2003 maxCount = points.count();
2004 if ( points.first() == points.last() )
2005 isRing = true;
2006 break;
2007 }
2008
2012 {
2013 return;
2014 }
2015 }
2016
2018 {
2019 double distance;
2021 renderOffsetVertexAlongLine( points, i, distance, context, placement );
2022
2023 return;
2024 }
2025
2026 QPointF prevPoint;
2027 if ( placement == Qgis::MarkerLinePlacement::SegmentCenter && !points.empty() )
2028 prevPoint = points.at( 0 );
2029
2030 QPointF symbolPoint;
2031 for ( ; i < maxCount; ++i )
2032 {
2034
2035 if ( isRing && placement == Qgis::MarkerLinePlacement::Vertex && i == points.count() - 1 )
2036 {
2037 continue; // don't draw the last marker - it has been drawn already
2038 }
2039
2041 {
2042 QPointF currentPoint = points.at( i );
2043 symbolPoint = QPointF( 0.5 * ( currentPoint.x() + prevPoint.x() ),
2044 0.5 * ( currentPoint.y() + prevPoint.y() ) );
2045 if ( rotateSymbols() )
2046 {
2047 double angle = std::atan2( currentPoint.y() - prevPoint.y(),
2048 currentPoint.x() - prevPoint.x() );
2049 setSymbolLineAngle( angle * 180 / M_PI );
2050 }
2051 prevPoint = currentPoint;
2052 }
2053 else
2054 {
2055 symbolPoint = points.at( i );
2056 // rotate marker (if desired)
2057 if ( rotateSymbols() )
2058 {
2059 double angle = markerAngle( points, isRing, i );
2060 setSymbolLineAngle( angle * 180 / M_PI );
2061 }
2062 }
2063
2064 mFinalVertex = symbolPoint;
2065 if ( i != points.count() - 1 || placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2066 renderSymbol( symbolPoint, context.feature(), rc, -1, context.selected() );
2067 }
2068}
2069
2070double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex )
2071{
2072 double angle = 0;
2073 const QPointF &pt = points[vertex];
2074
2075 if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
2076 {
2077 int prevIndex = vertex - 1;
2078 int nextIndex = vertex + 1;
2079
2080 if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
2081 {
2082 prevIndex = points.count() - 2;
2083 nextIndex = 1;
2084 }
2085
2086 QPointF prevPoint, nextPoint;
2087 while ( prevIndex >= 0 )
2088 {
2089 prevPoint = points[ prevIndex ];
2090 if ( prevPoint != pt )
2091 {
2092 break;
2093 }
2094 --prevIndex;
2095 }
2096
2097 while ( nextIndex < points.count() )
2098 {
2099 nextPoint = points[ nextIndex ];
2100 if ( nextPoint != pt )
2101 {
2102 break;
2103 }
2104 ++nextIndex;
2105 }
2106
2107 if ( prevIndex >= 0 && nextIndex < points.count() )
2108 {
2109 angle = _averageAngle( prevPoint, pt, nextPoint );
2110 }
2111 }
2112 else //no ring and vertex is at start / at end
2113 {
2114 if ( vertex == 0 )
2115 {
2116 while ( vertex < points.size() - 1 )
2117 {
2118 const QPointF &nextPt = points[vertex + 1];
2119 if ( pt != nextPt )
2120 {
2121 angle = MyLine( pt, nextPt ).angle();
2122 return angle;
2123 }
2124 ++vertex;
2125 }
2126 }
2127 else
2128 {
2129 // use last segment's angle
2130 while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
2131 {
2132 const QPointF &prevPt = points[vertex - 1];
2133 if ( pt != prevPt )
2134 {
2135 angle = MyLine( prevPt, pt ).angle();
2136 return angle;
2137 }
2138 --vertex;
2139 }
2140 }
2141 }
2142 return angle;
2143}
2144
2145void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context, Qgis::MarkerLinePlacement placement )
2146{
2147 if ( points.isEmpty() )
2148 return;
2149
2150 QgsRenderContext &rc = context.renderContext();
2151 if ( qgsDoubleNear( distance, 0.0 ) )
2152 {
2153 // rotate marker (if desired)
2154 if ( rotateSymbols() )
2155 {
2156 bool isRing = false;
2157 if ( points.first() == points.last() )
2158 isRing = true;
2159 double angle = markerAngle( points, isRing, vertex );
2160 setSymbolLineAngle( angle * 180 / M_PI );
2161 }
2162 mFinalVertex = points[vertex];
2163 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2164 renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() );
2165 return;
2166 }
2167
2168 int pointIncrement = distance > 0 ? 1 : -1;
2169 QPointF previousPoint = points[vertex];
2170 int startPoint = distance > 0 ? std::min( vertex + 1, static_cast<int>( points.count() ) - 1 ) : std::max( vertex - 1, 0 );
2171 int endPoint = distance > 0 ? points.count() - 1 : 0;
2172 double distanceLeft = std::fabs( distance );
2173
2174 for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
2175 {
2176 const QPointF &pt = points[i];
2177
2178 if ( previousPoint == pt ) // must not be equal!
2179 continue;
2180
2181 // create line segment
2182 MyLine l( previousPoint, pt );
2183
2184 if ( distanceLeft < l.length() )
2185 {
2186 //destination point is in current segment
2187 QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
2188 // rotate marker (if desired)
2189 if ( rotateSymbols() )
2190 {
2191 setSymbolLineAngle( l.angle() * 180 / M_PI );
2192 }
2193 mFinalVertex = markerPoint;
2194 if ( placement != Qgis::MarkerLinePlacement::LastVertex || mPlaceOnEveryPart || !mRenderingFeature )
2195 renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() );
2196 return;
2197 }
2198
2199 distanceLeft -= l.length();
2200 previousPoint = pt;
2201 }
2202
2203 //didn't find point
2204}
2205
2206void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
2207{
2208 if ( p.empty() )
2209 return;
2210
2211 QVector< QPointF > points = p;
2212 const bool closedRing = points.first() == points.last();
2213
2214 double lengthLeft = initialOffset;
2215
2216 double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
2217 if ( initialLagLeft < 0 && closedRing )
2218 {
2219 // tracking back around the ring from the first point, insert pseudo vertices before the first vertex
2220 QPointF lastPt = points.constLast();
2221 QVector< QPointF > pseudoPoints;
2222 for ( int i = points.count() - 2; i > 0; --i )
2223 {
2224 if ( initialLagLeft >= 0 )
2225 {
2226 break;
2227 }
2228
2229 const QPointF &pt = points[i];
2230
2231 if ( lastPt == pt ) // must not be equal!
2232 continue;
2233
2234 MyLine l( lastPt, pt );
2235 initialLagLeft += l.length();
2236 lastPt = pt;
2237
2238 pseudoPoints << pt;
2239 }
2240 std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
2241
2242 points = pseudoPoints;
2243 points.append( p );
2244 }
2245 else
2246 {
2247 while ( initialLagLeft < 0 )
2248 {
2249 dest << points.constFirst();
2250 initialLagLeft += intervalPainterUnits;
2251 }
2252 }
2253 if ( initialLag > 0 )
2254 {
2255 lengthLeft += intervalPainterUnits - initialLagLeft;
2256 }
2257
2258 QPointF lastPt = points[0];
2259 for ( int i = 1; i < points.count(); ++i )
2260 {
2261 const QPointF &pt = points[i];
2262
2263 if ( lastPt == pt ) // must not be equal!
2264 {
2265 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2266 {
2267 lastPt = points[0];
2268 i = 0;
2269 }
2270 continue;
2271 }
2272
2273 // for each line, find out dx and dy, and length
2274 MyLine l( lastPt, pt );
2275 QPointF diff = l.diffForInterval( intervalPainterUnits );
2276
2277 // if there's some length left from previous line
2278 // use only the rest for the first point in new line segment
2279 double c = 1 - lengthLeft / intervalPainterUnits;
2280
2281 lengthLeft += l.length();
2282
2283
2284 while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
2285 {
2286 // "c" is 1 for regular point or in interval (0,1] for begin of line segment
2287 lastPt += c * diff;
2288 lengthLeft -= intervalPainterUnits;
2289 dest << lastPt;
2290 c = 1; // reset c (if wasn't 1 already)
2291 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2292 break;
2293 }
2294 lastPt = pt;
2295
2296 if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
2297 break;
2298
2299 // if a closed ring, we keep looping around the ring until we hit the required number of points
2300 if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2301 {
2302 lastPt = points[0];
2303 i = 0;
2304 }
2305 }
2306
2307 if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
2308 {
2309 // pad with repeating last point to match desired size
2310 while ( dest.size() < numberPointsRequired )
2311 dest << points.constLast();
2312 }
2313}
2314
2315void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
2316{
2317 if ( !points.isEmpty() )
2318 {
2319 // calc length
2320 qreal length = 0;
2321 QPolygonF::const_iterator it = points.constBegin();
2322 QPointF last = *it;
2323 for ( ++it; it != points.constEnd(); ++it )
2324 {
2325 length += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2326 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2327 last = *it;
2328 }
2329 if ( qgsDoubleNear( length, 0.0 ) )
2330 return;
2331
2332 const double midPoint = length / 2;
2333
2334 QPointF pt;
2335 double thisSymbolAngle = 0;
2336
2337 if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
2338 {
2339 QVector< QPointF > angleStartPoints;
2340 QVector< QPointF > symbolPoints;
2341 QVector< QPointF > angleEndPoints;
2342 // collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
2343 collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
2344 collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
2345 collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
2346
2347 pt = symbolPoints.at( 1 );
2348 MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
2349 thisSymbolAngle = l.angle();
2350 }
2351 else
2352 {
2353 // find the segment where the central point lies
2354 it = points.constBegin();
2355 last = *it;
2356 qreal last_at = 0, next_at = 0;
2357 QPointF next;
2358 int segment = 0;
2359 for ( ++it; it != points.constEnd(); ++it )
2360 {
2361 next = *it;
2362 next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
2363 ( last.y() - it->y() ) * ( last.y() - it->y() ) );
2364 if ( next_at >= midPoint )
2365 break; // we have reached the center
2366 last = *it;
2367 last_at = next_at;
2368 segment++;
2369 }
2370
2371 // find out the central point on segment
2372 MyLine l( last, next ); // for line angle
2373 qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
2374 pt = last + ( next - last ) * k;
2375 thisSymbolAngle = l.angle();
2376 }
2377
2378 // draw the marker
2379 // rotate marker (if desired)
2380 if ( rotateSymbols() )
2381 {
2382 setSymbolLineAngle( thisSymbolAngle * 180 / M_PI );
2383 }
2384
2385 renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
2386
2387 }
2388}
2389
2391{
2392 return mMarker.get();
2393}
2394
2396{
2397 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
2398 {
2399 delete symbol;
2400 return false;
2401 }
2402
2403 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
2404 mColor = mMarker->color();
2405 return true;
2406}
2407
2408
2409
2410//
2411// QgsMarkerLineSymbolLayer
2412//
2413
2414QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval )
2415 : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval )
2416{
2418}
2419
2421
2423{
2424 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2426
2427 if ( props.contains( QStringLiteral( "interval" ) ) )
2428 interval = props[QStringLiteral( "interval" )].toDouble();
2429 if ( props.contains( QStringLiteral( "rotate" ) ) )
2430 rotate = ( props[QStringLiteral( "rotate" )].toString() == QLatin1String( "1" ) );
2431
2432 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval );
2433 setCommonProperties( x.get(), props );
2434 return x.release();
2435}
2436
2438{
2439 return QStringLiteral( "MarkerLine" );
2440}
2441
2442void QgsMarkerLineSymbolLayer::setColor( const QColor &color )
2443{
2444 mMarker->setColor( color );
2445 mColor = color;
2446}
2447
2449{
2450 return mMarker ? mMarker->color() : mColor;
2451}
2452
2454{
2455 // if being rotated, it gets initialized with every line segment
2456 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2457 if ( rotateSymbols() )
2459 mMarker->setRenderHints( hints );
2460
2461 mMarker->startRender( context.renderContext(), context.fields() );
2462}
2463
2465{
2466 mMarker->stopRender( context.renderContext() );
2467}
2468
2469
2471{
2472 std::unique_ptr< QgsMarkerLineSymbolLayer > x = std::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() );
2474 return x.release();
2475}
2476
2477void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2478{
2479 for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
2480 {
2481 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:LineSymbolizer" ) );
2482 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2483 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2484 element.appendChild( symbolizerElem );
2485
2486 // <Geometry>
2487 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2488
2489 QString gap;
2491 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) );
2493 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) );
2495 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) );
2497 // no way to get line/polygon's vertices, use a VendorOption
2498 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) );
2499
2501 {
2503 gap = qgsDoubleToString( interval );
2504 }
2505
2506 if ( !rotateSymbols() )
2507 {
2508 // markers in LineSymbolizer must be drawn following the line orientation,
2509 // use a VendorOption when no marker rotation
2510 symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "rotateMarker" ), QStringLiteral( "0" ) ) );
2511 }
2512
2513 // <Stroke>
2514 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2515 symbolizerElem.appendChild( strokeElem );
2516
2517 // <GraphicStroke>
2518 QDomElement graphicStrokeElem = doc.createElement( QStringLiteral( "se:GraphicStroke" ) );
2519 strokeElem.appendChild( graphicStrokeElem );
2520
2521 QgsSymbolLayer *layer = mMarker->symbolLayer( i );
2522 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
2523 {
2524 markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
2525 }
2526 else if ( layer )
2527 {
2528 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
2529 }
2530 else
2531 {
2532 graphicStrokeElem.appendChild( doc.createComment( QStringLiteral( "Missing marker line symbol layer. Skip it." ) ) );
2533 }
2534
2535 if ( !gap.isEmpty() )
2536 {
2537 QDomElement gapElem = doc.createElement( QStringLiteral( "se:Gap" ) );
2539 graphicStrokeElem.appendChild( gapElem );
2540 }
2541
2542 if ( !qgsDoubleNear( mOffset, 0.0 ) )
2543 {
2544 QDomElement perpOffsetElem = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
2546 perpOffsetElem.appendChild( doc.createTextNode( qgsDoubleToString( offset ) ) );
2547 symbolizerElem.appendChild( perpOffsetElem );
2548 }
2549 }
2550}
2551
2553{
2554 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2555
2556 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2557 if ( strokeElem.isNull() )
2558 return nullptr;
2559
2560 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
2561 if ( graphicStrokeElem.isNull() )
2562 return nullptr;
2563
2564 // retrieve vendor options
2565 bool rotateMarker = true;
2567
2568 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
2569 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
2570 {
2571 if ( it.key() == QLatin1String( "placement" ) )
2572 {
2573 if ( it.value() == QLatin1String( "points" ) )
2575 else if ( it.value() == QLatin1String( "firstPoint" ) )
2577 else if ( it.value() == QLatin1String( "lastPoint" ) )
2579 else if ( it.value() == QLatin1String( "centralPoint" ) )
2581 }
2582 else if ( it.value() == QLatin1String( "rotateMarker" ) )
2583 {
2584 rotateMarker = it.value() == QLatin1String( "0" );
2585 }
2586 }
2587
2588 std::unique_ptr< QgsMarkerSymbol > marker;
2589
2591 if ( l )
2592 {
2593 QgsSymbolLayerList layers;
2594 layers.append( l );
2595 marker.reset( new QgsMarkerSymbol( layers ) );
2596 }
2597
2598 if ( !marker )
2599 return nullptr;
2600
2601 double interval = 0.0;
2602 QDomElement gapElem = graphicStrokeElem.firstChildElement( QStringLiteral( "Gap" ) );
2603 if ( !gapElem.isNull() )
2604 {
2605 bool ok;
2606 double d = gapElem.firstChild().nodeValue().toDouble( &ok );
2607 if ( ok )
2608 interval = d;
2609 }
2610
2611 double offset = 0.0;
2612 QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( QStringLiteral( "PerpendicularOffset" ) );
2613 if ( !perpOffsetElem.isNull() )
2614 {
2615 bool ok;
2616 double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
2617 if ( ok )
2618 offset = d;
2619 }
2620
2621 double scaleFactor = 1.0;
2622 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2623 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2624 interval = interval * scaleFactor;
2625 offset = offset * scaleFactor;
2626
2628 x->setOutputUnit( sldUnitSize );
2630 x->setInterval( interval );
2631 x->setSubSymbol( marker.release() );
2632 x->setOffset( offset );
2633 return x;
2634}
2635
2637{
2638 mMarker->setSize( width );
2639}
2640
2642{
2643 if ( key == QgsSymbolLayer::PropertyWidth && mMarker && property )
2644 {
2645 mMarker->setDataDefinedSize( property );
2646 }
2648}
2649
2651{
2652 const double prevOpacity = mMarker->opacity();
2653 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
2655 mMarker->setOpacity( prevOpacity );
2656}
2657
2659{
2660 mMarker->setLineAngle( angle );
2661}
2662
2664{
2665 return mMarker->angle();
2666}
2667
2669{
2670 mMarker->setAngle( angle );
2671}
2672
2673void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2674{
2675 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2677
2678 mMarker->renderPoint( point, feature, context, layer, selected );
2679
2680 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2681}
2682
2684{
2685 return mMarker->size();
2686}
2687
2689{
2690 return mMarker->size( context );
2691}
2692
2694{
2696 mMarker->setOutputUnit( unit );
2697}
2698
2700{
2706 || ( mMarker && mMarker->usesMapUnits() );
2707}
2708
2710{
2711 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2712 if ( mMarker )
2713 attr.unite( mMarker->usedAttributes( context ) );
2714 return attr;
2715}
2716
2718{
2720 return true;
2721 if ( mMarker && mMarker->hasDataDefinedProperties() )
2722 return true;
2723 return false;
2724}
2725
2727{
2728 return ( mMarker->size( context ) / 2.0 ) +
2730}
2731
2732
2733//
2734// QgsHashedLineSymbolLayer
2735//
2736
2737QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval )
2738 : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval )
2739{
2740 setSubSymbol( new QgsLineSymbol() );
2741}
2742
2744
2746{
2747 bool rotate = DEFAULT_MARKERLINE_ROTATE;
2749
2750 if ( props.contains( QStringLiteral( "interval" ) ) )
2751 interval = props[QStringLiteral( "interval" )].toDouble();
2752 if ( props.contains( QStringLiteral( "rotate" ) ) )
2753 rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) );
2754
2755 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotate, interval );
2756 setCommonProperties( x.get(), props );
2757 if ( props.contains( QStringLiteral( "hash_angle" ) ) )
2758 {
2759 x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() );
2760 }
2761
2762 if ( props.contains( QStringLiteral( "hash_length" ) ) )
2763 x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() );
2764
2765 if ( props.contains( QStringLiteral( "hash_length_unit" ) ) )
2766 x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )].toString() ) );
2767
2768 if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) )
2769 x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )].toString() ) );
2770
2771 return x.release();
2772}
2773
2775{
2776 return QStringLiteral( "HashLine" );
2777}
2778
2780{
2781 // if being rotated, it gets initialized with every line segment
2782 Qgis::SymbolRenderHints hints = Qgis::SymbolRenderHints();
2783 if ( rotateSymbols() )
2785 mHashSymbol->setRenderHints( hints );
2786
2787 mHashSymbol->startRender( context.renderContext(), context.fields() );
2788}
2789
2791{
2792 mHashSymbol->stopRender( context.renderContext() );
2793}
2794
2796{
2798 map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle );
2799
2800 map[QStringLiteral( "hash_length" )] = QString::number( mHashLength );
2801 map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit );
2802 map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale );
2803
2804 return map;
2805}
2806
2808{
2809 std::unique_ptr< QgsHashedLineSymbolLayer > x = std::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() );
2811 x->setHashAngle( mHashAngle );
2812 x->setHashLength( mHashLength );
2813 x->setHashLengthUnit( mHashLengthUnit );
2814 x->setHashLengthMapUnitScale( mHashLengthMapUnitScale );
2815 return x.release();
2816}
2817
2818void QgsHashedLineSymbolLayer::setColor( const QColor &color )
2819{
2820 mHashSymbol->setColor( color );
2821 mColor = color;
2822}
2823
2825{
2826 return mHashSymbol ? mHashSymbol->color() : mColor;
2827}
2828
2830{
2831 return mHashSymbol.get();
2832}
2833
2835{
2836 if ( !symbol || symbol->type() != Qgis::SymbolType::Line )
2837 {
2838 delete symbol;
2839 return false;
2840 }
2841
2842 mHashSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
2843 mColor = mHashSymbol->color();
2844 return true;
2845}
2846
2847void QgsHashedLineSymbolLayer::setWidth( const double width )
2848{
2849 mHashLength = width;
2850}
2851
2853{
2854 return mHashLength;
2855}
2856
2858{
2859 return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale );
2860}
2861
2863{
2864 return ( mHashSymbol->width( context ) / 2.0 )
2865 + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale )
2867}
2868
2870{
2872 mHashSymbol->setOutputUnit( unit );
2873}
2874
2876{
2877 QSet<QString> attr = QgsLineSymbolLayer::usedAttributes( context );
2878 if ( mHashSymbol )
2879 attr.unite( mHashSymbol->usedAttributes( context ) );
2880 return attr;
2881}
2882
2884{
2886 return true;
2887 if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() )
2888 return true;
2889 return false;
2890}
2891
2893{
2894 if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property )
2895 {
2896 mHashSymbol->setDataDefinedWidth( property );
2897 }
2899}
2900
2902{
2903 return mHashLengthUnit == QgsUnitTypes::RenderMapUnits || mHashLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
2909 || ( mHashSymbol && mHashSymbol->usesMapUnits() );
2910}
2911
2913{
2914 mSymbolLineAngle = angle;
2915}
2916
2918{
2919 return mSymbolAngle;
2920}
2921
2923{
2924 mSymbolAngle = angle;
2925}
2926
2927void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected )
2928{
2929 double lineLength = mHashLength;
2931 {
2932 context.expressionContext().setOriginalValueVariable( mHashLength );
2934 }
2935 const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0;
2936
2937 double hashAngle = mHashAngle;
2939 {
2940 context.expressionContext().setOriginalValueVariable( mHashAngle );
2942 }
2943
2944 QgsPointXY center( point );
2945 QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2946 QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) );
2947
2948 QPolygonF points;
2949 points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() );
2950
2951 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2953
2954 mHashSymbol->renderPolyline( points, feature, context, layer, selected );
2955
2956 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
2957}
2958
2960{
2961 return mHashAngle;
2962}
2963
2965{
2966 mHashAngle = angle;
2967}
2968
2970{
2971 const double prevOpacity = mHashSymbol->opacity();
2972 mHashSymbol->setOpacity( mHashSymbol->opacity() * context.opacity() );
2974 mHashSymbol->setOpacity( prevOpacity );
2975}
2976
2977//
2978// QgsAbstractBrushedLineSymbolLayer
2979//
2980
2981void QgsAbstractBrushedLineSymbolLayer::renderPolylineUsingBrush( const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength )
2982{
2983 if ( !context.renderContext().painter() )
2984 return;
2985
2986 double offset = mOffset;
2988 {
2991 }
2992
2993 QPolygonF offsetPoints;
2994 if ( qgsDoubleNear( offset, 0 ) )
2995 {
2996 renderLine( points, context, patternThickness, patternLength, brush );
2997 }
2998 else
2999 {
3000 const double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3001
3002 const QList<QPolygonF> offsetLine = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
3003 for ( const QPolygonF &part : offsetLine )
3004 {
3005 renderLine( part, context, patternThickness, patternLength, brush );
3006 }
3007 }
3008}
3009
3010void QgsAbstractBrushedLineSymbolLayer::renderLine( const QPolygonF &points, QgsSymbolRenderContext &context, const double lineThickness,
3011 const double patternLength, const QBrush &sourceBrush )
3012{
3013 QPainter *p = context.renderContext().painter();
3014 if ( !p )
3015 return;
3016
3017 QBrush brush = sourceBrush;
3018
3019 // duplicate points mess up the calculations, we need to remove them first
3020 // we'll calculate the min/max coordinate at the same time, since we're already looping
3021 // through the points
3022 QPolygonF inputPoints;
3023 inputPoints.reserve( points.size() );
3024 QPointF prev;
3025 double minX = std::numeric_limits< double >::max();
3026 double minY = std::numeric_limits< double >::max();
3027 double maxX = std::numeric_limits< double >::lowest();
3028 double maxY = std::numeric_limits< double >::lowest();
3029
3030 for ( const QPointF &pt : std::as_const( points ) )
3031 {
3032 if ( !inputPoints.empty() && qgsDoubleNear( prev.x(), pt.x(), 0.01 ) && qgsDoubleNear( prev.y(), pt.y(), 0.01 ) )
3033 continue;
3034
3035 inputPoints << pt;
3036 prev = pt;
3037 minX = std::min( minX, pt.x() );
3038 minY = std::min( minY, pt.y() );
3039 maxX = std::max( maxX, pt.x() );
3040 maxY = std::max( maxY, pt.y() );
3041 }
3042
3043 if ( inputPoints.size() < 2 ) // nothing to render
3044 return;
3045
3046 // buffer size to extend out the temporary image, just to ensure that we don't clip out any antialiasing effects
3047 constexpr int ANTIALIAS_ALLOWANCE_PIXELS = 10;
3048 // amount of overlap to use when rendering adjacent line segments to ensure that no artifacts are visible between segments
3049 constexpr double ANTIALIAS_OVERLAP_PIXELS = 0.5;
3050
3051 // 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
3052 const int imageWidth = static_cast< int >( std::ceil( maxX - minX ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3053 const int imageHeight = static_cast< int >( std::ceil( maxY - minY ) + lineThickness * 2 ) + ANTIALIAS_ALLOWANCE_PIXELS * 2;
3054
3055 const bool isClosedLine = qgsDoubleNear( points.at( 0 ).x(), points.constLast().x(), 0.01 )
3056 && qgsDoubleNear( points.at( 0 ).y(), points.constLast().y(), 0.01 );
3057
3058 QImage temporaryImage( imageWidth, imageHeight, QImage::Format_ARGB32_Premultiplied );
3059 if ( temporaryImage.isNull() )
3060 {
3061 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for raster line symbol" ) );
3062 return;
3063 }
3064
3065 // clear temporary image contents
3066 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3067 return;
3068 temporaryImage.fill( Qt::transparent );
3069 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3070 return;
3071
3072 Qt::PenJoinStyle join = mPenJoinStyle;
3074 {
3077 if ( !QgsVariantUtils::isNull( exprVal ) )
3078 join = QgsSymbolLayerUtils::decodePenJoinStyle( exprVal.toString() );
3079 }
3080
3081 Qt::PenCapStyle cap = mPenCapStyle;
3083 {
3086 if ( !QgsVariantUtils::isNull( exprVal ) )
3087 cap = QgsSymbolLayerUtils::decodePenCapStyle( exprVal.toString() );
3088 }
3089
3090 // stroke out the path using the correct line cap/join style. We'll then use this as a clipping path
3091 QPainterPathStroker stroker;
3092 stroker.setWidth( lineThickness );
3093 stroker.setCapStyle( cap );
3094 stroker.setJoinStyle( join );
3095
3096 QPainterPath path;
3097 path.addPolygon( inputPoints );
3098 const QPainterPath stroke = stroker.createStroke( path ).simplified();
3099
3100 // prepare temporary image
3101 QPainter imagePainter;
3102 imagePainter.begin( &temporaryImage );
3103 context.renderContext().setPainterFlagsUsingContext( &imagePainter );
3104 imagePainter.translate( -minX + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS, -minY + lineThickness + ANTIALIAS_ALLOWANCE_PIXELS );
3105
3106 imagePainter.setClipPath( stroke, Qt::IntersectClip );
3107 imagePainter.setPen( Qt::NoPen );
3108
3109 QPointF segmentStartPoint = inputPoints.at( 0 );
3110
3111 // current brush progress through the image (horizontally). Used to track which column of image data to start the next brush segment using.
3112 double progressThroughImage = 0;
3113
3114 QgsPoint prevSegmentPolygonEndLeft;
3115 QgsPoint prevSegmentPolygonEndRight;
3116
3117 // for closed rings this will store the left/right polygon points of the start/end of the line
3118 QgsPoint startLinePolygonLeft;
3119 QgsPoint startLinePolygonRight;
3120
3121 for ( int i = 1; i < inputPoints.size(); ++i )
3122 {
3123 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3124 break;
3125
3126 const QPointF segmentEndPoint = inputPoints.at( i );
3127 const double segmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentStartPoint.x(), segmentStartPoint.y(),
3128 segmentEndPoint.x(), segmentEndPoint.y() ) - 90;
3129
3130 // left/right end points of the current segment polygon
3131 QgsPoint thisSegmentPolygonEndLeft;
3132 QgsPoint thisSegmentPolygonEndRight;
3133 // left/right end points of the current segment polygon, tweaked to avoid antialiasing artifacts
3134 QgsPoint thisSegmentPolygonEndLeftForPainter;
3135 QgsPoint thisSegmentPolygonEndRightForPainter;
3136 if ( i == 1 )
3137 {
3138 // first line segment has special handling -- we extend back out by half the image thickness so that the line cap is correctly drawn.
3139 // (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.)
3140 if ( isClosedLine )
3141 {
3142 // project the current segment out by half the image thickness to either side of the line
3143 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3144 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3145 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3146 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3147
3148 // 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
3149 // what angle the current segment polygon should START on.
3150 const double lastSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( points.at( points.size() - 2 ).x(), points.at( points.size() - 2 ).y(),
3151 segmentStartPoint.x(), segmentStartPoint.y() ) - 90;
3152
3153 // project out the LAST segment in the line by half the image thickness to either side of the line
3154 const QgsPoint lastSegmentStartPointLeft = QgsPoint( points.at( points.size() - 2 ) ).project( lineThickness / 2, lastSegmentAngleDegrees );
3155 const QgsPoint lastSegmentEndPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, lastSegmentAngleDegrees );
3156 const QgsPoint lastSegmentStartPointRight = QgsPoint( points.at( points.size() - 2 ) ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3157 const QgsPoint lastSegmentEndPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, lastSegmentAngleDegrees );
3158
3159 // the polygon representing the current segment STARTS at the points where the projected lines to the left/right
3160 // 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
3161 // join)
3162 QgsPoint intersectionPoint;
3163 bool isIntersection = false;
3164 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointLeft, lastSegmentEndPointLeft, startPointLeft, endPointLeft, prevSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3165 if ( !isIntersection )
3166 prevSegmentPolygonEndLeft = startPointLeft;
3167 isIntersection = false;
3168 QgsGeometryUtils::segmentIntersection( lastSegmentStartPointRight, lastSegmentEndPointRight, startPointRight, endPointRight, prevSegmentPolygonEndRight, isIntersection, 1e-8, true );
3169 if ( !isIntersection )
3170 prevSegmentPolygonEndRight = startPointRight;
3171
3172 startLinePolygonLeft = prevSegmentPolygonEndLeft;
3173 startLinePolygonRight = prevSegmentPolygonEndRight;
3174 }
3175 else
3176 {
3177 prevSegmentPolygonEndLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3178 if ( cap != Qt::PenCapStyle::FlatCap )
3179 prevSegmentPolygonEndLeft = prevSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees - 90 );
3180 prevSegmentPolygonEndRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3181 if ( cap != Qt::PenCapStyle::FlatCap )
3182 prevSegmentPolygonEndRight = prevSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees - 90 );
3183 }
3184 }
3185
3186 if ( i < inputPoints.size() - 1 )
3187 {
3188 // for all other segments except the last
3189
3190 // project the current segment out by half the image thickness to either side of the line
3191 const QgsPoint startPointLeft = QgsPoint( segmentStartPoint ).project( lineThickness / 2, segmentAngleDegrees );
3192 const QgsPoint endPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3193 const QgsPoint startPointRight = QgsPoint( segmentStartPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3194 const QgsPoint endPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3195
3196 // angle of NEXT line segment (i.e. not the one we are drawing right now). Used to determine
3197 // what angle the current segment polygon should end on
3198 const double nextSegmentAngleDegrees = 180.0 / M_PI * QgsGeometryUtils::lineAngle( segmentEndPoint.x(), segmentEndPoint.y(),
3199 inputPoints.at( i + 1 ).x(), inputPoints.at( i + 1 ).y() ) - 90;
3200
3201 // project out the next segment by half the image thickness to either side of the line
3202 const QgsPoint nextSegmentStartPointLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, nextSegmentAngleDegrees );
3203 const QgsPoint nextSegmentEndPointLeft = QgsPoint( inputPoints.at( i + 1 ) ).project( lineThickness / 2, nextSegmentAngleDegrees );
3204 const QgsPoint nextSegmentStartPointRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3205 const QgsPoint nextSegmentEndPointRight = QgsPoint( inputPoints.at( i + 1 ) ).project( -lineThickness / 2, nextSegmentAngleDegrees );
3206
3207 // the polygon representing the current segment ends at the points where the projected lines to the left/right
3208 // of THIS segment would intersect with the project lines to the left/right of the NEXT segment (i.e. simulate a miter style
3209 // join)
3210 QgsPoint intersectionPoint;
3211 bool isIntersection = false;
3212 QgsGeometryUtils::segmentIntersection( startPointLeft, endPointLeft, nextSegmentStartPointLeft, nextSegmentEndPointLeft, thisSegmentPolygonEndLeft, isIntersection, 1e-8, true );
3213 if ( !isIntersection )
3214 thisSegmentPolygonEndLeft = endPointLeft;
3215 isIntersection = false;
3216 QgsGeometryUtils::segmentIntersection( startPointRight, endPointRight, nextSegmentStartPointRight, nextSegmentEndPointRight, thisSegmentPolygonEndRight, isIntersection, 1e-8, true );
3217 if ( !isIntersection )
3218 thisSegmentPolygonEndRight = endPointRight;
3219
3220 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3221 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3222 }
3223 else
3224 {
3225 // last segment has special handling -- we extend forward by half the image thickness so that the line cap is correctly drawn
3226 // unless it's a closed line
3227 if ( isClosedLine )
3228 {
3229 thisSegmentPolygonEndLeft = startLinePolygonLeft;
3230 thisSegmentPolygonEndRight = startLinePolygonRight;
3231
3232 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3233 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight.project( ANTIALIAS_OVERLAP_PIXELS, segmentAngleDegrees + 90 );
3234 }
3235 else
3236 {
3237 thisSegmentPolygonEndLeft = QgsPoint( segmentEndPoint ).project( lineThickness / 2, segmentAngleDegrees );
3238 if ( cap != Qt::PenCapStyle::FlatCap )
3239 thisSegmentPolygonEndLeft = thisSegmentPolygonEndLeft.project( lineThickness / 2, segmentAngleDegrees + 90 );
3240 thisSegmentPolygonEndRight = QgsPoint( segmentEndPoint ).project( -lineThickness / 2, segmentAngleDegrees );
3241 if ( cap != Qt::PenCapStyle::FlatCap )
3242 thisSegmentPolygonEndRight = thisSegmentPolygonEndRight.project( lineThickness / 2, segmentAngleDegrees + 90 );
3243
3244 thisSegmentPolygonEndLeftForPainter = thisSegmentPolygonEndLeft;
3245 thisSegmentPolygonEndRightForPainter = thisSegmentPolygonEndRight;
3246 }
3247 }
3248
3249 // brush transform is designed to draw the image starting at the correct current progress through it (following on from
3250 // where we got with the previous segment), at the correct angle
3251 QTransform brushTransform;
3252 brushTransform.translate( segmentStartPoint.x(), segmentStartPoint.y() );
3253 brushTransform.rotate( -segmentAngleDegrees );
3254 if ( i == 1 && cap != Qt::PenCapStyle::FlatCap )
3255 {
3256 // special handling for first segment -- because we extend the line back by half its thickness (to show the cap),
3257 // we need to also do the same for the brush transform
3258 brushTransform.translate( -( lineThickness / 2 ), 0 );
3259 }
3260 brushTransform.translate( -progressThroughImage, -lineThickness / 2 );
3261
3262 brush.setTransform( brushTransform );
3263 imagePainter.setBrush( brush );
3264
3265 // now draw the segment polygon
3266 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3267 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3268 << thisSegmentPolygonEndRightForPainter.toQPointF()
3269 << prevSegmentPolygonEndRight.toQPointF()
3270 << prevSegmentPolygonEndLeft.toQPointF() );
3271
3272#if 0 // for debugging, will draw the segment polygons
3273 imagePainter.setPen( QPen( QColor( 0, 255, 255 ), 2 ) );
3274 imagePainter.setBrush( Qt::NoBrush );
3275 imagePainter.drawPolygon( QPolygonF() << prevSegmentPolygonEndLeft.toQPointF()
3276 << thisSegmentPolygonEndLeftForPainter.toQPointF()
3277 << thisSegmentPolygonEndRightForPainter.toQPointF()
3278 << prevSegmentPolygonEndRight.toQPointF()
3279 << prevSegmentPolygonEndLeft.toQPointF() );
3280 imagePainter.setPen( Qt::NoPen );
3281#endif
3282
3283 // calculate the new progress horizontal through the source image to account for the length
3284 // of the segment we've just drawn
3285 progressThroughImage += sqrt( std::pow( segmentStartPoint.x() - segmentEndPoint.x(), 2 )
3286 + std::pow( segmentStartPoint.y() - segmentEndPoint.y(), 2 ) )
3287 + ( i == 1 && cap != Qt::PenCapStyle::FlatCap ? lineThickness / 2 : 0 ); // for first point we extended the pattern out by half its thickess at the start
3288 progressThroughImage = fmod( progressThroughImage, patternLength );
3289
3290 // shuffle buffered variables for next loop
3291 segmentStartPoint = segmentEndPoint;
3292 prevSegmentPolygonEndLeft = thisSegmentPolygonEndLeft;
3293 prevSegmentPolygonEndRight = thisSegmentPolygonEndRight;
3294 }
3295 imagePainter.end();
3296
3297 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
3298 return;
3299
3300 // lastly, draw the temporary image onto the destination painter at the correct place
3301 p->drawImage( QPointF( minX - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS,
3302 minY - lineThickness - ANTIALIAS_ALLOWANCE_PIXELS ), temporaryImage );
3303}
3304
3305
3306//
3307// QgsRasterLineSymbolLayer
3308//
3309
3311 : mPath( path )
3312{
3313}
3314
3316
3317QgsSymbolLayer *QgsRasterLineSymbolLayer::create( const QVariantMap &properties )
3318{
3319 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique<QgsRasterLineSymbolLayer>();
3320
3321 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3322 {
3323 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3324 }
3325 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3326 {
3327 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3328 }
3329 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3330 {
3331 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3332 }
3333
3334 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3335 res->setPath( properties[QStringLiteral( "imageFile" )].toString() );
3336
3337 if ( properties.contains( QStringLiteral( "offset" ) ) )
3338 {
3339 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3340 }
3341 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3342 {
3343 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3344 }
3345 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3346 {
3347 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3348 }
3349
3350 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3351 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3352 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3353 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3354
3355 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3356 {
3357 res->setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3358 }
3359
3360 return res.release();
3361}
3362
3363
3365{
3366 QVariantMap map;
3367 map[QStringLiteral( "imageFile" )] = mPath;
3368
3369 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3370 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3371 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3372
3373 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3374 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3375
3376 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3377 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3378 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3379
3380 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3381
3382 return map;
3383}
3384
3386{
3387 std::unique_ptr< QgsRasterLineSymbolLayer > res = std::make_unique< QgsRasterLineSymbolLayer >( mPath );
3388 res->setWidth( mWidth );
3389 res->setWidthUnit( mWidthUnit );
3390 res->setWidthMapUnitScale( mWidthMapUnitScale );
3391 res->setPenJoinStyle( mPenJoinStyle );
3392 res->setPenCapStyle( mPenCapStyle );
3393 res->setOffsetUnit( mOffsetUnit );
3394 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3395 res->setOffset( mOffset );
3396 res->setOpacity( mOpacity );
3397 copyDataDefinedProperties( res.get() );
3398 copyPaintEffect( res.get() );
3399 return res.release();
3400}
3401
3402void QgsRasterLineSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3403{
3404 const QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
3405 if ( it != properties.end() && it.value().type() == QVariant::String )
3406 {
3407 if ( saving )
3408 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3409 else
3410 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3411 }
3412}
3413
3414void QgsRasterLineSymbolLayer::setPath( const QString &path )
3415{
3416 mPath = path;
3417}
3418
3420{
3421 return QStringLiteral( "RasterLine" );
3422}
3423
3425{
3426 double scaledHeight = context.renderContext().convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
3427
3429
3430 double opacity = mOpacity * context.opacity();
3431 bool cached = false;
3433 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3434 static_cast< int >( std::ceil( scaledHeight ) ) ),
3435 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3436}
3437
3439{
3440}
3441
3443{
3444 if ( !context.renderContext().painter() )
3445 return;
3446
3447 QImage sourceImage = mLineImage;
3451 {
3452 QString path = mPath;
3454 {
3455 context.setOriginalValueVariable( path );
3457 }
3458
3459 double strokeWidth = mWidth;
3461 {
3462 context.setOriginalValueVariable( strokeWidth );
3464 }
3465 const double scaledHeight = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3466
3468 double opacity = mOpacity;
3470 {
3473 }
3474 opacity *= context.opacity();
3475
3476 bool cached = false;
3478 QSize( static_cast< int >( std::round( originalSize.width() / originalSize.height() * std::max( 1.0, scaledHeight ) ) ),
3479 static_cast< int >( std::ceil( scaledHeight ) ) ),
3480 true, opacity, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
3481 }
3482
3483 if ( context.selected() )
3484 {
3486 }
3487
3488 const QBrush brush( sourceImage );
3489
3490 renderPolylineUsingBrush( points, context, brush, sourceImage.height(), sourceImage.width() );
3491}
3492
3494{
3496 mWidthUnit = unit;
3497 mOffsetUnit = unit;
3498}
3499
3501{
3503 if ( mWidthUnit != unit || mOffsetUnit != unit )
3504 {
3506 }
3507 return unit;
3508}
3509
3511{
3514}
3515
3517{
3519 mOffsetMapUnitScale = scale;
3520}
3521
3523{
3526 {
3527 return mWidthMapUnitScale;
3528 }
3529 return QgsMapUnitScale();
3530}
3531
3533{
3534 return ( mWidth / 2.0 ) + mOffset;
3535}
3536
3538{
3539 return QColor();
3540}
3541
3542
3543//
3544// QgsLineburstSymbolLayer
3545//
3546
3547QgsLineburstSymbolLayer::QgsLineburstSymbolLayer( const QColor &color, const QColor &color2 )
3549 , mColor2( color2 )
3550{
3551 setColor( color );
3552}
3553
3555
3556QgsSymbolLayer *QgsLineburstSymbolLayer::create( const QVariantMap &properties )
3557{
3558 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique<QgsLineburstSymbolLayer>();
3559
3560 if ( properties.contains( QStringLiteral( "line_width" ) ) )
3561 {
3562 res->setWidth( properties[QStringLiteral( "line_width" )].toDouble() );
3563 }
3564 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
3565 {
3566 res->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
3567 }
3568 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
3569 {
3570 res->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
3571 }
3572
3573 if ( properties.contains( QStringLiteral( "offset" ) ) )
3574 {
3575 res->setOffset( properties[QStringLiteral( "offset" )].toDouble() );
3576 }
3577 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3578 {
3579 res->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3580 }
3581 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3582 {
3583 res->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3584 }
3585
3586 if ( properties.contains( QStringLiteral( "joinstyle" ) ) )
3587 res->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( properties[QStringLiteral( "joinstyle" )].toString() ) );
3588 if ( properties.contains( QStringLiteral( "capstyle" ) ) )
3589 res->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( properties[QStringLiteral( "capstyle" )].toString() ) );
3590
3591 if ( properties.contains( QStringLiteral( "color_type" ) ) )
3592 res->setGradientColorType( static_cast< Qgis::GradientColorSource >( properties[QStringLiteral( "color_type" )].toInt() ) );
3593
3594 if ( properties.contains( QStringLiteral( "color" ) ) )
3595 {
3596 res->setColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
3597 }
3598 if ( properties.contains( QStringLiteral( "gradient_color2" ) ) )
3599 {
3600 res->setColor2( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "gradient_color2" )].toString() ) );
3601 }
3602
3603 //attempt to create color ramp from props
3604 if ( properties.contains( QStringLiteral( "rampType" ) ) && properties[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
3605 {
3606 res->setColorRamp( QgsCptCityColorRamp::create( properties ) );
3607 }
3608 else
3609 {
3610 res->setColorRamp( QgsGradientColorRamp::create( properties ) );
3611 }
3612
3613 return res.release();
3614}
3615
3617{
3618 QVariantMap map;
3619
3620 map[QStringLiteral( "line_width" )] = QString::number( mWidth );
3621 map[QStringLiteral( "line_width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
3622 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
3623
3624 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3625 map[QStringLiteral( "capstyle" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
3626
3627 map[QStringLiteral( "offset" )] = QString::number( mOffset );
3628 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3629 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3630
3631 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3632 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
3633 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
3634 if ( mGradientRamp )
3635 {
3636#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
3637 map.unite( mGradientRamp->properties() );
3638#else
3639 map.insert( mGradientRamp->properties() );
3640#endif
3641 }
3642
3643 return map;
3644}
3645
3647{
3648 std::unique_ptr< QgsLineburstSymbolLayer > res = std::make_unique< QgsLineburstSymbolLayer >();
3649 res->setWidth( mWidth );
3650 res->setWidthUnit( mWidthUnit );
3651 res->setWidthMapUnitScale( mWidthMapUnitScale );
3652 res->setPenJoinStyle( mPenJoinStyle );
3653 res->setPenCapStyle( mPenCapStyle );
3654 res->setOffsetUnit( mOffsetUnit );
3655 res->setOffsetMapUnitScale( mOffsetMapUnitScale );
3656 res->setOffset( mOffset );
3657 res->setColor( mColor );
3658 res->setColor2( mColor2 );
3659 res->setGradientColorType( mGradientColorType );
3660 if ( mGradientRamp )
3661 res->setColorRamp( mGradientRamp->clone() );
3662 copyDataDefinedProperties( res.get() );
3663 copyPaintEffect( res.get() );
3664 return res.release();
3665}
3666
3668{
3669 return QStringLiteral( "Lineburst" );
3670}
3671
3673{
3674}
3675
3677{
3678}
3679
3681{
3682 if ( !context.renderContext().painter() )
3683 return;
3684
3685 double strokeWidth = mWidth;
3687 {
3688 context.setOriginalValueVariable( strokeWidth );
3690 }
3691 const double scaledWidth = context.renderContext().convertToPainterUnits( strokeWidth, mWidthUnit, mWidthMapUnitScale );
3692
3693 //update alpha of gradient colors
3694 QColor color1 = mColor;
3696 {
3699 }
3700 if ( context.selected() )
3701 {
3702 color1 = context.renderContext().selectionColor();
3703 }
3704 color1.setAlphaF( context.opacity() * color1.alphaF() );
3705
3706 //second gradient color
3707 QColor color2 = mColor2;
3709 {
3712 }
3713
3714 //create a QGradient with the desired properties
3715 QGradient gradient = QLinearGradient( QPointF( 0, 0 ), QPointF( 0, scaledWidth ) );
3716 //add stops to gradient
3719 {
3720 //color ramp gradient
3721 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( mGradientRamp.get() );
3722 gradRamp->addStopsToGradient( &gradient, context.opacity() );
3723 }
3724 else
3725 {
3726 //two color gradient
3727 gradient.setColorAt( 0.0, color1 );
3728 gradient.setColorAt( 1.0, color2 );
3729 }
3730 const QBrush brush( gradient );
3731
3732 renderPolylineUsingBrush( points, context, brush, scaledWidth, 100 );
3733}
3734
3736{
3738 mWidthUnit = unit;
3739 mOffsetUnit = unit;
3740}
3741
3743{
3745 if ( mWidthUnit != unit || mOffsetUnit != unit )
3746 {
3748 }
3749 return unit;
3750}
3751
3753{
3756}
3757
3759{
3761 mOffsetMapUnitScale = scale;
3762}
3763
3765{
3768 {
3769 return mWidthMapUnitScale;
3770 }
3771 return QgsMapUnitScale();
3772}
3773
3775{
3776 return ( mWidth / 2.0 ) + mOffset;
3777}
3778
3780{
3781 return QColor();
3782}
3783
3785{
3786 return mGradientRamp.get();
3787}
3788
3790{
3791 mGradientRamp.reset( ramp );
3792}
MarkerLinePlacement
Defines how/where the symbols should be placed on a line.
Definition: qgis.h:1581
@ CurvePoint
Place symbols at every virtual curve point in the line (used when rendering curved geometry types onl...
@ InnerVertices
Inner vertices (i.e. all vertices except the first and last vertex) (since QGIS 3....
@ LastVertex
Place symbols on the last vertex in the line.
@ CentralPoint
Place symbols at the mid point of the line.
@ SegmentCenter
Place symbols at the center of every line segment.
@ Vertex
Place symbols on every vertex in the line.
@ Interval
Place symbols at regular intervals.
@ FirstVertex
Place symbols on the first vertex in the line.
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
GradientColorSource
Gradient color sources.
Definition: qgis.h:1603
@ ColorRamp
Gradient color ramp.
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ Marker
Marker symbol.
@ Line
Line symbol.
Base class for line symbol layer types which draws line sections using a QBrush.
void renderPolylineUsingBrush(const QPolygonF &points, QgsSymbolRenderContext &context, const QBrush &brush, double patternThickness, double patternLength)
Renders a polyline of points using the specified brush.
static bool isGeneralizableByDeviceBoundingBox(const QgsRectangle &envelope, float mapToPixelTol=1.0f)
Returns whether the device-envelope can be replaced by its BBOX when is applied the specified toleran...
virtual double vertexAngle(QgsVertexId vertex) const =0
Returns approximate angle at a vertex.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
Class for doing transforms between two map coordinate systems.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
Curve polygon geometry type.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:65
static double mapUnitScaleFactor(double scale, QgsUnitTypes::RenderUnit symbolUnits, QgsUnitTypes::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
QgsUnitTypes::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
Definition: qgsdxfexport.h:229
void clipValueToMapUnitScale(double &value, const QgsMapUnitScale &scale, double pixelToMMFactor) const
Clips value to scale minimum/maximum.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
static const QString EXPR_GEOMETRY_RING_NUM
Inbuilt variable name for geometry ring number variable.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
static bool segmentIntersection(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &q1, const QgsPoint &q2, QgsPoint &intersectionPoint, bool &isIntersection, double tolerance=1e-8, bool acceptImproperIntersection=false) SIP_HOLDGIL
Compute the intersection between two segments.
static double lineAngle(double x1, double y1, double x2, double y2) SIP_HOLDGIL
Calculates the direction of line joining two points in radians, clockwise from the north direction.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
Line symbol layer type which draws repeating line sections along a line feature.
double hashAngle() const
Returns the angle to use when drawing the hashed lines sections, in degrees clockwise.
QgsHashedLineSymbolLayer(bool rotateSymbol=true, double interval=3)
Constructor for QgsHashedLineSymbolLayer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setWidth(double width) override
Sets the width of the line symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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...
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.
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(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mWidthUnit
QgsUnitTypes::RenderUnit offsetUnit() const
Returns the units for the line's offset.
void setWidthMapUnitScale(const QgsMapUnitScale &scale)
void setWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the line's width.
void setOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the line's offset.
void setOffset(double offset)
Sets the line's offset.
RenderRingFilter mRingFilter
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit widthUnit() const
Returns 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.
QgsUnitTypes::RenderUnit mOffsetUnit
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
Line symbol layer type which draws a gradient pattern perpendicularly along a line.
std::unique_ptr< QgsColorRamp > mGradientRamp
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient line.
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
Qgis::GradientColorSource mGradientColorType
QString layerType() const override
Returns a string that represents this layer type.
~QgsLineburstSymbolLayer() override
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsLineburstSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLineburstSymbolLayer, using the settings serialized in the properties map (correspon...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QColor color() const override
Returns the "representative" color of the symbol layer.
QgsLineburstSymbolLayer(const QColor &color=DEFAULT_SIMPLELINE_COLOR, const QColor &color2=Qt::white)
Constructor for QgsLineburstSymbolLayer, with the specified start and end gradient colors.
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 setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets 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.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
Line symbol layer type which draws repeating marker symbols along a line feature.
Q_DECL_DEPRECATED bool rotateMarker() const
Shall the marker be rotated.
std::unique_ptr< QgsMarkerSymbol > mMarker
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsMarkerLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setDataDefinedProperty(QgsSymbolLayer::Property key, const QgsProperty &property) override
Sets a data defined property for the layer.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
double symbolAngle() const override
Returns the symbol's current angle, in degrees clockwise.
double width() const override
Returns the estimated width for the line symbol layer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsMarkerLineSymbolLayer, using the settings serialized in the properties map (correspo...
QgsMarkerLineSymbolLayer(bool rotateMarker=DEFAULT_MARKERLINE_ROTATE, double interval=DEFAULT_MARKERLINE_INTERVAL)
Constructor for QgsMarkerLineSymbolLayer.
void setWidth(double width) override
Sets the width of the line symbol layer.
QColor color() const override
Returns the "representative" color of the symbol layer.
void setSymbolLineAngle(double angle) override
Sets the line angle modification for the symbol's angle.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
~QgsMarkerLineSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QString layerType() const override
Returns a string that represents this layer type.
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM element.
void setSymbolAngle(double angle) override
Sets the symbol's angle, in degrees clockwise.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderSymbol(const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer=-1, bool selected=false) override
Renders the templated symbol at the specified point, using the given render context.
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
A class to represent a 2D point.
Definition: qgspointxy.h:59
QgsPointXY project(double distance, double bearing) const SIP_HOLDGIL
Returns a new point which corresponds to this point projected by a specified distance in a specified ...
Definition: qgspointxy.cpp:87
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
QPointF toQPointF() const SIP_HOLDGIL
Returns the point as a QPointF.
Definition: qgspoint.h:331
QgsPoint project(double distance, double azimuth, double inclination=90.0) const SIP_HOLDGIL
Returns a new point which corresponds to this point projected by a specified distance with specified ...
Definition: qgspoint.cpp:735
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
A store for object properties.
Definition: qgsproperty.h:230
Line symbol layer type which draws line sections using a raster image file.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsRasterLineSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double opacity() const
Returns the line opacity.
QString path() const
Returns the raster image path.
void setPath(const QString &path)
Set the raster image path.
QString layerType() const override
Returns a string that represents this layer type.
QgsUnitTypes::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.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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
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.
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 ...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsExpressionContext & expressionContext()
Gets the expression context.
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QColor selectionColor() const
Returns the color to use when rendering selected features.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsAbstractGeometry * geometry() const
Returns pointer to the unsegmentized geometry.
Scoped object for saving and restoring a QPainter object's state.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setDrawInsidePolygon(bool drawInsidePolygon)
Sets whether the line should only be drawn inside polygons, and any portion of the line which falls o...
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setPenCapStyle(Qt::PenCapStyle style)
Sets the pen cap style used to render the line (e.g.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsMapUnitScale mapUnitScale() const override
QVector< qreal > dxfCustomDashPattern(QgsUnitTypes::RenderUnit &unit) const override
Gets dash pattern.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleLineSymbolLayer, using the settings serialized in the properties map (correspo...
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.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setDashPatternOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the dash pattern offset.
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...
void setCustomDashPatternUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for lengths used in the custom dash pattern.
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.
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...
void setTrimDistanceStartUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the trim distance for the start of the line.
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.
void setTrimDistanceEndUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the trim distance for the end of the line.
Qt::PenCapStyle penCapStyle() const
Returns the pen cap style used to render the line (e.g.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets 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.
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.
bool alignDashPattern() const
Returns true if dash patterns should be aligned to the start and end of lines, by applying subtle twe...
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static QColor decodeColor(const QString &str)
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static double rescaleUom(double size, QgsUnitTypes::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
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 QgsUnitTypes::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static QVector< qreal > decodeRealVector(const QString &s)
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QString encodePenCapStyle(Qt::PenCapStyle style)
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static void appendPolyline(QPolygonF &target, const QPolygonF &line)
Appends a polyline line to an existing target polyline.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static double polylineLength(const QPolygonF &polyline)
Returns the total length of a polyline.
static void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static Qt::PenStyle decodePenStyle(const QString &str)
static QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QPolygonF polylineSubstring(const QPolygonF &polyline, double startOffset, double endOffset)
Returns the substring of a polyline which starts at startOffset from the beginning of the line and en...
static QString encodeRealVector(const QVector< qreal > &v)
Property
Data definable properties.
@ PropertyStrokeStyle
Stroke style (eg solid, dashed)
@ PropertyPlacement
Line marker placement.
@ PropertyFile
Filename, eg for svg files.
@ PropertyCapStyle
Line cap style.
@ PropertyLineDistance
Distance between lines, or length of lines for hash line symbols.
@ PropertyOffsetAlongLine
Offset along line.
@ PropertyCustomDash
Custom dash pattern.
@ PropertyJoinStyle
Line join style.
@ PropertyTrimEnd
Trim distance from end of line (since QGIS 3.20)
@ PropertyOpacity
Opacity.
@ PropertySecondaryColor
Secondary color (eg for gradient fills)
@ PropertyLineAngle
Line angle, or angle of hash lines for hash line symbols.
@ PropertyTrimStart
Trim distance from start of line (since QGIS 3.20)
@ PropertyOffset
Symbol offset.
@ PropertyStrokeWidth
Stroke width.
@ PropertyDashPatternOffset
Dash pattern offset,.
@ PropertyAverageAngleLength
Length to average symbol angles over.
@ PropertyInterval
Line marker interval.
@ PropertyStrokeColor
Stroke color.
@ PropertyWidth
Symbol width.
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
virtual void setColor(const QColor &color)
Sets the "representative" color for the symbol layer.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QgsPropertyCollection mDataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
const QgsFeature * feature() const
Returns the current feature being rendered.
QgsFields fields() const
Fields of the layer.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
QgsWkbTypes::GeometryType originalGeometryType() const
Returns the geometry type for the original feature geometry being rendered.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
qreal opacity() const
Returns the opacity for the symbol.
Definition: qgssymbol.h:495
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition: qgssymbol.h:502
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:152
Base class for templated line symbols, e.g.
bool rotateSymbols() const
Returns true if the repeating symbols be rotated to match their line segment orientation.
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...
double interval() const
Returns the interval between individual 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.
QgsUnitTypes::RenderUnit outputUnit() const FINAL
Returns the units to use for sizes and widths within the symbol layer.
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 setOffsetAlongLineUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit used for calculating the offset along line for symbols.
void setInterval(double interval)
Sets the interval between individual symbols.
QgsUnitTypes::RenderUnit averageAngleUnit() const
Returns the unit for the length over which the line's direction is averaged when calculating individu...
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.
QgsUnitTypes::RenderUnit offsetAlongLineUnit() const
Returns the unit used for calculating the offset along line for symbols.
void setPlacements(Qgis::MarkerLinePlacements placements)
Sets the placement of the symbols.
Q_DECL_DEPRECATED void setPlacement(Qgis::MarkerLinePlacement placement)
Sets the placement of the symbols.
QgsUnitTypes::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setAverageAngleUnit(QgsUnitTypes::RenderUnit unit)
Sets the unit for the length over which the line's direction is averaged when calculating individual ...
void setIntervalUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the interval between symbols.
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 QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
@ RenderUnknownUnit
Mixed or unknown units.
Definition: qgsunittypes.h:175
@ RenderMetersInMapUnits
Meters value as Map units.
Definition: qgsunittypes.h:176
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
@ RenderInches
Inches.
Definition: qgsunittypes.h:174
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
float threshold() const
Gets the simplification threshold of the vector layer managed.
@ AntialiasingSimplification
The geometries can be rendered with 'AntiAliasing' disabled because of it is '1-pixel size'.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2466
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition: qgis.h:2739
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:2761
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
QMap< QString, QString > QgsStringMap
Definition: qgis.h:3022
#define DEFAULT_MARKERLINE_INTERVAL
#define DEFAULT_SIMPLELINE_WIDTH
#define DEFAULT_MARKERLINE_ROTATE
#define DEFAULT_SIMPLELINE_PENSTYLE
#define DEFAULT_SIMPLELINE_COLOR
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QLineF segment(int index, QRectF rect, double radius)
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:29
QList< QPolygonF > offsetLine(QPolygonF polyline, double dist, QgsWkbTypes::GeometryType geometryType)
calculate geometry shifted by a specified distance
Single variable definition for use within a QgsExpressionContextScope.
Utility class for identifying a unique vertex within a geometry.
Definition: qgsvertexid.h:31
Qgis::VertexType type
Vertex type.
Definition: qgsvertexid.h:98