Skip to content

Commit 830e499

Browse files
Sushant NadavadeSushant Nadavade
authored andcommitted
Added RotatingCalipers to geometry
1 parent 3eb521b commit 830e499

File tree

2 files changed

+666
-0
lines changed

2 files changed

+666
-0
lines changed
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
package com.thealgorithms.geometry;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
7+
/**
8+
* A class implementing the Rotating Calipers algorithm for geometric computations on convex polygons.
9+
*
10+
* The Rotating Calipers algorithm is an efficient technique for solving various geometric problems
11+
* on convex polygons, including:
12+
* - Computing the diameter (maximum distance between any two points)
13+
* - Computing the width (minimum distance between parallel supporting lines)
14+
* - Finding the minimum-area bounding rectangle
15+
*
16+
* Algorithm Description:
17+
* 1. Compute the convex hull of the given points
18+
* 2. Use rotating calipers (parallel lines) that rotate around the convex hull
19+
* 3. For each rotation, compute the desired geometric property
20+
* 4. Return the optimal result
21+
*
22+
* Time Complexity: O(n) where n is the number of points in the convex hull
23+
* Space Complexity: O(n) for storing the convex hull
24+
*
25+
* Reference:
26+
* Shamos, M. I. (1978). Computational Geometry.
27+
*
28+
* @author TheAlgorithms
29+
*/
30+
public final class RotatingCalipers {
31+
32+
private RotatingCalipers() {
33+
}
34+
35+
/**
36+
* Represents a pair of points with their distance.
37+
*/
38+
public static record PointPair(Point p1, Point p2, double distance) {
39+
@Override
40+
public String toString() {
41+
return String.format("PointPair(%s, %s, distance=%.2f)", p1, p2, distance);
42+
}
43+
}
44+
45+
/**
46+
* Represents a rectangle with its area.
47+
*/
48+
public static record Rectangle(Point bottomLeft, Point topRight, double area) {
49+
@Override
50+
public String toString() {
51+
return String.format("Rectangle(%s, %s, area=%.2f)", bottomLeft, topRight, area);
52+
}
53+
}
54+
55+
/**
56+
* Computes the diameter of a convex polygon using rotating calipers.
57+
* The diameter is the maximum distance between any two points of the polygon.
58+
*
59+
* @param points List of points representing a convex polygon
60+
* @return PointPair containing the two points with maximum distance and the distance
61+
* @throws IllegalArgumentException if points is null or has less than 2 points
62+
*/
63+
public static PointPair computeDiameter(List<Point> points) {
64+
if (points == null || points.size() < 2) {
65+
throw new IllegalArgumentException("Points list must contain at least 2 points");
66+
}
67+
68+
List<Point> hull = ConvexHull.convexHullRecursive(new ArrayList<>(points));
69+
if (hull.size() < 2) {
70+
throw new IllegalArgumentException("Convex hull must contain at least 2 points");
71+
}
72+
73+
hull = ensureCounterClockwiseOrder(hull);
74+
75+
if (hull.size() == 2) {
76+
Point p1 = hull.get(0);
77+
Point p2 = hull.get(1);
78+
return new PointPair(p1, p2, distance(p1, p2));
79+
}
80+
81+
int n = hull.size();
82+
PointPair maxPair = null;
83+
double maxDistance = 0.0;
84+
85+
int j = 1;
86+
for (int i = 0; i < n; i++) {
87+
Point p1 = hull.get(i);
88+
89+
while (true) {
90+
Point next = hull.get((j + 1) % n);
91+
double dist1 = distance(p1, hull.get(j));
92+
double dist2 = distance(p1, next);
93+
94+
if (dist2 > dist1) {
95+
j = (j + 1) % n;
96+
} else {
97+
break;
98+
}
99+
}
100+
101+
double dist = distance(p1, hull.get(j));
102+
if (dist > maxDistance) {
103+
maxDistance = dist;
104+
maxPair = new PointPair(p1, hull.get(j), dist);
105+
}
106+
}
107+
108+
return maxPair;
109+
}
110+
111+
/**
112+
* Computes the width of a convex polygon using rotating calipers.
113+
* The width is the minimum distance between two parallel supporting lines.
114+
*
115+
* @param points List of points representing a convex polygon
116+
* @return The minimum width of the polygon
117+
* @throws IllegalArgumentException if points is null or has less than 2 points
118+
*/
119+
public static double computeWidth(List<Point> points) {
120+
if (points == null || points.size() < 2) {
121+
throw new IllegalArgumentException("Points list must contain at least 2 points");
122+
}
123+
124+
List<Point> hull = ConvexHull.convexHullRecursive(new ArrayList<>(points));
125+
if (hull.size() < 2) {
126+
throw new IllegalArgumentException("Convex hull must contain at least 2 points");
127+
}
128+
129+
hull = ensureCounterClockwiseOrder(hull);
130+
131+
if (hull.size() == 2) {
132+
return 0.0;
133+
}
134+
135+
int n = hull.size();
136+
double minWidth = Double.MAX_VALUE;
137+
138+
int j = 1;
139+
for (int i = 0; i < n; i++) {
140+
Point p1 = hull.get(i);
141+
Point p2 = hull.get((i + 1) % n);
142+
143+
while (true) {
144+
Point next = hull.get((j + 1) % n);
145+
double dist1 = distanceToLine(p1, p2, hull.get(j));
146+
double dist2 = distanceToLine(p1, p2, next);
147+
148+
if (dist2 > dist1) {
149+
j = (j + 1) % n;
150+
} else {
151+
break;
152+
}
153+
}
154+
155+
double width = distanceToLine(p1, p2, hull.get(j));
156+
minWidth = Math.min(minWidth, width);
157+
}
158+
159+
return minWidth;
160+
}
161+
162+
/**
163+
* Computes the minimum-area bounding rectangle of a convex polygon using rotating calipers.
164+
*
165+
* @param points List of points representing a convex polygon
166+
* @return Rectangle containing the minimum-area bounding rectangle
167+
* @throws IllegalArgumentException if points is null or has less than 2 points
168+
*/
169+
public static Rectangle computeMinimumAreaBoundingRectangle(List<Point> points) {
170+
if (points == null || points.size() < 2) {
171+
throw new IllegalArgumentException("Points list must contain at least 2 points");
172+
}
173+
174+
List<Point> hull = ConvexHull.convexHullRecursive(new ArrayList<>(points));
175+
if (hull.size() < 2) {
176+
throw new IllegalArgumentException("Convex hull must contain at least 2 points");
177+
}
178+
179+
hull = ensureCounterClockwiseOrder(hull);
180+
181+
if (hull.size() == 2) {
182+
Point p1 = hull.get(0);
183+
Point p2 = hull.get(1);
184+
return new Rectangle(p1, p2, 0.0);
185+
}
186+
187+
int n = hull.size();
188+
double minArea = Double.MAX_VALUE;
189+
Rectangle bestRectangle = null;
190+
191+
for (int i = 0; i < n; i++) {
192+
Point p1 = hull.get(i);
193+
Point p2 = hull.get((i + 1) % n);
194+
195+
int j = findAntipodalPoint(hull, i);
196+
197+
double edgeLength = distance(p1, p2);
198+
double height = distanceToLine(p1, p2, hull.get(j));
199+
200+
double area = edgeLength * height;
201+
202+
if (area < minArea) {
203+
minArea = area;
204+
Point bottomLeft = computeRectangleCorner(p1, p2, hull.get(j), true);
205+
Point topRight = computeRectangleCorner(p1, p2, hull.get(j), false);
206+
bestRectangle = new Rectangle(bottomLeft, topRight, area);
207+
}
208+
}
209+
210+
return bestRectangle;
211+
}
212+
213+
/**
214+
* Finds the antipodal point for a given edge using rotating calipers.
215+
*/
216+
private static int findAntipodalPoint(List<Point> hull, int edgeStart) {
217+
int n = hull.size();
218+
int j = (edgeStart + 1) % n;
219+
220+
Point p1 = hull.get(edgeStart);
221+
Point p2 = hull.get((edgeStart + 1) % n);
222+
223+
while (true) {
224+
Point next = hull.get((j + 1) % n);
225+
double dist1 = distanceToLine(p1, p2, hull.get(j));
226+
double dist2 = distanceToLine(p1, p2, next);
227+
228+
if (dist2 > dist1) {
229+
j = (j + 1) % n;
230+
} else {
231+
break;
232+
}
233+
}
234+
235+
return j;
236+
}
237+
238+
/**
239+
* Computes a corner of the bounding rectangle.
240+
*/
241+
private static Point computeRectangleCorner(Point p1, Point p2, Point antipodal, boolean isBottomLeft) {
242+
int minX = Math.min(Math.min(p1.x(), p2.x()), antipodal.x());
243+
int maxX = Math.max(Math.max(p1.x(), p2.x()), antipodal.x());
244+
int minY = Math.min(Math.min(p1.y(), p2.y()), antipodal.y());
245+
int maxY = Math.max(Math.max(p1.y(), p2.y()), antipodal.y());
246+
247+
if (isBottomLeft) {
248+
return new Point(minX, minY);
249+
} else {
250+
return new Point(maxX, maxY);
251+
}
252+
}
253+
254+
/**
255+
* Computes the Euclidean distance between two points.
256+
*/
257+
private static double distance(Point p1, Point p2) {
258+
int dx = p2.x() - p1.x();
259+
int dy = p2.y() - p1.y();
260+
return Math.sqrt(dx * dx + dy * dy);
261+
}
262+
263+
/**
264+
* Computes the perpendicular distance from a point to a line defined by two points.
265+
*/
266+
private static double distanceToLine(Point lineStart, Point lineEnd, Point point) {
267+
int dx = lineEnd.x() - lineStart.x();
268+
int dy = lineEnd.y() - lineStart.y();
269+
270+
if (dx == 0 && dy == 0) {
271+
return distance(lineStart, point);
272+
}
273+
274+
int px = point.x() - lineStart.x();
275+
int py = point.y() - lineStart.y();
276+
277+
double crossProduct = Math.abs(px * dy - py * dx);
278+
double lineLength = Math.sqrt(dx * dx + dy * dy);
279+
280+
return crossProduct / lineLength;
281+
}
282+
283+
/**
284+
* Ensures the hull points are in counter-clockwise order for rotating calipers.
285+
* The convex hull algorithm returns points sorted by natural order, but rotating calipers
286+
* requires counter-clockwise ordering.
287+
*/
288+
private static List<Point> ensureCounterClockwiseOrder(List<Point> hull) {
289+
if (hull.size() <= 2) {
290+
return hull;
291+
}
292+
293+
Point bottomMost = hull.get(0);
294+
int bottomIndex = 0;
295+
for (int i = 1; i < hull.size(); i++) {
296+
Point p = hull.get(i);
297+
if (p.y() < bottomMost.y() || (p.y() == bottomMost.y() && p.x() < bottomMost.x())) {
298+
bottomMost = p;
299+
bottomIndex = i;
300+
}
301+
}
302+
303+
List<Point> orderedHull = new ArrayList<>();
304+
for (int i = 0; i < hull.size(); i++) {
305+
orderedHull.add(hull.get((bottomIndex + i) % hull.size()));
306+
}
307+
308+
if (orderedHull.size() >= 3) {
309+
Point p1 = orderedHull.get(0);
310+
Point p2 = orderedHull.get(1);
311+
Point p3 = orderedHull.get(2);
312+
313+
if (Point.orientation(p1, p2, p3) < 0) {
314+
Collections.reverse(orderedHull);
315+
Collections.rotate(orderedHull, 1);
316+
}
317+
}
318+
319+
return orderedHull;
320+
}
321+
}

0 commit comments

Comments
 (0)