Skip to content

Commit 20225da

Browse files
committed
Avoid atan2 in mrr
1 parent 16b2d05 commit 20225da

File tree

1 file changed

+82
-34
lines changed

1 file changed

+82
-34
lines changed

geo/src/algorithm/minimum_rotated_rect.rs

+82-34
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use num_traits::Float;
1+
use geo_types::LineString;
2+
use num_traits::Bounded;
23

3-
use crate::{
4-
algorithm::{centroid::Centroid, rotate::Rotate, BoundingRect, CoordsIter},
5-
Area, ConvexHull, CoordFloat, GeoFloat, GeoNum, LinesIter, Polygon,
6-
};
4+
use crate::{algorithm::CoordsIter, ConvexHull, CoordFloat, GeoFloat, GeoNum, Polygon};
75
/// Return the minimum bounding rectangle(MBR) of geometry
86
/// reference: <https://en.wikipedia.org/wiki/Minimum_bounding_box>
97
/// minimum rotated rect is the rectangle that can enclose all points given
@@ -20,11 +18,11 @@ use crate::{
2018
/// assert_eq!(
2119
/// mbr.exterior(),
2220
/// &LineString::from(vec![
23-
/// (1.7000000000000006, 24.6),
24-
/// (1.4501458363715918, 30.446587428904767),
25-
/// (14.4, 31.0),
26-
/// (14.649854163628408, 25.153412571095235),
27-
/// (1.7000000000000006, 24.6),
21+
/// (1.7000000000000004, 24.600000000000005),
22+
/// (14.649854163628412, 25.153412571095238),
23+
/// (14.400000000000002, 31.000000000000007),
24+
/// (1.4501458363715916, 30.446587428904774),
25+
/// (1.7000000000000004, 24.600000000000005),
2826
/// ])
2927
/// );
3028
/// ```
@@ -41,24 +39,74 @@ where
4139
type Scalar = T;
4240

4341
fn minimum_rotated_rect(&self) -> Option<Polygon<Self::Scalar>> {
44-
let convex_poly = ConvexHull::convex_hull(self);
45-
let mut min_area: T = Float::max_value();
46-
let mut min_angle: T = T::zero();
47-
let mut rect_poly: Option<Polygon<T>> = None;
48-
let rotate_point = convex_poly.centroid();
49-
for line in convex_poly.exterior().lines_iter() {
50-
let (ci, cii) = line.points();
51-
let angle = (cii.y() - ci.y()).atan2(cii.x() - ci.x()).to_degrees();
52-
let rotated_poly = Rotate::rotate_around_point(&convex_poly, -angle, rotate_point?);
53-
let tmp_poly = rotated_poly.bounding_rect()?.to_polygon();
54-
let area = tmp_poly.unsigned_area();
42+
let hull = ConvexHull::convex_hull(self);
43+
44+
// We take unit vectors along and perpendicular to each edge, then use
45+
// them to build a rotation matrix and apply it to the convex hull,
46+
// keeping track of the best AABB found.
47+
//
48+
// See also the discussion at
49+
// https://gis.stackexchange.com/questions/22895/finding-minimum-area-rectangle-for-given-points/22934
50+
let mut min_area = <T as Bounded>::max_value();
51+
let mut best_edge = None;
52+
let (mut best_min_x, mut best_max_x) = (T::zero(), T::zero());
53+
let (mut best_min_y, mut best_max_y) = (T::zero(), T::zero());
54+
for edge in hull.exterior().lines() {
55+
let (dx, dy) = edge.delta().x_y();
56+
let norm = dx.hypot(dy);
57+
if norm.is_zero() {
58+
continue;
59+
}
60+
let edge = (dx / norm, dy / norm);
61+
62+
let (mut min_x, mut max_x) = (<T as Bounded>::max_value(), <T as Bounded>::min_value());
63+
let (mut min_y, mut max_y) = (<T as Bounded>::max_value(), <T as Bounded>::min_value());
64+
for point in hull.exterior().points() {
65+
let x = point.y() * edge.1 + point.x() * edge.0;
66+
let y = point.y() * edge.0 - point.x() * edge.1;
67+
68+
min_x = min_x.min(x);
69+
max_x = max_x.max(x);
70+
min_y = min_y.min(y);
71+
max_y = max_y.max(y);
72+
}
73+
74+
let area = (max_y - min_y) * (max_x - min_x);
5575
if area < min_area {
5676
min_area = area;
57-
min_angle = angle;
58-
rect_poly = Some(tmp_poly);
77+
best_edge = Some(edge);
78+
best_min_x = min_x;
79+
best_max_x = max_x;
80+
best_min_y = min_y;
81+
best_max_y = max_y;
5982
}
6083
}
61-
Some(rect_poly?.rotate_around_point(min_angle, rotate_point?))
84+
85+
if let Some(e) = best_edge {
86+
let p1 = (
87+
best_min_x * e.0 - best_min_y * e.1,
88+
best_min_x * e.1 + best_min_y * e.0,
89+
);
90+
let p2 = (
91+
best_max_x * e.0 - best_min_y * e.1,
92+
best_max_x * e.1 + best_min_y * e.0,
93+
);
94+
let p3 = (
95+
best_max_x * e.0 - best_max_y * e.1,
96+
best_max_x * e.1 + best_max_y * e.0,
97+
);
98+
let p4 = (
99+
best_min_x * e.0 - best_max_y * e.1,
100+
best_min_x * e.1 + best_max_y * e.0,
101+
);
102+
let rectangle = Polygon::new(
103+
LineString(vec![p1.into(), p2.into(), p3.into(), p4.into(), p1.into()]),
104+
vec![],
105+
);
106+
Some(Polygon::from(rectangle))
107+
} else {
108+
None
109+
}
62110
}
63111
}
64112

@@ -75,11 +123,11 @@ mod test {
75123
assert_eq!(
76124
mbr.exterior(),
77125
&LineString::from(vec![
78-
(1.7000000000000006, 24.6),
79-
(1.4501458363715918, 30.446587428904767),
80-
(14.4, 31.0),
81-
(14.649854163628408, 25.153412571095235),
82-
(1.7000000000000006, 24.6),
126+
(1.7000000000000004, 24.600000000000005),
127+
(14.649854163628412, 25.153412571095238),
128+
(14.400000000000002, 31.000000000000007),
129+
(1.4501458363715916, 30.446587428904774),
130+
(1.7000000000000004, 24.600000000000005),
83131
])
84132
);
85133
}
@@ -90,11 +138,11 @@ mod test {
90138
assert_eq!(
91139
mbr.exterior(),
92140
&LineString::from(vec![
93-
(1.7000000000000006, 24.6),
94-
(1.4501458363715918, 30.446587428904767),
95-
(14.4, 31.0),
96-
(14.649854163628408, 25.153412571095235),
97-
(1.7000000000000006, 24.6),
141+
(1.7000000000000004, 24.600000000000005),
142+
(14.649854163628412, 25.153412571095238),
143+
(14.400000000000002, 31.000000000000007),
144+
(1.4501458363715916, 30.446587428904774),
145+
(1.7000000000000004, 24.600000000000005),
98146
])
99147
);
100148
}

0 commit comments

Comments
 (0)