/src/geos/src/algorithm/CircularArcs.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * GEOS - Geometry Engine Open Source |
4 | | * http://geos.osgeo.org |
5 | | * |
6 | | * Copyright (C) 2024 ISciences, LLC |
7 | | * |
8 | | * This is free software; you can redistribute and/or modify it under |
9 | | * the terms of the GNU Lesser General Public Licence as published |
10 | | * by the Free Software Foundation. |
11 | | * See the COPYING file for more information. |
12 | | * |
13 | | **********************************************************************/ |
14 | | |
15 | | #include <geos/algorithm/Angle.h> |
16 | | #include <geos/algorithm/CircularArcs.h> |
17 | | #include <geos/algorithm/Orientation.h> |
18 | | #include <geos/geom/Envelope.h> |
19 | | #include <geos/geom/Quadrant.h> |
20 | | #include <geos/math/DD.h> |
21 | | |
22 | | using geos::geom::CoordinateXY; |
23 | | |
24 | | namespace geos { |
25 | | namespace algorithm { |
26 | | |
27 | | CoordinateXY |
28 | | CircularArcs::getMidpoint(const CoordinateXY& p0, const CoordinateXY& p2, const CoordinateXY& center, double radius, bool isCCW) |
29 | 0 | { |
30 | 0 | double start = getAngle(p0, center); |
31 | 0 | double stop = getAngle(p2, center); |
32 | 0 | double mid = getMidpointAngle(start, stop, isCCW); |
33 | 0 | return createPoint(center, radius, mid); |
34 | 0 | } |
35 | | |
36 | | double |
37 | | CircularArcs::getAngle(const CoordinateXY& p, const CoordinateXY& center) |
38 | 0 | { |
39 | 0 | return std::atan2(p.y - center.y, p.x - center.x); |
40 | 0 | } |
41 | | |
42 | | double |
43 | | CircularArcs::getMidpointAngle(double theta0, double theta2, bool isCCW) |
44 | 0 | { |
45 | 0 | if (!isCCW) { |
46 | 0 | return getMidpointAngle(theta2, theta0, true); |
47 | 0 | } |
48 | | |
49 | 0 | double mid = (theta0 + theta2) / 2; |
50 | 0 | if (!Angle::isWithinCCW(mid, theta0, theta2)) { |
51 | 0 | mid += MATH_PI; |
52 | 0 | } |
53 | |
|
54 | 0 | return mid; |
55 | 0 | } |
56 | | |
57 | | CoordinateXY |
58 | | CircularArcs::createPoint(const CoordinateXY& center, double radius, double theta) |
59 | 0 | { |
60 | 0 | return { center.x + radius* std::cos(theta), center.y + radius* std::sin(theta) }; |
61 | 0 | } |
62 | | |
63 | | template<typename T> |
64 | | CoordinateXY getCenterImpl(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) |
65 | 38.9k | { |
66 | | // Circumcenter formulas from Graphics Gems III |
67 | 38.9k | T p0x{p0.x}; |
68 | 38.9k | T p0y{p0.y}; |
69 | 38.9k | T p1x{p1.x}; |
70 | 38.9k | T p1y{p1.y}; |
71 | 38.9k | T p2x{p2.x}; |
72 | 38.9k | T p2y{p2.y}; |
73 | | |
74 | 38.9k | T ax = p1x - p2x; |
75 | 38.9k | T ay = p1y - p2y; |
76 | 38.9k | T bx = p2x - p0x; |
77 | 38.9k | T by = p2y - p0y; |
78 | 38.9k | T cx = p0x - p1x; |
79 | 38.9k | T cy = p0y - p1y; |
80 | | |
81 | 38.9k | T d1 = -(bx*cx + by*cy); |
82 | 38.9k | T d2 = -(cx*ax + cy*ay); |
83 | 38.9k | T d3 = -(ax*bx + ay*by); |
84 | | |
85 | 38.9k | T e1 = d2*d3; |
86 | 38.9k | T e2 = d3*d1; |
87 | 38.9k | T e3 = d1*d2; |
88 | 38.9k | T e = e1 + e2 + e3; |
89 | | |
90 | 38.9k | T G3x = p0.x + p1.x + p2.x; |
91 | 38.9k | T G3y = p0.y + p1.y + p2.y; |
92 | 38.9k | T Hx = (e1*p0.x + e2*p1.x + e3*p2.x) / e; |
93 | 38.9k | T Hy = (e1*p0.y + e2*p1.y + e3*p2.y) / e; |
94 | | |
95 | 38.9k | T rx = 0.5*(G3x - Hx); |
96 | 38.9k | T ry = 0.5*(G3y - Hy); |
97 | | |
98 | | if constexpr (std::is_same_v<T, math::DD>) { |
99 | | return {rx.doubleValue(), ry.doubleValue()}; |
100 | 38.9k | } else { |
101 | 38.9k | return {rx, ry}; |
102 | 38.9k | } |
103 | 38.9k | } |
104 | | |
105 | | CoordinateXY |
106 | | CircularArcs::getCenter(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) |
107 | 40.9k | { |
108 | 40.9k | if (p0.equals2D(p2)) { |
109 | | // Closed circle |
110 | 1.92k | return { 0.5*(p0.x + p1.x), 0.5*(p0.y + p1.y) }; |
111 | 1.92k | } |
112 | | |
113 | 38.9k | return getCenterImpl<double>(p0, p1, p2); |
114 | 40.9k | } |
115 | | |
116 | | void |
117 | | CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, |
118 | | const geom::CoordinateXY& p2) |
119 | 40.9k | { |
120 | 40.9k | using geom::Quadrant; |
121 | | |
122 | 40.9k | e.expandToInclude(p0); |
123 | 40.9k | e.expandToInclude(p1); |
124 | 40.9k | e.expandToInclude(p2); |
125 | | |
126 | 40.9k | CoordinateXY center = getCenter(p0, p1, p2); |
127 | | |
128 | | // zero-length arc |
129 | 40.9k | if (center.equals2D(p0) || center.equals2D(p1)) { |
130 | 650 | return; |
131 | 650 | } |
132 | | |
133 | | // collinear |
134 | 40.2k | if (std::isnan(center.x)) { |
135 | 12.0k | return; |
136 | 12.0k | } |
137 | | |
138 | | //* 1 | 0 |
139 | | //* --+-- |
140 | | //* 2 | 3 |
141 | 28.2k | const auto pa0 = Quadrant::pseudoAngle(center, p0); |
142 | 28.2k | const auto pa1 = Quadrant::pseudoAngle(center, p1); |
143 | 28.2k | const auto pa2 = Quadrant::pseudoAngle(center, p2); |
144 | | |
145 | 28.2k | auto q0 = static_cast<int>(pa0); |
146 | 28.2k | auto q2 = static_cast<int>(pa2); |
147 | 28.2k | double R = center.distance(p1); |
148 | | |
149 | 28.2k | if (q0 == q2) { |
150 | | // Start and end quadrants are the same. Either the arc crosses all |
151 | | // the axes, or none of the axes. |
152 | | |
153 | 8.65k | const bool isBetween = pa1 > std::min(pa0, pa2) && pa1 < std::max(pa0, pa2); |
154 | | |
155 | 8.65k | if (!isBetween) { |
156 | 8.11k | e.expandToInclude({center.x, center.y + R}); |
157 | 8.11k | e.expandToInclude({center.x - R, center.y}); |
158 | 8.11k | e.expandToInclude({center.x, center.y - R}); |
159 | 8.11k | e.expandToInclude({center.x + R, center.y}); |
160 | 8.11k | } |
161 | | |
162 | 8.65k | return; |
163 | 8.65k | } |
164 | | |
165 | 19.5k | auto orientation = Orientation::index(p0, p1, p2); |
166 | | |
167 | 19.5k | if (orientation == Orientation::CLOCKWISE) { |
168 | 12.7k | std::swap(q0, q2); |
169 | 12.7k | } |
170 | | |
171 | 63.4k | for (auto q = q0 + 1; (q % 4) != ((q2+1) % 4); q++) { |
172 | 43.8k | switch (q % 4) { |
173 | 11.3k | case Quadrant::NW: |
174 | 11.3k | e.expandToInclude({center.x, center.y + R}); |
175 | 11.3k | break; |
176 | 10.4k | case Quadrant::SW: |
177 | 10.4k | e.expandToInclude({center.x - R, center.y}); |
178 | 10.4k | break; |
179 | 13.1k | case Quadrant::SE: |
180 | 13.1k | e.expandToInclude({center.x, center.y - R}); |
181 | 13.1k | break; |
182 | 8.92k | case Quadrant::NE: |
183 | 8.92k | e.expandToInclude({center.x + R, center.y}); |
184 | 8.92k | break; |
185 | 43.8k | } |
186 | 43.8k | } |
187 | 19.5k | } |
188 | | |
189 | | } |
190 | | } |