Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/algorithm/MinimumDiameter.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2023 Paul Ramsey <pramsey@cleverelephant.ca>
7
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
8
 * Copyright (C) 2005 Refractions Research Inc.
9
 *
10
 * This is free software; you can redistribute and/or modify it under
11
 * the terms of the GNU Lesser General Public Licence as published
12
 * by the Free Software Foundation.
13
 * See the COPYING file for more information.
14
 *
15
 **********************************************************************/
16
17
#include <geos/constants.h>
18
#include <geos/algorithm/MinimumDiameter.h>
19
#include <geos/algorithm/ConvexHull.h>
20
#include <geos/geom/Geometry.h>
21
#include <geos/geom/LineSegment.h>
22
#include <geos/geom/Polygon.h>
23
#include <geos/geom/Point.h>
24
#include <geos/geom/LineString.h>
25
#include <geos/geom/GeometryFactory.h>
26
#include <geos/geom/CoordinateSequence.h>
27
#include <geos/util.h>
28
29
#include <typeinfo>
30
#include <cmath> // for fabs()
31
32
using namespace geos::geom;
33
34
namespace geos {
35
namespace algorithm { // geos.algorithm
36
37
/**
38
 * Computes the minimum diameter of a Geometry.
39
 * The minimum diameter is defined to be the
40
 * width of the smallest band that
41
 * contains the geometry,
42
 * where a band is a strip of the plane defined
43
 * by two parallel lines.
44
 * This can be thought of as the smallest hole that the geometry can be
45
 * moved through, with a single rotation.
46
 *
47
 * The first step in the algorithm is computing the convex hull of the Geometry.
48
 * If the input Geometry is known to be convex, a hint can be supplied to
49
 * avoid this computation.
50
 *
51
 * @see ConvexHull
52
 *
53
 * @version 1.4
54
 */
55
56
/**
57
 * Compute a minimum diameter for a giver {@link Geometry}.
58
 *
59
 * @param newInputGeom a Geometry
60
 */
61
MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom)
62
0
{
63
0
    minWidthPt = Coordinate::getNull();
64
0
    minPtIndex = 0;
65
0
    minWidth = 0.0;
66
0
    inputGeom = newInputGeom;
67
0
    isConvex = false;
68
0
    convexHullPts = nullptr;
69
0
}
70
71
/**
72
 * Compute a minimum diameter for a giver Geometry,
73
 * with a hint if
74
 * the Geometry is convex
75
 * (e.g. a convex Polygon or LinearRing,
76
 * or a two-point LineString, or a Point).
77
 *
78
 * @param newInputGeom a Geometry which is convex
79
 * @param newIsConvex <code>true</code> if the input geometry is convex
80
 */
81
MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom, const bool newIsConvex)
82
0
{
83
0
    minWidthPt = Coordinate::getNull();
84
0
    minWidth = 0.0;
85
0
    inputGeom = newInputGeom;
86
0
    isConvex = newIsConvex;
87
0
    convexHullPts = nullptr;
88
0
}
89
90
/**
91
 * Gets the length of the minimum diameter of the input Geometry
92
 *
93
 * @return the length of the minimum diameter
94
 */
95
double
96
MinimumDiameter::getLength()
97
0
{
98
0
    computeMinimumDiameter();
99
0
    return minWidth;
100
0
}
101
102
/**
103
 * Gets the {@link Coordinate} forming one end of the minimum diameter
104
 *
105
 * @return a coordinate forming one end of the minimum diameter
106
 */
107
const Coordinate&
108
MinimumDiameter::getWidthCoordinate()
109
0
{
110
0
    computeMinimumDiameter();
111
0
    return minWidthPt;
112
0
}
113
114
/**
115
 * Gets the segment forming the base of the minimum diameter
116
 *
117
 * @return the segment forming the base of the minimum diameter
118
 */
119
std::unique_ptr<LineString>
120
MinimumDiameter::getSupportingSegment()
121
0
{
122
0
    computeMinimumDiameter();
123
0
    const GeometryFactory* fact = inputGeom->getFactory();
124
0
    return minBaseSeg.toGeometry(*fact);
125
0
}
126
127
/**
128
 * Gets a LineString which is a minimum diameter
129
 *
130
 * @return a LineString which is a minimum diameter
131
 */
132
std::unique_ptr<LineString>
133
MinimumDiameter::getDiameter()
134
0
{
135
0
    computeMinimumDiameter();
136
    // return empty linestring if no minimum width calculated
137
0
    if(minWidthPt.isNull()) {
138
0
        return std::unique_ptr<LineString>(inputGeom->getFactory()->createLineString());
139
0
    }
140
141
0
    Coordinate basePt;
142
0
    minBaseSeg.project(minWidthPt, basePt);
143
144
0
    auto cl = detail::make_unique<CoordinateSequence>(2u);
145
0
    cl->setAt(basePt, 0);
146
0
    cl->setAt(minWidthPt, 1);
147
0
    return inputGeom->getFactory()->createLineString(std::move(cl));
148
0
}
149
150
/* private */
151
void
152
MinimumDiameter::computeMinimumDiameter()
153
0
{
154
    // check if computation is cached
155
0
    if(!minWidthPt.isNull()) {
156
0
        return;
157
0
    }
158
0
    if(isConvex) {
159
0
        computeWidthConvex(inputGeom);
160
0
    }
161
0
    else {
162
0
        ConvexHull ch(inputGeom);
163
0
        std::unique_ptr<Geometry> convexGeom = ch.getConvexHull();
164
0
        computeWidthConvex(convexGeom.get());
165
0
    }
166
0
}
167
168
/* private */
169
void
170
MinimumDiameter::computeWidthConvex(const Geometry* geom)
171
0
{
172
    //System.out.println("Input = " + geom);
173
0
    if(typeid(*geom) == typeid(Polygon)) {
174
0
        const Polygon* p = dynamic_cast<const Polygon*>(geom);
175
0
        convexHullPts = p->getExteriorRing()->getCoordinates();
176
0
    }
177
0
    else {
178
0
        convexHullPts = geom->getCoordinates();
179
0
    }
180
181
    // special cases for lines or points or degenerate rings
182
0
    switch(convexHullPts->getSize()) {
183
0
    case 0:
184
0
        minWidth = 0.0;
185
0
        minWidthPt = Coordinate::getNull();
186
0
        break;
187
0
    case 1:
188
0
        minWidth = 0.0;
189
0
        minWidthPt = convexHullPts->getAt(0);
190
0
        minBaseSeg.p0 = convexHullPts->getAt(0);
191
0
        minBaseSeg.p1 = convexHullPts->getAt(0);
192
0
        break;
193
0
    case 2:
194
0
    case 3:
195
0
        minWidth = 0.0;
196
0
        minWidthPt = convexHullPts->getAt(0);
197
0
        minBaseSeg.p0 = convexHullPts->getAt(0);
198
0
        minBaseSeg.p1 = convexHullPts->getAt(1);
199
0
        break;
200
0
    default:
201
0
        computeConvexRingMinDiameter(convexHullPts.get());
202
0
    }
203
0
}
204
205
/**
206
 * Compute the width information for a ring of {@link Coordinate}s.
207
 * Leaves the width information in the instance variables.
208
 *
209
 * @param pts
210
 * @return
211
 */
212
void
213
MinimumDiameter::computeConvexRingMinDiameter(const CoordinateSequence* pts)
214
0
{
215
0
    minWidth = DoubleInfinity;
216
0
    unsigned int currMaxIndex = 1;
217
0
    LineSegment seg;
218
219
    // for each segment, find a vertex at max distance, and pick the minimum
220
0
    const std::size_t npts = pts->getSize();
221
0
    for(std::size_t i = 1; i < npts; ++i) {
222
0
        seg.p0 = pts->getAt(i - 1);
223
0
        seg.p1 = pts->getAt(i);
224
0
        currMaxIndex = findMaxPerpDistance(pts, &seg, currMaxIndex);
225
0
    }
226
0
}
227
228
unsigned int
229
MinimumDiameter::findMaxPerpDistance(const CoordinateSequence* pts,
230
                                     const LineSegment* seg, unsigned int startIndex)
231
0
{
232
0
    double maxPerpDistance = seg->distancePerpendicular(pts->getAt(startIndex));
233
0
    double nextPerpDistance = maxPerpDistance;
234
0
    unsigned int maxIndex = startIndex;
235
0
    unsigned int nextIndex = maxIndex;
236
0
    while(nextPerpDistance >= maxPerpDistance) {
237
0
        maxPerpDistance = nextPerpDistance;
238
0
        maxIndex = nextIndex;
239
0
        nextIndex = getNextIndex(pts, maxIndex);
240
0
        if (nextIndex == startIndex)
241
0
            break;
242
0
        nextPerpDistance = seg->distancePerpendicular(pts->getAt(nextIndex));
243
0
    }
244
245
    // found maximum width for this segment - update global min dist if appropriate
246
0
    if(maxPerpDistance < minWidth) {
247
0
        minPtIndex = maxIndex;
248
0
        minWidth = maxPerpDistance;
249
0
        minWidthPt = pts->getAt(minPtIndex);
250
0
        minBaseSeg = *seg;
251
0
    }
252
0
    return maxIndex;
253
0
}
254
255
unsigned int
256
MinimumDiameter::getNextIndex(const CoordinateSequence* pts,
257
                              unsigned int index)
258
0
{
259
0
    if(++index >= pts->getSize()) {
260
0
        index = 0;
261
0
    }
262
0
    return index;
263
0
}
264
265
std::unique_ptr<Geometry>
266
MinimumDiameter::getMinimumRectangle()
267
0
{
268
0
    computeMinimumDiameter();
269
270
0
    if(minWidthPt.isNull() || !convexHullPts) {
271
        //return empty polygon
272
0
        return std::unique_ptr<Geometry>(inputGeom->getFactory()->createPolygon());
273
0
    }
274
275
    // check if minimum rectangle is degenerate (a point or line segment)
276
0
    if(minWidth == 0.0) {
277
0
        if(minBaseSeg.p0.equals2D(minBaseSeg.p1)) {
278
0
            return std::unique_ptr<Geometry>(inputGeom->getFactory()->createPoint(minBaseSeg.p0));
279
0
        }
280
      //-- Min rectangle is a line. Use the diagonal of the extent
281
0
      return computeMaximumLine(convexHullPts.get(), inputGeom->getFactory());
282
0
    }
283
284
    // deltas for the base segment of the minimum diameter
285
0
    double dx = minBaseSeg.p1.x - minBaseSeg.p0.x;
286
0
    double dy = minBaseSeg.p1.y - minBaseSeg.p0.y;
287
288
0
    double minPara = DoubleInfinity;
289
0
    double maxPara = DoubleNegInfinity;
290
0
    double minPerp = DoubleInfinity;
291
0
    double maxPerp = DoubleNegInfinity;
292
293
    // compute maxima and minima of lines parallel and perpendicular to base segment
294
0
    std::size_t const n = convexHullPts->getSize();
295
0
    for(std::size_t i = 0; i < n; ++i) {
296
297
0
        double paraC = computeC(dx, dy, convexHullPts->getAt(i));
298
0
        if(paraC > maxPara) {
299
0
            maxPara = paraC;
300
0
        }
301
0
        if(paraC < minPara) {
302
0
            minPara = paraC;
303
0
        }
304
305
0
        double perpC = computeC(-dy, dx, convexHullPts->getAt(i));
306
0
        if(perpC > maxPerp) {
307
0
            maxPerp = perpC;
308
0
        }
309
0
        if(perpC < minPerp) {
310
0
            minPerp = perpC;
311
0
        }
312
0
    }
313
314
    // compute lines along edges of minimum rectangle
315
0
    LineSegment maxPerpLine = computeSegmentForLine(-dx, -dy, maxPerp);
316
0
    LineSegment minPerpLine = computeSegmentForLine(-dx, -dy, minPerp);
317
0
    LineSegment maxParaLine = computeSegmentForLine(-dy, dx, maxPara);
318
0
    LineSegment minParaLine = computeSegmentForLine(-dy, dx, minPara);
319
320
    // compute vertices of rectangle (where the para/perp max & min lines intersect)
321
0
    Coordinate p0 = maxParaLine.lineIntersection(maxPerpLine);
322
0
    Coordinate p1 = minParaLine.lineIntersection(maxPerpLine);
323
0
    Coordinate p2 = minParaLine.lineIntersection(minPerpLine);
324
0
    Coordinate p3 = maxParaLine.lineIntersection(minPerpLine);
325
326
0
    auto seq = detail::make_unique<CoordinateSequence>(5u, 2u);
327
0
    seq->setAt(p0, 0);
328
0
    seq->setAt(p1, 1);
329
0
    seq->setAt(p2, 2);
330
0
    seq->setAt(p3, 3);
331
0
    seq->setAt(p0, 4); // close
332
333
0
    std::unique_ptr<LinearRing> shell = inputGeom->getFactory()->createLinearRing(std::move(seq));
334
0
    return inputGeom->getFactory()->createPolygon(std::move(shell));
335
0
}
336
337
// private static
338
std::unique_ptr<Geometry>
339
MinimumDiameter::computeMaximumLine(const geom::CoordinateSequence* pts,
340
        const GeometryFactory* factory)
341
0
{
342
    //-- find max and min pts for X and Y
343
0
    Coordinate ptMinX = pts->getAt(0);
344
0
    Coordinate ptMaxX = pts->getAt(0);
345
0
    Coordinate ptMinY = pts->getAt(0);
346
0
    Coordinate ptMaxY = pts->getAt(0);
347
348
0
    std::size_t const n = pts->getSize();
349
0
    for(std::size_t i = 1; i < n; ++i) {
350
0
      const Coordinate& p = pts->getAt(i);
351
0
      if (p.x < ptMinX.x) ptMinX = p;
352
0
      if (p.x > ptMaxX.x) ptMaxX = p;
353
0
      if (p.y < ptMinY.y) ptMinY = p;
354
0
      if (p.y > ptMaxY.y) ptMaxY = p;
355
0
    }
356
0
    Coordinate p0 = ptMinX;
357
0
    Coordinate p1 = ptMaxX;
358
    //-- line is vertical - use Y pts
359
0
    if (p0.x == p1.x) {
360
0
      p0 = ptMinY;
361
0
      p1 = ptMaxY;
362
0
    }
363
364
0
    auto seq = detail::make_unique<CoordinateSequence>(2u, 2u);
365
0
    seq->setAt(p0, 0);
366
0
    seq->setAt(p1, 1);
367
368
0
    return factory->createLineString(std::move(seq));
369
0
}
370
371
double
372
MinimumDiameter::computeC(double a, double b, const Coordinate& p)
373
0
{
374
0
    return a * p.y - b * p.x;
375
0
}
376
377
LineSegment
378
MinimumDiameter::computeSegmentForLine(double a, double b, double c)
379
0
{
380
0
    Coordinate p0;
381
0
    Coordinate p1;
382
    /*
383
    * Line eqn is ax + by = c
384
    * Slope is a/b.
385
    * If slope is steep, use y values as the inputs
386
    */
387
0
    if(fabs(b) > fabs(a)) {
388
0
        p0 = Coordinate(0.0, c / b);
389
0
        p1 = Coordinate(1.0, c / b - a / b);
390
0
    }
391
0
    else {
392
0
        p0 = Coordinate(c / a, 0.0);
393
0
        p1 = Coordinate(c / a - b / a, 1.0);
394
0
    }
395
0
    return LineSegment(p0, p1);
396
0
}
397
398
399
std::unique_ptr<Geometry>
400
MinimumDiameter::getMinimumRectangle(Geometry* geom)
401
0
{
402
0
    MinimumDiameter md(geom);
403
0
    return md.getMinimumRectangle();
404
0
}
405
406
std::unique_ptr<Geometry>
407
MinimumDiameter::getMinimumDiameter(Geometry* geom)
408
0
{
409
0
    MinimumDiameter md(geom);
410
0
    return md.getDiameter();
411
0
}
412
413
} // namespace geos.algorithm
414
} // namespace geos