/src/geos/src/algorithm/ConvexHull.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) 2011 Sandro Santilli <strk@kbt.io> |
7 | | * Copyright (C) 2005-2006 Refractions Research Inc. |
8 | | * Copyright (C) 2001-2002 Vivid Solutions Inc. |
9 | | * |
10 | | * This is free software; you can redistribute and/or modify it under |
11 | | * the terms of the GNU Lesser General Public Licence as published |
12 | | * by the Free Software Foundation. |
13 | | * See the COPYING file for more information. |
14 | | * |
15 | | ********************************************************************** |
16 | | * |
17 | | * Last port: algorithm/ConvexHull.java r407 (JTS-1.12+) |
18 | | * |
19 | | **********************************************************************/ |
20 | | |
21 | | #include <geos/algorithm/ConvexHull.h> |
22 | | #include <geos/algorithm/PointLocation.h> |
23 | | #include <geos/algorithm/Orientation.h> |
24 | | #include <geos/geom/GeometryFactory.h> |
25 | | #include <geos/geom/Coordinate.h> |
26 | | #include <geos/geom/Point.h> |
27 | | #include <geos/geom/Polygon.h> |
28 | | #include <geos/geom/LineString.h> |
29 | | #include <geos/geom/CoordinateSequence.h> |
30 | | #include <geos/util.h> |
31 | | #include <geos/util/Interrupt.h> |
32 | | |
33 | | #include <typeinfo> |
34 | | #include <algorithm> |
35 | | |
36 | | |
37 | | using geos::geom::Geometry; |
38 | | using geos::geom::GeometryFactory; |
39 | | using geos::geom::Coordinate; |
40 | | using geos::geom::Point; |
41 | | using geos::geom::Polygon; |
42 | | using geos::geom::LineString; |
43 | | using geos::geom::LinearRing; |
44 | | using geos::geom::CoordinateSequence; |
45 | | |
46 | | |
47 | | namespace geos { |
48 | | namespace algorithm { // geos.algorithm |
49 | | |
50 | | namespace { |
51 | | |
52 | | /** |
53 | | * This is the class used to sort in radial order. |
54 | | * It's defined here as it's a static one. |
55 | | */ |
56 | | class RadiallyLessThen { |
57 | | |
58 | | private: |
59 | | |
60 | | const Coordinate* origin; |
61 | | |
62 | | static int |
63 | | polarCompare(const Coordinate* o, const Coordinate* p, |
64 | | const Coordinate* q) |
65 | 0 | { |
66 | 0 | int orient = Orientation::index(*o, *p, *q); |
67 | |
|
68 | 0 | if(orient == Orientation::COUNTERCLOCKWISE) { |
69 | 0 | return 1; |
70 | 0 | } |
71 | 0 | if(orient == Orientation::CLOCKWISE) { |
72 | 0 | return -1; |
73 | 0 | } |
74 | | |
75 | | /** |
76 | | * The points are collinear, |
77 | | * so compare based on distance from the origin. |
78 | | * The points p and q are >= to the origin, |
79 | | * so they lie in the closed half-plane above the origin. |
80 | | * If they are not in a horizontal line, |
81 | | * the Y ordinate can be tested to determine distance. |
82 | | * This is more robust than computing the distance explicitly. |
83 | | */ |
84 | 0 | if (p->y > q->y) return 1; |
85 | 0 | if (p->y < q->y) return -1; |
86 | | |
87 | | /** |
88 | | * The points lie in a horizontal line, which should also contain the origin |
89 | | * (since they are collinear). |
90 | | * Also, they must be above the origin. |
91 | | * Use the X ordinate to determine distance. |
92 | | */ |
93 | 0 | if (p->x > q->x) return 1; |
94 | 0 | if (p->x < q->x) return -1; |
95 | | |
96 | | // Assert: p = q |
97 | 0 | return 0; |
98 | 0 | } |
99 | | |
100 | | public: |
101 | | |
102 | 0 | RadiallyLessThen(const Coordinate* c): origin(c) {} |
103 | | |
104 | | bool |
105 | | operator()(const Coordinate* p1, const Coordinate* p2) |
106 | 0 | { |
107 | 0 | return (polarCompare(origin, p1, p2) == -1); |
108 | 0 | } |
109 | | |
110 | | }; |
111 | | |
112 | | } // unnamed namespace |
113 | | |
114 | | /* private */ |
115 | | std::unique_ptr<CoordinateSequence> |
116 | | ConvexHull::toCoordinateSequence(Coordinate::ConstVect& cv) |
117 | 0 | { |
118 | 0 | auto cs = detail::make_unique<CoordinateSequence>(cv.size()); |
119 | |
|
120 | 0 | for(std::size_t i = 0; i < cv.size(); ++i) { |
121 | 0 | cs->setAt(*(cv[i]), i); // Coordinate copy |
122 | 0 | } |
123 | |
|
124 | 0 | return cs; |
125 | 0 | } |
126 | | |
127 | | /* private */ |
128 | | void |
129 | | ConvexHull::computeInnerOctolateralPts(const Coordinate::ConstVect& p_inputPts, |
130 | | Coordinate::ConstVect& pts) |
131 | 0 | { |
132 | | // Initialize all slots with first input coordinate |
133 | 0 | pts = Coordinate::ConstVect(8, p_inputPts[0]); |
134 | |
|
135 | 0 | for(std::size_t i = 1, n = p_inputPts.size(); i < n; ++i) { |
136 | 0 | if(p_inputPts[i]->x < pts[0]->x) { |
137 | 0 | pts[0] = p_inputPts[i]; |
138 | 0 | } |
139 | 0 | if(p_inputPts[i]->x - p_inputPts[i]->y < pts[1]->x - pts[1]->y) { |
140 | 0 | pts[1] = p_inputPts[i]; |
141 | 0 | } |
142 | 0 | if(p_inputPts[i]->y > pts[2]->y) { |
143 | 0 | pts[2] = p_inputPts[i]; |
144 | 0 | } |
145 | 0 | if(p_inputPts[i]->x + p_inputPts[i]->y > pts[3]->x + pts[3]->y) { |
146 | 0 | pts[3] = p_inputPts[i]; |
147 | 0 | } |
148 | 0 | if(p_inputPts[i]->x > pts[4]->x) { |
149 | 0 | pts[4] = p_inputPts[i]; |
150 | 0 | } |
151 | 0 | if(p_inputPts[i]->x - p_inputPts[i]->y > pts[5]->x - pts[5]->y) { |
152 | 0 | pts[5] = p_inputPts[i]; |
153 | 0 | } |
154 | 0 | if(p_inputPts[i]->y < pts[6]->y) { |
155 | 0 | pts[6] = p_inputPts[i]; |
156 | 0 | } |
157 | 0 | if(p_inputPts[i]->x + p_inputPts[i]->y < pts[7]->x + pts[7]->y) { |
158 | 0 | pts[7] = p_inputPts[i]; |
159 | 0 | } |
160 | 0 | } |
161 | |
|
162 | 0 | } |
163 | | |
164 | | |
165 | | /* private */ |
166 | | bool |
167 | | ConvexHull::computeInnerOctolateralRing(const Coordinate::ConstVect& p_inputPts, |
168 | | Coordinate::ConstVect& dest) |
169 | 0 | { |
170 | 0 | computeInnerOctolateralPts(p_inputPts, dest); |
171 | | |
172 | | // Remove consecutive equal Coordinates |
173 | | // unique() returns an iterator to the end of the resulting |
174 | | // sequence, we erase from there to the end. |
175 | 0 | dest.erase(std::unique(dest.begin(), dest.end()), dest.end()); |
176 | | |
177 | | // points must all lie in a line |
178 | 0 | if(dest.size() < 3) { |
179 | 0 | return false; |
180 | 0 | } |
181 | | |
182 | | // close ring |
183 | 0 | dest.push_back(dest[0]); |
184 | |
|
185 | 0 | return true; |
186 | 0 | } |
187 | | |
188 | | /* private */ |
189 | | void |
190 | | ConvexHull::reduce(Coordinate::ConstVect& pts) |
191 | 0 | { |
192 | 0 | Coordinate::ConstVect polyPts; |
193 | |
|
194 | 0 | if(! computeInnerOctolateralRing(pts, polyPts)) { |
195 | | // unable to compute interior polygon for some reason |
196 | 0 | return; |
197 | 0 | } |
198 | | |
199 | | // add points defining polygon |
200 | 0 | Coordinate::ConstSet reducedSet; |
201 | 0 | reducedSet.insert(polyPts.begin(), polyPts.end()); |
202 | | |
203 | | /* |
204 | | * Add all unique points not in the interior poly. |
205 | | * CGAlgorithms.isPointInRing is not defined for points |
206 | | * actually on the ring, but this doesn't matter since |
207 | | * the points of the interior polygon are forced to be |
208 | | * in the reduced set. |
209 | | * |
210 | | * @@TIP: there should be a std::algo for this |
211 | | */ |
212 | 0 | for(std::size_t i = 0, n = pts.size(); i < n; ++i) { |
213 | 0 | if(!PointLocation::isInRing(*(pts[i]), polyPts)) { |
214 | 0 | reducedSet.insert(pts[i]); |
215 | 0 | } |
216 | 0 | } |
217 | |
|
218 | 0 | inputPts.assign(reducedSet.begin(), reducedSet.end()); |
219 | |
|
220 | 0 | if(inputPts.size() < 3) { |
221 | 0 | padArray3(inputPts); |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | | /* private */ |
226 | | void |
227 | | ConvexHull::padArray3(geom::Coordinate::ConstVect& pts) |
228 | 0 | { |
229 | 0 | for(std::size_t i = pts.size(); i < 3; ++i) { |
230 | 0 | pts.push_back(pts[0]); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | std::unique_ptr<Geometry> |
235 | | ConvexHull::getConvexHull() |
236 | 0 | { |
237 | 0 | std::unique_ptr<Geometry> fewPointsGeom = createFewPointsResult(); |
238 | 0 | if (fewPointsGeom != nullptr) |
239 | 0 | return fewPointsGeom; |
240 | | |
241 | 0 | util::CoordinateArrayFilter filter(inputPts); |
242 | 0 | inputGeom->apply_ro(&filter); |
243 | |
|
244 | 0 | std::size_t nInputPts = inputPts.size(); |
245 | | |
246 | | // use heuristic to reduce points, if large |
247 | 0 | if(nInputPts > TUNING_REDUCE_SIZE) { |
248 | 0 | reduce(inputPts); |
249 | 0 | } |
250 | 0 | else { |
251 | 0 | inputPts.clear(); |
252 | 0 | extractUnique(inputPts, NO_COORD_INDEX); |
253 | 0 | } |
254 | |
|
255 | 0 | GEOS_CHECK_FOR_INTERRUPTS(); |
256 | | |
257 | | // sort points for Graham scan. |
258 | 0 | preSort(inputPts); |
259 | |
|
260 | 0 | GEOS_CHECK_FOR_INTERRUPTS(); |
261 | | |
262 | | // Use Graham scan to find convex hull. |
263 | 0 | Coordinate::ConstVect cHS; |
264 | 0 | grahamScan(inputPts, cHS); |
265 | |
|
266 | 0 | GEOS_CHECK_FOR_INTERRUPTS(); |
267 | |
|
268 | 0 | return lineOrPolygon(cHS); |
269 | 0 | } |
270 | | |
271 | | /* private */ |
272 | | void |
273 | | ConvexHull::preSort(Coordinate::ConstVect& pts) |
274 | 0 | { |
275 | | // find the lowest point in the set. If two or more points have |
276 | | // the same minimum y coordinate choose the one with the minimum x. |
277 | | // This focal point is put in array location pts[0]. |
278 | 0 | for(std::size_t i = 1, n = pts.size(); i < n; ++i) { |
279 | 0 | const Coordinate* p0 = pts[0]; // this will change |
280 | 0 | const Coordinate* pi = pts[i]; |
281 | 0 | if((pi->y < p0->y) || ((pi->y == p0->y) && (pi->x < p0->x))) { |
282 | 0 | const Coordinate* t = p0; |
283 | 0 | pts[0] = pi; |
284 | 0 | pts[i] = t; |
285 | 0 | } |
286 | 0 | } |
287 | | |
288 | | // sort the points radially around the focal point. |
289 | 0 | std::sort(pts.begin(), pts.end(), RadiallyLessThen(pts[0])); |
290 | 0 | } |
291 | | |
292 | | /*private*/ |
293 | | void |
294 | | ConvexHull::grahamScan(const Coordinate::ConstVect& c, |
295 | | Coordinate::ConstVect& ps) |
296 | 0 | { |
297 | 0 | ps.push_back(c[0]); |
298 | 0 | ps.push_back(c[1]); |
299 | 0 | ps.push_back(c[2]); |
300 | |
|
301 | 0 | for(std::size_t i = 3, n = c.size(); i < n; ++i) { |
302 | 0 | const Coordinate* p = ps.back(); |
303 | 0 | ps.pop_back(); |
304 | 0 | while(!ps.empty() && |
305 | 0 | Orientation::index( |
306 | 0 | *(ps.back()), *p, *(c[i])) > 0) { |
307 | 0 | p = ps.back(); |
308 | 0 | ps.pop_back(); |
309 | 0 | } |
310 | 0 | ps.push_back(p); |
311 | 0 | ps.push_back(c[i]); |
312 | 0 | } |
313 | 0 | ps.push_back(c[0]); |
314 | 0 | } |
315 | | |
316 | | |
317 | | /*private*/ |
318 | | bool |
319 | | ConvexHull::isBetween(const Coordinate& c1, const Coordinate& c2, const Coordinate& c3) |
320 | 0 | { |
321 | 0 | if(Orientation::index(c1, c2, c3) != 0) { |
322 | 0 | return false; |
323 | 0 | } |
324 | 0 | if(c1.x != c3.x) { |
325 | 0 | if(c1.x <= c2.x && c2.x <= c3.x) { |
326 | 0 | return true; |
327 | 0 | } |
328 | 0 | if(c3.x <= c2.x && c2.x <= c1.x) { |
329 | 0 | return true; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | if(c1.y != c3.y) { |
333 | 0 | if(c1.y <= c2.y && c2.y <= c3.y) { |
334 | 0 | return true; |
335 | 0 | } |
336 | 0 | if(c3.y <= c2.y && c2.y <= c1.y) { |
337 | 0 | return true; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | return false; |
341 | 0 | } |
342 | | |
343 | | |
344 | | /* private */ |
345 | | std::unique_ptr<Geometry> |
346 | | ConvexHull::lineOrPolygon(const Coordinate::ConstVect& input) |
347 | 0 | { |
348 | 0 | Coordinate::ConstVect cleaned; |
349 | |
|
350 | 0 | cleanRing(input, cleaned); |
351 | |
|
352 | 0 | if(cleaned.size() == 3) { // shouldn't this be 2 ?? |
353 | 0 | cleaned.resize(2); |
354 | 0 | auto cl1 = toCoordinateSequence(cleaned); |
355 | 0 | return geomFactory->createLineString(std::move(cl1)); |
356 | 0 | } |
357 | 0 | auto cl2 = toCoordinateSequence(cleaned); |
358 | 0 | std::unique_ptr<LinearRing> linearRing = geomFactory->createLinearRing(std::move(cl2)); |
359 | 0 | return geomFactory->createPolygon(std::move(linearRing)); |
360 | 0 | } |
361 | | |
362 | | /*private*/ |
363 | | void |
364 | | ConvexHull::cleanRing(const Coordinate::ConstVect& original, |
365 | | Coordinate::ConstVect& cleaned) |
366 | 0 | { |
367 | 0 | std::size_t npts = original.size(); |
368 | |
|
369 | 0 | const Coordinate* last = original[npts - 1]; |
370 | | |
371 | | //util::Assert::equals(*(original[0]), *last); |
372 | 0 | assert(last); |
373 | 0 | assert(original[0]->equals2D(*last)); |
374 | |
|
375 | 0 | const Coordinate* prev = nullptr; |
376 | 0 | for(std::size_t i = 0; i < npts - 1; ++i) { |
377 | 0 | const Coordinate* curr = original[i]; |
378 | 0 | const Coordinate* next = original[i + 1]; |
379 | | |
380 | | // skip consecutive equal coordinates |
381 | 0 | if(curr->equals2D(*next)) { |
382 | 0 | continue; |
383 | 0 | } |
384 | | |
385 | 0 | if(prev != nullptr && isBetween(*prev, *curr, *next)) { |
386 | 0 | continue; |
387 | 0 | } |
388 | | |
389 | 0 | cleaned.push_back(curr); |
390 | 0 | prev = curr; |
391 | 0 | } |
392 | |
|
393 | 0 | cleaned.push_back(last); |
394 | |
|
395 | 0 | } |
396 | | |
397 | | |
398 | | /* |
399 | | * Returns true if the extraction is stopped |
400 | | * by the maxPts restriction. That is, if there |
401 | | * are yet more points to be processed. |
402 | | */ |
403 | | /* private */ |
404 | | bool |
405 | | ConvexHull::extractUnique(Coordinate::ConstVect& pts, std::size_t maxPts) |
406 | 0 | { |
407 | 0 | util::UniqueCoordinateArrayFilter filter(pts, maxPts); |
408 | 0 | inputGeom->apply_ro(&filter); |
409 | 0 | return filter.isDone(); |
410 | 0 | } |
411 | | |
412 | | |
413 | | /* private */ |
414 | | std::unique_ptr<Geometry> |
415 | | ConvexHull::createFewPointsResult() |
416 | 0 | { |
417 | 0 | Coordinate::ConstVect uniquePts; |
418 | 0 | bool ok = extractUnique(uniquePts, 2); |
419 | |
|
420 | 0 | if (ok) { |
421 | 0 | return nullptr; |
422 | 0 | } |
423 | | |
424 | 0 | if(uniquePts.size() == 0) { // Return an empty geometry |
425 | 0 | return geomFactory->createEmptyGeometry(); |
426 | 0 | } |
427 | 0 | else if(uniquePts.size() == 1) { // Return a Point |
428 | | // Copy the Coordinate from the ConstVect |
429 | 0 | return std::unique_ptr<Geometry>(geomFactory->createPoint(*(uniquePts[0]))); |
430 | 0 | } |
431 | 0 | else { // Return a LineString |
432 | 0 | auto cs = toCoordinateSequence(uniquePts); |
433 | 0 | return geomFactory->createLineString(std::move(cs)); |
434 | 0 | } |
435 | 0 | } |
436 | | |
437 | | |
438 | | } // namespace geos.algorithm |
439 | | } // namespace geos |