QGIS API Documentation 3.41.0-Master (88383c3d16f)
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
229{
230 installMasks( context, true );
231
232 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
233}
234
236{
237 removeMasks( context, true );
238
239 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
240}
241
242inline qreal euclidean_distance( QPointF po, QPointF pd )
243{
244 return QgsGeometryUtilsBase::distance2D( po.x(), po.y(), pd.x(), pd.y() );
245}
246
247QPolygonF straightArrow( QPointF po, QPointF pd,
248 qreal startWidth, qreal width,
249 qreal headWidth, qreal headHeight,
251 qreal offset )
252{
253 QPolygonF polygon; // implicitly shared
254 // vector length
255 qreal length = euclidean_distance( po, pd );
256
257 // shift points if there is not enough room for the head(s)
258 if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
259 {
260 po = pd - ( pd - po ) / length * headWidth;
261 length = headWidth;
262 }
263 else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
264 {
265 pd = po + ( pd - po ) / length * headWidth;
266 length = headWidth;
267 }
268 else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
269 {
270 const QPointF v = ( pd - po ) / length * headWidth;
271 const QPointF npo = ( po + pd ) / 2.0 - v;
272 const QPointF npd = ( po + pd ) / 2.0 + v;
273 po = npo;
274 pd = npd;
275 length = 2 * headWidth;
276 }
277
278 const qreal bodyLength = length - headWidth;
279
280 // unit vector
281 const QPointF unitVec = ( pd - po ) / length;
282 // perpendicular vector
283 const QPointF perpVec( -unitVec.y(), unitVec.x() );
284
285 // set offset
286 po += perpVec * offset;
287 pd += perpVec * offset;
288
289 if ( headType == QgsArrowSymbolLayer::HeadDouble )
290 {
291 // first head
292 polygon << po;
294 {
295 polygon << po + unitVec *headWidth + perpVec *headHeight;
296 polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
297
298 polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
299
300 // second head
301 polygon << po + unitVec *bodyLength + perpVec *headHeight;
302 }
303 polygon << pd;
304
306 {
307 polygon << po + unitVec *bodyLength - perpVec *headHeight;
308 polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
309
310 // end of the first head
311 polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
312 polygon << po + unitVec *headWidth - perpVec *headHeight;
313 }
314 }
315 else if ( headType == QgsArrowSymbolLayer::HeadSingle )
316 {
318 {
319 polygon << po + perpVec * ( startWidth * 0.5 );
320 polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
321 polygon << po + unitVec *bodyLength + perpVec *headHeight;
322 }
323 else
324 {
325 polygon << po;
326 }
327 polygon << pd;
329 {
330 polygon << po + unitVec *bodyLength - perpVec *headHeight;
331 polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
332 polygon << po - perpVec * ( startWidth * 0.5 );
333 }
334 else
335 {
336 polygon << po;
337 }
338 }
339 else if ( headType == QgsArrowSymbolLayer::HeadReversed )
340 {
341 polygon << po;
343 {
344 polygon << po + unitVec *headWidth + perpVec *headHeight;
345 polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
346
347 polygon << pd + perpVec * ( startWidth * 0.5 );
348 }
349 else
350 {
351 polygon << pd;
352 }
354 {
355 polygon << pd - perpVec * ( startWidth * 0.5 );
356
357 polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
358 polygon << po + unitVec *headWidth - perpVec *headHeight;
359 }
360 else
361 {
362 polygon << pd;
363 }
364 }
365 // close the polygon
366 polygon << polygon.first();
367
368 return polygon;
369}
370
371// Make sure a given angle is between 0 and 2 pi
372inline qreal clampAngle( qreal a )
373{
374 if ( a > 2 * M_PI )
375 return a - 2 * M_PI;
376 if ( a < 0.0 )
377 return a + 2 * M_PI;
378 return a;
379}
380
385bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius )
386{
387 qreal cx, cy;
388
389 // AB and BC vectors
390 const QPointF ab = b - a;
391 const QPointF bc = c - b;
392
393 // AB and BC middles
394 const QPointF ab2 = ( a + b ) / 2.0;
395 const QPointF bc2 = ( b + c ) / 2.0;
396
397 // Aligned points
398 if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
399 return false;
400
401 // in case AB is horizontal
402 if ( ab.y() == 0 )
403 {
404 cx = ab2.x();
405 cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
406 }
407 //# BC horizontal
408 else if ( bc.y() == 0 )
409 {
410 cx = bc2.x();
411 cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
412 }
413 // Otherwise
414 else
415 {
416 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() );
417 cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
418 }
419 // Radius
420 radius = QgsGeometryUtilsBase::distance2D( a.x(), a.y(), cx, cy );
421 // Center
422 center.setX( cx );
423 center.setY( cy );
424 return true;
425}
426
427QPointF circlePoint( QPointF center, qreal radius, qreal angle )
428{
429 // Y is oriented downward
430 return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
431}
432
433void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
434{
435 const QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
436 if ( direction == 1 )
437 {
438 if ( angle_o < angle_d )
439 path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
440 else
441 path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
442 }
443 else
444 {
445 if ( angle_o < angle_d )
446 path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
447 else
448 path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
449 }
450}
451
452// Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
453void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
454{
455 // start point
456 const QPointF A = circlePoint( center, startRadius, startAngle );
457 // end point
458 const QPointF B = circlePoint( center, endRadius, endAngle );
459 // middle points
460 qreal deltaAngle;
461
462 deltaAngle = endAngle - startAngle;
463 if ( direction * deltaAngle < 0.0 )
464 deltaAngle = deltaAngle + direction * 2 * M_PI;
465
466 const QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
467 const QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
468 const QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
469
470 qreal cRadius;
471 QPointF cCenter;
472 // first circle arc
473 if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
474 {
475 // aligned points => draw a straight line
476 path.lineTo( I2 );
477 }
478 else
479 {
480 // angles in the new circle
481 const qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
482 const qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
483 pathArcTo( path, cCenter, cRadius, a1, a2, direction );
484 }
485
486 // second circle arc
487 if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
488 {
489 // aligned points => draw a straight line
490 path.lineTo( B );
491 }
492 else
493 {
494 // angles in the new circle
495 const qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
496 const qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
497 pathArcTo( path, cCenter, cRadius, a1, a2, direction );
498 }
499}
500
501QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
502 qreal startWidth, qreal width,
503 qreal headWidth, qreal headHeight,
505 qreal offset )
506{
507 qreal circleRadius;
508 QPointF circleCenter;
509 if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
510 {
511 // aligned points => draw a straight arrow
512 return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
513 }
514
515 // angles of each point
516 const qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
517 const qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
518 const qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
519
520 // arc direction : 1 = counter-clockwise, -1 = clockwise
521 const int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
522
523 // arrow type, independent of the direction
524 int aType = 0;
525 if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
526 aType = direction;
527 else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
528 aType = -direction;
529
530 qreal deltaAngle = angle_d - angle_o;
531 if ( direction * deltaAngle < 0.0 )
532 deltaAngle = deltaAngle + direction * 2 * M_PI;
533
534 const qreal length = euclidean_distance( po, pd );
535 // for close points and deltaAngle < 180, draw a straight line
536 if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
537 ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
538 ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
539 {
540 return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
541 }
542
543 // adjust coordinates to include offset
544 circleRadius += offset;
545 po = circlePoint( circleCenter, circleRadius, angle_o );
546 pm = circlePoint( circleCenter, circleRadius, angle_m );
547 pd = circlePoint( circleCenter, circleRadius, angle_d );
548
549 const qreal headAngle = direction * std::atan( headWidth / circleRadius );
550
551 QPainterPath path;
552
553 if ( headType == QgsArrowSymbolLayer::HeadDouble )
554 {
555 // the first head
556 path.moveTo( po );
557 if ( aType <= 0 )
558 {
559 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
560
561 pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
562
563 // the second head
564 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
565 path.lineTo( pd );
566 }
567 else
568 {
569 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
570 }
571 if ( aType >= 0 )
572 {
573 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
574
575 pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
576
577 // the end of the first head
578 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
579 path.lineTo( po );
580 }
581 else
582 {
583 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
584 }
585 }
586 else if ( headType == QgsArrowSymbolLayer::HeadSingle )
587 {
588 if ( aType <= 0 )
589 {
590 path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
591
592 spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
593
594 // the arrow head
595 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
596 path.lineTo( pd );
597 }
598 else
599 {
600 path.moveTo( po );
601 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
602 }
603 if ( aType >= 0 )
604 {
605 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
606
607 spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
608
609 path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
610 }
611 else
612 {
613 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
614 path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
615 }
616 }
617 else if ( headType == QgsArrowSymbolLayer::HeadReversed )
618 {
619 path.moveTo( po );
620 if ( aType <= 0 )
621 {
622 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
623 path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
624
625 spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
626 }
627 else
628 {
629 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
630 }
631 if ( aType >= 0 )
632 {
633 path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
634
635 spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
636
637 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
638 path.lineTo( po );
639 }
640 else
641 {
642 path.lineTo( pd );
643 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
644 }
645 }
646
647 return path.toSubpathPolygons().at( 0 );
648}
649
650void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
651{
652 if ( !dataDefinedProperties().hasActiveProperties() )
653 return; // shortcut if case there is no data defined properties at all
654
655 QVariant exprVal;
656 bool ok;
658 {
660 if ( !QgsVariantUtils::isNull( exprVal ) )
661 {
662 const double w = exprVal.toDouble( &ok );
663 if ( ok )
664 {
665 mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
666 }
667 }
668 }
670 {
673 if ( !QgsVariantUtils::isNull( exprVal ) )
674 {
675 const double w = exprVal.toDouble( &ok );
676 if ( ok )
677 {
678 mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
679 }
680 }
681 }
683 {
686 if ( !QgsVariantUtils::isNull( exprVal ) )
687 {
688 const double w = exprVal.toDouble( &ok );
689 if ( ok )
690 {
691 mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
692 }
693 }
694 }
696 {
699 if ( !QgsVariantUtils::isNull( exprVal ) )
700 {
701 const double w = exprVal.toDouble( &ok );
702 if ( ok )
703 {
704 mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
705 }
706 }
707 }
709 {
710 context.setOriginalValueVariable( offset() );
712 const double w = exprVal.toDouble( &ok );
713 if ( ok )
714 {
715 mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
716 }
717 }
718
720 {
723 if ( !QgsVariantUtils::isNull( exprVal ) )
724 {
725 const HeadType h = QgsSymbolLayerUtils::decodeArrowHeadType( exprVal, &ok );
726 if ( ok )
727 {
728 mComputedHeadType = h;
729 }
730 }
731 }
732
734 {
737 if ( !QgsVariantUtils::isNull( exprVal ) )
738 {
739 const ArrowType h = QgsSymbolLayerUtils::decodeArrowType( exprVal, &ok );
740 if ( ok )
741 {
742 mComputedArrowType = h;
743 }
744 }
745 }
746}
747
748void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
749{
750 Q_UNUSED( points )
751
752 if ( !context.renderContext().painter() )
753 {
754 return;
755 }
756
757 context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
758 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
760
761 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
763
764 const double prevOpacity = mSymbol->opacity();
765 mSymbol->setOpacity( prevOpacity * context.opacity() );
766
767 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
768 if ( isCurved() )
769 {
770 _resolveDataDefined( context );
771
772 if ( ! isRepeated() )
773 {
774 if ( points.size() >= 3 )
775 {
776 // origin point
777 const QPointF po( points.at( 0 ) );
778 // middle point
779 const QPointF pm( points.at( points.size() / 2 ) );
780 // destination point
781 const QPointF pd( points.back() );
782
783 const QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
784 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
785 }
786 // straight arrow
787 else if ( points.size() == 2 )
788 {
789 // origin point
790 const QPointF po( points.at( 0 ) );
791 // destination point
792 const QPointF pd( points.at( 1 ) );
793
794 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
795 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
796 }
797 }
798 else
799 {
800 for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
801 {
802 if ( context.renderContext().renderingStopped() )
803 break;
804
805 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
806 _resolveDataDefined( context );
807
808 if ( points.size() - pIdx >= 3 )
809 {
810 // origin point
811 const QPointF po( points.at( pIdx ) );
812 // middle point
813 const QPointF pm( points.at( pIdx + 1 ) );
814 // destination point
815 const QPointF pd( points.at( pIdx + 2 ) );
816
817 const QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
818 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
819 }
820 // straight arrow
821 else if ( points.size() - pIdx == 2 )
822 {
823 // origin point
824 const QPointF po( points.at( pIdx ) );
825 // destination point
826 const QPointF pd( points.at( pIdx + 1 ) );
827
828 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
829 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
830 }
831 }
832 }
833 }
834 else
835 {
836 if ( !isRepeated() )
837 {
838 _resolveDataDefined( context );
839
840 if ( !points.isEmpty() )
841 {
842 // origin point
843 const QPointF po( points.at( 0 ) );
844 // destination point
845 const QPointF pd( points.back() );
846
847 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
848 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
849 }
850 }
851 else
852 {
853 // only straight arrows
854 for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
855 {
856 if ( context.renderContext().renderingStopped() )
857 break;
858
859 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
860 _resolveDataDefined( context );
861
862 // origin point
863 const QPointF po( points.at( pIdx ) );
864 // destination point
865 const QPointF pd( points.at( pIdx + 1 ) );
866
867 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
868
869 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
870 }
871 }
872 }
873
875
876 mSymbol->setOpacity( prevOpacity );
878}
879
880void QgsArrowSymbolLayer::setColor( const QColor &c )
881{
882 if ( mSymbol )
883 mSymbol->setColor( c );
884
885 mColor = c;
886}
887
889{
890 return mSymbol.get() ? mSymbol->color() : mColor;
891}
892
897
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
RenderUnit
Rendering size units.
Definition qgis.h:4930
@ 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 startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
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.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
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.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
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...
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
@ 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:231
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
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.