Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsshapegenerator.cpp
3 : : ----------------
4 : : begin : March 2021
5 : : copyright : (C) 2021 Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsshapegenerator.h"
19 : : #include "qgsgeometryutils.h"
20 : : #include <QLineF>
21 : : #include <QList>
22 : : #include <algorithm>
23 : :
24 : 0 : QLineF segment( int index, QRectF rect )
25 : : {
26 : 0 : switch ( index )
27 : : {
28 : : case 0:
29 : 0 : return QLineF( rect.left(),
30 : 0 : rect.top(),
31 : 0 : rect.right(),
32 : 0 : rect.top() );
33 : : case 1:
34 : 0 : return QLineF( rect.right(),
35 : 0 : rect.top(),
36 : 0 : rect.right(),
37 : 0 : rect.bottom() );
38 : : case 2:
39 : 0 : return QLineF( rect.right(),
40 : 0 : rect.bottom(),
41 : 0 : rect.left(),
42 : 0 : rect.bottom() );
43 : : case 3:
44 : 0 : return QLineF( rect.left(),
45 : 0 : rect.bottom(),
46 : 0 : rect.left(),
47 : 0 : rect.top() );
48 : : default:
49 : 0 : return QLineF();
50 : : }
51 : 0 : }
52 : :
53 : 0 : QPolygonF QgsShapeGenerator::createBalloon( const QgsPointXY &origin, const QRectF &rect, double wedgeWidth )
54 : : {
55 : 0 : int balloonSegment = -1;
56 : 0 : QPointF balloonSegmentPoint1;
57 : 0 : QPointF balloonSegmentPoint2;
58 : :
59 : : //first test if the point is in the frame. In that case we don't need a balloon and can just use a rect
60 : 0 : if ( rect.contains( origin.toQPointF() ) )
61 : : {
62 : 0 : balloonSegment = -1;
63 : 0 : }
64 : : else
65 : : {
66 : : //edge list
67 : 0 : QList<QLineF> segmentList;
68 : 0 : segmentList << segment( 0, rect );
69 : 0 : segmentList << segment( 1, rect );
70 : 0 : segmentList << segment( 2, rect );
71 : 0 : segmentList << segment( 3, rect );
72 : :
73 : : // find closest edge / closest edge point
74 : 0 : double minEdgeDist = std::numeric_limits<double>::max();
75 : 0 : int minEdgeIndex = -1;
76 : 0 : QLineF minEdge;
77 : 0 : QgsPointXY minEdgePoint( 0, 0 );
78 : :
79 : 0 : for ( int i = 0; i < 4; ++i )
80 : : {
81 : 0 : QLineF currentSegment = segmentList.at( i );
82 : 0 : QgsPointXY currentMinDistPoint;
83 : 0 : double currentMinDist = origin.sqrDistToSegment( currentSegment.x1(), currentSegment.y1(), currentSegment.x2(), currentSegment.y2(), currentMinDistPoint );
84 : 0 : bool isPreferredSegment = false;
85 : 0 : if ( qgsDoubleNear( currentMinDist, minEdgeDist ) )
86 : : {
87 : : // two segments are close - work out which looks nicer
88 : 0 : const double angle = fmod( origin.azimuth( currentMinDistPoint ) + 360.0, 360.0 );
89 : 0 : if ( angle < 45 || angle > 315 )
90 : 0 : isPreferredSegment = i == 0;
91 : 0 : else if ( angle < 135 )
92 : 0 : isPreferredSegment = i == 3;
93 : 0 : else if ( angle < 225 )
94 : 0 : isPreferredSegment = i == 2;
95 : : else
96 : 0 : isPreferredSegment = i == 1;
97 : 0 : }
98 : 0 : else if ( currentMinDist < minEdgeDist )
99 : 0 : isPreferredSegment = true;
100 : :
101 : 0 : if ( isPreferredSegment )
102 : : {
103 : 0 : minEdgeIndex = i;
104 : 0 : minEdgePoint = currentMinDistPoint;
105 : 0 : minEdgeDist = currentMinDist;
106 : 0 : minEdge = currentSegment;
107 : 0 : }
108 : 0 : }
109 : :
110 : 0 : if ( minEdgeIndex >= 0 )
111 : : {
112 : 0 : balloonSegment = minEdgeIndex;
113 : 0 : QPointF minEdgeEnd = minEdge.p2();
114 : 0 : balloonSegmentPoint1 = QPointF( minEdgePoint.x(), minEdgePoint.y() );
115 : :
116 : 0 : const double segmentLength = minEdge.length();
117 : 0 : const double clampedWedgeWidth = std::clamp( wedgeWidth, 0.0, segmentLength );
118 : 0 : if ( std::sqrt( minEdgePoint.sqrDist( minEdgeEnd.x(), minEdgeEnd.y() ) ) < clampedWedgeWidth )
119 : : {
120 : 0 : double x = 0;
121 : 0 : double y = 0;
122 : 0 : QgsGeometryUtils::pointOnLineWithDistance( minEdge.p2().x(), minEdge.p2().y(), minEdge.p1().x(), minEdge.p1().y(), clampedWedgeWidth, x, y );
123 : 0 : balloonSegmentPoint1 = QPointF( x, y );
124 : 0 : }
125 : :
126 : : {
127 : 0 : double x = 0;
128 : 0 : double y = 0;
129 : 0 : QgsGeometryUtils::pointOnLineWithDistance( balloonSegmentPoint1.x(), balloonSegmentPoint1.y(), minEdge.p2().x(), minEdge.p2().y(), clampedWedgeWidth, x, y );
130 : 0 : balloonSegmentPoint2 = QPointF( x, y );
131 : : }
132 : 0 : }
133 : 0 : }
134 : :
135 : 0 : QPolygonF poly;
136 : 0 : poly.reserve( 12 );
137 : 0 : for ( int i = 0; i < 4; ++i )
138 : : {
139 : 0 : QLineF currentSegment = segment( i, rect );
140 : 0 : poly << currentSegment.p1();
141 : :
142 : 0 : if ( i == balloonSegment )
143 : : {
144 : 0 : poly << balloonSegmentPoint1;
145 : 0 : poly << origin.toQPointF();
146 : 0 : poly << balloonSegmentPoint2;
147 : 0 : }
148 : :
149 : 0 : poly << currentSegment.p2();
150 : 0 : }
151 : 0 : if ( poly.at( 0 ) != poly.at( poly.count() - 1 ) )
152 : 0 : poly << poly.at( 0 );
153 : 0 : return poly;
154 : 0 : }
|