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