QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgsshapegenerator.cpp
Go to the documentation of this file.
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
20#include <algorithm>
21
22#include "qgsgeometryutils.h"
23
24#include <QLineF>
25#include <QList>
26#include <QPainterPath>
27
28QLineF segment( int index, QRectF rect, double radius )
29{
30 const int yMultiplier = rect.height() < 0 ? -1 : 1;
31 switch ( index )
32 {
33 case 0:
34 return QLineF( rect.left() + radius,
35 rect.top(),
36 rect.right() - radius,
37 rect.top() );
38 case 1:
39 return QLineF( rect.right(),
40 rect.top() + yMultiplier * radius,
41 rect.right(),
42 rect.bottom() - yMultiplier * radius );
43 case 2:
44 return QLineF( rect.right() - radius,
45 rect.bottom(),
46 rect.left() + radius,
47 rect.bottom() );
48 case 3:
49 return QLineF( rect.left(),
50 rect.bottom() - yMultiplier * radius,
51 rect.left(),
52 rect.top() + yMultiplier * radius );
53 default:
54 return QLineF();
55 }
56}
57
58QPolygonF QgsShapeGenerator::createBalloon( const QgsPointXY &origin, const QRectF &rect, double wedgeWidth )
59{
60 return createBalloon( origin, rect, wedgeWidth, 0 ).toFillPolygon();
61}
62
63QPainterPath QgsShapeGenerator::createBalloon( const QgsPointXY &origin, const QRectF &rect, double wedgeWidth, double cornerRadius )
64{
65 int balloonSegment = -1;
66 QPointF balloonSegmentPoint1;
67 QPointF balloonSegmentPoint2;
68
69 const bool invertedY = rect.height() < 0;
70
71 cornerRadius = std::min( cornerRadius, std::min( std::fabs( rect.height() ), rect.width() ) / 2.0 );
72
73 //first test if the point is in the frame. In that case we don't need a balloon and can just use a rect
74 if ( rect.contains( origin.toQPointF() ) )
75 {
76 balloonSegment = -1;
77 }
78 else
79 {
80 //edge list
81 QList<QLineF> segmentList;
82 segmentList << segment( 0, rect, cornerRadius );
83 segmentList << segment( 1, rect, cornerRadius );
84 segmentList << segment( 2, rect, cornerRadius );
85 segmentList << segment( 3, rect, cornerRadius );
86
87 // find closest edge / closest edge point
88 double minEdgeDist = std::numeric_limits<double>::max();
89 int minEdgeIndex = -1;
90 QLineF minEdge;
91 QgsPointXY minEdgePoint( 0, 0 );
92
93 for ( int i = 0; i < 4; ++i )
94 {
95 QLineF currentSegment = segmentList.at( i );
96 QgsPointXY currentMinDistPoint;
97 double currentMinDist = origin.sqrDistToSegment( currentSegment.x1(), currentSegment.y1(), currentSegment.x2(), currentSegment.y2(), currentMinDistPoint );
98 bool isPreferredSegment = false;
99 if ( qgsDoubleNear( currentMinDist, minEdgeDist ) )
100 {
101 // two segments are close - work out which looks nicer
102 const double angle = fmod( origin.azimuth( currentMinDistPoint ) + 360.0, 360.0 );
103 if ( angle < 45 || angle > 315 )
104 isPreferredSegment = i == 0;
105 else if ( angle < 135 )
106 isPreferredSegment = i == 3;
107 else if ( angle < 225 )
108 isPreferredSegment = i == 2;
109 else
110 isPreferredSegment = i == 1;
111 }
112 else if ( currentMinDist < minEdgeDist )
113 isPreferredSegment = true;
114
115 if ( isPreferredSegment )
116 {
117 minEdgeIndex = i;
118 minEdgePoint = currentMinDistPoint;
119 minEdgeDist = currentMinDist;
120 minEdge = currentSegment;
121 }
122 }
123
124 if ( minEdgeIndex >= 0 )
125 {
126 balloonSegment = minEdgeIndex;
127 QPointF minEdgeEnd = minEdge.p2();
128 balloonSegmentPoint1 = QPointF( minEdgePoint.x(), minEdgePoint.y() );
129
130 const double segmentLength = minEdge.length();
131 const double clampedWedgeWidth = std::clamp( wedgeWidth, 0.0, segmentLength );
132 if ( std::sqrt( minEdgePoint.sqrDist( minEdgeEnd.x(), minEdgeEnd.y() ) ) < clampedWedgeWidth )
133 {
134 double x = 0;
135 double y = 0;
136 QgsGeometryUtilsBase::pointOnLineWithDistance( minEdge.p2().x(), minEdge.p2().y(), minEdge.p1().x(), minEdge.p1().y(), clampedWedgeWidth, x, y );
137 balloonSegmentPoint1 = QPointF( x, y );
138 }
139
140 {
141 double x = 0;
142 double y = 0;
143 QgsGeometryUtilsBase::pointOnLineWithDistance( balloonSegmentPoint1.x(), balloonSegmentPoint1.y(), minEdge.p2().x(), minEdge.p2().y(), clampedWedgeWidth, x, y );
144 balloonSegmentPoint2 = QPointF( x, y );
145 }
146 }
147 }
148
149 QPainterPath path;
150 QPointF p0;
151 QPointF p1;
152 for ( int i = 0; i < 4; ++i )
153 {
154 QLineF currentSegment = segment( i, rect, cornerRadius );
155 if ( i == 0 )
156 {
157 p0 = currentSegment.p1();
158 path.moveTo( currentSegment.p1() );
159 }
160 else
161 {
162 if ( invertedY )
163 path.arcTo( std::min( p1.x(), currentSegment.p1().x() ),
164 std::min( p1.y(), currentSegment.p1().y() ),
165 cornerRadius, cornerRadius,
166 i == 1 ? -90 : ( i == 2 ? 0 : 90 ),
167 90 );
168 else
169 path.arcTo( std::min( p1.x(), currentSegment.p1().x() ),
170 std::min( p1.y(), currentSegment.p1().y() ),
171 cornerRadius, cornerRadius,
172 i == 1 ? 90 : ( i == 2 ? 0 : -90 ),
173 -90 );
174 }
175
176 if ( i == balloonSegment )
177 {
178 path.lineTo( balloonSegmentPoint1 );
179 path.lineTo( origin.toQPointF() );
180 path.lineTo( balloonSegmentPoint2 );
181 }
182
183 p1 = currentSegment.p2();
184 path.lineTo( p1 );
185 }
186
187 if ( invertedY )
188 path.arcTo( std::min( p1.x(), p0.x() ),
189 std::min( p1.y(), p0.y() ),
190 cornerRadius, cornerRadius,
191 180, 90 );
192 else
193 path.arcTo( std::min( p1.x(), p0.x() ),
194 std::min( p1.y(), p0.y() ),
195 cornerRadius, cornerRadius,
196 -180, -90 );
197
198 return path;
199}
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
Represents a 2D point.
Definition qgspointxy.h:60
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:186
double azimuth(const QgsPointXY &other) const
Calculates azimuth between this point and other one (clockwise in degree, starting from north).
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
double sqrDistToSegment(double x1, double y1, double x2, double y2, QgsPointXY &minDistPoint, double epsilon=Qgis::DEFAULT_SEGMENT_EPSILON) const
Returns the minimum distance between this point and a segment.
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:165
static QPolygonF createBalloon(const QgsPointXY &origin, const QRectF &rect, double wedgeWidth)
Generates a "balloon"/"talking bubble" style shape (as a QPolygonF).
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
QLineF segment(int index, QRectF rect, double radius)