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