pub(crate) mod adj;
pub(crate) mod build;
mod geom;
mod simplify;
mod topo;
pub use build::RegionError;
use geo::{Coord, MultiPolygon, Rect};
use crate::adj::AdjacencyMatrix;
use crate::dcel::{Dcel, FaceId, HalfEdgeId};
use crate::rtree::SpatialIndex;
use crate::unit::UnitId;
#[derive(Clone)]
pub struct Region {
pub(crate) dcel: Dcel<Coord<f64>>,
pub(crate) face_to_unit: Vec<UnitId>,
pub(crate) geometries: Vec<MultiPolygon<f64>>,
pub(crate) area: Vec<f64>,
pub(crate) perimeter: Vec<f64>,
pub(crate) exterior_boundary_length: Vec<f64>,
pub(crate) centroid: Vec<Coord<f64>>,
pub(crate) bounds: Vec<Rect<f64>>,
pub(crate) bounds_all: Rect<f64>,
pub(crate) is_exterior: Vec<bool>,
pub(crate) edge_length: Vec<f64>,
pub(crate) adjacent: AdjacencyMatrix,
pub(crate) touching: AdjacencyMatrix,
pub(crate) rtree: SpatialIndex,
pub(crate) unit_to_faces: Vec<Vec<FaceId>>,
pub(crate) face_inner_cycles: Vec<Vec<HalfEdgeId>>,
}
impl Region {
#[inline]
pub fn num_units(&self) -> usize { self.geometries.len() }
#[inline]
pub fn unit_ids(&self) -> impl Iterator<Item = UnitId> + '_ {
(0..self.num_units()).map(|i| UnitId(i as u32))
}
#[inline]
pub fn geometry(&self, unit: UnitId) -> &MultiPolygon<f64> {
&self.geometries[unit.0 as usize]
}
#[inline]
pub fn edge_weight_at(&self, csr_idx: usize) -> f64 {
self.adjacent.weight_at(csr_idx)
}
pub fn validate(&self) -> Result<(), RegionError> {
use crate::dcel::HalfEdgeId;
let nhe = self.dcel.num_half_edges();
for h in 0..nhe {
let twin = self.dcel.half_edge(HalfEdgeId(h)).twin;
let twin_twin = self.dcel.half_edge(twin).twin;
if twin_twin != HalfEdgeId(h) {
return Err(RegionError::ValidationError(
format!("half-edge {h}: twin(twin) = {} != {h}", twin_twin.0),
));
}
}
for h in 0..nhe {
let he = self.dcel.half_edge(HalfEdgeId(h));
let next_prev = self.dcel.half_edge(he.next).prev;
if next_prev != HalfEdgeId(h) {
return Err(RegionError::ValidationError(
format!("half-edge {h}: next({}).prev = {} != {h}", he.next.0, next_prev.0),
));
}
let prev_next = self.dcel.half_edge(he.prev).next;
if prev_next != HalfEdgeId(h) {
return Err(RegionError::ValidationError(
format!("half-edge {h}: prev({}).next = {} != {h}", he.prev.0, prev_next.0),
));
}
}
for f in 1..self.dcel.num_faces() {
if self.dcel.face(crate::dcel::FaceId(f)).half_edge.is_none() {
return Err(RegionError::ValidationError(
format!("face {f}: bounded face has no half-edge"),
));
}
}
for u in 0..self.num_units() {
if self.area[u] < 0.0 {
return Err(RegionError::ValidationError(
format!("unit {u}: negative area {}", self.area[u]),
));
}
}
Ok(())
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use geo::{Coord, LineString, MultiPolygon, Polygon, Rect};
use crate::dcel::{Dcel, OUTER_FACE};
use crate::rtree::SpatialIndex;
use crate::unit::UnitId;
use super::Region;
use super::adj::{build_adjacent, build_touching};
pub(crate) fn make_two_unit_region() -> Region {
let mut dcel: Dcel<Coord<f64>> = Dcel::new();
let a = dcel.add_vertex(Coord { x: 0.0, y: 0.0 });
let b = dcel.add_vertex(Coord { x: 1.0, y: 0.0 });
let c = dcel.add_vertex(Coord { x: 2.0, y: 0.0 });
let d = dcel.add_vertex(Coord { x: 0.0, y: 1.0 });
let e = dcel.add_vertex(Coord { x: 1.0, y: 1.0 });
let f = dcel.add_vertex(Coord { x: 2.0, y: 1.0 });
let left = dcel.add_face(); let right = dcel.add_face();
let (ab, ba) = dcel.add_edge(a, b, left, OUTER_FACE);
let (be, eb) = dcel.add_edge(b, e, left, right );
let (ed, de) = dcel.add_edge(e, d, left, OUTER_FACE);
let (da, ad) = dcel.add_edge(d, a, left, OUTER_FACE);
let (bc, cb) = dcel.add_edge(b, c, right, OUTER_FACE);
let (cf, fc) = dcel.add_edge(c, f, right, OUTER_FACE);
let (fe, ef) = dcel.add_edge(f, e, right, OUTER_FACE);
dcel.set_next(ab, be); dcel.set_next(be, ed);
dcel.set_next(ed, da); dcel.set_next(da, ab);
dcel.set_next(bc, cf); dcel.set_next(cf, fe);
dcel.set_next(fe, eb); dcel.set_next(eb, bc);
dcel.set_next(ad, de); dcel.set_next(de, ef);
dcel.set_next(ef, fc); dcel.set_next(fc, cb);
dcel.set_next(cb, ba); dcel.set_next(ba, ad);
dcel.face_mut(left ).half_edge = Some(ab);
dcel.face_mut(right ).half_edge = Some(bc);
dcel.face_mut(OUTER_FACE).half_edge = Some(ad);
let face_to_unit = vec![
UnitId::EXTERIOR, UnitId(0), UnitId(1), ];
let edge_length = vec![1.0; 7];
let adj = build_adjacent (&dcel, &face_to_unit, &edge_length, 2);
let touching = build_touching(&dcel, &face_to_unit, 2);
let bounds = vec![
Rect::new(Coord { x: 0.0, y: 0.0 }, Coord { x: 1.0, y: 1.0 }),
Rect::new(Coord { x: 1.0, y: 0.0 }, Coord { x: 2.0, y: 1.0 }),
];
let rtree = SpatialIndex::new(&bounds);
let make_poly = |pts: &[(f64, f64)]| -> MultiPolygon<f64> {
MultiPolygon(vec![Polygon::new(
LineString(pts.iter().map(|&(x, y)| Coord { x, y }).collect()),
vec![],
)])
};
let unit_to_faces = crate::region::build::compute_unit_to_faces(&face_to_unit, 2);
let face_inner_cycles = crate::region::build::compute_face_inner_cycles(&dcel);
Region {
dcel,
face_to_unit,
geometries: vec![
make_poly(&[(0.0,0.0),(1.0,0.0),(1.0,1.0),(0.0,1.0),(0.0,0.0)]),
make_poly(&[(1.0,0.0),(2.0,0.0),(2.0,1.0),(1.0,1.0),(1.0,0.0)]),
],
area: vec![10.0, 20.0],
perimeter: vec![4.0, 4.0 ],
exterior_boundary_length: vec![3.0, 3.0 ],
centroid: vec![
Coord { x: 0.5, y: 0.5 },
Coord { x: 1.5, y: 0.5 },
],
bounds,
bounds_all: Rect::new(Coord { x: 0.0, y: 0.0 }, Coord { x: 2.0, y: 1.0 }),
is_exterior: vec![true, true],
edge_length,
adjacent: adj,
touching,
rtree,
unit_to_faces,
face_inner_cycles,
}
}
}