Coverage Report

Created: 2025-11-16 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/algorithm/construct/MaximumInscribedCircle.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2020 Paul Ramsey <pramsey@cleverelephant.ca>
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/construct/MaximumInscribedCircle.java
16
 * https://github.com/locationtech/jts/commit/f0b9a808bdf8a973de435f737e37b7a221e231cb
17
 *
18
 **********************************************************************/
19
20
#include <geos/algorithm/construct/MaximumInscribedCircle.h>
21
#include <geos/algorithm/construct/ExactMaxInscribedCircle.h>
22
#include <geos/geom/Coordinate.h>
23
#include <geos/geom/CoordinateSequence.h>
24
#include <geos/geom/Envelope.h>
25
#include <geos/geom/Geometry.h>
26
#include <geos/geom/GeometryFactory.h>
27
#include <geos/geom/LineString.h>
28
#include <geos/geom/Point.h>
29
#include <geos/geom/Polygon.h>
30
#include <geos/geom/MultiPolygon.h>
31
#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
32
#include <geos/operation/distance/IndexedFacetDistance.h>
33
#include <geos/util/Interrupt.h>
34
35
#include <typeinfo> // for dynamic_cast
36
#include <cassert>
37
38
using namespace geos::geom;
39
40
41
namespace geos {
42
namespace algorithm { // geos.algorithm
43
namespace construct { // geos.algorithm.construct
44
45
46
/* public */
47
MaximumInscribedCircle::MaximumInscribedCircle(const Geometry* polygonal, double p_tolerance)
48
0
    : inputGeom(polygonal)
49
0
    , inputGeomBoundary(polygonal->getBoundary())
50
0
    , tolerance(p_tolerance)
51
0
    , indexedDistance(inputGeomBoundary.get())
52
0
    , ptLocator(*polygonal)
53
0
    , factory(polygonal->getFactory())
54
0
    , done(false)
55
0
{
56
0
    if (!(typeid(*polygonal) == typeid(Polygon) ||
57
0
          typeid(*polygonal) == typeid(MultiPolygon))) {
58
0
        throw util::IllegalArgumentException("Input must be a Polygon or MultiPolygon");
59
0
    }
60
61
0
    if (polygonal->isEmpty()) {
62
0
        throw util::IllegalArgumentException("Empty input is not supported");
63
0
    }
64
0
}
65
66
67
/* public static */
68
std::unique_ptr<Point>
69
MaximumInscribedCircle::getCenter(const Geometry* polygonal, double tolerance)
70
0
{
71
0
    MaximumInscribedCircle mic(polygonal, tolerance);
72
0
    return mic.getCenter();
73
0
}
74
75
/* public static */
76
std::unique_ptr<LineString>
77
MaximumInscribedCircle::getRadiusLine(const Geometry* polygonal, double tolerance)
78
0
{
79
0
    MaximumInscribedCircle mic(polygonal, tolerance);
80
0
    return mic.getRadiusLine();
81
0
}
82
83
/* public static */
84
bool
85
MaximumInscribedCircle::isRadiusWithin(const Geometry* polygonal, double maxRadius)
86
0
{
87
0
    MaximumInscribedCircle mic(polygonal, -1);
88
0
    return mic.isRadiusWithin(maxRadius);
89
0
}
90
91
/* public static */
92
std::size_t
93
MaximumInscribedCircle::computeMaximumIterations(const Geometry* geom, double toleranceDist)
94
0
{
95
0
    double diam = geom->getEnvelopeInternal()->getDiameter();
96
0
    double ncells = diam / toleranceDist;
97
    //-- Using log of ncells allows control over number of iterations
98
0
    int factor = (int) std::log(ncells);
99
0
    if (factor < 1) factor = 1;
100
0
    return (std::size_t) (2000 + 2000 * factor);
101
0
}
102
103
/* public */
104
std::unique_ptr<Point>
105
MaximumInscribedCircle::getCenter()
106
0
{
107
0
    compute();
108
0
    return factory->createPoint(centerPt);
109
0
}
110
111
/* public */
112
std::unique_ptr<Point>
113
MaximumInscribedCircle::getRadiusPoint()
114
0
{
115
0
    compute();
116
0
    return factory->createPoint(radiusPt);
117
0
}
118
119
/* public */
120
std::unique_ptr<LineString>
121
MaximumInscribedCircle::getRadiusLine()
122
0
{
123
0
    compute();
124
125
0
    auto cl = detail::make_unique<CoordinateSequence>(2u);
126
0
    cl->setAt(centerPt, 0);
127
0
    cl->setAt(radiusPt, 1);
128
0
    return factory->createLineString(std::move(cl));
129
0
}
130
131
int INITIAL_GRID_SIDE = 25;
132
133
/* private */
134
void
135
MaximumInscribedCircle::createInitialGrid(const Envelope* env, Cell::CellQueue& cellQueue)
136
0
{
137
0
    if (!env->isFinite()) {
138
0
        throw util::GEOSException("Non-finite envelope encountered.");
139
0
    }
140
141
0
    double cellSize = std::max(env->getWidth(), env->getHeight());
142
0
    double hSide = cellSize / 2.0;
143
144
    // Collapsed geometries just end up using the centroid
145
    // as the answer and skip all the other machinery
146
0
    if (cellSize == 0) return;
147
148
0
    CoordinateXY c;
149
0
    env->centre(c);
150
0
    cellQueue.emplace(c.x, c.y, hSide, distanceToBoundary(c.x, c.y));
151
0
}
152
153
/* private */
154
double
155
MaximumInscribedCircle::distanceToBoundary(double x, double y)
156
0
{
157
0
    Coordinate coord(x, y);
158
0
    std::unique_ptr<Point> pt(factory->createPoint(coord));
159
0
    return distanceToBoundary(*pt.get());
160
0
}
161
162
/* private */
163
double
164
MaximumInscribedCircle::distanceToBoundary(const Point& pt)
165
0
{
166
0
    double dist = indexedDistance.distance(&pt);
167
    // double dist = inputGeomBoundary->distance(pt.get());
168
0
    bool isOutside = (Location::EXTERIOR == ptLocator.locate(pt.getCoordinate()));
169
0
    if (isOutside) return -dist;
170
0
    return dist;
171
0
}
172
173
/* private */
174
MaximumInscribedCircle::Cell
175
MaximumInscribedCircle::createInteriorPointCell(const Geometry* geom)
176
0
{
177
0
    std::unique_ptr<Point> p = geom->getInteriorPoint();
178
0
    Cell cell(p->getX(), p->getY(), 0, distanceToBoundary(*p.get()));
179
0
    return cell;
180
0
}
181
182
183
/* public */
184
bool
185
MaximumInscribedCircle::isRadiusWithin(double maxRadius)
186
0
{
187
0
    if (maxRadius < 0) {
188
0
        throw util::IllegalArgumentException("Radius length must be non-negative");
189
0
    }
190
    //-- handle 0 corner case, to provide maximum domain
191
0
    if (maxRadius == 0) {
192
0
        return false;
193
0
    }
194
0
    maximumRadius = maxRadius;
195
196
    /**
197
     * Check if envelope dimension is smaller than diameter
198
     */
199
0
    const Envelope* env = inputGeom->getEnvelopeInternal();
200
0
    double maxDiam = 2 * maximumRadius;
201
0
    if (env->getWidth() < maxDiam || env->getHeight() < maxDiam) {
202
0
        return true;
203
0
    }
204
205
0
    tolerance = maxRadius * MAX_RADIUS_FRACTION;
206
0
    compute();
207
0
    double radius = centerPt.distance(radiusPt);
208
0
    return radius <= maximumRadius;
209
0
}
210
211
212
/* private */
213
void
214
MaximumInscribedCircle::compute()
215
0
{
216
    // check if already computed
217
0
    if (done) return;
218
219
    /**
220
     * Handle flat geometries.
221
     */
222
0
    if (inputGeom->getArea() == 0.0) {
223
0
        const CoordinateXY* c = inputGeom->getCoordinate();
224
0
        createResult(*c, *c);
225
0
        return;
226
0
    }
227
228
    /**
229
     * Optimization for small simple convex polygons
230
     */
231
0
    if (ExactMaxInscribedCircle::isSupported(inputGeom)) {
232
0
        auto polygonal = static_cast<const Polygon*>(inputGeom);
233
0
        std::pair<CoordinateXY, CoordinateXY> centreRadius = ExactMaxInscribedCircle::computeRadius(polygonal);
234
0
        createResult(centreRadius.first, centreRadius.second);
235
0
        return;
236
0
    }
237
238
    //-- only needed for approximation
239
0
    if (tolerance <= 0) {
240
0
        throw util::IllegalArgumentException("Tolerance must be positive");
241
0
    }
242
243
0
    computeApproximation();
244
0
}
245
246
247
/* private */
248
void MaximumInscribedCircle::createResult(
249
    const CoordinateXY& c, const CoordinateXY& r)
250
0
{
251
0
    centerPt = c;
252
0
    radiusPt = r;
253
0
}
254
255
256
/* private */
257
void
258
MaximumInscribedCircle::computeApproximation()
259
0
{
260
    // Priority queue of cells, ordered by maximum distance from boundary
261
0
    Cell::CellQueue cellQueue;
262
263
0
    createInitialGrid(inputGeom->getEnvelopeInternal(), cellQueue);
264
265
    // use the area centroid as the initial candidate center point
266
0
    Cell farthestCell = createInteriorPointCell(inputGeom);
267
268
    /**
269
     * Carry out the branch-and-bound search
270
     * of the cell space
271
     */
272
0
    std::size_t maxIter = computeMaximumIterations(inputGeom, tolerance);
273
0
    std::size_t iterationCount = 0;
274
0
    while (!cellQueue.empty() && iterationCount < maxIter) {
275
        // pick the most promising cell from the queue
276
0
        Cell cell = cellQueue.top();
277
0
        cellQueue.pop();
278
//    std::cout << iterationCount << "] Dist: " << cell.getDistance() << "  size: " << cell.getHSize() << std::endl;
279
280
0
        if ((iterationCount++ % 1000) == 0) {
281
0
            GEOS_CHECK_FOR_INTERRUPTS();
282
0
        }
283
284
        //-- if cell must be closer than farthest, terminate since all remaining cells in queue are even closer.
285
0
        if (cell.getMaxDistance() < farthestCell.getDistance())
286
0
            break;
287
288
        // update the center cell if the candidate is further from the boundary
289
0
        if (cell.getDistance() > farthestCell.getDistance()) {
290
0
            farthestCell = cell;
291
0
        }
292
293
        //-- search termination when checking max radius predicate
294
0
        if (maximumRadius >= 0) {
295
            //-- found a inside point further than max radius
296
0
            if (farthestCell.getDistance() > maximumRadius)
297
0
                break;
298
        //-- no cells can have larger radius
299
0
        if (cell.getMaxDistance() < maximumRadius)
300
0
            break;
301
0
        }
302
303
        /**
304
        * Refine this cell if the potential distance improvement
305
        * is greater than the required tolerance.
306
        * Otherwise the cell is pruned (not investigated further),
307
        * since no point in it is further than
308
        * the current farthest distance.
309
        */
310
0
        double potentialIncrease = cell.getMaxDistance() - farthestCell.getDistance();
311
0
        if (potentialIncrease > tolerance) {
312
            // split the cell into four sub-cells
313
0
            double h2 = cell.getHSize() / 2;
314
0
            cellQueue.emplace(cell.getX()-h2, cell.getY()-h2, h2, distanceToBoundary(cell.getX()-h2, cell.getY()-h2));
315
0
            cellQueue.emplace(cell.getX()+h2, cell.getY()-h2, h2, distanceToBoundary(cell.getX()+h2, cell.getY()-h2));
316
0
            cellQueue.emplace(cell.getX()-h2, cell.getY()+h2, h2, distanceToBoundary(cell.getX()-h2, cell.getY()+h2));
317
0
            cellQueue.emplace(cell.getX()+h2, cell.getY()+h2, h2, distanceToBoundary(cell.getX()+h2, cell.getY()+h2));
318
0
        }
319
0
    }
320
321
    // the farthest cell is the best approximation to the MIC center
322
0
    Cell centerCell = farthestCell;
323
0
    centerPt.x = centerCell.getX();
324
0
    centerPt.y = centerCell.getY();
325
326
    // compute radius point
327
0
    std::unique_ptr<Point> centerPoint(factory->createPoint(centerPt));
328
0
    const auto& nearestPts = indexedDistance.nearestPoints(centerPoint.get());
329
0
    radiusPt = nearestPts->getAt(0);
330
331
    // flag computation
332
0
    done = true;
333
0
}
334
335
336
337
338
} // namespace geos.algorithm.construct
339
} // namespace geos.algorithm
340
} // namespace geos