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