use geo_types::{Coord, Geometry};
use crate::algorithm::centroid::get_centroid;
use crate::algorithm::interior_point::{dimension_non_empty, interior_point};
use crate::algorithm::locate::simple_point_in_area_locator::locate;
use crate::geom::location::INTERIOR;
use crate::geometry_adapter::is_geometry_empty;
pub fn centroid_first_interior_point(geom: &Geometry<f64>) -> Option<Coord<f64>> {
if is_geometry_empty(geom) {
return None;
}
let dim = dimension_non_empty(geom);
if dim != 2 {
return interior_point(geom);
}
if let Some(centroid) = get_centroid(geom)
&& locate(centroid, geom) == INTERIOR
{
return Some(centroid);
}
interior_point(geom)
}
#[cfg(test)]
mod tests {
use super::centroid_first_interior_point;
use crate::algorithm::centroid::get_centroid;
use crate::algorithm::interior_point::interior_point;
use crate::algorithm::locate::simple_point_in_area_locator::locate;
use crate::geom::location::{BOUNDARY, EXTERIOR, INTERIOR};
use geo_types::{
Coord, Geometry, GeometryCollection, LineString, MultiPoint, MultiPolygon, Point, Polygon,
};
fn polygon(rings: Vec<Vec<(f64, f64)>>) -> Geometry<f64> {
let mut rings = rings.into_iter();
let shell = LineString::from(rings.next().expect("a polygon needs a shell"));
Geometry::Polygon(Polygon::new(shell, rings.map(LineString::from).collect()))
}
fn coord(x: f64, y: f64) -> Coord<f64> {
Coord { x, y }
}
fn triangle() -> Geometry<f64> {
polygon(vec![vec![(0.0, 0.0), (10.0, 0.0), (0.0, 10.0), (0.0, 0.0)]])
}
fn square() -> Geometry<f64> {
polygon(vec![vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0),
]])
}
fn donut() -> Geometry<f64> {
polygon(vec![
vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0),
],
vec![(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)],
])
}
fn c_shape() -> Geometry<f64> {
polygon(vec![vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 2.0),
(3.0, 2.0),
(3.0, 8.0),
(10.0, 8.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0),
]])
}
fn l_shape() -> Geometry<f64> {
polygon(vec![vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 3.0),
(3.0, 3.0),
(3.0, 10.0),
(0.0, 10.0),
(0.0, 0.0),
]])
}
fn shell_identical_to_hole() -> Geometry<f64> {
let ring = vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)];
polygon(vec![ring.clone(), ring])
}
fn collinear_ring() -> Geometry<f64> {
polygon(vec![vec![(0.0, 0.0), (5.0, 0.0), (10.0, 0.0), (0.0, 0.0)]])
}
fn line() -> Geometry<f64> {
Geometry::LineString(LineString::from(vec![(0.0, 0.0), (10.0, 10.0)]))
}
#[test]
fn returns_the_centroid_when_it_lies_inside_an_areal_geometry() {
let geom = triangle();
let centroid = get_centroid(&geom).expect("a triangle has a centroid");
assert_eq!(locate(centroid, &geom), INTERIOR);
assert_eq!(centroid, coord(3.333333333333333, 3.333333333333333));
assert_eq!(centroid_first_interior_point(&geom), Some(centroid));
assert_eq!(interior_point(&geom), Some(coord(2.5, 5.0)));
}
#[test]
fn returns_the_centroid_for_a_square_where_both_branches_agree() {
let geom = square();
let centroid = get_centroid(&geom).expect("a square has a centroid");
assert_eq!(centroid, coord(5.0, 5.0));
assert_eq!(centroid_first_interior_point(&geom), Some(centroid));
assert_eq!(interior_point(&geom), Some(centroid));
}
#[test]
fn returns_the_centroid_for_a_collection_whose_areal_part_accepts_it() {
let geom = Geometry::GeometryCollection(GeometryCollection(vec![
polygon(vec![vec![
(0.0, 0.0),
(4.0, 0.0),
(4.0, 4.0),
(0.0, 4.0),
(0.0, 0.0),
]]),
Geometry::LineString(LineString::from(vec![(0.0, 50.0), (10.0, 60.0)])),
]));
let centroid = get_centroid(&geom).expect("the collection has a centroid");
assert_eq!(centroid, coord(2.0, 2.0));
assert_eq!(locate(centroid, &geom), INTERIOR);
assert_eq!(centroid_first_interior_point(&geom), Some(centroid));
}
#[test]
fn falls_back_when_the_centroid_lands_in_a_hole() {
let geom = donut();
assert_eq!(locate(get_centroid(&geom).unwrap(), &geom), EXTERIOR);
assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
assert_eq!(centroid_first_interior_point(&geom), Some(coord(1.5, 5.0)));
}
#[test]
fn falls_back_when_the_centroid_lands_in_a_notch() {
for geom in [c_shape(), l_shape()] {
let centroid = get_centroid(&geom).expect("both shapes have a centroid");
assert_eq!(locate(centroid, &geom), EXTERIOR);
assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
assert_ne!(centroid_first_interior_point(&geom), Some(centroid));
}
}
#[test]
fn falls_back_for_a_multi_polygon_whose_centroid_is_between_its_parts() {
let geom = Geometry::MultiPolygon(MultiPolygon(vec![
Polygon::new(
LineString::from(vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0),
]),
vec![],
),
Polygon::new(
LineString::from(vec![
(20.0, 0.0),
(30.0, 0.0),
(30.0, 10.0),
(20.0, 10.0),
(20.0, 0.0),
]),
vec![],
),
]));
assert_eq!(get_centroid(&geom), Some(coord(15.0, 5.0)));
assert_eq!(locate(coord(15.0, 5.0), &geom), EXTERIOR);
assert_eq!(centroid_first_interior_point(&geom), Some(coord(5.0, 5.0)));
}
#[test]
fn falls_back_when_the_shell_is_its_own_hole() {
let geom = shell_identical_to_hole();
assert_eq!(get_centroid(&geom), Some(coord(2.0, 2.0)));
assert_eq!(locate(coord(2.0, 2.0), &geom), EXTERIOR);
assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
}
#[test]
fn rejects_a_centroid_that_lands_on_the_boundary() {
let geom = collinear_ring();
let centroid = get_centroid(&geom).expect("a collinear ring has a centroid");
assert_eq!(centroid, coord(5.0, 0.0));
assert_eq!(locate(centroid, &geom), BOUNDARY);
assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
}
#[test]
fn falls_back_for_a_collection_whose_centroid_is_dragged_off_by_a_line() {
let geom = Geometry::GeometryCollection(GeometryCollection(vec![
collinear_ring(),
Geometry::LineString(LineString::from(vec![(0.0, 50.0), (10.0, 60.0)])),
]));
assert_eq!(get_centroid(&geom), Some(coord(5.0, 22.781745930520227)));
assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
}
#[test]
fn delegates_every_dimension_below_two() {
let point = Geometry::Point(Point::new(5.0, 5.0));
let points = Geometry::MultiPoint(MultiPoint(vec![
Point::new(0.0, 0.0),
Point::new(10.0, 10.0),
]));
let mixed = Geometry::GeometryCollection(GeometryCollection(vec![
Geometry::Point(Point::new(5.0, 5.0)),
line(),
]));
for geom in [point, line(), points, mixed] {
assert_eq!(centroid_first_interior_point(&geom), interior_point(&geom));
}
}
#[test]
fn a_lineal_centroid_would_have_been_rejected_anyway() {
let geom = line();
let centroid = get_centroid(&geom).expect("a line has a centroid");
assert_eq!(centroid, coord(5.0, 5.0));
assert_eq!(locate(centroid, &geom), EXTERIOR);
assert_eq!(centroid_first_interior_point(&geom), Some(coord(0.0, 0.0)));
}
#[test]
fn answers_none_for_every_empty_shape() {
let empty_polygon = Geometry::Polygon(Polygon::new(LineString(vec![]), vec![]));
let empty_multi_polygon = Geometry::MultiPolygon(MultiPolygon(vec![]));
let multi_polygon_of_one_empty =
Geometry::MultiPolygon(MultiPolygon(vec![Polygon::new(LineString(vec![]), vec![])]));
let hole_without_a_shell = Geometry::Polygon(Polygon::new(
LineString(vec![]),
vec![LineString::from(vec![
(0.0, 0.0),
(4.0, 0.0),
(4.0, 4.0),
(0.0, 0.0),
])],
));
for geom in [
empty_polygon,
empty_multi_polygon,
multi_polygon_of_one_empty,
hole_without_a_shell,
] {
assert_eq!(centroid_first_interior_point(&geom), None);
assert_eq!(interior_point(&geom), None);
}
}
#[test]
fn always_returns_either_the_centroid_or_the_algorithms_own_point() {
for geom in [
triangle(),
square(),
donut(),
c_shape(),
l_shape(),
shell_identical_to_hole(),
collinear_ring(),
line(),
] {
let result = centroid_first_interior_point(&geom);
assert!(result == get_centroid(&geom) || result == interior_point(&geom));
}
}
}