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