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