interior_point/algorithm/interior_point.rs
1//! Ports `InteriorPoint.java`.
2//!
3//! @jts InteriorPoint
4
5use geo_types::{Coord, Geometry};
6
7use crate::algorithm::interior_point_area::interior_point_area;
8use crate::algorithm::interior_point_line::interior_point_line;
9use crate::algorithm::interior_point_point::interior_point_point;
10use crate::geometry_adapter::{dimension, is_geometry_empty};
11
12/// Computes a location of an interior point in a `Geometry`.
13/// Handles all geometry types.
14///
15/// For collections, the interior point is computed for the collection of
16/// non-empty elements of highest dimension:
17///
18/// - **Dimension 2** (Polygon/MultiPolygon) — the point is in the interior of
19/// the widest scan-line section.
20/// - **Dimension 1** (LineString/MultiLineString) — the point is the interior
21/// vertex closest to the centroid.
22/// - **Dimension 0** (Point/MultiPoint) — the point is the point closest to
23/// the centroid.
24///
25/// Returns the location of an interior point, or `None` if the input is empty.
26///
27/// @jts InteriorPoint#getInteriorPoint(Geometry)
28/// @jts-deviate module-level name — `get_interior_point` would collide with the
29/// same static factory in the other three modules.
30pub fn interior_point(geom: &Geometry<f64>) -> Option<Coord<f64>> {
31 if is_geometry_empty(geom) {
32 return None;
33 }
34
35 let interior_pt;
36 let dim = dimension_non_empty(geom);
37 // this should not happen, but just in case...
38 if dim < 0 {
39 return None;
40 }
41 if dim == 0 {
42 interior_pt = interior_point_point(geom);
43 } else if dim == 1 {
44 interior_pt = interior_point_line(geom);
45 } else {
46 interior_pt = interior_point_area(geom);
47 }
48 interior_pt
49}
50
51/// @jts InteriorPoint#dimensionNonEmpty(Geometry)
52pub(crate) fn dimension_non_empty(geom: &Geometry<f64>) -> i32 {
53 // JTS builds the filter and applies it; here the filter is the traversal,
54 // so this is a single call.
55 dimension_non_empty_filter(geom)
56}
57
58/// @jts InteriorPoint.DimensionNonEmptyFilter#filter(Geometry)
59/// @jts InteriorPoint.DimensionNonEmptyFilter#getDimension()
60/// @jts-deviate GeometryFilter / Geometry.apply() are not part of the adapted
61/// geometry model, so the filter becomes a recursive traversal with identical
62/// semantics. The receptacle is preserved, per the structure rule: the function
63/// keeps the filter's name and its body mirrors `filter(Geometry elem)`,
64/// returning what `getDimension()` would have reported.
65fn dimension_non_empty_filter(elem: &Geometry<f64>) -> i32 {
66 let mut dim = -1;
67 if let Geometry::GeometryCollection(gc) = elem {
68 for g in &gc.0 {
69 let elem_dim = dimension_non_empty_filter(g);
70 if elem_dim > dim {
71 dim = elem_dim;
72 }
73 }
74 return dim;
75 }
76 if !is_geometry_empty(elem) {
77 let elem_dim = dimension(elem);
78 if elem_dim > dim {
79 dim = elem_dim;
80 }
81 }
82 dim
83}