mod extract;
mod reassemble;
mod simplify;
pub use extract::build;
pub use reassemble::{reassemble_features, reassemble_geometry};
pub use simplify::simplify_arcs;
use geo_types::Coord;
use std::collections::HashMap;
pub type ArcId = usize;
#[derive(Clone, Debug, PartialEq)]
pub struct Arc {
pub coords: Vec<Coord<f64>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ArcRef {
pub arc_id: ArcId,
pub reversed: bool,
}
#[derive(Clone, Debug, Default)]
pub struct ArcGraph {
pub(super) arcs: Vec<Arc>,
pub(super) canonical_index: HashMap<Vec<(u64, u64)>, ArcId>,
}
impl ArcGraph {
#[must_use]
pub fn arcs(&self) -> &[Arc] {
&self.arcs
}
#[must_use]
pub fn arc(&self, id: ArcId) -> Option<&Arc> {
self.arcs.get(id)
}
#[must_use]
pub fn len(&self) -> usize {
self.arcs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.arcs.is_empty()
}
}
#[derive(Clone, Debug)]
pub enum FeatureArcs {
Point(Coord<f64>),
MultiPoint(Vec<Coord<f64>>),
LineString(LineStringArcs),
MultiLineString(Vec<LineStringArcs>),
Polygon(PolygonArcs),
MultiPolygon(Vec<PolygonArcs>),
}
#[derive(Clone, Debug)]
pub struct LineStringArcs(pub(super) Vec<ArcRef>);
impl LineStringArcs {
#[must_use]
pub fn arcs(&self) -> &[ArcRef] {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct PolygonArcs {
pub(super) exterior: Vec<ArcRef>,
pub(super) interiors: Vec<Vec<ArcRef>>,
}
impl PolygonArcs {
#[must_use]
pub fn exterior(&self) -> &[ArcRef] {
&self.exterior
}
#[must_use]
pub fn interiors(&self) -> &[Vec<ArcRef>] {
&self.interiors
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geo::GeoFeature;
use geo_types::{Geometry, LineString, Polygon};
fn polygon_feature(rings: &[Vec<[f64; 2]>]) -> GeoFeature {
let mut iter = rings.iter().map(|r| LineString::from(r.clone()));
let exterior = iter.next().expect("at least an exterior");
let interiors = iter.collect();
GeoFeature::new(Geometry::Polygon(Polygon::new(exterior, interiors)))
}
fn line_feature(coords: Vec<[f64; 2]>) -> GeoFeature {
GeoFeature::new(Geometry::LineString(LineString::from(coords)))
}
#[test]
fn isolated_ring_becomes_one_closed_arc() {
let p = polygon_feature(&[vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]]);
let (graph, fa) = build(&[p]);
assert_eq!(graph.len(), 1);
match &fa[0] {
FeatureArcs::Polygon(pa) => {
assert_eq!(pa.exterior.len(), 1);
assert_eq!(pa.interiors.len(), 0);
}
other => panic!("expected polygon, got {other:?}"),
}
let arc = &graph.arcs()[0];
assert_eq!(arc.coords.first(), arc.coords.last());
}
#[test]
fn two_polygons_share_one_arc() {
let a = polygon_feature(&[vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]]);
let b = polygon_feature(&[vec![[1.0, 0.0], [2.0, 0.0], [2.0, 1.0], [1.0, 1.0], [1.0, 0.0]]]);
let (graph, fa) = build(&[a, b]);
assert_eq!(graph.len(), 3);
let FeatureArcs::Polygon(pa) = &fa[0] else { panic!() };
let FeatureArcs::Polygon(pb) = &fa[1] else { panic!() };
assert_eq!(pa.exterior.len(), 2);
assert_eq!(pb.exterior.len(), 2);
let a_ids: std::collections::HashSet<ArcId> = pa.exterior.iter().map(|r| r.arc_id).collect();
let b_ids: std::collections::HashSet<ArcId> = pb.exterior.iter().map(|r| r.arc_id).collect();
let shared: Vec<ArcId> = a_ids.intersection(&b_ids).copied().collect();
assert_eq!(shared.len(), 1, "exactly one arc is shared");
let a_ref = pa.exterior.iter().find(|r| r.arc_id == shared[0]).unwrap();
let b_ref = pb.exterior.iter().find(|r| r.arc_id == shared[0]).unwrap();
assert_ne!(a_ref.reversed, b_ref.reversed);
}
#[test]
fn line_through_polygon_vertex_splits_both() {
let p = polygon_feature(&[vec![[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]);
let l = line_feature(vec![[5.0, -1.0], [4.0, 0.0], [5.0, 1.0]]);
let (graph, fa) = build(&[p, l]);
assert_eq!(graph.len(), 3);
match &fa[0] {
FeatureArcs::Polygon(pa) => assert_eq!(pa.exterior.len(), 1),
_ => panic!(),
}
match &fa[1] {
FeatureArcs::LineString(la) => assert_eq!(la.0.len(), 2),
_ => panic!(),
}
}
#[test]
fn three_way_junction_splits_lines() {
let a = line_feature(vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]);
let b = line_feature(vec![[1.0, -1.0], [1.0, 0.0], [1.0, 1.0]]);
let (graph, fa) = build(&[a, b]);
assert_eq!(graph.len(), 4);
match &fa[0] {
FeatureArcs::LineString(la) => assert_eq!(la.0.len(), 2),
_ => panic!(),
}
match &fa[1] {
FeatureArcs::LineString(lb) => assert_eq!(lb.0.len(), 2),
_ => panic!(),
}
}
#[test]
fn duplicate_isolated_rings_dedupe() {
let a = polygon_feature(&[vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]]);
let b = polygon_feature(&[vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]]);
let (graph, fa) = build(&[a, b]);
assert_eq!(graph.len(), 1);
let FeatureArcs::Polygon(pa) = &fa[0] else { panic!() };
let FeatureArcs::Polygon(pb) = &fa[1] else { panic!() };
assert_eq!(pa.exterior.len(), 1);
assert_eq!(pb.exterior.len(), 1);
assert_eq!(pa.exterior[0].arc_id, pb.exterior[0].arc_id);
}
#[test]
fn point_passes_through() {
let p = GeoFeature::new(Geometry::Point(geo_types::Point::new(13.4, 52.5)));
let (graph, fa) = build(&[p]);
assert_eq!(graph.len(), 0);
match &fa[0] {
FeatureArcs::Point(c) => {
assert!((c.x - 13.4).abs() < 1e-12);
assert!((c.y - 52.5).abs() < 1e-12);
}
_ => panic!(),
}
}
}