/src/geos/src/algorithm/MinimumDiameter.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * GEOS - Geometry Engine Open Source |
4 | | * http://geos.osgeo.org |
5 | | * |
6 | | * Copyright (C) 2023 Paul Ramsey <pramsey@cleverelephant.ca> |
7 | | * Copyright (C) 2001-2002 Vivid Solutions Inc. |
8 | | * Copyright (C) 2005 Refractions Research 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 | | #include <geos/constants.h> |
18 | | #include <geos/algorithm/MinimumDiameter.h> |
19 | | #include <geos/algorithm/ConvexHull.h> |
20 | | #include <geos/geom/Geometry.h> |
21 | | #include <geos/geom/LineSegment.h> |
22 | | #include <geos/geom/Polygon.h> |
23 | | #include <geos/geom/Point.h> |
24 | | #include <geos/geom/LineString.h> |
25 | | #include <geos/geom/GeometryFactory.h> |
26 | | #include <geos/geom/CoordinateSequence.h> |
27 | | #include <geos/util.h> |
28 | | |
29 | | #include <typeinfo> |
30 | | #include <cmath> // for fabs() |
31 | | |
32 | | using namespace geos::geom; |
33 | | |
34 | | namespace geos { |
35 | | namespace algorithm { // geos.algorithm |
36 | | |
37 | | /** |
38 | | * Computes the minimum diameter of a Geometry. |
39 | | * The minimum diameter is defined to be the |
40 | | * width of the smallest band that |
41 | | * contains the geometry, |
42 | | * where a band is a strip of the plane defined |
43 | | * by two parallel lines. |
44 | | * This can be thought of as the smallest hole that the geometry can be |
45 | | * moved through, with a single rotation. |
46 | | * |
47 | | * The first step in the algorithm is computing the convex hull of the Geometry. |
48 | | * If the input Geometry is known to be convex, a hint can be supplied to |
49 | | * avoid this computation. |
50 | | * |
51 | | * @see ConvexHull |
52 | | * |
53 | | * @version 1.4 |
54 | | */ |
55 | | |
56 | | /** |
57 | | * Compute a minimum diameter for a giver {@link Geometry}. |
58 | | * |
59 | | * @param newInputGeom a Geometry |
60 | | */ |
61 | | MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom) |
62 | 0 | { |
63 | 0 | minWidthPt = Coordinate::getNull(); |
64 | 0 | minPtIndex = 0; |
65 | 0 | minWidth = 0.0; |
66 | 0 | inputGeom = newInputGeom; |
67 | 0 | isConvex = false; |
68 | 0 | convexHullPts = nullptr; |
69 | 0 | } |
70 | | |
71 | | /** |
72 | | * Compute a minimum diameter for a giver Geometry, |
73 | | * with a hint if |
74 | | * the Geometry is convex |
75 | | * (e.g. a convex Polygon or LinearRing, |
76 | | * or a two-point LineString, or a Point). |
77 | | * |
78 | | * @param newInputGeom a Geometry which is convex |
79 | | * @param newIsConvex <code>true</code> if the input geometry is convex |
80 | | */ |
81 | | MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom, const bool newIsConvex) |
82 | 0 | { |
83 | 0 | minWidthPt = Coordinate::getNull(); |
84 | 0 | minWidth = 0.0; |
85 | 0 | inputGeom = newInputGeom; |
86 | 0 | isConvex = newIsConvex; |
87 | 0 | convexHullPts = nullptr; |
88 | 0 | } |
89 | | |
90 | | /** |
91 | | * Gets the length of the minimum diameter of the input Geometry |
92 | | * |
93 | | * @return the length of the minimum diameter |
94 | | */ |
95 | | double |
96 | | MinimumDiameter::getLength() |
97 | 0 | { |
98 | 0 | computeMinimumDiameter(); |
99 | 0 | return minWidth; |
100 | 0 | } |
101 | | |
102 | | /** |
103 | | * Gets the {@link Coordinate} forming one end of the minimum diameter |
104 | | * |
105 | | * @return a coordinate forming one end of the minimum diameter |
106 | | */ |
107 | | const Coordinate& |
108 | | MinimumDiameter::getWidthCoordinate() |
109 | 0 | { |
110 | 0 | computeMinimumDiameter(); |
111 | 0 | return minWidthPt; |
112 | 0 | } |
113 | | |
114 | | /** |
115 | | * Gets the segment forming the base of the minimum diameter |
116 | | * |
117 | | * @return the segment forming the base of the minimum diameter |
118 | | */ |
119 | | std::unique_ptr<LineString> |
120 | | MinimumDiameter::getSupportingSegment() |
121 | 0 | { |
122 | 0 | computeMinimumDiameter(); |
123 | 0 | const GeometryFactory* fact = inputGeom->getFactory(); |
124 | 0 | return minBaseSeg.toGeometry(*fact); |
125 | 0 | } |
126 | | |
127 | | /** |
128 | | * Gets a LineString which is a minimum diameter |
129 | | * |
130 | | * @return a LineString which is a minimum diameter |
131 | | */ |
132 | | std::unique_ptr<LineString> |
133 | | MinimumDiameter::getDiameter() |
134 | 0 | { |
135 | 0 | computeMinimumDiameter(); |
136 | | // return empty linestring if no minimum width calculated |
137 | 0 | if(minWidthPt.isNull()) { |
138 | 0 | return std::unique_ptr<LineString>(inputGeom->getFactory()->createLineString()); |
139 | 0 | } |
140 | | |
141 | 0 | Coordinate basePt; |
142 | 0 | minBaseSeg.project(minWidthPt, basePt); |
143 | |
|
144 | 0 | auto cl = detail::make_unique<CoordinateSequence>(2u); |
145 | 0 | cl->setAt(basePt, 0); |
146 | 0 | cl->setAt(minWidthPt, 1); |
147 | 0 | return inputGeom->getFactory()->createLineString(std::move(cl)); |
148 | 0 | } |
149 | | |
150 | | /* private */ |
151 | | void |
152 | | MinimumDiameter::computeMinimumDiameter() |
153 | 0 | { |
154 | | // check if computation is cached |
155 | 0 | if(!minWidthPt.isNull()) { |
156 | 0 | return; |
157 | 0 | } |
158 | 0 | if(isConvex) { |
159 | 0 | computeWidthConvex(inputGeom); |
160 | 0 | } |
161 | 0 | else { |
162 | 0 | ConvexHull ch(inputGeom); |
163 | 0 | std::unique_ptr<Geometry> convexGeom = ch.getConvexHull(); |
164 | 0 | computeWidthConvex(convexGeom.get()); |
165 | 0 | } |
166 | 0 | } |
167 | | |
168 | | /* private */ |
169 | | void |
170 | | MinimumDiameter::computeWidthConvex(const Geometry* geom) |
171 | 0 | { |
172 | | //System.out.println("Input = " + geom); |
173 | 0 | if(typeid(*geom) == typeid(Polygon)) { |
174 | 0 | const Polygon* p = dynamic_cast<const Polygon*>(geom); |
175 | 0 | convexHullPts = p->getExteriorRing()->getCoordinates(); |
176 | 0 | } |
177 | 0 | else { |
178 | 0 | convexHullPts = geom->getCoordinates(); |
179 | 0 | } |
180 | | |
181 | | // special cases for lines or points or degenerate rings |
182 | 0 | switch(convexHullPts->getSize()) { |
183 | 0 | case 0: |
184 | 0 | minWidth = 0.0; |
185 | 0 | minWidthPt = Coordinate::getNull(); |
186 | 0 | break; |
187 | 0 | case 1: |
188 | 0 | minWidth = 0.0; |
189 | 0 | minWidthPt = convexHullPts->getAt(0); |
190 | 0 | minBaseSeg.p0 = convexHullPts->getAt(0); |
191 | 0 | minBaseSeg.p1 = convexHullPts->getAt(0); |
192 | 0 | break; |
193 | 0 | case 2: |
194 | 0 | case 3: |
195 | 0 | minWidth = 0.0; |
196 | 0 | minWidthPt = convexHullPts->getAt(0); |
197 | 0 | minBaseSeg.p0 = convexHullPts->getAt(0); |
198 | 0 | minBaseSeg.p1 = convexHullPts->getAt(1); |
199 | 0 | break; |
200 | 0 | default: |
201 | 0 | computeConvexRingMinDiameter(convexHullPts.get()); |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | /** |
206 | | * Compute the width information for a ring of {@link Coordinate}s. |
207 | | * Leaves the width information in the instance variables. |
208 | | * |
209 | | * @param pts |
210 | | * @return |
211 | | */ |
212 | | void |
213 | | MinimumDiameter::computeConvexRingMinDiameter(const CoordinateSequence* pts) |
214 | 0 | { |
215 | 0 | minWidth = DoubleInfinity; |
216 | 0 | unsigned int currMaxIndex = 1; |
217 | 0 | LineSegment seg; |
218 | | |
219 | | // for each segment, find a vertex at max distance, and pick the minimum |
220 | 0 | const std::size_t npts = pts->getSize(); |
221 | 0 | for(std::size_t i = 1; i < npts; ++i) { |
222 | 0 | seg.p0 = pts->getAt(i - 1); |
223 | 0 | seg.p1 = pts->getAt(i); |
224 | 0 | currMaxIndex = findMaxPerpDistance(pts, &seg, currMaxIndex); |
225 | 0 | } |
226 | 0 | } |
227 | | |
228 | | unsigned int |
229 | | MinimumDiameter::findMaxPerpDistance(const CoordinateSequence* pts, |
230 | | const LineSegment* seg, unsigned int startIndex) |
231 | 0 | { |
232 | 0 | double maxPerpDistance = seg->distancePerpendicular(pts->getAt(startIndex)); |
233 | 0 | double nextPerpDistance = maxPerpDistance; |
234 | 0 | unsigned int maxIndex = startIndex; |
235 | 0 | unsigned int nextIndex = maxIndex; |
236 | 0 | while(nextPerpDistance >= maxPerpDistance) { |
237 | 0 | maxPerpDistance = nextPerpDistance; |
238 | 0 | maxIndex = nextIndex; |
239 | 0 | nextIndex = getNextIndex(pts, maxIndex); |
240 | 0 | if (nextIndex == startIndex) |
241 | 0 | break; |
242 | 0 | nextPerpDistance = seg->distancePerpendicular(pts->getAt(nextIndex)); |
243 | 0 | } |
244 | | |
245 | | // found maximum width for this segment - update global min dist if appropriate |
246 | 0 | if(maxPerpDistance < minWidth) { |
247 | 0 | minPtIndex = maxIndex; |
248 | 0 | minWidth = maxPerpDistance; |
249 | 0 | minWidthPt = pts->getAt(minPtIndex); |
250 | 0 | minBaseSeg = *seg; |
251 | 0 | } |
252 | 0 | return maxIndex; |
253 | 0 | } |
254 | | |
255 | | unsigned int |
256 | | MinimumDiameter::getNextIndex(const CoordinateSequence* pts, |
257 | | unsigned int index) |
258 | 0 | { |
259 | 0 | if(++index >= pts->getSize()) { |
260 | 0 | index = 0; |
261 | 0 | } |
262 | 0 | return index; |
263 | 0 | } |
264 | | |
265 | | std::unique_ptr<Geometry> |
266 | | MinimumDiameter::getMinimumRectangle() |
267 | 0 | { |
268 | 0 | computeMinimumDiameter(); |
269 | |
|
270 | 0 | if(minWidthPt.isNull() || !convexHullPts) { |
271 | | //return empty polygon |
272 | 0 | return std::unique_ptr<Geometry>(inputGeom->getFactory()->createPolygon()); |
273 | 0 | } |
274 | | |
275 | | // check if minimum rectangle is degenerate (a point or line segment) |
276 | 0 | if(minWidth == 0.0) { |
277 | 0 | if(minBaseSeg.p0.equals2D(minBaseSeg.p1)) { |
278 | 0 | return std::unique_ptr<Geometry>(inputGeom->getFactory()->createPoint(minBaseSeg.p0)); |
279 | 0 | } |
280 | | //-- Min rectangle is a line. Use the diagonal of the extent |
281 | 0 | return computeMaximumLine(convexHullPts.get(), inputGeom->getFactory()); |
282 | 0 | } |
283 | | |
284 | | // deltas for the base segment of the minimum diameter |
285 | 0 | double dx = minBaseSeg.p1.x - minBaseSeg.p0.x; |
286 | 0 | double dy = minBaseSeg.p1.y - minBaseSeg.p0.y; |
287 | |
|
288 | 0 | double minPara = DoubleInfinity; |
289 | 0 | double maxPara = DoubleNegInfinity; |
290 | 0 | double minPerp = DoubleInfinity; |
291 | 0 | double maxPerp = DoubleNegInfinity; |
292 | | |
293 | | // compute maxima and minima of lines parallel and perpendicular to base segment |
294 | 0 | std::size_t const n = convexHullPts->getSize(); |
295 | 0 | for(std::size_t i = 0; i < n; ++i) { |
296 | |
|
297 | 0 | double paraC = computeC(dx, dy, convexHullPts->getAt(i)); |
298 | 0 | if(paraC > maxPara) { |
299 | 0 | maxPara = paraC; |
300 | 0 | } |
301 | 0 | if(paraC < minPara) { |
302 | 0 | minPara = paraC; |
303 | 0 | } |
304 | |
|
305 | 0 | double perpC = computeC(-dy, dx, convexHullPts->getAt(i)); |
306 | 0 | if(perpC > maxPerp) { |
307 | 0 | maxPerp = perpC; |
308 | 0 | } |
309 | 0 | if(perpC < minPerp) { |
310 | 0 | minPerp = perpC; |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | // compute lines along edges of minimum rectangle |
315 | 0 | LineSegment maxPerpLine = computeSegmentForLine(-dx, -dy, maxPerp); |
316 | 0 | LineSegment minPerpLine = computeSegmentForLine(-dx, -dy, minPerp); |
317 | 0 | LineSegment maxParaLine = computeSegmentForLine(-dy, dx, maxPara); |
318 | 0 | LineSegment minParaLine = computeSegmentForLine(-dy, dx, minPara); |
319 | | |
320 | | // compute vertices of rectangle (where the para/perp max & min lines intersect) |
321 | 0 | Coordinate p0 = maxParaLine.lineIntersection(maxPerpLine); |
322 | 0 | Coordinate p1 = minParaLine.lineIntersection(maxPerpLine); |
323 | 0 | Coordinate p2 = minParaLine.lineIntersection(minPerpLine); |
324 | 0 | Coordinate p3 = maxParaLine.lineIntersection(minPerpLine); |
325 | |
|
326 | 0 | auto seq = detail::make_unique<CoordinateSequence>(5u, 2u); |
327 | 0 | seq->setAt(p0, 0); |
328 | 0 | seq->setAt(p1, 1); |
329 | 0 | seq->setAt(p2, 2); |
330 | 0 | seq->setAt(p3, 3); |
331 | 0 | seq->setAt(p0, 4); // close |
332 | |
|
333 | 0 | std::unique_ptr<LinearRing> shell = inputGeom->getFactory()->createLinearRing(std::move(seq)); |
334 | 0 | return inputGeom->getFactory()->createPolygon(std::move(shell)); |
335 | 0 | } |
336 | | |
337 | | // private static |
338 | | std::unique_ptr<Geometry> |
339 | | MinimumDiameter::computeMaximumLine(const geom::CoordinateSequence* pts, |
340 | | const GeometryFactory* factory) |
341 | 0 | { |
342 | | //-- find max and min pts for X and Y |
343 | 0 | Coordinate ptMinX = pts->getAt(0); |
344 | 0 | Coordinate ptMaxX = pts->getAt(0); |
345 | 0 | Coordinate ptMinY = pts->getAt(0); |
346 | 0 | Coordinate ptMaxY = pts->getAt(0); |
347 | |
|
348 | 0 | std::size_t const n = pts->getSize(); |
349 | 0 | for(std::size_t i = 1; i < n; ++i) { |
350 | 0 | const Coordinate& p = pts->getAt(i); |
351 | 0 | if (p.x < ptMinX.x) ptMinX = p; |
352 | 0 | if (p.x > ptMaxX.x) ptMaxX = p; |
353 | 0 | if (p.y < ptMinY.y) ptMinY = p; |
354 | 0 | if (p.y > ptMaxY.y) ptMaxY = p; |
355 | 0 | } |
356 | 0 | Coordinate p0 = ptMinX; |
357 | 0 | Coordinate p1 = ptMaxX; |
358 | | //-- line is vertical - use Y pts |
359 | 0 | if (p0.x == p1.x) { |
360 | 0 | p0 = ptMinY; |
361 | 0 | p1 = ptMaxY; |
362 | 0 | } |
363 | |
|
364 | 0 | auto seq = detail::make_unique<CoordinateSequence>(2u, 2u); |
365 | 0 | seq->setAt(p0, 0); |
366 | 0 | seq->setAt(p1, 1); |
367 | |
|
368 | 0 | return factory->createLineString(std::move(seq)); |
369 | 0 | } |
370 | | |
371 | | double |
372 | | MinimumDiameter::computeC(double a, double b, const Coordinate& p) |
373 | 0 | { |
374 | 0 | return a * p.y - b * p.x; |
375 | 0 | } |
376 | | |
377 | | LineSegment |
378 | | MinimumDiameter::computeSegmentForLine(double a, double b, double c) |
379 | 0 | { |
380 | 0 | Coordinate p0; |
381 | 0 | Coordinate p1; |
382 | | /* |
383 | | * Line eqn is ax + by = c |
384 | | * Slope is a/b. |
385 | | * If slope is steep, use y values as the inputs |
386 | | */ |
387 | 0 | if(fabs(b) > fabs(a)) { |
388 | 0 | p0 = Coordinate(0.0, c / b); |
389 | 0 | p1 = Coordinate(1.0, c / b - a / b); |
390 | 0 | } |
391 | 0 | else { |
392 | 0 | p0 = Coordinate(c / a, 0.0); |
393 | 0 | p1 = Coordinate(c / a - b / a, 1.0); |
394 | 0 | } |
395 | 0 | return LineSegment(p0, p1); |
396 | 0 | } |
397 | | |
398 | | |
399 | | std::unique_ptr<Geometry> |
400 | | MinimumDiameter::getMinimumRectangle(Geometry* geom) |
401 | 0 | { |
402 | 0 | MinimumDiameter md(geom); |
403 | 0 | return md.getMinimumRectangle(); |
404 | 0 | } |
405 | | |
406 | | std::unique_ptr<Geometry> |
407 | | MinimumDiameter::getMinimumDiameter(Geometry* geom) |
408 | 0 | { |
409 | 0 | MinimumDiameter md(geom); |
410 | 0 | return md.getDiameter(); |
411 | 0 | } |
412 | | |
413 | | } // namespace geos.algorithm |
414 | | } // namespace geos |