Skip to main content

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}