Coverage Report

Created: 2025-11-08 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/include/geos/geom/CircularArc.h
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
#pragma once
16
17
#include <geos/export.h>
18
#include <geos/geom/Coordinate.h>
19
#include <geos/geom/LineSegment.h>
20
#include <geos/geom/Quadrant.h>
21
#include <geos/algorithm/CircularArcs.h>
22
#include <geos/algorithm/Orientation.h>
23
#include <geos/triangulate/quadedge/TrianglePredicate.h>
24
25
namespace geos {
26
namespace geom {
27
28
/// A CircularArc is a reference to three points that define a circular arc.
29
/// It provides for the lazy calculation of various arc properties such as the center, radius, and orientation
30
class GEOS_DLL CircularArc {
31
public:
32
33
    using CoordinateXY = geom::CoordinateXY;
34
35
0
    CircularArc() : CircularArc({0, 0}, {0, 0}, {0, 0}) {}
36
37
    CircularArc(const CoordinateXY& q0, const CoordinateXY& q1, const CoordinateXY& q2)
38
0
        : p0(q0)
39
0
        , p1(q1)
40
0
        , p2(q2)
41
0
        , m_center_known(false)
42
0
        , m_radius_known(false)
43
0
        , m_orientation_known(false)
44
0
    {}
45
46
    CircularArc(double theta0, double theta2, const CoordinateXY& center, double radius, int orientation)
47
        : p0(algorithm::CircularArcs::createPoint(center, radius, theta0)),
48
          p1(algorithm::CircularArcs::createPoint(center, radius, algorithm::CircularArcs::getMidpointAngle(theta0, theta2, orientation==algorithm::Orientation::COUNTERCLOCKWISE))),
49
          p2(algorithm::CircularArcs::createPoint(center, radius, theta2)),
50
          m_center(center),
51
          m_radius(radius),
52
          m_orientation(orientation),
53
          m_center_known(true),
54
          m_radius_known(true),
55
          m_orientation_known(true)
56
0
    {}
57
58
    CircularArc(const CoordinateXY& q0, const CoordinateXY& q2, const CoordinateXY& center, double radius, int orientation)
59
        : p0(q0),
60
          p1(algorithm::CircularArcs::getMidpoint(q0, q2, center, radius, orientation==algorithm::Orientation::COUNTERCLOCKWISE)),
61
          p2(q2),
62
          m_center(center),
63
          m_radius(radius),
64
          m_orientation(orientation),
65
          m_center_known(true),
66
          m_radius_known(true),
67
          m_orientation_known(true)
68
0
    {}
69
70
    CoordinateXY p0;
71
    CoordinateXY p1;
72
    CoordinateXY p2;
73
74
    /// Return the orientation of the arc as one of:
75
    /// - algorithm::Orientation::CLOCKWISE,
76
    /// - algorithm::Orientation::COUNTERCLOCKWISE
77
    /// - algorithm::Orientation::COLLINEAR
78
0
    int getOrientation() const {
79
0
        if (!m_orientation_known) {
80
0
            m_orientation = algorithm::Orientation::index(p0, p1, p2);
81
0
            m_orientation_known = true;
82
0
        }
83
0
        return m_orientation;
84
0
    }
85
86
0
    bool isCCW() const {
87
0
        return getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE;
88
0
    }
89
90
    /// Return the center point of the circle associated with this arc
91
0
    const CoordinateXY& getCenter() const {
92
0
        if (!m_center_known) {
93
0
            m_center = algorithm::CircularArcs::getCenter(p0, p1, p2);
94
0
            m_center_known = true;
95
0
        }
96
97
0
        return m_center;
98
0
    }
99
100
    /// Return the radius of the circle associated with this arc
101
0
    double getRadius() const {
102
0
        if (!m_radius_known) {
103
0
            m_radius = getCenter().distance(p0);
104
0
            m_radius_known = true;
105
0
        }
106
107
0
        return m_radius;
108
0
    }
109
110
    /// Return whether this arc forms a complete circle
111
0
    bool isCircle() const {
112
0
        return p0.equals(p2);
113
0
    }
114
115
    /// Returns whether this arc forms a straight line (p0, p1, and p2 are collinear)
116
0
    bool isLinear() const {
117
0
        return !std::isfinite(getRadius());
118
0
    }
119
120
    /// Return the inner angle of the sector associated with this arc
121
0
    double getAngle() const {
122
0
        if (isCircle()) {
123
0
            return 2*MATH_PI;
124
0
        }
125
126
        /// Even Rouault:
127
        /// potential optimization?: using crossproduct(p0 - center, p2 - center) = radius * radius * sin(angle)
128
        /// could yield the result by just doing a single asin(), instead of 2 atan2()
129
        /// actually one should also likely compute dotproduct(p0 - center, p2 - center) = radius * radius * cos(angle),
130
        /// and thus angle = atan2(crossproduct(p0 - center, p2 - center) , dotproduct(p0 - center, p2 - center) )
131
0
        auto t0 = theta0();
132
0
        auto t2 = theta2();
133
134
0
        if (getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE) {
135
0
            std::swap(t0, t2);
136
0
        }
137
138
0
        if (t0 < t2) {
139
0
            t0 += 2*MATH_PI;
140
0
        }
141
142
0
        auto diff = t0-t2;
143
144
0
        return diff;
145
0
    }
146
147
    /// Return the length of the arc
148
0
    double getLength() const {
149
0
        if (isLinear()) {
150
0
            return p0.distance(p2);
151
0
        }
152
153
0
        return getAngle()*getRadius();
154
0
    }
155
156
    /// Return the area enclosed by the arc p0-p1-p2 and the line segment p2-p0
157
0
    double getArea() const {
158
0
        if (isLinear()) {
159
0
            return 0;
160
0
        }
161
162
0
        auto R = getRadius();
163
0
        auto theta = getAngle();
164
0
        return R*R/2*(theta - std::sin(theta));
165
0
    }
166
167
    /// Return the distance from the centerpoint of the arc to the line segment formed by the end points of the arc.
168
0
    double getSagitta() const {
169
0
        CoordinateXY midpoint = algorithm::CircularArcs::getMidpoint(p0, p2, getCenter(), getRadius(), isCCW());
170
0
        return algorithm::Distance::pointToSegment(midpoint, p0, p2);
171
0
    }
172
173
    /// Return the angle of p0
174
0
    double theta0() const {
175
0
        return algorithm::CircularArcs::getAngle(p0, getCenter());
176
0
    }
177
178
    /// Return the angle of p2
179
0
    double theta2() const {
180
0
        return algorithm::CircularArcs::getAngle(p2, getCenter());
181
0
    }
182
183
    /// Check to see if a coordinate lies on the arc
184
    /// Only the angle is checked, so it is assumed that the point lies on
185
    /// the circle of which this arc is a part.
186
0
    bool containsPointOnCircle(const CoordinateXY& q) const {
187
0
        double theta = std::atan2(q.y - getCenter().y, q.x - getCenter().x);
188
0
        return containsAngle(theta);
189
0
    }
190
191
    /// Check to see if a coordinate lies on the arc, after testing whether
192
    /// it lies on the circle.
193
0
    bool containsPoint(const CoordinateXY& q) const {
194
0
        if (q == p0 || q == p1 || q == p2) {
195
0
            return true;
196
0
        }
197
198
        //auto dist = std::abs(q.distance(getCenter()) - getRadius());
199
200
        //if (dist > 1e-8) {
201
        //    return false;
202
        //}
203
204
0
        if (triangulate::quadedge::TrianglePredicate::isInCircleRobust(p0, p1, p2, q) != geom::Location::BOUNDARY) {
205
0
            return false;
206
0
        }
207
208
0
        return containsPointOnCircle(q);
209
0
    }
210
211
    /// Check to see if a given angle lies on this arc
212
0
    bool containsAngle(double theta) const {
213
0
        auto t0 = theta0();
214
0
        auto t2 = theta2();
215
216
0
        if (theta == t0 || theta == t2) {
217
0
            return true;
218
0
        }
219
220
0
        if (getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE) {
221
0
            std::swap(t0, t2);
222
0
        }
223
224
0
        t2 -= t0;
225
0
        theta -= t0;
226
227
0
        if (t2 < 0) {
228
0
            t2 += 2*MATH_PI;
229
0
        }
230
0
        if (theta < 0) {
231
0
            theta += 2*MATH_PI;
232
0
        }
233
234
0
        return theta >= t2;
235
0
    }
236
237
    /// Return true if the arc is pointing positive in the y direction
238
    /// at the location of a specified point. The point is assumed to
239
    /// be on the arc.
240
0
    bool isUpwardAtPoint(const CoordinateXY& q) const {
241
0
        auto quad = geom::Quadrant::quadrant(getCenter(), q);
242
0
        bool isUpward;
243
244
0
        if (getOrientation() == algorithm::Orientation::CLOCKWISE) {
245
0
            isUpward = (quad == geom::Quadrant::SW || quad == geom::Quadrant::NW);
246
0
        } else {
247
0
            isUpward = (quad == geom::Quadrant::SE || quad == geom::Quadrant::NE);
248
0
        }
249
250
0
        return isUpward;
251
0
    }
252
253
    // Split an arc at a specified point.
254
    // The point is assumed to be o the arc.
255
0
    std::pair<CircularArc, CircularArc> splitAtPoint(const CoordinateXY& q) const {
256
0
        return {
257
0
            CircularArc(p0, q, getCenter(), getRadius(), getOrientation()),
258
0
            CircularArc(q, p2, getCenter(), getRadius(), getOrientation())
259
0
        };
260
0
    }
261
262
    class Iterator {
263
    public:
264
        using iterator_category = std::forward_iterator_tag;
265
        using difference_type = std::ptrdiff_t;
266
        using value_type = geom::CoordinateXY;
267
        using pointer = const geom::CoordinateXY*;
268
        using reference = const geom::CoordinateXY&;
269
270
0
        Iterator(const CircularArc& arc, int i) : m_arc(arc), m_i(i) {}
271
272
0
        reference operator*() const {
273
0
            return m_i == 0 ? m_arc.p0 : (m_i == 1 ? m_arc.p1 : m_arc.p2);
274
0
        }
275
276
0
        Iterator& operator++() {
277
0
            m_i++;
278
0
            return *this;
279
0
        }
280
281
0
        Iterator operator++(int) {
282
0
            Iterator ret = *this;
283
0
            m_i++;
284
0
            return ret;
285
0
        }
286
287
0
        bool operator==(const Iterator& other) const {
288
0
            return m_i == other.m_i;
289
0
        }
290
291
0
        bool operator!=(const Iterator& other) const {
292
0
            return !(*this == other);
293
0
        }
294
295
    private:
296
        const CircularArc& m_arc;
297
        int m_i;
298
299
    };
300
301
0
    Iterator begin() const {
302
0
        return Iterator(*this, 0);
303
0
    }
304
305
0
    Iterator end() const {
306
0
        return Iterator(*this, 3);
307
0
    }
308
309
private:
310
    mutable CoordinateXY m_center;
311
    mutable double m_radius;
312
    mutable int m_orientation;
313
    mutable bool m_center_known = false;
314
    mutable bool m_radius_known = false;
315
    mutable bool m_orientation_known = false;
316
};
317
318
}
319
}