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