Coverage Report

Created: 2025-11-24 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}