Coverage Report

Created: 2026-06-10 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/algorithm/InteriorPointArea.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2019 Martin Davis <mtnclimb@gmail.com>
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
 * Last port: algorithm/InteriorPointArea.java (JTS-1.17+)
16
 * https://github.com/locationtech/jts/commit/a140ca30cc51be4f65c950a30b0a8f51a6df75ba
17
 *
18
 **********************************************************************/
19
20
#include <geos/algorithm/InteriorPointArea.h>
21
#include <geos/geom/Coordinate.h>
22
#include <geos/geom/Geometry.h>
23
#include <geos/geom/GeometryCollection.h>
24
#include <geos/geom/Polygon.h>
25
#include <geos/geom/LinearRing.h>
26
#include <geos/geom/LineString.h>
27
#include <geos/geom/Envelope.h>
28
#include <geos/geom/GeometryFactory.h>
29
#include <geos/util/Interrupt.h>
30
31
#include <algorithm>
32
#include <vector>
33
#include <typeinfo>
34
#include <memory> // for unique_ptr
35
36
using namespace geos::geom;
37
38
namespace geos {
39
namespace algorithm { // geos.algorithm
40
41
// file statistics
42
namespace {
43
44
double
45
avg(double a, double b)
46
0
{
47
0
    return (a + b) / 2.0;
48
0
}
49
50
/**
51
 * Finds a safe scan line Y ordinate by projecting
52
 * the polygon segments
53
 * to the Y axis and finding the
54
 * Y-axis interval which contains the centre of the Y extent.
55
 * The centre of
56
 * this interval is returned as the scan line Y-ordinate.
57
 * <p>
58
 * Note that in the case of (degenerate, invalid)
59
 * zero-area polygons the computed Y value
60
 * may be equal to a vertex Y-ordinate.
61
 *
62
 * @author mdavis
63
 *
64
 */
65
class ScanLineYOrdinateFinder {
66
public:
67
    static double
68
    getScanLineY(const Polygon& poly)
69
0
    {
70
0
        ScanLineYOrdinateFinder finder(poly);
71
0
        return finder.getScanLineY();
72
0
    }
73
74
    ScanLineYOrdinateFinder(const Polygon& nPoly)
75
0
        : poly(nPoly)
76
0
    {
77
        // initialize using extremal values
78
0
        hiY = poly.getEnvelopeInternal()->getMaxY();
79
0
        loY = poly.getEnvelopeInternal()->getMinY();
80
0
        centreY = avg(loY, hiY);
81
0
    }
82
83
    double
84
    getScanLineY()
85
0
    {
86
0
        process(*poly.getExteriorRing());
87
0
        for (std::size_t i = 0; i < poly.getNumInteriorRing(); i++) {
88
0
            process(*poly.getInteriorRingN(i));
89
0
        }
90
0
        double bisectY = avg(hiY, loY);
91
0
        return bisectY;
92
0
    }
93
94
private:
95
    const Polygon& poly;
96
97
    double centreY;
98
    double hiY;
99
    double loY;
100
101
    void
102
    process(const LineString& line)
103
0
    {
104
0
        const CoordinateSequence* seq = line.getCoordinatesRO();
105
0
        for (std::size_t i = 0, s = seq->size(); i < s; i++) {
106
0
            double y = seq->getY(i);
107
0
            updateInterval(y);
108
0
        }
109
0
    }
110
111
    void
112
    updateInterval(double y)
113
0
    {
114
0
        if (y <= centreY) {
115
0
            if (y > loY) {
116
0
                loY = y;
117
0
            }
118
0
        }
119
0
        else {
120
0
            if (y < hiY) {
121
0
                hiY = y;
122
0
            }
123
0
        }
124
0
    }
125
};
126
127
class InteriorPointPolygon {
128
public:
129
    InteriorPointPolygon(const Polygon& poly)
130
0
        : polygon(poly)
131
0
    {
132
0
        interiorPointY = ScanLineYOrdinateFinder::getScanLineY(polygon);
133
0
    }
134
135
    bool
136
    getInteriorPoint(CoordinateXY& ret) const
137
0
    {
138
0
        ret = interiorPoint;
139
0
        return true;
140
0
    }
141
142
    double getWidth() const
143
0
    {
144
0
        return interiorSectionWidth;
145
0
    }
146
147
    void
148
    process()
149
0
    {
150
0
        std::vector<double> crossings;
151
152
        /*
153
         * This results in returning a null Coordinate
154
         */
155
0
        if (polygon.isEmpty()) return;
156
        /*
157
         * set default interior point in case polygon has zero area
158
         */
159
0
        interiorPoint = *polygon.getCoordinate();
160
161
0
        const LinearRing* shell = polygon.getExteriorRing();
162
0
        scanRing(*shell, crossings);
163
0
        for (std::size_t i = 0; i < polygon.getNumInteriorRing(); i++) {
164
0
            const LinearRing* hole = polygon.getInteriorRingN(i);
165
0
            scanRing(*hole, crossings);
166
0
        }
167
0
        findBestMidpoint(crossings);
168
0
    }
169
170
private:
171
    const Polygon& polygon;
172
    double interiorPointY;
173
    double interiorSectionWidth = 0.0;
174
    CoordinateXY interiorPoint;
175
176
    void scanRing(const LinearRing& ring, std::vector<double>& crossings)
177
0
    {
178
        // skip rings which don't cross scan line
179
0
        if (! intersectsHorizontalLine(ring.getEnvelopeInternal(), interiorPointY))
180
0
            return;
181
182
0
        const CoordinateSequence* seq = ring.getCoordinatesRO();
183
0
        for (std::size_t i = 1; i < seq->size(); i++) {
184
0
            const CoordinateXY& ptPrev = seq->getAt<CoordinateXY>(i - 1);
185
0
            const CoordinateXY& pt = seq->getAt<CoordinateXY>(i);
186
0
            addEdgeCrossing(ptPrev, pt, interiorPointY, crossings);
187
0
        }
188
0
    }
189
190
    static void addEdgeCrossing(const CoordinateXY& p0, const CoordinateXY& p1, double scanY, std::vector<double>& crossings)
191
0
    {
192
        // skip non-crossing segments
193
0
        if (!intersectsHorizontalLine(p0, p1, scanY))
194
0
            return;
195
0
        if (! isEdgeCrossingCounted(p0, p1, scanY))
196
0
            return;
197
198
        // edge intersects scan line, so add a crossing
199
0
        double xInt = intersection(p0, p1, scanY);
200
0
        crossings.push_back(xInt);
201
0
    }
202
203
    void findBestMidpoint(std::vector<double>& crossings)
204
0
    {
205
        // zero-area polygons will have no crossings
206
0
        if (crossings.empty()) return;
207
208
        //Assert.isTrue(0 == crossings.size() % 2, "Interior Point robustness failure: odd number of scanline crossings");
209
210
0
        sort(crossings.begin(), crossings.end());
211
        /*
212
         * Entries in crossings list are expected to occur in pairs representing a
213
         * section of the scan line interior to the polygon (which may be zero-length)
214
        */
215
0
        for (std::size_t i = 0; i < crossings.size(); i += 2) {
216
0
            double x1 = crossings[i];
217
            // crossings count must be even so this should be safe
218
0
            double x2 = crossings[i + 1];
219
220
0
            double width = x2 - x1;
221
0
            if (width > interiorSectionWidth) {
222
0
                interiorSectionWidth = width;
223
0
                double interiorPointX = avg(x1, x2);
224
0
                interiorPoint = Coordinate(interiorPointX, interiorPointY);
225
0
            }
226
0
        }
227
0
    }
228
229
    static bool
230
    isEdgeCrossingCounted(const CoordinateXY& p0, const CoordinateXY& p1, double scanY)
231
0
    {
232
        // skip horizontal lines
233
0
        if (p0.y == p1.y)
234
0
            return false;
235
        // handle cases where vertices lie on scan-line
236
        // downward segment does not include start point
237
0
        if (p0.y == scanY && p1.y < scanY)
238
0
            return false;
239
        // upward segment does not include endpoint
240
0
        if (p1.y == scanY && p0.y < scanY)
241
0
            return false;
242
0
        return true;
243
0
    }
244
245
    static double
246
    intersection(const CoordinateXY& p0, const CoordinateXY& p1, double Y)
247
0
    {
248
0
        double x0 = p0.x;
249
0
        double x1 = p1.x;
250
251
0
        if (x0 == x1)
252
0
            return x0;
253
254
        // Assert: segDX is non-zero, due to previous equality test
255
0
        double segDX = x1 - x0;
256
0
        double segDY = p1.y - p0.y;
257
0
        double m = segDY / segDX;
258
0
        double x = x0 + ((Y - p0.y) / m);
259
0
        return x;
260
0
    }
261
262
    static bool
263
    intersectsHorizontalLine(const Envelope* env, double y)
264
0
    {
265
0
        if (y < env->getMinY())
266
0
            return false;
267
0
        if (y > env->getMaxY())
268
0
            return false;
269
0
        return true;
270
0
    }
271
272
    static bool
273
    intersectsHorizontalLine(const CoordinateXY& p0, const CoordinateXY& p1, double y)
274
0
    {
275
        // both ends above?
276
0
        if (p0.y > y && p1.y > y)
277
0
            return false;
278
        // both ends below?
279
0
        if (p0.y < y && p1.y < y)
280
0
            return false;
281
        // segment must intersect line
282
0
        return true;
283
0
    }
284
};
285
286
} // anonymous namespace
287
288
InteriorPointArea::InteriorPointArea(const Geometry* g)
289
0
{
290
0
    maxWidth = -1;
291
0
    process(g);
292
0
}
293
294
bool
295
InteriorPointArea::getInteriorPoint(Coordinate& ret) const
296
0
{
297
    // GEOS-specific code
298
0
    if (maxWidth < 0)
299
0
        return false;
300
301
0
    ret = interiorPoint;
302
0
    return true;
303
0
}
304
305
/*private*/
306
void
307
InteriorPointArea::process(const Geometry* geom)
308
0
{
309
0
    if (geom->isEmpty())
310
0
        return;
311
312
0
    const Polygon* poly = dynamic_cast<const Polygon*>(geom);
313
0
    if (poly) {
314
0
        processPolygon(poly);
315
0
        return;
316
0
    }
317
318
0
    const GeometryCollection* gc = dynamic_cast<const GeometryCollection*>(geom);
319
0
    if (gc) {
320
0
        for (std::size_t i = 0, n = gc->getNumGeometries(); i < n; i++) {
321
0
            process(gc->getGeometryN(i));
322
0
            GEOS_CHECK_FOR_INTERRUPTS();
323
0
        }
324
0
    }
325
0
}
326
327
/*private*/
328
void
329
InteriorPointArea::processPolygon(const Polygon* polygon)
330
0
{
331
0
    InteriorPointPolygon intPtPoly(*polygon);
332
0
    intPtPoly.process();
333
0
    double width = intPtPoly.getWidth();
334
0
    if (width > maxWidth) {
335
0
        maxWidth = width;
336
0
        intPtPoly.getInteriorPoint(interiorPoint);
337
0
    }
338
0
}
339
340
} // namespace geos.algorithm
341
} // namespace geos