Skip to main content

interior_point/
lib.rs

1//! JTS InteriorPoint algorithm ported to Rust.
2//!
3//! Computes an interior point (representative point) of a geometry.
4//! The point is guaranteed to lie inside the geometry for area geometries.
5
6mod interior_point_area;
7mod interior_point_line;
8mod interior_point_point;
9
10#[cfg(feature = "wasm")]
11mod wasm;
12
13use geo_types::{Coord, Geometry};
14
15use interior_point_area::interior_point_area;
16use interior_point_line::interior_point_line;
17use interior_point_point::interior_point_point;
18
19/// Computes an interior point of the given geometry.
20///
21/// For different geometry dimensions:
22/// - Area (Polygon/MultiPolygon): uses scanline algorithm
23/// - Line (LineString/MultiLineString): finds vertex closest to centroid
24/// - Point (Point/MultiPoint): finds point closest to centroid
25///
26/// For GeometryCollections, uses the highest-dimension component.
27///
28/// Returns `None` if the geometry is empty.
29pub fn interior_point(geometry: &Geometry<f64>) -> Option<Coord<f64>> {
30    let dim = dimension_non_empty(geometry);
31    if dim < 0 {
32        return None;
33    }
34
35    match dim {
36        2 => interior_point_area(geometry),
37        1 => interior_point_line(geometry),
38        _ => interior_point_point(geometry),
39    }
40}
41
42/// Determines the highest dimension of non-empty components in a geometry.
43/// Returns -1 if no non-empty components exist.
44fn dimension_non_empty(geometry: &Geometry<f64>) -> i32 {
45    if is_geometry_empty(geometry) {
46        return -1;
47    }
48
49    match geometry {
50        Geometry::Point(_) | Geometry::MultiPoint(_) => 0,
51        Geometry::LineString(_) | Geometry::MultiLineString(_) => 1,
52        Geometry::Polygon(_) | Geometry::MultiPolygon(_) => 2,
53        Geometry::GeometryCollection(gc) => {
54            gc.0.iter().map(dimension_non_empty).max().unwrap_or(-1)
55        }
56        _ => -1,
57    }
58}
59
60/// Check if a geometry is empty.
61fn is_geometry_empty(geometry: &Geometry<f64>) -> bool {
62    match geometry {
63        Geometry::Point(_) => false, // geo-types Point cannot be empty
64        Geometry::MultiPoint(mp) => mp.0.is_empty(),
65        Geometry::LineString(ls) => ls.0.is_empty(),
66        Geometry::MultiLineString(mls) => {
67            mls.0.is_empty() || mls.0.iter().all(|ls| ls.0.is_empty())
68        }
69        Geometry::Polygon(p) => p.exterior().0.is_empty(),
70        Geometry::MultiPolygon(mp) => {
71            mp.0.is_empty() || mp.0.iter().all(|p| p.exterior().0.is_empty())
72        }
73        Geometry::GeometryCollection(gc) => gc.0.is_empty() || gc.0.iter().all(is_geometry_empty),
74        _ => true,
75    }
76}