Coverage Report

Created: 2025-08-30 06:52

/src/geos/src/algorithm/ConvexHull.cpp
Line
Count
Source (jump to first uncovered line)
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
7
 * Copyright (C) 2005-2006 Refractions Research Inc.
8
 * Copyright (C) 2001-2002 Vivid Solutions 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
 * Last port: algorithm/ConvexHull.java r407 (JTS-1.12+)
18
 *
19
 **********************************************************************/
20
21
#include <geos/algorithm/ConvexHull.h>
22
#include <geos/algorithm/PointLocation.h>
23
#include <geos/algorithm/Orientation.h>
24
#include <geos/geom/GeometryFactory.h>
25
#include <geos/geom/Coordinate.h>
26
#include <geos/geom/Point.h>
27
#include <geos/geom/Polygon.h>
28
#include <geos/geom/LineString.h>
29
#include <geos/geom/CoordinateSequence.h>
30
#include <geos/util.h>
31
#include <geos/util/Interrupt.h>
32
33
#include <typeinfo>
34
#include <algorithm>
35
36
37
using geos::geom::Geometry;
38
using geos::geom::GeometryFactory;
39
using geos::geom::Coordinate;
40
using geos::geom::Point;
41
using geos::geom::Polygon;
42
using geos::geom::LineString;
43
using geos::geom::LinearRing;
44
using geos::geom::CoordinateSequence;
45
46
47
namespace geos {
48
namespace algorithm { // geos.algorithm
49
50
namespace {
51
52
/**
53
 * This is the class used to sort in radial order.
54
 * It's defined here as it's a static one.
55
 */
56
class RadiallyLessThen {
57
58
private:
59
60
    const Coordinate* origin;
61
62
    static int
63
    polarCompare(const Coordinate* o, const Coordinate* p,
64
                 const Coordinate* q)
65
0
    {
66
0
        int orient = Orientation::index(*o, *p, *q);
67
68
0
        if(orient == Orientation::COUNTERCLOCKWISE) {
69
0
            return 1;
70
0
        }
71
0
        if(orient == Orientation::CLOCKWISE) {
72
0
            return -1;
73
0
        }
74
75
        /**
76
         * The points are collinear,
77
         * so compare based on distance from the origin.
78
         * The points p and q are >= to the origin,
79
         * so they lie in the closed half-plane above the origin.
80
         * If they are not in a horizontal line,
81
         * the Y ordinate can be tested to determine distance.
82
         * This is more robust than computing the distance explicitly.
83
         */
84
0
        if (p->y > q->y) return 1;
85
0
        if (p->y < q->y) return -1;
86
87
        /**
88
         * The points lie in a horizontal line, which should also contain the origin
89
         * (since they are collinear).
90
         * Also, they must be above the origin.
91
         * Use the X ordinate to determine distance.
92
         */
93
0
        if (p->x > q->x) return 1;
94
0
        if (p->x < q->x) return -1;
95
96
      // Assert: p = q
97
0
        return 0;
98
0
    }
99
100
public:
101
102
0
    RadiallyLessThen(const Coordinate* c): origin(c) {}
103
104
    bool
105
    operator()(const Coordinate* p1, const Coordinate* p2)
106
0
    {
107
0
        return (polarCompare(origin, p1, p2) == -1);
108
0
    }
109
110
};
111
112
} // unnamed namespace
113
114
/* private */
115
std::unique_ptr<CoordinateSequence>
116
ConvexHull::toCoordinateSequence(Coordinate::ConstVect& cv)
117
0
{
118
0
    auto cs = detail::make_unique<CoordinateSequence>(cv.size());
119
120
0
    for(std::size_t i = 0; i < cv.size(); ++i) {
121
0
        cs->setAt(*(cv[i]), i); // Coordinate copy
122
0
    }
123
124
0
    return cs;
125
0
}
126
127
/* private */
128
void
129
ConvexHull::computeInnerOctolateralPts(const Coordinate::ConstVect& p_inputPts,
130
                          Coordinate::ConstVect& pts)
131
0
{
132
    // Initialize all slots with first input coordinate
133
0
    pts = Coordinate::ConstVect(8, p_inputPts[0]);
134
135
0
    for(std::size_t i = 1, n = p_inputPts.size(); i < n; ++i) {
136
0
        if(p_inputPts[i]->x < pts[0]->x) {
137
0
            pts[0] = p_inputPts[i];
138
0
        }
139
0
        if(p_inputPts[i]->x - p_inputPts[i]->y < pts[1]->x - pts[1]->y) {
140
0
            pts[1] = p_inputPts[i];
141
0
        }
142
0
        if(p_inputPts[i]->y > pts[2]->y) {
143
0
            pts[2] = p_inputPts[i];
144
0
        }
145
0
        if(p_inputPts[i]->x + p_inputPts[i]->y > pts[3]->x + pts[3]->y) {
146
0
            pts[3] = p_inputPts[i];
147
0
        }
148
0
        if(p_inputPts[i]->x > pts[4]->x) {
149
0
            pts[4] = p_inputPts[i];
150
0
        }
151
0
        if(p_inputPts[i]->x - p_inputPts[i]->y > pts[5]->x - pts[5]->y) {
152
0
            pts[5] = p_inputPts[i];
153
0
        }
154
0
        if(p_inputPts[i]->y < pts[6]->y) {
155
0
            pts[6] = p_inputPts[i];
156
0
        }
157
0
        if(p_inputPts[i]->x + p_inputPts[i]->y < pts[7]->x + pts[7]->y) {
158
0
            pts[7] = p_inputPts[i];
159
0
        }
160
0
    }
161
162
0
}
163
164
165
/* private */
166
bool
167
ConvexHull::computeInnerOctolateralRing(const Coordinate::ConstVect& p_inputPts,
168
                           Coordinate::ConstVect& dest)
169
0
{
170
0
    computeInnerOctolateralPts(p_inputPts, dest);
171
172
    // Remove consecutive equal Coordinates
173
    // unique() returns an iterator to the end of the resulting
174
    // sequence, we erase from there to the end.
175
0
    dest.erase(std::unique(dest.begin(), dest.end()), dest.end());
176
177
    // points must all lie in a line
178
0
    if(dest.size() < 3) {
179
0
        return false;
180
0
    }
181
182
    // close ring
183
0
    dest.push_back(dest[0]);
184
185
0
    return true;
186
0
}
187
188
/* private */
189
void
190
ConvexHull::reduce(Coordinate::ConstVect& pts)
191
0
{
192
0
    Coordinate::ConstVect polyPts;
193
194
0
    if(! computeInnerOctolateralRing(pts, polyPts)) {
195
        // unable to compute interior polygon for some reason
196
0
        return;
197
0
    }
198
199
    // add points defining polygon
200
0
    Coordinate::ConstSet reducedSet;
201
0
    reducedSet.insert(polyPts.begin(), polyPts.end());
202
203
    /*
204
     * Add all unique points not in the interior poly.
205
     * CGAlgorithms.isPointInRing is not defined for points
206
     * actually on the ring, but this doesn't matter since
207
     * the points of the interior polygon are forced to be
208
     * in the reduced set.
209
     *
210
     * @@TIP: there should be a std::algo for this
211
     */
212
0
    for(std::size_t i = 0, n = pts.size(); i < n; ++i) {
213
0
        if(!PointLocation::isInRing(*(pts[i]), polyPts)) {
214
0
            reducedSet.insert(pts[i]);
215
0
        }
216
0
    }
217
218
0
    inputPts.assign(reducedSet.begin(), reducedSet.end());
219
220
0
    if(inputPts.size() < 3) {
221
0
        padArray3(inputPts);
222
0
    }
223
0
}
224
225
/* private */
226
void
227
ConvexHull::padArray3(geom::Coordinate::ConstVect& pts)
228
0
{
229
0
    for(std::size_t i = pts.size(); i < 3; ++i) {
230
0
        pts.push_back(pts[0]);
231
0
    }
232
0
}
233
234
std::unique_ptr<Geometry>
235
ConvexHull::getConvexHull()
236
0
{
237
0
    std::unique_ptr<Geometry> fewPointsGeom = createFewPointsResult();
238
0
    if (fewPointsGeom != nullptr)
239
0
        return fewPointsGeom;
240
241
0
    util::CoordinateArrayFilter filter(inputPts);
242
0
    inputGeom->apply_ro(&filter);
243
244
0
    std::size_t nInputPts = inputPts.size();
245
246
    // use heuristic to reduce points, if large
247
0
    if(nInputPts > TUNING_REDUCE_SIZE) {
248
0
        reduce(inputPts);
249
0
    }
250
0
    else {
251
0
        inputPts.clear();
252
0
        extractUnique(inputPts, NO_COORD_INDEX);
253
0
    }
254
255
0
    GEOS_CHECK_FOR_INTERRUPTS();
256
257
    // sort points for Graham scan.
258
0
    preSort(inputPts);
259
260
0
    GEOS_CHECK_FOR_INTERRUPTS();
261
262
    // Use Graham scan to find convex hull.
263
0
    Coordinate::ConstVect cHS;
264
0
    grahamScan(inputPts, cHS);
265
266
0
    GEOS_CHECK_FOR_INTERRUPTS();
267
268
0
    return lineOrPolygon(cHS);
269
0
}
270
271
/* private */
272
void
273
ConvexHull::preSort(Coordinate::ConstVect& pts)
274
0
{
275
    // find the lowest point in the set. If two or more points have
276
    // the same minimum y coordinate choose the one with the minimum x.
277
    // This focal point is put in array location pts[0].
278
0
    for(std::size_t i = 1, n = pts.size(); i < n; ++i) {
279
0
        const Coordinate* p0 = pts[0]; // this will change
280
0
        const Coordinate* pi = pts[i];
281
0
        if((pi->y < p0->y) || ((pi->y == p0->y) && (pi->x < p0->x))) {
282
0
            const Coordinate* t = p0;
283
0
            pts[0] = pi;
284
0
            pts[i] = t;
285
0
        }
286
0
    }
287
288
    // sort the points radially around the focal point.
289
0
    std::sort(pts.begin(), pts.end(), RadiallyLessThen(pts[0]));
290
0
}
291
292
/*private*/
293
void
294
ConvexHull::grahamScan(const Coordinate::ConstVect& c,
295
                       Coordinate::ConstVect& ps)
296
0
{
297
0
    ps.push_back(c[0]);
298
0
    ps.push_back(c[1]);
299
0
    ps.push_back(c[2]);
300
301
0
    for(std::size_t i = 3, n = c.size(); i < n; ++i) {
302
0
        const Coordinate* p = ps.back();
303
0
        ps.pop_back();
304
0
        while(!ps.empty() &&
305
0
                Orientation::index(
306
0
                    *(ps.back()), *p, *(c[i])) > 0) {
307
0
            p = ps.back();
308
0
            ps.pop_back();
309
0
        }
310
0
        ps.push_back(p);
311
0
        ps.push_back(c[i]);
312
0
    }
313
0
    ps.push_back(c[0]);
314
0
}
315
316
317
/*private*/
318
bool
319
ConvexHull::isBetween(const Coordinate& c1, const Coordinate& c2, const Coordinate& c3)
320
0
{
321
0
    if(Orientation::index(c1, c2, c3) != 0) {
322
0
        return false;
323
0
    }
324
0
    if(c1.x != c3.x) {
325
0
        if(c1.x <= c2.x && c2.x <= c3.x) {
326
0
            return true;
327
0
        }
328
0
        if(c3.x <= c2.x && c2.x <= c1.x) {
329
0
            return true;
330
0
        }
331
0
    }
332
0
    if(c1.y != c3.y) {
333
0
        if(c1.y <= c2.y && c2.y <= c3.y) {
334
0
            return true;
335
0
        }
336
0
        if(c3.y <= c2.y && c2.y <= c1.y) {
337
0
            return true;
338
0
        }
339
0
    }
340
0
    return false;
341
0
}
342
343
344
/* private */
345
std::unique_ptr<Geometry>
346
ConvexHull::lineOrPolygon(const Coordinate::ConstVect& input)
347
0
{
348
0
    Coordinate::ConstVect cleaned;
349
350
0
    cleanRing(input, cleaned);
351
352
0
    if(cleaned.size() == 3) { // shouldn't this be 2 ??
353
0
        cleaned.resize(2);
354
0
        auto cl1 = toCoordinateSequence(cleaned);
355
0
        return geomFactory->createLineString(std::move(cl1));
356
0
    }
357
0
    auto cl2 = toCoordinateSequence(cleaned);
358
0
    std::unique_ptr<LinearRing> linearRing = geomFactory->createLinearRing(std::move(cl2));
359
0
    return geomFactory->createPolygon(std::move(linearRing));
360
0
}
361
362
/*private*/
363
void
364
ConvexHull::cleanRing(const Coordinate::ConstVect& original,
365
                      Coordinate::ConstVect& cleaned)
366
0
{
367
0
    std::size_t npts = original.size();
368
369
0
    const Coordinate* last = original[npts - 1];
370
371
    //util::Assert::equals(*(original[0]), *last);
372
0
    assert(last);
373
0
    assert(original[0]->equals2D(*last));
374
375
0
    const Coordinate* prev = nullptr;
376
0
    for(std::size_t i = 0; i < npts - 1; ++i) {
377
0
        const Coordinate* curr = original[i];
378
0
        const Coordinate* next = original[i + 1];
379
380
        // skip consecutive equal coordinates
381
0
        if(curr->equals2D(*next)) {
382
0
            continue;
383
0
        }
384
385
0
        if(prev != nullptr &&  isBetween(*prev, *curr, *next)) {
386
0
            continue;
387
0
        }
388
389
0
        cleaned.push_back(curr);
390
0
        prev = curr;
391
0
    }
392
393
0
    cleaned.push_back(last);
394
395
0
}
396
397
398
/*
399
* Returns true if the extraction is stopped
400
* by the maxPts restriction. That is, if there
401
* are yet more points to be processed.
402
*/
403
/* private */
404
bool
405
ConvexHull::extractUnique(Coordinate::ConstVect& pts, std::size_t maxPts)
406
0
{
407
0
    util::UniqueCoordinateArrayFilter filter(pts, maxPts);
408
0
    inputGeom->apply_ro(&filter);
409
0
    return filter.isDone();
410
0
}
411
412
413
/* private */
414
std::unique_ptr<Geometry>
415
ConvexHull::createFewPointsResult()
416
0
{
417
0
    Coordinate::ConstVect uniquePts;
418
0
    bool ok = extractUnique(uniquePts, 2);
419
420
0
    if (ok) {
421
0
        return nullptr;
422
0
    }
423
424
0
    if(uniquePts.size() == 0) { // Return an empty geometry
425
0
        return geomFactory->createEmptyGeometry();
426
0
    }
427
0
    else if(uniquePts.size() == 1) { // Return a Point
428
        // Copy the Coordinate from the ConstVect
429
0
        return std::unique_ptr<Geometry>(geomFactory->createPoint(*(uniquePts[0])));
430
0
    }
431
0
    else { // Return a LineString
432
0
        auto cs = toCoordinateSequence(uniquePts);
433
0
        return geomFactory->createLineString(std::move(cs));
434
0
    }
435
0
}
436
437
438
} // namespace geos.algorithm
439
} // namespace geos