QGIS API Documentation 3.99.0-Master (7d2ca374f2d)
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
16#include "qgsarrowsymbollayer.h"
17
18#include <memory>
19
20#include "qgsfillsymbol.h"
22#include "qgsrendercontext.h"
23#include "qgssymbollayerutils.h"
24#include "qgsunittypes.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
31{
32 /* default values */
33 setOffset( 0.0 );
35
36 mSymbol = QgsFillSymbol::createSimple( QVariantMap() );
37}
38
40
42{
43 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
44 {
45 mSymbol.reset( static_cast<QgsFillSymbol *>( symbol ) );
46 return true;
47 }
48 delete symbol;
49 return false;
50}
51
52QgsSymbolLayer *QgsArrowSymbolLayer::create( const QVariantMap &props )
53{
55
56 if ( props.contains( u"arrow_width"_s ) )
57 l->setArrowWidth( props[u"arrow_width"_s].toDouble() );
58
59 if ( props.contains( u"arrow_width_unit"_s ) )
60 l->setArrowWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"arrow_width_unit"_s].toString() ) );
61
62 if ( props.contains( u"arrow_width_unit_scale"_s ) )
63 l->setArrowWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"arrow_width_unit_scale"_s].toString() ) );
64
65 if ( props.contains( u"arrow_start_width"_s ) )
66 l->setArrowStartWidth( props[u"arrow_start_width"_s].toDouble() );
67
68 if ( props.contains( u"arrow_start_width_unit"_s ) )
69 l->setArrowStartWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"arrow_start_width_unit"_s].toString() ) );
70
71 if ( props.contains( u"arrow_start_width_unit_scale"_s ) )
72 l->setArrowStartWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"arrow_start_width_unit_scale"_s].toString() ) );
73
74 if ( props.contains( u"is_curved"_s ) )
75 l->setIsCurved( props[u"is_curved"_s].toInt() == 1 );
76
77 if ( props.contains( u"is_repeated"_s ) )
78 l->setIsRepeated( props[u"is_repeated"_s].toInt() == 1 );
79
80 if ( props.contains( u"head_length"_s ) )
81 l->setHeadLength( props[u"head_length"_s].toDouble() );
82
83 if ( props.contains( u"head_length_unit"_s ) )
84 l->setHeadLengthUnit( QgsUnitTypes::decodeRenderUnit( props[u"head_length_unit"_s].toString() ) );
85
86 if ( props.contains( u"head_length_unit_scale"_s ) )
87 l->setHeadLengthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"head_length_unit_scale"_s].toString() ) );
88
89 if ( props.contains( u"head_thickness"_s ) )
90 l->setHeadThickness( props[u"head_thickness"_s].toDouble() );
91
92 if ( props.contains( u"head_thickness_unit"_s ) )
93 l->setHeadThicknessUnit( QgsUnitTypes::decodeRenderUnit( props[u"head_thickness_unit"_s].toString() ) );
94
95 if ( props.contains( u"head_thickness_unit_scale"_s ) )
96 l->setHeadThicknessUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"head_thickness_unit_scale"_s].toString() ) );
97
98 if ( props.contains( u"head_type"_s ) )
99 l->setHeadType( static_cast<HeadType>( props[u"head_type"_s].toInt() ) );
100
101 if ( props.contains( u"arrow_type"_s ) )
102 l->setArrowType( static_cast<ArrowType>( props[u"arrow_type"_s].toInt() ) );
103
104 if ( props.contains( u"offset"_s ) )
105 l->setOffset( props[u"offset"_s].toDouble() );
106
107 if ( props.contains( u"offset_unit"_s ) )
108 l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
109
110 if ( props.contains( u"offset_unit_scale"_s ) )
111 l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_unit_scale"_s].toString() ) );
112
113 if ( props.contains( u"ring_filter"_s ) )
114 l->setRingFilter( static_cast< RenderRingFilter>( props[u"ring_filter"_s].toInt() ) );
115
117
118 l->setSubSymbol( QgsFillSymbol::createSimple( props ).release() );
119
120 return l;
121}
122
124{
125 QgsArrowSymbolLayer *l = static_cast<QgsArrowSymbolLayer *>( create( properties() ) );
126 l->setSubSymbol( mSymbol->clone() );
128 return l;
129}
130
132{
133 return mSymbol.get();
134}
135
137{
138 return u"ArrowLine"_s;
139}
140
142{
143 QVariantMap map;
144
145 map[u"arrow_width"_s] = QString::number( arrowWidth() );
146 map[u"arrow_width_unit"_s] = QgsUnitTypes::encodeUnit( arrowWidthUnit() );
147 map[u"arrow_width_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowWidthUnitScale() );
148
149 map[u"arrow_start_width"_s] = QString::number( arrowStartWidth() );
150 map[u"arrow_start_width_unit"_s] = QgsUnitTypes::encodeUnit( arrowStartWidthUnit() );
151 map[u"arrow_start_width_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowStartWidthUnitScale() );
152
153 map[u"is_curved"_s] = QString::number( isCurved() ? 1 : 0 );
154 map[u"is_repeated"_s] = QString::number( isRepeated() ? 1 : 0 );
155
156 map[u"head_length"_s] = QString::number( headLength() );
157 map[u"head_length_unit"_s] = QgsUnitTypes::encodeUnit( headLengthUnit() );
158 map[u"head_length_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( headLengthUnitScale() );
159
160 map[u"head_thickness"_s] = QString::number( headThickness() );
161 map[u"head_thickness_unit"_s] = QgsUnitTypes::encodeUnit( headThicknessUnit() );
162 map[u"head_thickness_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( headThicknessUnitScale() );
163
164 map[u"head_type"_s] = QString::number( headType() );
165 map[u"arrow_type"_s] = QString::number( arrowType() );
166
167 map[u"offset"_s] = QString::number( offset() );
168 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( offsetUnit() );
169 map[u"offset_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetMapUnitScale() );
170
171 map[u"ring_filter"_s] = QString::number( static_cast< int >( mRingFilter ) );
172
173 return map;
174}
175
176QSet<QString> QgsArrowSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
177{
178 QSet<QString> attributes = QgsLineSymbolLayer::usedAttributes( context );
179
180 attributes.unite( mSymbol->usedAttributes( context ) );
181
182 return attributes;
183}
184
186{
188 return true;
189 if ( mSymbol && mSymbol->hasDataDefinedProperties() )
190 return true;
191 return false;
192}
193
203
205{
207 mArrowWidthUnit = unit;
208 mArrowStartWidthUnit = unit;
209 mHeadLengthUnit = unit;
210 mHeadThicknessUnit = unit;
211}
212
214{
215 mExpressionScope = std::make_unique<QgsExpressionContextScope>( );
220 mScaledOffset = context.renderContext().convertToPainterUnits( offset(), offsetUnit(), offsetMapUnitScale() );
221 mComputedHeadType = headType();
222 mComputedArrowType = arrowType();
223
224 // Store all defaults
225 mDefaultScaledArrowWidth = mScaledArrowWidth;
226 mDefaultScaledArrowStartWidth = mScaledArrowStartWidth;
227 mDefaultScaledHeadLength = mScaledHeadLength;
228 mDefaultScaledHeadThickness = mScaledHeadThickness;
229 mDefaultScaledOffset = mScaledOffset;
230 mDefaultComputedHeadType = mComputedHeadType;
231 mDefaultComputedArrowType = mComputedArrowType;
232
233 mSymbol->setRenderHints( mSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
234
235 mSymbol->startRender( context.renderContext(), context.fields() );
236}
237
239{
240 mSymbol->stopRender( context.renderContext() );
241}
242
244{
245 installMasks( context, true );
246
247 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
248}
249
251{
252 removeMasks( context, true );
253
254 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
255}
256
257inline qreal euclidean_distance( QPointF po, QPointF pd )
258{
259 return QgsGeometryUtilsBase::distance2D( po.x(), po.y(), pd.x(), pd.y() );
260}
261
262QPolygonF straightArrow( QPointF po, QPointF pd,
263 qreal startWidth, qreal width,
264 qreal headWidth, qreal headHeight,
266 qreal offset )
267{
268 QPolygonF polygon; // implicitly shared
269 // vector length
270 qreal length = euclidean_distance( po, pd );
271 if ( qgsDoubleNear( length, 0 ) )
272 return polygon;
273
274 // shift points if there is not enough room for the head(s)
275 if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
276 {
277 po = pd - ( pd - po ) / length * headWidth;
278 length = headWidth;
279 }
280 else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
281 {
282 pd = po + ( pd - po ) / length * headWidth;
283 length = headWidth;
284 }
285 else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
286 {
287 const QPointF v = ( pd - po ) / length * headWidth;
288 const QPointF npo = ( po + pd ) / 2.0 - v;
289 const QPointF npd = ( po + pd ) / 2.0 + v;
290 po = npo;
291 pd = npd;
292 length = 2 * headWidth;
293 }
294
295 const qreal bodyLength = length - headWidth;
296
297 // unit vector
298 const QPointF unitVec = ( pd - po ) / length;
299 // perpendicular vector
300 const QPointF perpVec( -unitVec.y(), unitVec.x() );
301
302 // set offset
303 po += perpVec * offset;
304 pd += perpVec * offset;
305
306 if ( headType == QgsArrowSymbolLayer::HeadDouble )
307 {
308 // first head
309 polygon << po;
311 {
312 polygon << po + unitVec *headWidth + perpVec *headHeight;
313 polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
314
315 polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
316
317 // second head
318 polygon << po + unitVec *bodyLength + perpVec *headHeight;
319 }
320 polygon << pd;
321
323 {
324 polygon << po + unitVec *bodyLength - perpVec *headHeight;
325 polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
326
327 // end of the first head
328 polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
329 polygon << po + unitVec *headWidth - perpVec *headHeight;
330 }
331 }
332 else if ( headType == QgsArrowSymbolLayer::HeadSingle )
333 {
335 {
336 polygon << po + perpVec * ( startWidth * 0.5 );
337 polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
338 polygon << po + unitVec *bodyLength + perpVec *headHeight;
339 }
340 else
341 {
342 polygon << po;
343 }
344 polygon << pd;
346 {
347 polygon << po + unitVec *bodyLength - perpVec *headHeight;
348 polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
349 polygon << po - perpVec * ( startWidth * 0.5 );
350 }
351 else
352 {
353 polygon << po;
354 }
355 }
356 else if ( headType == QgsArrowSymbolLayer::HeadReversed )
357 {
358 polygon << po;
360 {
361 polygon << po + unitVec *headWidth + perpVec *headHeight;
362 polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
363
364 polygon << pd + perpVec * ( startWidth * 0.5 );
365 }
366 else
367 {
368 polygon << pd;
369 }
371 {
372 polygon << pd - perpVec * ( startWidth * 0.5 );
373
374 polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
375 polygon << po + unitVec *headWidth - perpVec *headHeight;
376 }
377 else
378 {
379 polygon << pd;
380 }
381 }
382 // close the polygon
383 polygon << polygon.first();
384
385 return polygon;
386}
387
388// Make sure a given angle is between 0 and 2 pi
389inline qreal clampAngle( qreal a )
390{
391 if ( a > 2 * M_PI )
392 return a - 2 * M_PI;
393 if ( a < 0.0 )
394 return a + 2 * M_PI;
395 return a;
396}
397
402bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF &center, qreal &radius )
403{
404 qreal cx, cy;
405
406 // AB and BC vectors
407 const QPointF ab = b - a;
408 const QPointF bc = c - b;
409
410 // AB and BC middles
411 const QPointF ab2 = ( a + b ) / 2.0;
412 const QPointF bc2 = ( b + c ) / 2.0;
413
414 // Aligned points
415 if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
416 return false;
417
418 // in case AB is horizontal
419 if ( ab.y() == 0 )
420 {
421 cx = ab2.x();
422 cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
423 }
424 //# BC horizontal
425 else if ( bc.y() == 0 )
426 {
427 cx = bc2.x();
428 cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
429 }
430 // Otherwise
431 else
432 {
433 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() );
434 cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
435 }
436 // Radius
437 radius = QgsGeometryUtilsBase::distance2D( a.x(), a.y(), cx, cy );
438 // Center
439 center.setX( cx );
440 center.setY( cy );
441 return true;
442}
443
444QPointF circlePoint( QPointF center, qreal radius, qreal angle )
445{
446 // Y is oriented downward
447 return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
448}
449
450void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
451{
452 const QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
453 if ( direction == 1 )
454 {
455 if ( angle_o < angle_d )
456 path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
457 else
458 path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
459 }
460 else
461 {
462 if ( angle_o < angle_d )
463 path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
464 else
465 path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
466 }
467}
468
469// Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
470void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
471{
472 // start point
473 const QPointF A = circlePoint( center, startRadius, startAngle );
474 // end point
475 const QPointF B = circlePoint( center, endRadius, endAngle );
476 // middle points
477 qreal deltaAngle;
478
479 deltaAngle = endAngle - startAngle;
480 if ( direction * deltaAngle < 0.0 )
481 deltaAngle = deltaAngle + direction * 2 * M_PI;
482
483 const QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
484 const QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
485 const QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
486
487 qreal cRadius;
488 QPointF cCenter;
489 // first circle arc
490 if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
491 {
492 // aligned points => draw a straight line
493 path.lineTo( I2 );
494 }
495 else
496 {
497 // angles in the new circle
498 const qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
499 const qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
500 pathArcTo( path, cCenter, cRadius, a1, a2, direction );
501 }
502
503 // second circle arc
504 if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
505 {
506 // aligned points => draw a straight line
507 path.lineTo( B );
508 }
509 else
510 {
511 // angles in the new circle
512 const qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
513 const qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
514 pathArcTo( path, cCenter, cRadius, a1, a2, direction );
515 }
516}
517
518QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
519 qreal startWidth, qreal width,
520 qreal headWidth, qreal headHeight,
522 qreal offset )
523{
524 qreal circleRadius;
525 QPointF circleCenter;
526 if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
527 {
528 // aligned points => draw a straight arrow
529 return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
530 }
531
532 // angles of each point
533 const qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
534 const qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
535 const qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
536
537 // arc direction : 1 = counter-clockwise, -1 = clockwise
538 const int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
539
540 // arrow type, independent of the direction
541 int aType = 0;
542 if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
543 aType = direction;
544 else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
545 aType = -direction;
546
547 qreal deltaAngle = angle_d - angle_o;
548 if ( direction * deltaAngle < 0.0 )
549 deltaAngle = deltaAngle + direction * 2 * M_PI;
550
551 const qreal length = euclidean_distance( po, pd );
552 // for close points and deltaAngle < 180, draw a straight line
553 if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
554 ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
555 ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
556 {
557 return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
558 }
559
560 // adjust coordinates to include offset
561 circleRadius += offset;
562 po = circlePoint( circleCenter, circleRadius, angle_o );
563 pm = circlePoint( circleCenter, circleRadius, angle_m );
564 pd = circlePoint( circleCenter, circleRadius, angle_d );
565
566 const qreal headAngle = direction * std::atan( headWidth / circleRadius );
567
568 QPainterPath path;
569
570 if ( headType == QgsArrowSymbolLayer::HeadDouble )
571 {
572 // the first head
573 path.moveTo( po );
574 if ( aType <= 0 )
575 {
576 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
577
578 pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
579
580 // the second head
581 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
582 path.lineTo( pd );
583 }
584 else
585 {
586 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
587 }
588 if ( aType >= 0 )
589 {
590 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
591
592 pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
593
594 // the end of the first head
595 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
596 path.lineTo( po );
597 }
598 else
599 {
600 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
601 }
602 }
603 else if ( headType == QgsArrowSymbolLayer::HeadSingle )
604 {
605 if ( aType <= 0 )
606 {
607 path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
608
609 spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
610
611 // the arrow head
612 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
613 path.lineTo( pd );
614 }
615 else
616 {
617 path.moveTo( po );
618 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
619 }
620 if ( aType >= 0 )
621 {
622 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
623
624 spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
625
626 path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
627 }
628 else
629 {
630 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
631 path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
632 }
633 }
634 else if ( headType == QgsArrowSymbolLayer::HeadReversed )
635 {
636 path.moveTo( po );
637 if ( aType <= 0 )
638 {
639 path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
640 path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
641
642 spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
643 }
644 else
645 {
646 pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
647 }
648 if ( aType >= 0 )
649 {
650 path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
651
652 spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
653
654 path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
655 path.lineTo( po );
656 }
657 else
658 {
659 path.lineTo( pd );
660 pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
661 }
662 }
663
664 return path.toSubpathPolygons().at( 0 );
665}
666
667void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
668{
669 if ( !dataDefinedProperties().hasActiveProperties() )
670 return; // shortcut if case there is no data defined properties at all
671
672 QVariant exprVal;
673 bool ok;
675 {
677 if ( !QgsVariantUtils::isNull( exprVal ) )
678 {
679 const double w = exprVal.toDouble( &ok );
680 if ( ok )
681 {
682 mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
683 }
684 else
685 {
686 mScaledArrowWidth = mDefaultScaledArrowWidth;
687 }
688 }
689 else
690 {
691 mScaledArrowWidth = mDefaultScaledArrowWidth;
692 }
693 }
695 {
698 if ( !QgsVariantUtils::isNull( exprVal ) )
699 {
700 const double w = exprVal.toDouble( &ok );
701 if ( ok )
702 {
703 mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
704 }
705 else
706 {
707 mScaledArrowStartWidth = mDefaultScaledArrowStartWidth;
708 }
709 }
710 else
711 {
712 mScaledArrowStartWidth = mDefaultScaledArrowStartWidth;
713 }
714 }
716 {
719 if ( !QgsVariantUtils::isNull( exprVal ) )
720 {
721 const double w = exprVal.toDouble( &ok );
722 if ( ok )
723 {
724 mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
725 }
726 else
727 {
728 mScaledHeadLength = mDefaultScaledHeadLength;
729 }
730 }
731 else
732 {
733 mScaledHeadLength = mDefaultScaledHeadLength;
734 }
735 }
737 {
740 if ( !QgsVariantUtils::isNull( exprVal ) )
741 {
742 const double w = exprVal.toDouble( &ok );
743 if ( ok )
744 {
745 mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
746 }
747 else
748 {
749 mScaledHeadThickness = mDefaultScaledHeadThickness;
750 }
751 }
752 else
753 {
754 mScaledHeadThickness = mDefaultScaledHeadThickness;
755 }
756 }
758 {
759 context.setOriginalValueVariable( offset() );
761 if ( !QgsVariantUtils::isNull( exprVal ) )
762 {
763 const double w = exprVal.toDouble( &ok );
764 if ( ok )
765 {
766 mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
767 }
768 else
769 {
770 mScaledOffset = mDefaultScaledOffset;
771 }
772 }
773 else
774 {
775 mScaledOffset = mDefaultScaledOffset;
776 }
777 }
778
780 {
783 if ( !QgsVariantUtils::isNull( exprVal ) )
784 {
785 const HeadType h = QgsSymbolLayerUtils::decodeArrowHeadType( exprVal, &ok );
786 if ( ok )
787 {
788 mComputedHeadType = h;
789 }
790 else
791 {
792 mComputedHeadType = mDefaultComputedHeadType;
793 }
794 }
795 else
796 {
797 mComputedHeadType = mDefaultComputedHeadType;
798 }
799 }
800
802 {
805 if ( !QgsVariantUtils::isNull( exprVal ) )
806 {
807 const ArrowType h = QgsSymbolLayerUtils::decodeArrowType( exprVal, &ok );
808 if ( ok )
809 {
810 mComputedArrowType = h;
811 }
812 else
813 {
814 mComputedArrowType = mDefaultComputedArrowType;
815 }
816 }
817 else
818 {
819 mComputedArrowType = mDefaultComputedArrowType;
820 }
821 }
822}
823
824void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
825{
826 if ( !context.renderContext().painter() )
827 {
828 return;
829 }
830
831 context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
832 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
834
835 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
837
838 const double prevOpacity = mSymbol->opacity();
839 mSymbol->setOpacity( prevOpacity * context.opacity() );
840
841 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
842 if ( isCurved() )
843 {
844 _resolveDataDefined( context );
845
846 if ( ! isRepeated() )
847 {
848 if ( points.size() >= 3 )
849 {
850 // origin point
851 const QPointF po( points.at( 0 ) );
852 // middle point
853 const QPointF pm( points.at( points.size() / 2 ) );
854 // destination point
855 const QPointF pd( points.back() );
856
857 const QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
858 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
859 }
860 // straight arrow
861 else if ( points.size() == 2 )
862 {
863 // origin point
864 const QPointF po( points.at( 0 ) );
865 // destination point
866 const QPointF pd( points.at( 1 ) );
867
868 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
869 if ( !poly.isEmpty() )
870 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
871 }
872 }
873 else
874 {
875 for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
876 {
877 if ( context.renderContext().renderingStopped() )
878 break;
879
880 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
881 _resolveDataDefined( context );
882
883 if ( points.size() - pIdx >= 3 )
884 {
885 // origin point
886 const QPointF po( points.at( pIdx ) );
887 // middle point
888 const QPointF pm( points.at( pIdx + 1 ) );
889 // destination point
890 const QPointF pd( points.at( pIdx + 2 ) );
891
892 const QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
893 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
894 }
895 // straight arrow
896 else if ( points.size() - pIdx == 2 )
897 {
898 // origin point
899 const QPointF po( points.at( pIdx ) );
900 // destination point
901 const QPointF pd( points.at( pIdx + 1 ) );
902
903 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
904 if ( !poly.isEmpty() )
905 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
906 }
907 }
908 }
909 }
910 else
911 {
912 if ( !isRepeated() )
913 {
914 _resolveDataDefined( context );
915
916 if ( !points.isEmpty() )
917 {
918 // origin point
919 const QPointF po( points.at( 0 ) );
920 // destination point
921 const QPointF pd( points.back() );
922
923 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
924 if ( !poly.isEmpty() )
925 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
926 }
927 }
928 else
929 {
930 // only straight arrows
931 for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
932 {
933 if ( context.renderContext().renderingStopped() )
934 break;
935
936 mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
937 _resolveDataDefined( context );
938
939 // origin point
940 const QPointF po( points.at( pIdx ) );
941 // destination point
942 const QPointF pd( points.at( pIdx + 1 ) );
943
944 const QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
945
946 if ( !poly.isEmpty() )
947 mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
948 }
949 }
950 }
951
953
954 mSymbol->setOpacity( prevOpacity );
956}
957
958void QgsArrowSymbolLayer::setColor( const QColor &c )
959{
960 if ( mSymbol )
961 mSymbol->setColor( c );
962
963 mColor = c;
964}
965
967{
968 return mSymbol.get() ? mSymbol->color() : mColor;
969}
970
975
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:792
RenderUnit
Rendering size units.
Definition qgis.h:5305
@ Millimeters
Millimeters.
Definition qgis.h:5306
@ MapUnits
Map units.
Definition qgis.h:5307
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5313
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2838
@ Fill
Fill symbol.
Definition qgis.h:634
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.
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:60
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static std::unique_ptr< QgsFillSymbol > createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
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.
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.
void copyCommonProperties(QgsSymbolLayer *destLayer) const
Copies all common base class properties from this layer to another symbol layer.
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.
@ ArrowHeadLength
Arrow head length.
@ ArrowWidth
Arrow tail width.
@ ArrowHeadType
Arrow head type.
@ ArrowHeadThickness
Arrow head thickness.
@ ArrowStartWidth
Arrow tail start width.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
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.
QgsSymbolLayer(const QgsSymbolLayer &other)
Encapsulates the context in which a symbol is being rendered.
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6950
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.