/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 | | |