Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsarrowsymbollayer.cpp
3 : : ---------------------
4 : : begin : January 2016
5 : : copyright : (C) 2016 by Hugo Mercier
6 : : email : hugo dot mercier at oslandia dot com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #include "qgsarrowsymbollayer.h"
17 : : #include "qgssymbollayerutils.h"
18 : :
19 : 5 : QgsArrowSymbolLayer::QgsArrowSymbolLayer()
20 : 10 : {
21 : : /* default values */
22 : 5 : setOffset( 0.0 );
23 : 5 : setOffsetUnit( QgsUnitTypes::RenderMillimeters );
24 : :
25 : 5 : mSymbol.reset( static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
26 : 5 : }
27 : :
28 : 10 : bool QgsArrowSymbolLayer::setSubSymbol( QgsSymbol *symbol )
29 : : {
30 : 10 : if ( symbol && symbol->type() == QgsSymbol::Fill )
31 : : {
32 : 10 : mSymbol.reset( static_cast<QgsFillSymbol *>( symbol ) );
33 : 10 : return true;
34 : : }
35 : 0 : delete symbol;
36 : 0 : return false;
37 : 10 : }
38 : :
39 : 5 : QgsSymbolLayer *QgsArrowSymbolLayer::create( const QVariantMap &props )
40 : : {
41 : 5 : QgsArrowSymbolLayer *l = new QgsArrowSymbolLayer();
42 : :
43 : 10 : if ( props.contains( QStringLiteral( "arrow_width" ) ) )
44 : 10 : l->setArrowWidth( props[QStringLiteral( "arrow_width" )].toDouble() );
45 : :
46 : 10 : if ( props.contains( QStringLiteral( "arrow_width_unit" ) ) )
47 : 10 : l->setArrowWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "arrow_width_unit" )].toString() ) );
48 : :
49 : 10 : if ( props.contains( QStringLiteral( "arrow_width_unit_scale" ) ) )
50 : 10 : l->setArrowWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_width_unit_scale" )].toString() ) );
51 : :
52 : 10 : if ( props.contains( QStringLiteral( "arrow_start_width" ) ) )
53 : 10 : l->setArrowStartWidth( props[QStringLiteral( "arrow_start_width" )].toDouble() );
54 : :
55 : 10 : if ( props.contains( QStringLiteral( "arrow_start_width_unit" ) ) )
56 : 10 : l->setArrowStartWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "arrow_start_width_unit" )].toString() ) );
57 : :
58 : 10 : if ( props.contains( QStringLiteral( "arrow_start_width_unit_scale" ) ) )
59 : 10 : l->setArrowStartWidthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "arrow_start_width_unit_scale" )].toString() ) );
60 : :
61 : 10 : if ( props.contains( QStringLiteral( "is_curved" ) ) )
62 : 10 : l->setIsCurved( props[QStringLiteral( "is_curved" )].toInt() == 1 );
63 : :
64 : 10 : if ( props.contains( QStringLiteral( "is_repeated" ) ) )
65 : 10 : l->setIsRepeated( props[QStringLiteral( "is_repeated" )].toInt() == 1 );
66 : :
67 : 10 : if ( props.contains( QStringLiteral( "head_length" ) ) )
68 : 10 : l->setHeadLength( props[QStringLiteral( "head_length" )].toDouble() );
69 : :
70 : 10 : if ( props.contains( QStringLiteral( "head_length_unit" ) ) )
71 : 10 : l->setHeadLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "head_length_unit" )].toString() ) );
72 : :
73 : 10 : if ( props.contains( QStringLiteral( "head_length_unit_scale" ) ) )
74 : 10 : l->setHeadLengthUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_length_unit_scale" )].toString() ) );
75 : :
76 : 10 : if ( props.contains( QStringLiteral( "head_thickness" ) ) )
77 : 10 : l->setHeadThickness( props[QStringLiteral( "head_thickness" )].toDouble() );
78 : :
79 : 10 : if ( props.contains( QStringLiteral( "head_thickness_unit" ) ) )
80 : 10 : l->setHeadThicknessUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "head_thickness_unit" )].toString() ) );
81 : :
82 : 10 : if ( props.contains( QStringLiteral( "head_thickness_unit_scale" ) ) )
83 : 10 : l->setHeadThicknessUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "head_thickness_unit_scale" )].toString() ) );
84 : :
85 : 10 : if ( props.contains( QStringLiteral( "head_type" ) ) )
86 : 10 : l->setHeadType( static_cast<HeadType>( props[QStringLiteral( "head_type" )].toInt() ) );
87 : :
88 : 10 : if ( props.contains( QStringLiteral( "arrow_type" ) ) )
89 : 10 : l->setArrowType( static_cast<ArrowType>( props[QStringLiteral( "arrow_type" )].toInt() ) );
90 : :
91 : 10 : if ( props.contains( QStringLiteral( "offset" ) ) )
92 : 10 : l->setOffset( props[QStringLiteral( "offset" )].toDouble() );
93 : :
94 : 10 : if ( props.contains( QStringLiteral( "offset_unit" ) ) )
95 : 10 : l->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
96 : :
97 : 10 : if ( props.contains( QStringLiteral( "offset_unit_scale" ) ) )
98 : 10 : l->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_unit_scale" )].toString() ) );
99 : :
100 : 10 : if ( props.contains( QStringLiteral( "ring_filter" ) ) )
101 : 10 : l->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) );
102 : :
103 : 5 : l->restoreOldDataDefinedProperties( props );
104 : :
105 : 5 : l->setSubSymbol( QgsFillSymbol::createSimple( props ) );
106 : :
107 : 5 : return l;
108 : 0 : }
109 : :
110 : 0 : QgsArrowSymbolLayer *QgsArrowSymbolLayer::clone() const
111 : : {
112 : 0 : QgsArrowSymbolLayer *l = static_cast<QgsArrowSymbolLayer *>( create( properties() ) );
113 : 0 : l->setSubSymbol( mSymbol->clone() );
114 : 0 : copyDataDefinedProperties( l );
115 : 0 : copyPaintEffect( l );
116 : 0 : return l;
117 : 0 : }
118 : :
119 : 0 : QString QgsArrowSymbolLayer::layerType() const
120 : : {
121 : 0 : return QStringLiteral( "ArrowLine" );
122 : : }
123 : :
124 : 0 : QVariantMap QgsArrowSymbolLayer::properties() const
125 : : {
126 : 0 : QVariantMap map;
127 : :
128 : 0 : map[QStringLiteral( "arrow_width" )] = QString::number( arrowWidth() );
129 : 0 : map[QStringLiteral( "arrow_width_unit" )] = QgsUnitTypes::encodeUnit( arrowWidthUnit() );
130 : 0 : map[QStringLiteral( "arrow_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowWidthUnitScale() );
131 : :
132 : 0 : map[QStringLiteral( "arrow_start_width" )] = QString::number( arrowStartWidth() );
133 : 0 : map[QStringLiteral( "arrow_start_width_unit" )] = QgsUnitTypes::encodeUnit( arrowStartWidthUnit() );
134 : 0 : map[QStringLiteral( "arrow_start_width_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( arrowStartWidthUnitScale() );
135 : :
136 : 0 : map[QStringLiteral( "is_curved" )] = QString::number( isCurved() ? 1 : 0 );
137 : 0 : map[QStringLiteral( "is_repeated" )] = QString::number( isRepeated() ? 1 : 0 );
138 : :
139 : 0 : map[QStringLiteral( "head_length" )] = QString::number( headLength() );
140 : 0 : map[QStringLiteral( "head_length_unit" )] = QgsUnitTypes::encodeUnit( headLengthUnit() );
141 : 0 : map[QStringLiteral( "head_length_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headLengthUnitScale() );
142 : :
143 : 0 : map[QStringLiteral( "head_thickness" )] = QString::number( headThickness() );
144 : 0 : map[QStringLiteral( "head_thickness_unit" )] = QgsUnitTypes::encodeUnit( headThicknessUnit() );
145 : 0 : map[QStringLiteral( "head_thickness_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( headThicknessUnitScale() );
146 : :
147 : 0 : map[QStringLiteral( "head_type" )] = QString::number( headType() );
148 : 0 : map[QStringLiteral( "arrow_type" )] = QString::number( arrowType() );
149 : :
150 : 0 : map[QStringLiteral( "offset" )] = QString::number( offset() );
151 : 0 : map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( offsetUnit() );
152 : 0 : map[QStringLiteral( "offset_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetMapUnitScale() );
153 : :
154 : 0 : map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) );
155 : :
156 : 0 : return map;
157 : 0 : }
158 : 5 :
159 : 5 : QSet<QString> QgsArrowSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
160 : : {
161 : 0 : QSet<QString> attributes = QgsLineSymbolLayer::usedAttributes( context );
162 : 5 :
163 : 5 : attributes.unite( mSymbol->usedAttributes( context ) );
164 : :
165 : 0 : return attributes;
166 : 5 : }
167 : 5 :
168 : 0 : bool QgsArrowSymbolLayer::hasDataDefinedProperties() const
169 : 5 : {
170 : 5 : if ( QgsSymbolLayer::hasDataDefinedProperties() )
171 : 0 : return true;
172 : 0 : if ( mSymbol && mSymbol->hasDataDefinedProperties() )
173 : 5 : return true;
174 : 5 : return false;
175 : 5 : }
176 : 5 :
177 : 0 : bool QgsArrowSymbolLayer::usesMapUnits() const
178 : 5 : {
179 : 5 : return mArrowWidthUnit == QgsUnitTypes::RenderMapUnits || mArrowWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
180 : 5 : || mArrowStartWidthUnit == QgsUnitTypes::RenderMapUnits || mArrowStartWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
181 : 5 : || mHeadLengthUnit == QgsUnitTypes::RenderMapUnits || mHeadLengthUnit == QgsUnitTypes::RenderMetersInMapUnits
182 : 5 : || mHeadThicknessUnit == QgsUnitTypes::RenderMapUnits || mHeadThicknessUnit == QgsUnitTypes::RenderMetersInMapUnits
183 : 5 : || mWidthUnit == QgsUnitTypes::RenderMapUnits || mWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
184 : 5 : || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
185 : : }
186 : :
187 : 0 : void QgsArrowSymbolLayer::startRender( QgsSymbolRenderContext &context )
188 : : {
189 : 0 : mExpressionScope.reset( new QgsExpressionContextScope() );
190 : 0 : mScaledArrowWidth = context.renderContext().convertToPainterUnits( arrowWidth(), arrowWidthUnit(), arrowWidthUnitScale() );
191 : 0 : mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( arrowStartWidth(), arrowStartWidthUnit(), arrowStartWidthUnitScale() );
192 : 0 : mScaledHeadLength = context.renderContext().convertToPainterUnits( headLength(), headLengthUnit(), headLengthUnitScale() );
193 : 0 : mScaledHeadThickness = context.renderContext().convertToPainterUnits( headThickness(), headThicknessUnit(), headThicknessUnitScale() );
194 : 0 : mScaledOffset = context.renderContext().convertToPainterUnits( offset(), offsetUnit(), offsetMapUnitScale() );
195 : 0 : mComputedHeadType = headType();
196 : 0 : mComputedArrowType = arrowType();
197 : :
198 : 0 : mSymbol->startRender( context.renderContext() );
199 : 0 : }
200 : :
201 : 0 : void QgsArrowSymbolLayer::stopRender( QgsSymbolRenderContext &context )
202 : : {
203 : 0 : mSymbol->stopRender( context.renderContext() );
204 : 0 : }
205 : :
206 : 0 : inline qreal euclidean_distance( QPointF po, QPointF pd )
207 : : {
208 : 0 : return std::sqrt( ( po.x() - pd.x() ) * ( po.x() - pd.x() ) + ( po.y() - pd.y() ) * ( po.y() - pd.y() ) );
209 : : }
210 : :
211 : 0 : QPolygonF straightArrow( QPointF po, QPointF pd,
212 : : qreal startWidth, qreal width,
213 : : qreal headWidth, qreal headHeight,
214 : : QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType,
215 : : qreal offset )
216 : : {
217 : 0 : QPolygonF polygon; // implicitly shared
218 : : // vector length
219 : 0 : qreal length = euclidean_distance( po, pd );
220 : :
221 : : // shift points if there is not enough room for the head(s)
222 : 0 : if ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) )
223 : : {
224 : 0 : po = pd - ( pd - po ) / length * headWidth;
225 : 0 : length = headWidth;
226 : 0 : }
227 : 0 : else if ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) )
228 : : {
229 : 0 : pd = po + ( pd - po ) / length * headWidth;
230 : 0 : length = headWidth;
231 : 0 : }
232 : 0 : else if ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) )
233 : : {
234 : 0 : QPointF v = ( pd - po ) / length * headWidth;
235 : 0 : QPointF npo = ( po + pd ) / 2.0 - v;
236 : 0 : QPointF npd = ( po + pd ) / 2.0 + v;
237 : 0 : po = npo;
238 : 0 : pd = npd;
239 : 0 : length = 2 * headWidth;
240 : 0 : }
241 : :
242 : 0 : qreal bodyLength = length - headWidth;
243 : :
244 : : // unit vector
245 : 0 : QPointF unitVec = ( pd - po ) / length;
246 : : // perpendicular vector
247 : 0 : QPointF perpVec( -unitVec.y(), unitVec.x() );
248 : :
249 : : // set offset
250 : 0 : po += perpVec * offset;
251 : 0 : pd += perpVec * offset;
252 : :
253 : 0 : if ( headType == QgsArrowSymbolLayer::HeadDouble )
254 : : {
255 : : // first head
256 : 0 : polygon << po;
257 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
258 : : {
259 : 0 : polygon << po + unitVec *headWidth + perpVec *headHeight;
260 : 0 : polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
261 : :
262 : 0 : polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
263 : :
264 : : // second head
265 : 0 : polygon << po + unitVec *bodyLength + perpVec *headHeight;
266 : 0 : }
267 : 0 : polygon << pd;
268 : :
269 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
270 : : {
271 : 0 : polygon << po + unitVec *bodyLength - perpVec *headHeight;
272 : 0 : polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
273 : :
274 : : // end of the first head
275 : 0 : polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
276 : 0 : polygon << po + unitVec *headWidth - perpVec *headHeight;
277 : 0 : }
278 : 0 : }
279 : 0 : else if ( headType == QgsArrowSymbolLayer::HeadSingle )
280 : : {
281 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
282 : : {
283 : 0 : polygon << po + perpVec * ( startWidth * 0.5 );
284 : 0 : polygon << po + unitVec *bodyLength + perpVec * ( width * 0.5 );
285 : 0 : polygon << po + unitVec *bodyLength + perpVec *headHeight;
286 : 0 : }
287 : : else
288 : : {
289 : 0 : polygon << po;
290 : : }
291 : 0 : polygon << pd;
292 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
293 : : {
294 : 0 : polygon << po + unitVec *bodyLength - perpVec *headHeight;
295 : 0 : polygon << po + unitVec *bodyLength - perpVec * ( width * 0.5 );
296 : 0 : polygon << po - perpVec * ( startWidth * 0.5 );
297 : 0 : }
298 : : else
299 : : {
300 : 0 : polygon << po;
301 : : }
302 : 0 : }
303 : 0 : else if ( headType == QgsArrowSymbolLayer::HeadReversed )
304 : : {
305 : 0 : polygon << po;
306 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
307 : : {
308 : 0 : polygon << po + unitVec *headWidth + perpVec *headHeight;
309 : 0 : polygon << po + unitVec *headWidth + perpVec * ( width * 0.5 );
310 : :
311 : 0 : polygon << pd + perpVec * ( startWidth * 0.5 );
312 : 0 : }
313 : : else
314 : : {
315 : 0 : polygon << pd;
316 : : }
317 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowPlain || arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
318 : : {
319 : 0 : polygon << pd - perpVec * ( startWidth * 0.5 );
320 : :
321 : 0 : polygon << po + unitVec *headWidth - perpVec * ( width * 0.5 );
322 : 0 : polygon << po + unitVec *headWidth - perpVec *headHeight;
323 : 0 : }
324 : : else
325 : : {
326 : 0 : polygon << pd;
327 : : }
328 : 0 : }
329 : : // close the polygon
330 : 0 : polygon << polygon.first();
331 : :
332 : 0 : return polygon;
333 : 0 : }
334 : :
335 : : // Make sure a given angle is between 0 and 2 pi
336 : 0 : inline qreal clampAngle( qreal a )
337 : : {
338 : 0 : if ( a > 2 * M_PI )
339 : 0 : return a - 2 * M_PI;
340 : 0 : if ( a < 0.0 )
341 : 0 : return a + 2 * M_PI;
342 : 0 : return a;
343 : 0 : }
344 : :
345 : : /**
346 : : * Compute the circumscribed circle from three points
347 : : * \return FALSE if the three points are colinear
348 : : */
349 : 0 : bool pointsToCircle( QPointF a, QPointF b, QPointF c, QPointF ¢er, qreal &radius )
350 : : {
351 : : qreal cx, cy;
352 : :
353 : : // AB and BC vectors
354 : 0 : QPointF ab = b - a;
355 : 0 : QPointF bc = c - b;
356 : :
357 : : // AB and BC middles
358 : 0 : QPointF ab2 = ( a + b ) / 2.0;
359 : 0 : QPointF bc2 = ( b + c ) / 2.0;
360 : :
361 : : // Aligned points
362 : 0 : if ( std::fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
363 : 0 : return false;
364 : :
365 : : // in case AB is horizontal
366 : 0 : if ( ab.y() == 0 )
367 : : {
368 : 0 : cx = ab2.x();
369 : 0 : cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
370 : 0 : }
371 : : //# BC horizontal
372 : 0 : else if ( bc.y() == 0 )
373 : : {
374 : 0 : cx = bc2.x();
375 : 0 : cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
376 : 0 : }
377 : : // Otherwise
378 : : else
379 : : {
380 : 0 : 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() );
381 : 0 : cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
382 : : }
383 : : // Radius
384 : 0 : radius = std::sqrt( ( a.x() - cx ) * ( a.x() - cx ) + ( a.y() - cy ) * ( a.y() - cy ) );
385 : : // Center
386 : 0 : center.setX( cx );
387 : 0 : center.setY( cy );
388 : 0 : return true;
389 : 0 : }
390 : :
391 : 0 : QPointF circlePoint( QPointF center, qreal radius, qreal angle )
392 : : {
393 : : // Y is oriented downward
394 : 0 : return QPointF( std::cos( -angle ) * radius + center.x(), std::sin( -angle ) * radius + center.y() );
395 : : }
396 : :
397 : 0 : void pathArcTo( QPainterPath &path, QPointF circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
398 : : {
399 : 0 : QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
400 : 0 : if ( direction == 1 )
401 : : {
402 : 0 : if ( angle_o < angle_d )
403 : 0 : path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
404 : : else
405 : 0 : path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
406 : 0 : }
407 : : else
408 : : {
409 : 0 : if ( angle_o < angle_d )
410 : 0 : path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
411 : : else
412 : 0 : path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
413 : : }
414 : 0 : }
415 : :
416 : : // Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
417 : 0 : void spiralArcTo( QPainterPath &path, QPointF center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
418 : : {
419 : : // start point
420 : 0 : QPointF A = circlePoint( center, startRadius, startAngle );
421 : : // end point
422 : 0 : QPointF B = circlePoint( center, endRadius, endAngle );
423 : : // middle points
424 : : qreal deltaAngle;
425 : :
426 : 0 : deltaAngle = endAngle - startAngle;
427 : 0 : if ( direction * deltaAngle < 0.0 )
428 : 0 : deltaAngle = deltaAngle + direction * 2 * M_PI;
429 : :
430 : 0 : QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
431 : 0 : QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
432 : 0 : QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
433 : :
434 : : qreal cRadius;
435 : 0 : QPointF cCenter;
436 : : // first circle arc
437 : 0 : if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
438 : : {
439 : : // aligned points => draw a straight line
440 : 0 : path.lineTo( I2 );
441 : 0 : }
442 : : else
443 : : {
444 : : // angles in the new circle
445 : 0 : qreal a1 = std::atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
446 : 0 : qreal a2 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
447 : 0 : pathArcTo( path, cCenter, cRadius, a1, a2, direction );
448 : : }
449 : :
450 : : // second circle arc
451 : 0 : if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
452 : : {
453 : : // aligned points => draw a straight line
454 : 0 : path.lineTo( B );
455 : 0 : }
456 : : else
457 : : {
458 : : // angles in the new circle
459 : 0 : qreal a1 = std::atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
460 : 0 : qreal a2 = std::atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
461 : 0 : pathArcTo( path, cCenter, cRadius, a1, a2, direction );
462 : : }
463 : 0 : }
464 : :
465 : 0 : QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd,
466 : : qreal startWidth, qreal width,
467 : : qreal headWidth, qreal headHeight,
468 : : QgsArrowSymbolLayer::HeadType headType, QgsArrowSymbolLayer::ArrowType arrowType,
469 : : qreal offset )
470 : : {
471 : : qreal circleRadius;
472 : 0 : QPointF circleCenter;
473 : 0 : if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) )
474 : : {
475 : : // aligned points => draw a straight arrow
476 : 0 : return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
477 : : }
478 : :
479 : : // angles of each point
480 : 0 : qreal angle_o = clampAngle( std::atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
481 : 0 : qreal angle_m = clampAngle( std::atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
482 : 0 : qreal angle_d = clampAngle( std::atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
483 : :
484 : : // arc direction : 1 = counter-clockwise, -1 = clockwise
485 : 0 : int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
486 : :
487 : : // arrow type, independent of the direction
488 : 0 : int aType = 0;
489 : 0 : if ( arrowType == QgsArrowSymbolLayer::ArrowRightHalf )
490 : 0 : aType = direction;
491 : 0 : else if ( arrowType == QgsArrowSymbolLayer::ArrowLeftHalf )
492 : 0 : aType = -direction;
493 : :
494 : 0 : qreal deltaAngle = angle_d - angle_o;
495 : 0 : if ( direction * deltaAngle < 0.0 )
496 : 0 : deltaAngle = deltaAngle + direction * 2 * M_PI;
497 : :
498 : 0 : qreal length = euclidean_distance( po, pd );
499 : : // for close points and deltaAngle < 180, draw a straight line
500 : 0 : if ( std::fabs( deltaAngle ) < M_PI && ( ( ( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headWidth ) ) ||
501 : 0 : ( ( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headWidth ) ) ||
502 : 0 : ( ( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headWidth ) ) ) )
503 : : {
504 : 0 : return straightArrow( po, pd, startWidth, width, headWidth, headHeight, headType, arrowType, offset );
505 : : }
506 : :
507 : : // adjust coordinates to include offset
508 : 0 : circleRadius += offset;
509 : 0 : po = circlePoint( circleCenter, circleRadius, angle_o );
510 : 0 : pm = circlePoint( circleCenter, circleRadius, angle_m );
511 : 0 : pd = circlePoint( circleCenter, circleRadius, angle_d );
512 : :
513 : 0 : qreal headAngle = direction * std::atan( headWidth / circleRadius );
514 : :
515 : 0 : QPainterPath path;
516 : :
517 : 0 : if ( headType == QgsArrowSymbolLayer::HeadDouble )
518 : : {
519 : : // the first head
520 : 0 : path.moveTo( po );
521 : 0 : if ( aType <= 0 )
522 : : {
523 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
524 : :
525 : 0 : pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
526 : :
527 : : // the second head
528 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
529 : 0 : path.lineTo( pd );
530 : 0 : }
531 : : else
532 : : {
533 : 0 : pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
534 : : }
535 : 0 : if ( aType >= 0 )
536 : : {
537 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
538 : :
539 : 0 : pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
540 : :
541 : : // the end of the first head
542 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
543 : 0 : path.lineTo( po );
544 : 0 : }
545 : : else
546 : : {
547 : 0 : pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
548 : : }
549 : 0 : }
550 : 0 : else if ( headType == QgsArrowSymbolLayer::HeadSingle )
551 : : {
552 : 0 : if ( aType <= 0 )
553 : : {
554 : 0 : path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
555 : :
556 : 0 : spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
557 : :
558 : : // the arrow head
559 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_d - headAngle ) );
560 : 0 : path.lineTo( pd );
561 : 0 : }
562 : : else
563 : : {
564 : 0 : path.moveTo( po );
565 : 0 : pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
566 : : }
567 : 0 : if ( aType >= 0 )
568 : : {
569 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_d - headAngle ) );
570 : :
571 : 0 : spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
572 : :
573 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
574 : 0 : }
575 : : else
576 : : {
577 : 0 : pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
578 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
579 : : }
580 : 0 : }
581 : 0 : else if ( headType == QgsArrowSymbolLayer::HeadReversed )
582 : : {
583 : 0 : path.moveTo( po );
584 : 0 : if ( aType <= 0 )
585 : : {
586 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * headHeight, angle_o + headAngle ) );
587 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
588 : :
589 : 0 : spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
590 : 0 : }
591 : : else
592 : : {
593 : 0 : pathArcTo( path, circleCenter, circleRadius, angle_o, angle_d, direction );
594 : : }
595 : 0 : if ( aType >= 0 )
596 : : {
597 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
598 : :
599 : 0 : spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
600 : :
601 : 0 : path.lineTo( circlePoint( circleCenter, circleRadius - direction * headHeight, angle_o + headAngle ) );
602 : 0 : path.lineTo( po );
603 : 0 : }
604 : : else
605 : : {
606 : 0 : path.lineTo( pd );
607 : 0 : pathArcTo( path, circleCenter, circleRadius, angle_d, angle_o, -direction );
608 : : }
609 : 0 : }
610 : :
611 : 0 : return path.toSubpathPolygons().at( 0 );
612 : 0 : }
613 : :
614 : 0 : void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolRenderContext &context )
615 : : {
616 : 0 : if ( !dataDefinedProperties().hasActiveProperties() )
617 : 0 : return; // shortcut if case there is no data defined properties at all
618 : :
619 : 0 : QVariant exprVal;
620 : : bool ok;
621 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyArrowWidth ) )
622 : : {
623 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyArrowWidth, context.renderContext().expressionContext() );
624 : 0 : double w = exprVal.toDouble( &ok );
625 : 0 : if ( ok )
626 : : {
627 : 0 : mScaledArrowWidth = context.renderContext().convertToPainterUnits( w, arrowWidthUnit(), arrowWidthUnitScale() );
628 : 0 : }
629 : 0 : }
630 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyArrowStartWidth ) )
631 : : {
632 : 0 : context.setOriginalValueVariable( arrowStartWidth() );
633 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyArrowStartWidth, context.renderContext().expressionContext() );
634 : 0 : double w = exprVal.toDouble( &ok );
635 : 0 : if ( ok )
636 : : {
637 : 0 : mScaledArrowStartWidth = context.renderContext().convertToPainterUnits( w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
638 : 0 : }
639 : 0 : }
640 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyArrowHeadLength ) )
641 : : {
642 : 0 : context.setOriginalValueVariable( headLength() );
643 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyArrowHeadLength, context.renderContext().expressionContext() );
644 : 0 : double w = exprVal.toDouble( &ok );
645 : 0 : if ( ok )
646 : : {
647 : 0 : mScaledHeadLength = context.renderContext().convertToPainterUnits( w, headLengthUnit(), headLengthUnitScale() );
648 : 0 : }
649 : 0 : }
650 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyArrowHeadThickness ) )
651 : : {
652 : 0 : context.setOriginalValueVariable( headThickness() );
653 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyArrowHeadThickness, context.renderContext().expressionContext() );
654 : 0 : double w = exprVal.toDouble( &ok );
655 : 0 : if ( ok )
656 : : {
657 : 0 : mScaledHeadThickness = context.renderContext().convertToPainterUnits( w, headThicknessUnit(), headThicknessUnitScale() );
658 : 0 : }
659 : 0 : }
660 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyOffset ) )
661 : : {
662 : 0 : context.setOriginalValueVariable( offset() );
663 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext() );
664 : 0 : double w = exprVal.toDouble( &ok );
665 : 0 : if ( ok )
666 : : {
667 : 0 : mScaledOffset = context.renderContext().convertToPainterUnits( w, offsetUnit(), offsetMapUnitScale() );
668 : 0 : }
669 : 0 : }
670 : :
671 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyArrowHeadType ) )
672 : : {
673 : 0 : context.setOriginalValueVariable( headType() );
674 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyArrowHeadType, context.renderContext().expressionContext() );
675 : 0 : HeadType h = QgsSymbolLayerUtils::decodeArrowHeadType( exprVal, &ok );
676 : 0 : if ( ok )
677 : : {
678 : 0 : mComputedHeadType = h;
679 : 0 : }
680 : 0 : }
681 : :
682 : 0 : if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyArrowType ) )
683 : : {
684 : 0 : context.setOriginalValueVariable( arrowType() );
685 : 0 : exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyArrowType, context.renderContext().expressionContext() );
686 : 0 : ArrowType h = QgsSymbolLayerUtils::decodeArrowType( exprVal, &ok );
687 : 0 : if ( ok )
688 : : {
689 : 0 : mComputedArrowType = h;
690 : 0 : }
691 : 0 : }
692 : 0 : }
693 : :
694 : 0 : void QgsArrowSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
695 : : {
696 : 0 : Q_UNUSED( points )
697 : :
698 : 0 : if ( !context.renderContext().painter() )
699 : : {
700 : 0 : return;
701 : : }
702 : :
703 : 0 : context.renderContext().expressionContext().appendScope( mExpressionScope.get() );
704 : 0 : mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
705 : 0 : mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, 1, true ) );
706 : :
707 : 0 : const double prevOpacity = mSymbol->opacity();
708 : 0 : mSymbol->setOpacity( prevOpacity * context.opacity() );
709 : :
710 : 0 : if ( isCurved() )
711 : : {
712 : 0 : _resolveDataDefined( context );
713 : :
714 : 0 : if ( ! isRepeated() )
715 : : {
716 : 0 : if ( points.size() >= 3 )
717 : : {
718 : : // origin point
719 : 0 : QPointF po( points.at( 0 ) );
720 : : // middle point
721 : 0 : QPointF pm( points.at( points.size() / 2 ) );
722 : : // destination point
723 : 0 : QPointF pd( points.back() );
724 : :
725 : 0 : QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
726 : 0 : mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
727 : 0 : }
728 : : // straight arrow
729 : 0 : else if ( points.size() == 2 )
730 : : {
731 : : // origin point
732 : 0 : QPointF po( points.at( 0 ) );
733 : : // destination point
734 : 0 : QPointF pd( points.at( 1 ) );
735 : :
736 : 0 : QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
737 : 0 : mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
738 : 0 : }
739 : 0 : }
740 : : else
741 : : {
742 : 0 : for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
743 : : {
744 : 0 : if ( context.renderContext().renderingStopped() )
745 : 0 : break;
746 : :
747 : 0 : mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
748 : 0 : _resolveDataDefined( context );
749 : :
750 : 0 : if ( points.size() - pIdx >= 3 )
751 : : {
752 : : // origin point
753 : 0 : QPointF po( points.at( pIdx ) );
754 : : // middle point
755 : 0 : QPointF pm( points.at( pIdx + 1 ) );
756 : : // destination point
757 : 0 : QPointF pd( points.at( pIdx + 2 ) );
758 : :
759 : 0 : QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
760 : 0 : mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
761 : 0 : }
762 : : // straight arrow
763 : 0 : else if ( points.size() - pIdx == 2 )
764 : : {
765 : : // origin point
766 : 0 : QPointF po( points.at( pIdx ) );
767 : : // destination point
768 : 0 : QPointF pd( points.at( pIdx + 1 ) );
769 : :
770 : 0 : QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
771 : 0 : mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
772 : 0 : }
773 : 0 : }
774 : : }
775 : 0 : }
776 : : else
777 : : {
778 : 0 : if ( !isRepeated() )
779 : : {
780 : 0 : _resolveDataDefined( context );
781 : :
782 : 0 : if ( !points.isEmpty() )
783 : : {
784 : : // origin point
785 : 0 : QPointF po( points.at( 0 ) );
786 : : // destination point
787 : 0 : QPointF pd( points.back() );
788 : :
789 : 0 : QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
790 : 0 : mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
791 : 0 : }
792 : 0 : }
793 : : else
794 : : {
795 : : // only straight arrows
796 : 0 : for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
797 : : {
798 : 0 : if ( context.renderContext().renderingStopped() )
799 : 0 : break;
800 : :
801 : 0 : mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
802 : 0 : _resolveDataDefined( context );
803 : :
804 : : // origin point
805 : 0 : QPointF po( points.at( pIdx ) );
806 : : // destination point
807 : 0 : QPointF pd( points.at( pIdx + 1 ) );
808 : :
809 : 0 : QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadLength, mScaledHeadThickness, mComputedHeadType, mComputedArrowType, mScaledOffset );
810 : :
811 : 0 : mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
812 : 0 : }
813 : : }
814 : : }
815 : :
816 : 0 : mSymbol->setOpacity( prevOpacity );
817 : 0 : context.renderContext().expressionContext().popScope();
818 : 0 : }
819 : :
820 : 0 : void QgsArrowSymbolLayer::setColor( const QColor &c )
821 : : {
822 : 0 : if ( mSymbol )
823 : 0 : mSymbol->setColor( c );
824 : :
825 : 0 : mColor = c;
826 : 0 : }
827 : :
828 : 0 : QColor QgsArrowSymbolLayer::color() const
829 : : {
830 : 0 : return mSymbol.get() ? mSymbol->color() : mColor;
831 : : }
832 : :
833 : 0 : bool QgsArrowSymbolLayer::canCauseArtifactsBetweenAdjacentTiles() const
834 : : {
835 : 0 : return true;
836 : : }
837 : :
|