Skip to main content

ezu_features/ops/
convert.rs

1//! Conversion helpers between the crate's owned `(i32, i32)` /
2//! [`Polygon`] geometry and the float-based shape types used by `geo`
3//! and `i_overlay`.
4//!
5//! The crate stores tile-local pixel coordinates as integers. Numeric
6//! ops (centroid, buffer, simplify, …) work in `f64` and round back to
7//! `i32` at the boundary. Helpers here are kept small and allocation-
8//! explicit so call sites can see when a copy happens.
9
10use crate::Polygon;
11
12/// Path = open or closed polyline as float points.
13pub type Path = Vec<[f64; 2]>;
14/// Contour set: one outer ring + zero-or-more holes, all as float
15/// points. Matches `i_overlay`'s `Shape` shape (one entry in
16/// `Shapes<P>`).
17pub type FloatShape = Vec<Path>;
18
19#[inline]
20pub fn pt_to_f(p: (i32, i32)) -> [f64; 2] {
21    [p.0 as f64, p.1 as f64]
22}
23
24#[inline]
25pub fn pt_to_i(p: [f64; 2]) -> (i32, i32) {
26    (p[0].round() as i32, p[1].round() as i32)
27}
28
29pub fn line_to_f(line: &[(i32, i32)]) -> Path {
30    line.iter().copied().map(pt_to_f).collect()
31}
32
33pub fn line_to_i(path: &[[f64; 2]]) -> Vec<(i32, i32)> {
34    path.iter().copied().map(pt_to_i).collect()
35}
36
37/// Convert a `Polygon` to a contour set (exterior first, holes after).
38/// Rings are emitted as-is; callers needing a specific winding must
39/// orient beforehand.
40pub fn polygon_to_f(p: &Polygon) -> FloatShape {
41    let mut shape = Vec::with_capacity(1 + p.holes.len());
42    shape.push(line_to_f(&p.exterior));
43    for h in &p.holes {
44        shape.push(line_to_f(h));
45    }
46    shape
47}
48
49/// Reverse of [`polygon_to_f`]. The first contour becomes the
50/// exterior, subsequent contours become holes.
51pub fn polygon_from_f(shape: &[Path]) -> Option<Polygon> {
52    let (exterior, holes) = shape.split_first()?;
53    Some(Polygon {
54        exterior: line_to_i(exterior),
55        holes: holes.iter().map(|h| line_to_i(h)).collect(),
56    })
57}
58
59/// Flatten an `i_overlay`-style `Shapes` (one entry per output
60/// polygon) into our `Vec<Polygon>`.
61pub fn polygons_from_shapes(shapes: &[FloatShape]) -> Vec<Polygon> {
62    shapes.iter().filter_map(|s| polygon_from_f(s)).collect()
63}