Coverage Report

Created: 2026-08-13 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/simplify/RingHull.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2022 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
#include <geos/simplify/RingHull.h>
16
#include <geos/simplify/LinkedRing.h>
17
#include <geos/simplify/RingHullIndex.h>
18
19
#include <geos/algorithm/Orientation.h>
20
#include <geos/geom/Envelope.h>
21
#include <geos/geom/Coordinate.h>
22
#include <geos/geom/CoordinateSequence.h>
23
#include <geos/geom/GeometryFactory.h>
24
#include <geos/geom/LineString.h>
25
#include <geos/geom/LinearRing.h>
26
#include <geos/geom/Polygon.h>
27
#include <geos/geom/Triangle.h>
28
#include <geos/index/VertexSequencePackedRtree.h>
29
#include <geos/util.h>
30
31
using geos::algorithm::Orientation;
32
using geos::geom::Envelope;
33
using geos::geom::Coordinate;
34
using geos::geom::CoordinateSequence;
35
using geos::geom::GeometryFactory;
36
using geos::geom::LineString;
37
using geos::geom::LinearRing;
38
using geos::geom::Polygon;
39
using geos::geom::Triangle;
40
using geos::index::VertexSequencePackedRtree;
41
42
namespace geos {
43
namespace simplify { // geos.simplify
44
45
46
47
/*
48
* Creates a new instance.
49
*
50
* @param ring the ring vertices to process
51
* @param isOuter whether the hull is outer or inner
52
*/
53
RingHull::RingHull(const LinearRing* p_ring, bool p_isOuter)
54
0
    : inputRing(p_ring)
55
0
    , vertex(inputRing->getCoordinates())
56
0
{
57
0
    init(*vertex, p_isOuter);
58
0
}
59
60
/* public */
61
void
62
RingHull::setMinVertexNum(std::size_t minVertexNum)
63
0
{
64
0
    targetVertexNum = static_cast<double>(minVertexNum);
65
0
}
66
67
/* public */
68
void
69
RingHull::setMaxAreaDelta(double maxAreaDelta)
70
0
{
71
0
    targetAreaDelta = maxAreaDelta;
72
0
}
73
74
/* public */
75
const Envelope*
76
RingHull::getEnvelope() const
77
0
{
78
0
    return inputRing->getEnvelopeInternal();
79
0
}
80
81
/* public */
82
std::unique_ptr<LinearRing>
83
RingHull::getHull(RingHullIndex& hullIndex)
84
0
{
85
0
    compute(hullIndex);
86
0
    std::unique_ptr<CoordinateSequence> hullPts = vertexRing->getCoordinates();
87
    // std::vector<Coordinate> hullPts;
88
    // std::copy(vrCoords.begin(), vrCoords.end(), hullPts.back_inserter());
89
0
    return inputRing->getFactory()->createLinearRing(std::move(hullPts));
90
0
}
91
92
/* private */
93
void
94
RingHull::init(CoordinateSequence& ring, bool isOuter)
95
0
{
96
    /**
97
     * Ensure ring is oriented according to outer/inner:
98
     * - outer, CW
99
     * - inner: CCW
100
     */
101
0
    bool orientCW = isOuter;
102
0
    if (orientCW == Orientation::isCCW(inputRing->getCoordinatesRO()))
103
0
    {
104
0
        ring.reverse();
105
0
    }
106
107
0
    vertexRing.reset(new LinkedRing(ring));
108
0
    vertexIndex.reset(new VertexSequencePackedRtree(ring));
109
110
    //-- remove duplicate final vertex
111
0
    vertexIndex->remove(ring.size() - 1);
112
113
0
    for (std::size_t i = 0; i < vertexRing->size(); i++) {
114
0
        addCorner(i, cornerQueue);
115
0
    }
116
0
}
117
118
119
/* private */
120
void
121
RingHull::addCorner(std::size_t i, Corner::PriorityQueue& queue)
122
0
{
123
    //-- convex corners are left untouched
124
0
    if (isConvex(*vertexRing, i))
125
0
      return;
126
    //-- corner is concave or flat - both can be removed
127
0
    Corner corner (i,
128
0
        vertexRing->prev(i),
129
0
        vertexRing->next(i),
130
0
        area(*vertexRing, i));
131
0
    queue.push(corner);
132
0
}
133
134
/* public static */
135
bool
136
RingHull::isConvex(const LinkedRing& vertexRing, std::size_t index)
137
0
{
138
0
    const Coordinate& pp = vertexRing.prevCoordinate(index);
139
0
    const Coordinate& p = vertexRing.getCoordinate(index);
140
0
    const Coordinate& pn = vertexRing.nextCoordinate(index);
141
0
    return Orientation::CLOCKWISE == Orientation::index(pp, p, pn);
142
0
}
143
144
/* public static */
145
double
146
RingHull::area(const LinkedRing& vertexRing, std::size_t index)
147
0
{
148
0
    const Coordinate& pp = vertexRing.prevCoordinate(index);
149
0
    const Coordinate& p = vertexRing.getCoordinate(index);
150
0
    const Coordinate& pn = vertexRing.nextCoordinate(index);
151
0
    return Triangle::area(pp, p, pn);
152
0
}
153
154
/* public */
155
void
156
RingHull::compute(RingHullIndex& hullIndex)
157
0
{
158
0
    while (! cornerQueue.empty()
159
0
        && vertexRing->size() > 3)
160
0
    {
161
0
        Corner corner = cornerQueue.top();
162
0
        cornerQueue.pop();
163
        //-- a corner may no longer be valid due to removal of adjacent corners
164
0
        if (corner.isRemoved(*vertexRing))
165
0
            continue;
166
0
        if (isAtTarget(corner)) {
167
0
            return;
168
0
        }
169
        //System.out.println(corner.toLineString(vertexList));
170
        /**
171
        * Corner is concave or flat - remove it if possible.
172
        */
173
0
        if (isRemovable(corner, hullIndex)) {
174
0
            removeCorner(corner, cornerQueue);
175
0
        }
176
0
    }
177
0
}
178
179
/* private */
180
bool
181
RingHull::isAtTarget(const Corner& corner)
182
0
{
183
0
    if (targetVertexNum >= 0) {
184
0
        double dVertexRingSize = static_cast<double>(vertexRing->size());
185
0
        return dVertexRingSize < targetVertexNum;
186
0
    }
187
0
    if (targetAreaDelta >= 0) {
188
        //-- include candidate corder to avoid overshooting target
189
        // (important for very small target area deltas)
190
0
        return areaDelta + corner.getArea() > targetAreaDelta;
191
0
    }
192
    //-- no target set
193
0
    return true;
194
0
}
195
196
/**
197
* Removes a corner by removing the apex vertex from the ring.
198
* Two new corners are created with apexes
199
* at the other vertices of the corner
200
* (if they are non-convex and thus removable).
201
*
202
* @param corner the corner to remove
203
* @param cornerQueue the corner queue
204
*/
205
/* private */
206
void
207
RingHull::removeCorner(const Corner& corner, Corner::PriorityQueue& queue)
208
0
{
209
0
    std::size_t index = corner.getIndex();
210
0
    std::size_t prev = vertexRing->prev(index);
211
0
    std::size_t next = vertexRing->next(index);
212
0
    vertexRing->remove(index);
213
0
    vertexIndex->remove(index);
214
0
    areaDelta += corner.getArea();
215
216
    //-- potentially add the new corners created
217
0
    addCorner(prev, queue);
218
0
    addCorner(next, queue);
219
0
}
220
221
/* private */
222
bool
223
RingHull::isRemovable(const Corner& corner, const RingHullIndex& hullIndex) const
224
0
{
225
0
    Envelope cornerEnv;
226
0
    corner.envelope(*vertexRing, cornerEnv);
227
0
    if (hasIntersectingVertex(corner, cornerEnv, this))
228
0
        return false;
229
    //-- no other rings to check
230
0
    if (hullIndex.size() == 0)
231
0
        return true;
232
    //-- check other rings for intersections
233
0
    std::vector<const RingHull*> queryResult = hullIndex.query(cornerEnv);
234
0
    for (const RingHull* hull : queryResult) {
235
      //-- this hull was already checked above
236
0
        if (hull == this)
237
0
            continue;
238
0
        if (hasIntersectingVertex(corner, cornerEnv, hull))
239
0
            return false;
240
0
    }
241
0
    return true;
242
0
}
243
244
/**
245
* Tests if any vertices in a hull intersect the corner triangle.
246
* Uses the vertex spatial index for efficiency.
247
*
248
* @param corner the corner vertices
249
* @param cornerEnv the envelope of the corner
250
* @param hull the hull to test
251
* @return true if there is an intersecting vertex
252
*/
253
/* private */
254
bool
255
RingHull::hasIntersectingVertex(
256
    const Corner& corner,
257
    const Envelope& cornerEnv,
258
    const RingHull* hull) const
259
0
{
260
0
    std::vector<std::size_t> result;
261
0
    hull->query(cornerEnv, result);
262
0
    for (std::size_t index : result)
263
0
    {
264
        //-- skip vertices of corner
265
0
        if (hull == this && corner.isVertex(index))
266
0
            continue;
267
268
0
        const Coordinate& v = hull->getCoordinate(index);
269
        //--- does corner triangle contain vertex?
270
0
        if (corner.intersects(v, *vertexRing))
271
0
            return true;
272
0
    }
273
0
    return false;
274
0
}
275
276
/* private */
277
const Coordinate&
278
RingHull::getCoordinate(std::size_t index) const
279
0
{
280
0
    return vertexRing->getCoordinate(index);
281
0
}
282
283
284
/* private */
285
void
286
RingHull::query(
287
    const Envelope& cornerEnv,
288
    std::vector<std::size_t>& result) const
289
0
{
290
0
    vertexIndex->query(cornerEnv, result);
291
0
}
292
293
294
void
295
RingHull::queryHull(const Envelope& queryEnv, std::vector<Coordinate>& pts)
296
0
{
297
0
    std::vector<std::size_t> result;
298
0
    vertexIndex->query(queryEnv, result);
299
300
0
    for (std::size_t index : result) {
301
        //-- skip if already removed
302
0
        if (! vertexRing->hasCoordinate(index))
303
0
            continue;
304
0
        const Coordinate& v = vertexRing->getCoordinate(index);
305
0
        pts.push_back(v);
306
0
    }
307
0
}
308
309
/* public */
310
std::unique_ptr<Polygon>
311
RingHull::toGeometry() const
312
0
{
313
0
    auto fact = GeometryFactory::create();
314
0
    std::unique_ptr<CoordinateSequence> coords = vertexRing->getCoordinates();
315
    // std::vector<Coordinate> coordCopy;
316
    // std::copy(coords.begin(), coords.end(), coordCopy.back_inserter());
317
0
    return fact->createPolygon(fact->createLinearRing(std::move(coords)));
318
0
}
319
320
321
322
// ------- Corner ------------------------------------------
323
324
/* public */
325
bool
326
RingHull::Corner::isVertex(std::size_t p_index) const
327
0
{
328
0
    return p_index == index
329
0
        || p_index == prev
330
0
        || p_index == next;
331
0
}
332
333
/* public */
334
std::size_t
335
RingHull::Corner::getIndex() const
336
0
{
337
0
    return index;
338
0
}
339
340
/* public */
341
double
342
RingHull::Corner::getArea() const
343
0
{
344
0
    return area;
345
0
}
346
347
348
/* public */
349
void
350
RingHull::Corner::envelope(const LinkedRing& ring, Envelope& env) const
351
0
{
352
0
    const Coordinate& pp = ring.getCoordinate(prev);
353
0
    const Coordinate& p = ring.getCoordinate(index);
354
0
    const Coordinate& pn = ring.getCoordinate(next);
355
0
    env.init(pp, pn);
356
0
    env.expandToInclude(p);
357
0
    return;
358
0
}
359
360
/* public */
361
bool
362
RingHull::Corner::intersects(const Coordinate& v, const LinkedRing& ring) const
363
0
{
364
0
    const Coordinate& pp = ring.getCoordinate(prev);
365
0
    const Coordinate& p = ring.getCoordinate(index);
366
0
    const Coordinate& pn = ring.getCoordinate(next);
367
0
    return Triangle::intersects(pp, p, pn, v);
368
0
}
369
370
/* public */
371
bool
372
RingHull::Corner::isRemoved(const LinkedRing& ring) const
373
0
{
374
0
    return ring.prev(index) != prev
375
0
        || ring.next(index) != next;
376
0
}
377
378
/* public */
379
std::unique_ptr<LineString>
380
RingHull::Corner::toLineString(const LinkedRing& ring)
381
0
{
382
0
    auto coords = detail::make_unique<CoordinateSequence>();
383
0
    coords->add(ring.getCoordinate(prev));
384
0
    coords->add(ring.getCoordinate(index));
385
0
    coords->add(ring.getCoordinate(next));
386
0
    auto gfact = GeometryFactory::create();
387
0
    return gfact->createLineString(std::move(coords));
388
0
}
389
390
391
} // namespace geos.simplify
392
} // namespace geos