Coverage Report

Created: 2025-10-12 06:49

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