1use geo_types::{Coord, Geometry};
21
22use crate::algorithm::centroid::get_centroid;
23use crate::algorithm::interior_point::{dimension_non_empty, interior_point};
24use crate::algorithm::locate::simple_point_in_area_locator::locate;
25use crate::geom::location::INTERIOR;
26use crate::geometry_adapter::is_geometry_empty;
27
28pub fn centroid_first_interior_point(geom: &Geometry<f64>) -> Option<Coord<f64>> {
46 if is_geometry_empty(geom) {
47 return None;
48 }
49
50 let dim = dimension_non_empty(geom);
51 if dim != 2 {
52 return interior_point(geom);
53 }
54
55 if let Some(centroid) = get_centroid(geom)
56 && locate(centroid, geom) == INTERIOR
57 {
58 return Some(centroid);
59 }
60 interior_point(geom)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::centroid_first_interior_point;
66 use crate::algorithm::centroid::get_centroid;
67 use crate::algorithm::interior_point::interior_point;
68 use crate::algorithm::locate::simple_point_in_area_locator::locate;
69 use crate::geom::location::{BOUNDARY, EXTERIOR, INTERIOR};
70 use geo_types::{
71 Coord, Geometry, GeometryCollection, LineString, MultiPoint, MultiPolygon, Point, Polygon,
72 };
73
74 fn polygon(rings: Vec<Vec<(f64, f64)>>) -> Geometry<f64> {
76 let mut rings = rings.into_iter();
77 let shell = LineString::from(rings.next().expect("a polygon needs a shell"));
78 Geometry::Polygon(Polygon::new(shell, rings.map(LineString::from).collect()))
79 }
80
81 fn coord(x: f64, y: f64) -> Coord<f64> {
82 Coord { x, y }
83 }
84
85 fn triangle() -> Geometry<f64> {
86 polygon(vec![vec![(0.0, 0.0), (10.0, 0.0), (0.0, 10.0), (0.0, 0.0)]])
87 }
88
89 fn square() -> Geometry<f64> {
90 polygon(vec![vec![
91 (0.0, 0.0),
92 (10.0, 0.0),
93 (10.0, 10.0),
94 (0.0, 10.0),
95 (0.0, 0.0),
96 ]])
97 }
98
99 fn donut() -> Geometry<f64> {
101 polygon(vec![
102 vec![
103 (0.0, 0.0),
104 (10.0, 0.0),
105 (10.0, 10.0),
106 (0.0, 10.0),
107 (0.0, 0.0),
108 ],
109 vec![(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)],
110 ])
111 }
112
113 fn c_shape() -> Geometry<f64> {
116 polygon(vec![vec![
117 (0.0, 0.0),
118 (10.0, 0.0),
119 (10.0, 2.0),
120 (3.0, 2.0),
121 (3.0, 8.0),
122 (10.0, 8.0),
123 (10.0, 10.0),
124 (0.0, 10.0),
125 (0.0, 0.0),
126 ]])
127 }
128
129 fn l_shape() -> Geometry<f64> {
131 polygon(vec![vec![
132 (0.0, 0.0),
133 (10.0, 0.0),
134 (10.0, 3.0),
135 (3.0, 3.0),
136 (3.0, 10.0),
137 (0.0, 10.0),
138 (0.0, 0.0),
139 ]])
140 }
141
142 fn shell_identical_to_hole() -> Geometry<f64> {
144 let ring = vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)];
145 polygon(vec![ring.clone(), ring])
146 }
147
148 fn collinear_ring() -> Geometry<f64> {
150 polygon(vec![vec![(0.0, 0.0), (5.0, 0.0), (10.0, 0.0), (0.0, 0.0)]])
151 }
152
153 fn line() -> Geometry<f64> {
154 Geometry::LineString(LineString::from(vec![(0.0, 0.0), (10.0, 10.0)]))
155 }
156
157 #[test]
158 fn returns_the_centroid_when_it_lies_inside_an_areal_geometry() {
159 let geom = triangle();
160 let centroid = get_centroid(&geom).expect("a triangle has a centroid");
161 assert_eq!(locate(centroid, &geom), INTERIOR);
162 assert_eq!(centroid, coord(3.333333333333333, 3.333333333333333));
163 assert_eq!(centroid_first_interior_point(&geom), Some(centroid));
164 assert_eq!(interior_point(&geom), Some(coord(2.5, 5.0)));
167 }
168
169 #[test]
172 fn returns_the_centroid_for_a_square_where_both_branches_agree() {
173 let geom = square();
174 let centroid = get_centroid(&geom).expect("a square has a centroid");
175 assert_eq!(centroid, coord(5.0, 5.0));
176 assert_eq!(centroid_first_interior_point(&geom), Some(centroid));
177 assert_eq!(interior_point(&geom), Some(centroid));
178 }
179
180 #[test]
181 fn returns_the_centroid_for_a_collection_whose_areal_part_accepts_it() {
182 let geom = Geometry::GeometryCollection(GeometryCollection(vec![
183 polygon(vec![vec![
184 (0.0, 0.0),
185 (4.0, 0.0),
186 (4.0, 4.0),
187 (0.0, 4.0),
188 (0.0, 0.0),
189 ]]),
190 Geometry::LineString(LineString::from(vec![(0.0, 50.0), (10.0, 60.0)])),
191 ]));
192 let centroid = get_centroid(&geom).expect("the collection has a centroid");
193 assert_eq!(centroid, coord(2.0, 2.0));
194 assert_eq!(locate(centroid, &geom), INTERIOR);
195 assert_eq!(centroid_first_interior_point(&geom), Some(centroid));
196 }
197
198 #[test]
199 fn falls_back_when_the_centroid_lands_in_a_hole() {
200 let geom = donut();
201 assert_eq!(locate(get_centroid(&geom).unwrap(), &geom), EXTERIOR);
202 assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
203 assert_eq!(centroid_first_interior_point(&geom), Some(coord(1.5, 5.0)));
204 }
205
206 #[test]
207 fn falls_back_when_the_centroid_lands_in_a_notch() {
208 for geom in [c_shape(), l_shape()] {
209 let centroid = get_centroid(&geom).expect("both shapes have a centroid");
210 assert_eq!(locate(centroid, &geom), EXTERIOR);
211 assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
212 assert_ne!(centroid_first_interior_point(&geom), Some(centroid));
213 }
214 }
215
216 #[test]
217 fn falls_back_for_a_multi_polygon_whose_centroid_is_between_its_parts() {
218 let geom = Geometry::MultiPolygon(MultiPolygon(vec![
219 Polygon::new(
220 LineString::from(vec![
221 (0.0, 0.0),
222 (10.0, 0.0),
223 (10.0, 10.0),
224 (0.0, 10.0),
225 (0.0, 0.0),
226 ]),
227 vec![],
228 ),
229 Polygon::new(
230 LineString::from(vec![
231 (20.0, 0.0),
232 (30.0, 0.0),
233 (30.0, 10.0),
234 (20.0, 10.0),
235 (20.0, 0.0),
236 ]),
237 vec![],
238 ),
239 ]));
240 assert_eq!(get_centroid(&geom), Some(coord(15.0, 5.0)));
241 assert_eq!(locate(coord(15.0, 5.0), &geom), EXTERIOR);
242 assert_eq!(centroid_first_interior_point(&geom), Some(coord(5.0, 5.0)));
243 }
244
245 #[test]
246 fn falls_back_when_the_shell_is_its_own_hole() {
247 let geom = shell_identical_to_hole();
248 assert_eq!(get_centroid(&geom), Some(coord(2.0, 2.0)));
249 assert_eq!(locate(coord(2.0, 2.0), &geom), EXTERIOR);
250 assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
251 }
252
253 #[test]
257 fn rejects_a_centroid_that_lands_on_the_boundary() {
258 let geom = collinear_ring();
259 let centroid = get_centroid(&geom).expect("a collinear ring has a centroid");
260 assert_eq!(centroid, coord(5.0, 0.0));
261 assert_eq!(locate(centroid, &geom), BOUNDARY);
262 assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
263 assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
264 }
265
266 #[test]
269 fn falls_back_for_a_collection_whose_centroid_is_dragged_off_by_a_line() {
270 let geom = Geometry::GeometryCollection(GeometryCollection(vec![
271 collinear_ring(),
272 Geometry::LineString(LineString::from(vec![(0.0, 50.0), (10.0, 60.0)])),
273 ]));
274 assert_eq!(get_centroid(&geom), Some(coord(5.0, 22.781745930520227)));
275 assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
276 }
277
278 #[test]
279 fn delegates_every_dimension_below_two() {
280 let point = Geometry::Point(Point::new(5.0, 5.0));
281 let points = Geometry::MultiPoint(MultiPoint(vec![
282 Point::new(0.0, 0.0),
283 Point::new(10.0, 10.0),
284 ]));
285 let mixed = Geometry::GeometryCollection(GeometryCollection(vec![
286 Geometry::Point(Point::new(5.0, 5.0)),
287 line(),
288 ]));
289 for geom in [point, line(), points, mixed] {
290 assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
291 }
292 }
293
294 #[test]
298 fn a_lineal_centroid_would_have_been_rejected_anyway() {
299 let geom = line();
300 let centroid = get_centroid(&geom).expect("a line has a centroid");
301 assert_eq!(centroid, coord(5.0, 5.0));
302 assert_eq!(locate(centroid, &geom), EXTERIOR);
303 assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
304 }
305
306 #[test]
307 fn answers_none_for_every_empty_shape() {
308 let empty_polygon = Geometry::Polygon(Polygon::new(LineString(vec![]), vec![]));
309 let empty_multi_polygon = Geometry::MultiPolygon(MultiPolygon(vec![]));
310 let multi_polygon_of_one_empty =
311 Geometry::MultiPolygon(MultiPolygon(vec![Polygon::new(LineString(vec![]), vec![])]));
312 let hole_without_a_shell = Geometry::Polygon(Polygon::new(
313 LineString(vec![]),
314 vec![LineString::from(vec![
315 (0.0, 0.0),
316 (4.0, 0.0),
317 (4.0, 4.0),
318 (0.0, 0.0),
319 ])],
320 ));
321 for geom in [
322 empty_polygon,
323 empty_multi_polygon,
324 multi_polygon_of_one_empty,
325 hole_without_a_shell,
326 ] {
327 assert_eq!(centroid_first_interior_point(&geom), None);
328 assert_eq!(interior_point(&geom), None);
329 }
330 }
331
332 #[test]
335 fn always_returns_either_the_centroid_or_the_algorithms_own_point() {
336 for geom in [
337 triangle(),
338 square(),
339 donut(),
340 c_shape(),
341 l_shape(),
342 shell_identical_to_hole(),
343 collinear_ring(),
344 line(),
345 ] {
346 let result = centroid_first_interior_point(&geom);
347 assert!(result == get_centroid(&geom) || result == interior_point(&geom));
348 }
349 }
350}