Coverage Report

Created: 2025-03-15 06:58

/src/geos/src/simplify/PolygonHullSimplifier.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) 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/PolygonHullSimplifier.h>
16
#include <geos/simplify/RingHull.h>
17
#include <geos/simplify/RingHullIndex.h>
18
19
#include <geos/algorithm/Area.h>
20
#include <geos/geom/Geometry.h>
21
#include <geos/geom/GeometryFactory.h>
22
#include <geos/geom/LinearRing.h>
23
#include <geos/geom/Polygon.h>
24
#include <geos/geom/MultiPolygon.h>
25
#include <geos/util/math.h>
26
#include <geos/util/IllegalArgumentException.h>
27
28
29
using geos::algorithm::Area;
30
using geos::geom::Geometry;
31
using geos::geom::GeometryFactory;
32
using geos::geom::LinearRing;
33
using geos::geom::Polygon;
34
using geos::geom::MultiPolygon;
35
36
37
namespace geos {
38
namespace simplify { // geos.simplify
39
40
41
/* public static */
42
std::unique_ptr<Geometry>
43
PolygonHullSimplifier::hull(const Geometry* geom, bool bOuter, double vertexNumFraction)
44
0
{
45
0
    PolygonHullSimplifier hull(geom, bOuter);
46
0
    hull.setVertexNumFraction(std::abs(vertexNumFraction));
47
0
    return hull.getResult();
48
0
}
49
50
51
/* public static */
52
std::unique_ptr<Geometry>
53
PolygonHullSimplifier::hullByAreaDelta(const Geometry* geom, bool bOuter, double areaDeltaRatio)
54
0
{
55
0
    PolygonHullSimplifier hull(geom, bOuter);
56
0
    hull.setAreaDeltaRatio(std::abs(areaDeltaRatio));
57
0
    return hull.getResult();
58
0
}
59
60
61
/* public */
62
void
63
PolygonHullSimplifier::setVertexNumFraction(double p_vertexNumFraction)
64
0
{
65
0
    vertexNumFraction = util::clamp(p_vertexNumFraction, 0.0, 1.0);
66
0
}
67
68
69
/* public */
70
void
71
PolygonHullSimplifier::setAreaDeltaRatio(double p_areaDeltaRatio)
72
0
{
73
0
    areaDeltaRatio = p_areaDeltaRatio;
74
0
}
75
76
77
/* public */
78
std::unique_ptr<Geometry>
79
PolygonHullSimplifier::getResult()
80
0
{
81
    //-- handle trivial parameter values
82
0
    if (vertexNumFraction == 1 || areaDeltaRatio == 0) {
83
0
        return inputGeom->clone();
84
0
    }
85
86
0
    if (inputGeom->getGeometryTypeId() == geom::GEOS_MULTIPOLYGON) {
87
        /**
88
        * Only outer hulls where there is more than one polygon
89
        * can potentially overlap.
90
        * Shell outer hulls could overlap adjacent shell hulls
91
        * or hole hulls surrounding them;
92
        * hole outer hulls could overlap contained shell hulls.
93
        */
94
0
        bool isOverlapPossible = isOuter && (inputGeom->getNumGeometries() > 1);
95
0
        if (isOverlapPossible) {
96
0
            return computeMultiPolygonAll(static_cast<const MultiPolygon*>(inputGeom));
97
0
        }
98
0
        else {
99
0
            return computeMultiPolygonEach(static_cast<const MultiPolygon*>(inputGeom));
100
0
        }
101
0
    }
102
0
    else if (inputGeom->getGeometryTypeId() == geom::GEOS_POLYGON) {
103
0
        return computePolygon(static_cast<const Polygon*>(inputGeom));
104
0
    }
105
0
    throw util::IllegalArgumentException("Input geometry must be polygonal");
106
0
}
107
108
109
/* private */
110
std::unique_ptr<Geometry>
111
PolygonHullSimplifier::computeMultiPolygonAll(const MultiPolygon* multiPoly)
112
0
{
113
0
    RingHullIndex hullIndex;
114
0
    std::size_t nPoly = multiPoly->getNumGeometries();
115
0
    std::vector<std::vector<RingHull*>> polyHulls;
116
117
    //TODO: investigate if reordering input elements improves result
118
119
    //-- prepare element polygon hulls and index
120
0
    for (std::size_t i = 0 ; i < nPoly; i++) {
121
0
        const Polygon* poly = multiPoly->getGeometryN(i);
122
0
        std::vector<RingHull*> ringHulls = initPolygon(poly, hullIndex);
123
0
        polyHulls.push_back(ringHulls);
124
0
    }
125
126
    //-- compute hull polygons
127
0
    std::vector<std::unique_ptr<Polygon>> polys;
128
0
    for (std::size_t i = 0; i < nPoly; i++) {
129
0
        const Polygon* poly = multiPoly->getGeometryN(i);
130
0
        std::unique_ptr<Polygon> polyHull = polygonHull(poly, polyHulls[i], hullIndex);
131
0
        polys.emplace_back(polyHull.release());
132
0
    }
133
0
    return geomFactory->createMultiPolygon(std::move(polys));
134
0
}
135
136
137
/* private */
138
std::unique_ptr<Geometry>
139
PolygonHullSimplifier::computeMultiPolygonEach(const MultiPolygon* multiPoly)
140
0
{
141
0
    std::vector<std::unique_ptr<Polygon>> polys;
142
0
    for (std::size_t i = 0 ; i < multiPoly->getNumGeometries(); i++) {
143
0
        const Polygon* poly = multiPoly->getGeometryN(i);
144
0
        std::unique_ptr<Polygon> polyHull = computePolygon(poly);
145
0
        polys.emplace_back(polyHull.release());
146
0
    }
147
0
    return geomFactory->createMultiPolygon(std::move(polys));
148
0
}
149
150
151
/* private */
152
std::unique_ptr<Polygon>
153
PolygonHullSimplifier::computePolygon(const Polygon* poly)
154
0
{
155
0
    RingHullIndex hullIndex;
156
    /**
157
     * For a single polygon overlaps are only possible for inner hulls
158
     * and where holes are present.
159
     */
160
0
    bool isOverlapPossible = ! isOuter && (poly->getNumInteriorRing() > 0);
161
0
    hullIndex.enabled(isOverlapPossible);
162
163
0
    std::vector<RingHull*> inHulls = initPolygon(poly, hullIndex);
164
0
    std::unique_ptr<Polygon> polyHull = polygonHull(poly, inHulls, hullIndex);
165
0
    return polyHull;
166
0
}
167
168
169
/* private */
170
std::vector<RingHull*>
171
PolygonHullSimplifier::initPolygon(const Polygon* poly, RingHullIndex& hullIndex)
172
0
{
173
0
    std::vector<RingHull*> hulls;
174
0
    if (poly->isEmpty())
175
0
        return hulls;
176
177
0
    double areaTotal = 0.0;
178
0
    if (areaDeltaRatio >= 0) {
179
0
        areaTotal = ringArea(poly);
180
0
    }
181
0
    hulls.push_back(createRingHull(poly->getExteriorRing(), isOuter, areaTotal, hullIndex));
182
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
183
        //Assert: interior ring is not empty
184
0
        RingHull* ringHull = createRingHull(poly->getInteriorRingN(i), ! isOuter, areaTotal, hullIndex);
185
0
        hulls.push_back(ringHull);
186
0
    }
187
0
    return hulls;
188
0
}
189
190
191
/* private */
192
double
193
PolygonHullSimplifier::ringArea(const Polygon* poly) const
194
0
{
195
0
    double area = Area::ofRing(poly->getExteriorRing()->getCoordinatesRO());
196
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
197
0
        area += Area::ofRing(poly->getInteriorRingN(i)->getCoordinatesRO());
198
0
    }
199
0
    return area;
200
0
}
201
202
203
/* private */
204
RingHull*
205
PolygonHullSimplifier::createRingHull(const LinearRing* ring, bool p_isOuter, double areaTotal, RingHullIndex& hullIndex)
206
0
{
207
    // Create the RingHull in our memory store and
208
    // then grab back the raw pointer.
209
0
    RingHull* ringHull = new RingHull(ring, p_isOuter);
210
0
    ringStore.emplace_back(ringHull);
211
    // ringStore.emplace_back(new RingHull(ring, p_isOuter));
212
    // RingHull* ringHull = ringStore.back().get();
213
214
0
    double dNumPoints = static_cast<double>(ring->getNumPoints());
215
0
    if (vertexNumFraction >= 0) {
216
0
        std::size_t targetVertexCount = static_cast<std::size_t>(
217
0
            std::ceil(vertexNumFraction * (dNumPoints - 1)));
218
0
        ringHull->setMinVertexNum(targetVertexCount);
219
0
    }
220
0
    else if (areaDeltaRatio >= 0) {
221
0
        double linearRingArea = Area::ofRing(ring->getCoordinatesRO());
222
0
        double linearRingWeight = linearRingArea / areaTotal;
223
0
        double maxAreaDelta = linearRingWeight * areaDeltaRatio * linearRingArea;
224
0
        ringHull->setMaxAreaDelta(maxAreaDelta);
225
0
    }
226
0
    if (hullIndex.enabled()) {
227
0
        hullIndex.add(ringHull);
228
0
    }
229
0
    return ringHull;
230
0
}
231
232
233
/* private */
234
std::unique_ptr<Polygon>
235
PolygonHullSimplifier::polygonHull(const Polygon* poly, std::vector<RingHull*>& ringHulls, RingHullIndex& hullIndex) const
236
0
{
237
0
    if (poly->isEmpty())
238
0
        return poly->clone();
239
240
0
    std::size_t ringIndex = 0;
241
0
    std::unique_ptr<LinearRing> shellHull = ringHulls[ringIndex++]->getHull(hullIndex);
242
0
    std::vector<std::unique_ptr<LinearRing>> holeHulls;
243
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
244
0
        std::unique_ptr<LinearRing> polyHull = ringHulls[ringIndex++]->getHull(hullIndex);
245
        //TODO: handle empty
246
0
        holeHulls.emplace_back(polyHull.release());
247
0
    }
248
0
    return geomFactory->createPolygon(
249
0
        std::move(shellHull),
250
0
        std::move(holeHulls));
251
0
}
252
253
254
} // namespace geos.simplify
255
} // namespace geos
256