QGIS API Documentation 3.39.0-Master (47f7b3a4989)
Loading...
Searching...
No Matches
qgsarrowsymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsarrowsymbollayer.cpp
3 ---------------------
4 begin : January 2016
5 copyright : (C) 2016 by Hugo Mercier
6 email : hugo dot mercier at oslandia 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
17#include "qgsarrowsymbollayer.h"
18#include "qgssymbollayerutils.h"
19#include "qgsfillsymbol.h"
20#include "qgsrendercontext.h"
21#include "qgsunittypes.h"
22
24{
25 /* default values */
26 setOffset( 0.0 );
28
29 mSymbol.reset( static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
30}
31
33
35{
36 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
37 {
38 mSymbol.reset( static_cast<QgsFillSymbol *>( symbol ) );
39 return true;
40 }
41 delete symbol;
42 return false;
43}
44
45QgsSymbolLayer *QgsArrowSymbolLayer::create( const QVariantMap &props )
46{
48
49 if ( props.contains( QStringLiteral( "arrow_width" ) ) )
50 l->setArrowWidth( props[QStringLiteral( "arrow_width" )].toDouble() );
51
52 if ( props.contains( QStringLiteral( "arrow_width_unit" ) ) )
53 l->setArrowWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "arrow_width_unit" )].toString() ) );
54
55 if ( props.contains( QStringLiteral( "arrow_width_unit_scale" ) ) )
56 l->setArrowWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_width_unit_scale" )].toString() ) );
57
58 if ( props.contains( QStringLiteral( "arrow_start_width" ) ) )
59 l->setArrowStartWidth( props[QStringLiteral( "arrow_start_width" )].toDouble() );
60
61 if ( props.contains( QStringLiteral( "arrow_start_width_unit" ) ) )
62 l->setArrowStartWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "arrow_start_width_unit" )].toString() ) );
63
64 if ( props.contains( QStringLiteral( "arrow_start_width_unit_scale" ) ) )
65 l->setArrowStartWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_start_width_unit_scale" )].toString() ) );
66
67 if ( props.contains( QStringLiteral( "is_curved" ) ) )
68 l->setIsCurved( props[QStringLiteral( "is_curved" )].toInt() == 1 );
69
70 if ( props.contains( QStringLiteral( "is_repeated" ) ) )
71 l->setIsRepeated( props[QStringLiteral( "is_repeated" )].toInt() == 1 );
72
73 if ( props.contains( QStringLiteral( "head_length" ) ) )
74 l->setHeadLength( props[QStringLiteral( "head_length" )].toDouble() );
75
76 if ( props.contains( QStringLiteral( "head_length_unit" ) ) )
77 l->setHeadLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "head_length_unit" )].toString() ) );
78
79 if ( props.contains( QStringLiteral( "head_length_unit_scale" ) ) )
80 l->setHeadLengthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_length_unit_scale" )].toString() ) );
81
82 if ( props.contains( QStringLiteral( "head_thickness" ) ) )
83 l->setHeadThickness( props[QStringLiteral( "head_thickness" )].toDouble() );
84
85 if ( props.contains( QStringLiteral( "head_thickness_unit" ) ) )
86 l->setHeadThicknessUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "head_thickness_unit" )].toString() ) );
87
88 if ( props.contains( QStringLiteral( "head_thickness_unit_scale" ) ) )
89 l->setHeadThicknessUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_thickness_unit_scale" )].toString() ) );
90
91 if ( props.contains( QStringLiteral( "head_type" ) ) )
92 l->setHeadType( static_cast<HeadType>( props[QStringLiteral( "head_type" )].toInt() ) );
93
94 if ( props.contains( QStringLiteral( "arrow_type" ) ) )
95 l->setArrowType( static_cast<ArrowType>( props[QStringLiteral( "arrow_type" )].toInt() ) );
96
97 if ( props.contains( QStringLiteral( "offset" ) ) )
98 l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
99
100 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
101 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
102
103 if ( props.contains( QStringLiteral( "offset_unit_scale" ) ) )
104 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_unit_scale" )].toString() ) );
105
106 if ( props.contains( QStringLiteral( "ring_filter" ) ) )
107 l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
108
110
112
113 return l;
114}
115
117{
118 QgsArrowSymbolLayer *l = static_cast<QgsArrowSymbolLayer *>( create( properties() ) );
119 l->setSubSymbol( mSymbol->clone() );
121 copyPaintEffect( l );
122 return l;
123}
124
126{
127 return mSymbol.get();
128}
129
131{
132 return QStringLiteral( "ArrowLine" );
133}
134
136{
137 QVariantMap map;
138
139 map[QStringLiteral( "arrow_width" )] = QString::number( arrowWidth() );
140 map[QStringLiteral( "arrow_width_unit" )] = QgsUnitTypes::encodeUnit( arrowWidthUnit() );
141 map[QStringLiteral( "arrow_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowWidthUnitScale() );
142
143 map[QStringLiteral( "arrow_start_width" )] = QString::number( arrowStartWidth() );
144 map[QStringLiteral( "arrow_start_width_unit" )] = QgsUnitTypes::encodeUnit( arrowStartWidthUnit() );
145 map[QStringLiteral( "arrow_start_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowStartWidthUnitScale() );
146
147 map[QStringLiteral( "is_curved" )] = QString::number( isCurved() ? 1 : 0 );
148 map[QStringLiteral( "is_repeated" )] = QString::number( isRepeated() ? 1 : 0 );
149
150 map[QStringLiteral( "head_length" )] = QString::number( headLength() );
151 map[QStringLiteral( "head_length_unit" )] = QgsUnitTypes::encodeUnit( headLengthUnit() );
152 map[QStringLiteral( "head_length_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headLengthUnitScale() );
153
154 map[QStringLiteral( "head_thickness" )] = QString::number( headThickness() );
155 map[QStringLiteral( "head_thickness_unit" )] = QgsUnitTypes::encodeUnit( headThicknessUnit() );
156 map[QStringLiteral( "head_thickness_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headThicknessUnitScale() );
157
158 map[QStringLiteral( "head_type" )] = QString::number( headType() );
159 map[QStringLiteral( "arrow_type" )] = QString::number( arrowType() );
160
161 map[QStringLiteral( "offset" )] = QString::number( offset() );
162 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( offsetUnit() );
163 map[QStringLiteral( "offset_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetMapUnitScale() );
164
165 map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
166
167 return map;
168}
169
170QSet<QString> QgsArrowSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
171{
172 QSet<QString> attributes = QgsLineSymbolLayer::usedAttributes( context );
173
174 attributes.unite( mSymbol->usedAttributes( context ) );
175
176 return attributes;
177}
178
180{
182 return true;
183 if ( mSymbol && mSymbol->hasDataDefinedProperties() )
184 return true;
185 return false;
186}
187
197
199{
201 mArrowWidthUnit = unit;
202 mArrowStartWidthUnit = unit;
203 mHeadLengthUnit = unit;
204 mHeadThicknessUnit = unit;
205}
206
208{
209 mExpressionScope.reset( new QgsExpressionContextScope() );
214 mScaledOffset = context.renderContext().convertToPainterUnits( offset(), offsetUnit(), offsetMapUnitScale() );
215 mComputedHeadType = headType();
216 mComputedArrowType = arrowType();
217
218 mSymbol->setRenderHints( mSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
219
220 mSymbol->startRender( context.renderContext(), context.fields() );
221}
222
224{
225 mSymbol->stopRender( context.renderContext() );
226}
227
228inline qreal euclidean_distance( QPointF po, QPointF pd )
229{
230 return QgsGeometryUtilsBase::distance2D( po.x(), po.y(), pd.x(), pd.y() );
231}
232
233QPolygonF straightArrow( QPointF po, QPointF pd,
234 qreal startWidth, qreal width,
235 qreal headWidth, qreal headHeight,
237 qreal offset )
238{
239 QPolygonF polygon; // implicitly shared
240 // vector length
241 qreal length = euclidean_distance( po, pd );
242
243 // shift points if there is not enough room for the head(s)
244 if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
245 {
246 po = pd - ( pd - po ) / length * headWidth;
247 length = headWidth;
248 }
249 else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
250 {
251 pd = po + ( pd - po ) / length * headWidth;
252 length = headWidth;
253 }
254 else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
255 {
256 const QPointF v = ( pd - po ) / length * headWidth;
257 const QPointF npo = ( po + pd ) / 2.0 - v;
258 const QPointF npd = ( po + pd ) / 2.0 + v;
259 po = npo;
260 pd = npd;
261 length = 2 * headWidth;
262 }
263
264 const qreal bodyLength = length - headWidth;
265
266 // unit vector
267 const QPointF unitVec = ( pd - po ) / length;
268 // perpendicular vector
269 const QPointF perpVec( -unitVec.y(), unitVec.x() );
270
271 // set offset
272 po += perpVec * offset;
273 pd += perpVec * offset;
274
275 if ( headType == QgsArrowSymbolLayer::HeadDouble )
276 {
277 // first head
278 polygon << po;
280 {
281 polygon << po + unitVec *headWidth + perpVec *headHeight;
282 polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
283
284 polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
285
286 // second head
287 polygon << po + unitVec *bodyLength + perpVec *headHeight;
288 }
289 polygon << pd;
290
292 {
293 polygon << po + unitVec *bodyLength - perpVec *headHeight;
294 polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
295
296 // end of the first head
297 polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
298 polygon << po + unitVec *headWidth - perpVec *headHeight;
299 }
300 }
301 else if ( headType == QgsArrowSymbolLayer::HeadSingle )
302 {
304 {
305 polygon << po + perpVec * ( startWidth * 0.5 );
306 polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
307 polygon << po + unitVec *bodyLength + perpVec *headHeight;
308 }
309 else
310 {
311 polygon << po;
312 }
313 polygon << pd;
315 {
316 polygon << po + unitVec *bodyLength - perpVec *headHeight;
317 polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
318 polygon << po - perpVec * ( startWidth * 0.5 );
319 }
320 else
321 {
322 polygon << po;
323 }
324 }
325 else if ( headType == QgsArrowSymbolLayer::HeadReversed )
326 {
327 polygon << po;
329 {
330 polygon << po + unitVec *headWidth + perpVec *headHeight;
331 polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
332
333 polygon << pd + perpVec * ( startWidth * 0.5 );
334 }
335 else
336 {
337 polygon << pd;
338 }
340 {
341 polygon << pd - perpVec * ( startWidth * 0.5 );
342
343 polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
344 polygon << po + unitVec *headWidth - perpVec *headHeight;
345 }
346 else
347 {
348 polygon << pd;
349 }
350 }
351 // close the polygon
352 polygon << polygon.first();
353
354 return polygon;
355}
356
357// Make sure a given angle is between 0 and 2 pi
358inline qreal clampAngle( qreal a )
359{
360 if ( a > 2 * M_PI )
361 return a - 2 * M_PI;
362 if ( a < 0.0 )
363 return a + 2 * M_PI;
364 return a;
365}
366
371bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius )
372{
373 qreal cx, cy;
374
375 // AB and BC vectors
376 const QPointF ab = b - a;
377 const QPointF bc = c - b;
378
379 // AB and BC middles
380 const QPointF ab2 = ( a + b ) / 2.0;
381 const QPointF bc2 = ( b + c ) / 2.0;
382
383 // Aligned points
384 if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
385 return false;
386
387 // in case AB is horizontal
388 if ( ab.y() == 0 )
389 {
390 cx = ab2.x();
391 cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
392 }
393 //# BC horizontal
394 else if ( bc.y() == 0 )
395 {
396 cx = bc2.x();
397 cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
398 }
399 // Otherwise
400 else
401 {
402 cx = ( bc2.y() - ab2.y() + bc.x() * bc2.x() / bc.y() - ab.x() * ab2.x() / ab.y() ) / ( bc.x() / bc.y() - ab.x() / ab.y() );
403 cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
404 }
405 // Radius
406 radius = QgsGeometryUtilsBase::distance2D( a.x(), a.y(), cx, cy );
407 // Center
408 center.setX( cx );
409 center.setY( cy );
410 return true;
411}
412
413QPointF circlePoint( QPointF center, qreal radius, qreal angle )
414{
415 // Y is oriented downward
416 return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
417}
418
419void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
420{
421 const QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
422 if ( direction == 1 )
423 {
424 if ( angle_o < angle_d )
425 path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
426 else
427 path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
428 }
429 else
430 {
431 if ( angle_o < angle_d )
432 path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
433 else
434 path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
435 }
436}
437
438// Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
439void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
440{
441 // start point
442 const QPointF A = circlePoint( center, startRadius, startAngle );
443 // end point
444 const QPointF B = circlePoint( center, endRadius, endAngle );
445 // middle points
446 qreal deltaAngle;
447
448 deltaAngle = endAngle - startAngle;
449 if ( direction * deltaAngle < 0.0 )
450 deltaAngle = deltaAngle + direction * 2 * M_PI;
451
452 const QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
453 const QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
454 const QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
455
456 qreal cRadius;
457 QPointF cCenter;
458 // first circle arc
459 if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
460 {
461 // aligned points => draw a straight line
462 path.lineTo( I2 );
463 }
464 else
465 {
466 // angles in the new circle
467 const qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
468 const qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
469 pathArcTo( path, cCenter, cRadius, a1, a2, direction );
470 }
471
472 // second circle arc
473 if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
474 {
475 // aligned points => draw a straight line
476 path.lineTo( B );
477 }
478 else
479 {
480 // angles in the new circle
481 const qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
482 const qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
483 pathArcTo( path, cCenter, cRadius, a1, a2, direction );
484 }
485}
486
487QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
488 qreal startWidth, qreal width,
489 qreal headWidth, qreal headHeight,
491 qreal offset )
492{
493 qreal circleRadius;
494 QPointF circleCenter;
495 if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
496 {
497 // aligned points => draw a straight arrow
498 return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
499 }
500
501 // angles of each point
502 const qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
503 const qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
504 const qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
505
506 // arc direction : 1 = counter-clockwise, -1 = clockwise
507 const int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
508
509 // arrow type, independent of the direction
510 int aType = 0;
511 if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
512 aType = direction;
513 else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
514 aType = -direction;
515
516 qreal deltaAngle = angle_d - angle_o;
517 if ( direction * deltaAngle < 0.0 )
518 deltaAngle = deltaAngle + direction * 2 * M_PI;
519
520 const qreal length = euclidean_distance( po, pd );
521 // for close points and deltaAngle < 180, draw a straight line
522 if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
523 ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
524 ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
525 {
526 return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
527 }
528
529 // adjust coordinates to include offset
530 circleRadius += offset;
531 po = circlePoint( circleCenter, circleRadius, angle_o );
532 pm = circlePoint( circleCenter, circleRadius, angle_m );
533 pd = circlePoint( circleCenter, circleRadius, angle_d );
534
535 const qreal headAngle = direction * std::atan( headWidth / circleRadius );
536
537 QPainterPath path;
538
539 if ( headType == QgsArrowSymbolLayer::HeadDouble )
540 {
541 // the first head
542 path.moveTo( po );
543 if ( aType <= 0 )
544 {
545 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
546
547 pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
548
549 // the second head
550 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
551 path.lineTo( pd );
552 }
553 else
554 {
555 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
556 }
557 if ( aType >= 0 )
558 {
559 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
560
561 pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
562
563 // the end of the first head
564 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
565 path.lineTo( po );
566 }
567 else
568 {
569 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
570 }
571 }
572 else if ( headType == QgsArrowSymbolLayer::HeadSingle )
573 {
574 if ( aType <= 0 )
575 {
576 path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
577
578 spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
579
580 // the arrow head
581 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
582 path.lineTo( pd );
583 }
584 else
585 {
586 path.moveTo( po );
587 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
588 }
589 if ( aType >= 0 )
590 {
591 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
592
593 spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
594
595 path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
596 }
597 else
598 {
599 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
600 path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
601 }
602 }
603 else if ( headType == QgsArrowSymbolLayer::HeadReversed )
604 {
605 path.moveTo( po );
606 if ( aType <= 0 )
607 {
608 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
609 path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
610
611 spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
612 }
613 else
614 {
615 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
616 }
617 if ( aType >= 0 )
618 {
619 path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
620
621 spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
622
623 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
624 path.lineTo( po );
625 }
626 else
627 {
628 path.lineTo( pd );
629 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
630 }
631 }
632
633 return path.toSubpathPolygons().at( 0 );
634}
635
636void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
637{
638 if ( !dataDefinedProperties().hasActiveProperties() )
639 return; // shortcut if case there is no data defined properties at all
640
641 QVariant exprVal;
642 bool ok;
644 {
646 if ( !QgsVariantUtils::isNull( exprVal ) )
647 {
648 const double w = exprVal.toDouble( &ok );
649 if ( ok )
650 {
651 mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
652 }
653 }
654 }
656 {
659 if ( !QgsVariantUtils::isNull( exprVal ) )
660 {
661 const double w = exprVal.toDouble( &ok );
662 if ( ok )
663 {
664 mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
665 }
666 }
667 }
669 {
672 if ( !QgsVariantUtils::isNull( exprVal ) )
673 {
674 const double w = exprVal.toDouble( &ok );
675 if ( ok )
676 {
677 mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
678 }
679 }
680 }
682 {
685 if ( !QgsVariantUtils::isNull( exprVal ) )
686 {
687 const double w = exprVal.toDouble( &ok );
688 if ( ok )
689 {
690 mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
691 }
692 }
693 }
695 {
696 context.setOriginalValueVariable( offset() );
698 const double w = exprVal.toDouble( &ok );
699 if ( ok )
700 {
701 mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
702 }
703 }
704
706 {
709 if ( !QgsVariantUtils::isNull( exprVal ) )
710 {
711 const HeadType h = QgsSymbolLayerUtils::decodeArrowHeadType( exprVal, &ok );
712 if ( ok )
713 {
714 mComputedHeadType = h;
715 }
716 }
717 }
718
720 {
723 if ( !QgsVariantUtils::isNull( exprVal ) )
724 {
725 const ArrowType h = QgsSymbolLayerUtils::decodeArrowType( exprVal, &ok );
726 if ( ok )
727 {
728 mComputedArrowType = h;
729 }
730 }
731 }
732}
733
734void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
735{
736 Q_UNUSED( points )
737
738 if ( !context.renderContext().painter() )
739 {
740 return;
741 }
742
743 context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
744 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
746
747 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
749
750 const double prevOpacity = mSymbol->opacity();
751 mSymbol->setOpacity( prevOpacity * context.opacity() );
752
753 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
754 if ( isCurved() )
755 {
756 _resolveDataDefined( context );
757
758 if ( ! isRepeated() )
759 {
760 if ( points.size() >= 3 )
761 {
762 // origin point
763 const QPointF po( points.at( 0 ) );
764 // middle point
765 const QPointF pm( points.at( points.size() / 2 ) );
766 // destination point
767 const QPointF pd( points.back() );
768
769 const QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
770 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
771 }
772 // straight arrow
773 else if ( points.size() == 2 )
774 {
775 // origin point
776 const QPointF po( points.at( 0 ) );
777 // destination point
778 const QPointF pd( points.at( 1 ) );
779
780 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
781 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
782 }
783 }
784 else
785 {
786 for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
787 {
788 if ( context.renderContext().renderingStopped() )
789 break;
790
791 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
792 _resolveDataDefined( context );
793
794 if ( points.size() - pIdx >= 3 )
795 {
796 // origin point
797 const QPointF po( points.at( pIdx ) );
798 // middle point
799 const QPointF pm( points.at( pIdx + 1 ) );
800 // destination point
801 const QPointF pd( points.at( pIdx + 2 ) );
802
803 const QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
804 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
805 }
806 // straight arrow
807 else if ( points.size() - pIdx == 2 )
808 {
809 // origin point
810 const QPointF po( points.at( pIdx ) );
811 // destination point
812 const QPointF pd( points.at( pIdx + 1 ) );
813
814 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
815 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
816 }
817 }
818 }
819 }
820 else
821 {
822 if ( !isRepeated() )
823 {
824 _resolveDataDefined( context );
825
826 if ( !points.isEmpty() )
827 {
828 // origin point
829 const QPointF po( points.at( 0 ) );
830 // destination point
831 const QPointF pd( points.back() );
832
833 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
834 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
835 }
836 }
837 else
838 {
839 // only straight arrows
840 for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
841 {
842 if ( context.renderContext().renderingStopped() )
843 break;
844
845 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
846 _resolveDataDefined( context );
847
848 // origin point
849 const QPointF po( points.at( pIdx ) );
850 // destination point
851 const QPointF pd( points.at( pIdx + 1 ) );
852
853 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
854
855 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
856 }
857 }
858 }
859
861
862 mSymbol->setOpacity( prevOpacity );
864}
865
866void QgsArrowSymbolLayer::setColor( const QColor &c )
867{
868 if ( mSymbol )
869 mSymbol->setColor( c );
870
871 mColor = c;
872}
873
875{
876 return mSymbol.get() ? mSymbol->color() : mColor;
877}
878
883
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer (since QGIS 3.38)
RenderUnit
Rendering size units.
Definition qgis.h:4494
@ Millimeters
Millimeters.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ Fill
Fill symbol.
Line symbol layer used for representing lines as arrows.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool isCurved() const
Returns whether it is a curved arrow or a straight one.
ArrowType arrowType() const
Gets the current arrow type.
QString layerType() const override
Returns a string that represents this layer type.
HeadType
Possible head types.
HeadType headType() const
Gets the current head type.
void setArrowStartWidth(double width)
Sets the arrow start width.
bool isRepeated() const
Returns whether the arrow is repeated along the line or not.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setArrowWidth(double width)
Sets the arrow width.
void setArrowType(ArrowType type)
Sets the arrow type.
void setHeadLengthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the head length.
~QgsArrowSymbolLayer() override
QgsMapUnitScale headThicknessUnitScale() const
Gets the scale for the head height.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
Qgis::RenderUnit arrowWidthUnit() const
Gets the unit for the arrow width.
Qgis::RenderUnit headLengthUnit() const
Gets the unit for the head length.
Qgis::RenderUnit headThicknessUnit() const
Gets the unit for the head height.
void setArrowWidthUnit(Qgis::RenderUnit unit)
Sets the unit for the arrow width.
void setIsCurved(bool isCurved)
Sets whether it is a curved arrow or a straight one.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setHeadThickness(double thickness)
Sets the arrow head height.
QgsArrowSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setHeadLengthUnit(Qgis::RenderUnit unit)
Sets the unit for the head length.
void setIsRepeated(bool isRepeated)
Sets whether the arrow is repeated along the line.
QColor color() const override
Returns the "representative" color of the symbol layer.
double arrowStartWidth() const
Gets current arrow start width. Only meaningful for single headed arrows.
void setArrowStartWidthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the arrow start width.
void setHeadThicknessUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the head height.
void setHeadType(HeadType type)
Sets the head type.
QgsMapUnitScale arrowStartWidthUnitScale() const
Gets the scale for the arrow start width.
double headThickness() const
Gets the current arrow head height.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void setArrowWidthUnitScale(const QgsMapUnitScale &scale)
Sets the scale for the arrow width.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Create a new QgsArrowSymbolLayer.
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
ArrowType
Possible arrow types.
double arrowWidth() const
Gets current arrow width.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsMapUnitScale arrowWidthUnitScale() const
Gets the scale for the arrow width.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void renderPolyline(const QPolygonF &points, QgsSymbolRenderContext &context) override
Renders the line symbol layer along the line joining points, using the given render context.
void setArrowStartWidthUnit(Qgis::RenderUnit unit)
Sets the unit for the arrow start width.
Qgis::RenderUnit arrowStartWidthUnit() const
Gets the unit for the arrow start width.
QgsMapUnitScale headLengthUnitScale() const
Gets the scale for the head length.
QgsArrowSymbolLayer()
Simple constructor.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setHeadLength(double length)
Sets the arrow head length.
double headLength() const
Gets the current arrow head length.
void setHeadThicknessUnit(Qgis::RenderUnit unit)
Sets the unit for the head height.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static const QString EXPR_GEOMETRY_POINT_COUNT
Inbuilt variable name for point count variable.
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
Qgis::RenderUnit mOffsetUnit
RenderRingFilter
Options for filtering rings when the line symbol layer is being used to render a polygon's rings.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qgis::RenderUnit mWidthUnit
void setOffset(double offset)
Sets the line's offset.
RenderRingFilter mRingFilter
void setOffsetUnit(Qgis::RenderUnit unit)
Sets the unit for the line's offset.
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the line's offset.
void setRingFilter(QgsLineSymbolLayer::RenderRingFilter filter)
Sets the line symbol layer's ring filter, which controls which rings are rendered when the line symbo...
double offset() const
Returns the line's offset.
const QgsMapUnitScale & offsetMapUnitScale() const
Returns the map unit scale for the line's offset.
Qgis::RenderUnit offsetUnit() const
Returns the units for the line's offset.
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
static QgsArrowSymbolLayer::HeadType decodeArrowHeadType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow head type.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QgsArrowSymbolLayer::ArrowType decodeArrowType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow type.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
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.
@ ArrowHeadLength
Arrow head length.
@ ArrowWidth
Arrow tail width.
@ ArrowHeadType
Arrow head type.
@ ArrowHeadThickness
Arrow head thickness.
@ ArrowStartWidth
Arrow tail start width.
@ Offset
Symbol offset.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
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.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:94
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:156
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
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
void pathArcTo(QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction)
void spiralArcTo(QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction)
qreal euclidean_distance(QPointF po, QPointF pd)
QPointF circlePoint(QPointF center, qreal radius, qreal angle)
QPolygonF straightArrow(QPointF po, QPointF pd, qreal startWidth, qreal width, qreal headWidth, qreal headHeight, QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType, qreal offset)
bool pointsToCircle(QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius)
Compute the circumscribed circle from three points.
qreal clampAngle(qreal a)
QPolygonF curvedArrow(QPointF po, QPointF pm, QPointF pd, qreal startWidth, qreal width, qreal headWidth, qreal headHeight, QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType, qreal offset)
Single variable definition for use within a QgsExpressionContextScope.