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