/src/geos/src/coverage/TPVWSimplifier.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 | | * Copyright (c) 2022 Martin Davis. |
8 | | * |
9 | | * This is free software; you can redistribute and/or modify it under |
10 | | * the terms of the GNU Lesser General Public Licence as published |
11 | | * by the Free Software Foundation. |
12 | | * See the COPYING file for more information. |
13 | | * |
14 | | **********************************************************************/ |
15 | | |
16 | | #include <geos/coverage/TPVWSimplifier.h> |
17 | | #include <geos/coverage/Corner.h> |
18 | | #include <geos/geom/Coordinate.h> |
19 | | #include <geos/geom/CoordinateSequence.h> |
20 | | #include <geos/geom/Envelope.h> |
21 | | #include <geos/geom/Geometry.h> |
22 | | #include <geos/geom/GeometryFactory.h> |
23 | | #include <geos/geom/LineString.h> |
24 | | #include <geos/geom/MultiLineString.h> |
25 | | |
26 | | #include <geos/simplify/LinkedLine.h> |
27 | | |
28 | | using geos::geom::Coordinate; |
29 | | using geos::geom::CoordinateSequence; |
30 | | using geos::geom::Envelope; |
31 | | using geos::geom::Geometry; |
32 | | using geos::geom::GeometryFactory; |
33 | | using geos::geom::LineString; |
34 | | using geos::geom::MultiLineString; |
35 | | using geos::simplify::LinkedLine; |
36 | | |
37 | | |
38 | | namespace geos { |
39 | | namespace coverage { // geos.coverage |
40 | | |
41 | | |
42 | | typedef TPVWSimplifier::Edge Edge; |
43 | | typedef TPVWSimplifier::EdgeIndex EdgeIndex; |
44 | | |
45 | | |
46 | | /* public static */ |
47 | | std::unique_ptr<MultiLineString> |
48 | | TPVWSimplifier::simplify( |
49 | | const MultiLineString* lines, |
50 | | double distanceTolerance) |
51 | 0 | { |
52 | 0 | TPVWSimplifier simp(lines, distanceTolerance); |
53 | 0 | std::unique_ptr<MultiLineString> result = simp.simplify(); |
54 | 0 | return result; |
55 | 0 | } |
56 | | |
57 | | |
58 | | /* public static */ |
59 | | std::unique_ptr<MultiLineString> |
60 | | TPVWSimplifier::simplify( |
61 | | const MultiLineString* p_lines, |
62 | | std::vector<bool>& p_freeRings, |
63 | | const MultiLineString* p_constraintLines, |
64 | | double distanceTolerance) |
65 | 0 | { |
66 | 0 | TPVWSimplifier simp(p_lines, distanceTolerance); |
67 | 0 | simp.setFreeRingIndices(p_freeRings); |
68 | 0 | simp.setConstraints(p_constraintLines); |
69 | 0 | std::unique_ptr<MultiLineString> result = simp.simplify(); |
70 | 0 | return result; |
71 | 0 | } |
72 | | |
73 | | |
74 | | /* public */ |
75 | | TPVWSimplifier::TPVWSimplifier( |
76 | | const MultiLineString* lines, |
77 | | double distanceTolerance) |
78 | 0 | : inputLines(lines) |
79 | 0 | , areaTolerance(distanceTolerance*distanceTolerance) |
80 | 0 | , geomFactory(inputLines->getFactory()) |
81 | 0 | , constraintLines(nullptr) |
82 | 0 | {} |
83 | | |
84 | | |
85 | | /* private */ |
86 | | void |
87 | | TPVWSimplifier::setConstraints(const MultiLineString* constraints) |
88 | 0 | { |
89 | 0 | constraintLines = constraints; |
90 | 0 | } |
91 | | |
92 | | /* public */ |
93 | | void |
94 | | TPVWSimplifier::setFreeRingIndices(std::vector<bool>& freeRing) |
95 | 0 | { |
96 | | //XXX Assert: bit set has same size as number of lines. |
97 | 0 | isFreeRing = freeRing; |
98 | 0 | } |
99 | | |
100 | | /* private */ |
101 | | std::unique_ptr<MultiLineString> |
102 | | TPVWSimplifier::simplify() |
103 | 0 | { |
104 | 0 | std::vector<bool> emptyList; |
105 | 0 | std::vector<Edge> edges = createEdges(inputLines, isFreeRing); |
106 | 0 | std::vector<Edge> constraintEdges = createEdges(constraintLines, emptyList); |
107 | |
|
108 | 0 | EdgeIndex edgeIndex; |
109 | 0 | edgeIndex.add(edges); |
110 | 0 | edgeIndex.add(constraintEdges); |
111 | |
|
112 | 0 | std::vector<std::unique_ptr<LineString>> result; |
113 | 0 | for (auto& edge : edges) { |
114 | 0 | std::unique_ptr<CoordinateSequence> ptsSimp = edge.simplify(edgeIndex); |
115 | 0 | auto ls = geomFactory->createLineString(std::move(ptsSimp)); |
116 | 0 | result.emplace_back(ls.release()); |
117 | 0 | } |
118 | 0 | return geomFactory->createMultiLineString(std::move(result)); |
119 | 0 | } |
120 | | |
121 | | /* private */ |
122 | | std::vector<Edge> |
123 | | TPVWSimplifier::createEdges( |
124 | | const MultiLineString* lines, |
125 | | std::vector<bool>& freeRing) |
126 | 0 | { |
127 | 0 | std::vector<Edge> edges; |
128 | |
|
129 | 0 | if (lines == nullptr) |
130 | 0 | return edges; |
131 | | |
132 | 0 | for (std::size_t i = 0; i < lines->getNumGeometries(); i++) { |
133 | 0 | const LineString* line = lines->getGeometryN(i); |
134 | 0 | bool isFree = freeRing.empty() ? false : freeRing[i]; |
135 | 0 | edges.emplace_back(line, isFree, areaTolerance); |
136 | 0 | } |
137 | 0 | return edges; |
138 | 0 | } |
139 | | |
140 | | /************************************************************************/ |
141 | | |
142 | | TPVWSimplifier::Edge::Edge(const LineString* p_inputLine, bool p_isFreeRing, double p_areaTolerance) |
143 | 0 | : areaTolerance(p_areaTolerance) |
144 | 0 | , isFreeRing(p_isFreeRing) |
145 | 0 | , envelope(p_inputLine->getEnvelopeInternal()) |
146 | 0 | , nbPts(p_inputLine->getNumPoints()) |
147 | 0 | , linkedLine(*(p_inputLine->getCoordinatesRO())) |
148 | 0 | , vertexIndex(*(p_inputLine->getCoordinatesRO())) |
149 | 0 | , minEdgeSize(p_inputLine->getCoordinatesRO()->isRing() ? 3 : 2) |
150 | 0 | { |
151 | | // linkedLine = new LinkedLine(pts); |
152 | | // minEdgeSize = linkedLine.isRing() ? 3 : 2; |
153 | | // vertexIndex = new VertexSequencePackedRtree(pts); |
154 | | //-- remove ring duplicate final vertex |
155 | 0 | if (linkedLine.isRing()) { |
156 | 0 | vertexIndex.remove(nbPts-1); |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | /* private */ |
161 | | const Coordinate& |
162 | | TPVWSimplifier::Edge::getCoordinate(std::size_t index) const |
163 | 0 | { |
164 | 0 | return linkedLine.getCoordinate(index); |
165 | 0 | } |
166 | | |
167 | | /* public */ |
168 | | const Envelope* |
169 | | TPVWSimplifier::Edge::getEnvelopeInternal() const |
170 | 0 | { |
171 | 0 | return envelope; |
172 | 0 | } |
173 | | |
174 | | /* public */ |
175 | | std::size_t |
176 | | TPVWSimplifier::Edge::size() const |
177 | 0 | { |
178 | 0 | return linkedLine.size(); |
179 | 0 | } |
180 | | |
181 | | /* private */ |
182 | | std::unique_ptr<CoordinateSequence> |
183 | | TPVWSimplifier::Edge::simplify(EdgeIndex& edgeIndex) |
184 | 0 | { |
185 | 0 | Corner::PriorityQueue cornerQueue; |
186 | 0 | createQueue(cornerQueue); |
187 | 0 | while (! cornerQueue.empty() && size() > minEdgeSize) { |
188 | | //Corner corner = cornerQueue.poll(); |
189 | 0 | Corner corner = cornerQueue.top(); |
190 | 0 | cornerQueue.pop(); |
191 | | |
192 | | //-- a corner may no longer be valid due to removal of adjacent corners |
193 | 0 | if (corner.isRemoved()) |
194 | 0 | continue; |
195 | | //System.out.println(corner.toLineString(edge)); |
196 | | //-- done when all small corners are removed |
197 | 0 | if (corner.getArea() > areaTolerance) |
198 | 0 | break; |
199 | 0 | if (isRemovable(corner, edgeIndex) ) { |
200 | 0 | removeCorner(corner, cornerQueue); |
201 | 0 | } |
202 | 0 | } |
203 | 0 | return linkedLine.getCoordinates(); |
204 | 0 | } |
205 | | |
206 | | /* private */ |
207 | | void |
208 | | TPVWSimplifier::Edge::createQueue(Corner::PriorityQueue& cornerQueue) |
209 | 0 | { |
210 | 0 | std::size_t minIndex = (linkedLine.isRing() && isFreeRing) ? 0 : 1; |
211 | 0 | std::size_t maxIndex = nbPts - 1; |
212 | 0 | for (std::size_t i = minIndex; i < maxIndex; i++) { |
213 | 0 | addCorner(i, cornerQueue); |
214 | 0 | } |
215 | 0 | return; |
216 | 0 | } |
217 | | |
218 | | /* private */ |
219 | | void |
220 | | TPVWSimplifier::Edge::addCorner( |
221 | | std::size_t i, |
222 | | Corner::PriorityQueue& cornerQueue) |
223 | 0 | { |
224 | 0 | if (isFreeRing || (i != 0 && i != nbPts-1)) { |
225 | 0 | Corner corner(&linkedLine, i); |
226 | 0 | if (corner.getArea() <= areaTolerance) { |
227 | 0 | cornerQueue.push(corner); |
228 | 0 | } |
229 | 0 | } |
230 | 0 | } |
231 | | |
232 | | /* private */ |
233 | | bool |
234 | | TPVWSimplifier::Edge::isRemovable( |
235 | | Corner& corner, |
236 | | EdgeIndex& edgeIndex) const |
237 | 0 | { |
238 | 0 | Envelope cornerEnv = corner.envelope(); |
239 | | //-- check nearby lines for violating intersections |
240 | | //-- the query also returns this line for checking |
241 | 0 | std::vector<const Edge*> edgeHits = edgeIndex.query(cornerEnv); |
242 | 0 | for (const Edge* edge : edgeHits) { |
243 | 0 | if (hasIntersectingVertex(corner, cornerEnv, *edge)) |
244 | 0 | return false; |
245 | | //-- check if corner base equals line (2-pts) |
246 | | //-- if so, don't remove corner, since that would collapse to the line |
247 | 0 | if (edge != this && edge->size() == 2) { |
248 | | // TODO xxxxxx make linkedLine coordinates local |
249 | | // to linkedline and return a reference, update |
250 | | // simplify to clone reference at final step |
251 | 0 | auto linePts = edge->linkedLine.getCoordinates(); |
252 | 0 | if (corner.isBaseline(linePts->getAt(0), linePts->getAt(1))) |
253 | 0 | return false; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | return true; |
257 | 0 | } |
258 | | |
259 | | |
260 | | /* private */ |
261 | | bool |
262 | | TPVWSimplifier::Edge::hasIntersectingVertex( |
263 | | const Corner& corner, |
264 | | const Envelope& cornerEnv, |
265 | | const Edge& edge) const |
266 | 0 | { |
267 | 0 | std::vector<std::size_t> result = edge.query(cornerEnv); |
268 | 0 | for (std::size_t index : result) { |
269 | |
|
270 | 0 | const Coordinate& v = edge.getCoordinate(index); |
271 | | // ok if corner touches another line - should only happen at endpoints |
272 | 0 | if (corner.isVertex(v)) |
273 | 0 | continue; |
274 | | |
275 | | //--- does corner triangle contain vertex? |
276 | 0 | if (corner.intersects(v)) |
277 | 0 | return true; |
278 | 0 | } |
279 | 0 | return false; |
280 | 0 | } |
281 | | |
282 | | /* private */ |
283 | | std::vector<std::size_t> |
284 | | TPVWSimplifier::Edge::query(const Envelope& cornerEnv) const |
285 | 0 | { |
286 | 0 | std::vector<std::size_t> result; |
287 | 0 | vertexIndex.query(cornerEnv, result); |
288 | 0 | return result; |
289 | 0 | } |
290 | | |
291 | | |
292 | | /* private */ |
293 | | void |
294 | | TPVWSimplifier::Edge::removeCorner( |
295 | | Corner& corner, |
296 | | Corner::PriorityQueue& cornerQueue) |
297 | 0 | { |
298 | 0 | std::size_t index = corner.getIndex(); |
299 | 0 | std::size_t prev = linkedLine.prev(index); |
300 | 0 | std::size_t next = linkedLine.next(index); |
301 | 0 | linkedLine.remove(index); |
302 | 0 | vertexIndex.remove(index); |
303 | | |
304 | | //-- potentially add the new corners created |
305 | 0 | addCorner(prev, cornerQueue); |
306 | 0 | addCorner(next, cornerQueue); |
307 | 0 | } |
308 | | |
309 | | /************************************************************************/ |
310 | | |
311 | | |
312 | | /* public */ |
313 | | void |
314 | | TPVWSimplifier::EdgeIndex::add(std::vector<Edge>& edges) |
315 | 0 | { |
316 | 0 | for (Edge& edge : edges) { |
317 | 0 | index.insert(&edge); |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | /* public */ |
322 | | std::vector<const Edge*> |
323 | | TPVWSimplifier::EdgeIndex::query(const Envelope& queryEnv) |
324 | 0 | { |
325 | 0 | std::vector<const Edge*> hits; |
326 | 0 | index.query(queryEnv, hits); |
327 | 0 | return hits; |
328 | 0 | } |
329 | | |
330 | | |
331 | | |
332 | | } // geos.coverage |
333 | | } // geos |