use super::{Arc, ArcGraph, ArcId, ArcRef, FeatureArcs, LineStringArcs, PolygonArcs};
use crate::geo::GeoFeature;
use geo_types::{Coord, Geometry, LineString, Polygon};
use std::collections::{HashMap, HashSet};
type CoordKey = (u64, u64);
#[inline]
fn key(c: Coord<f64>) -> CoordKey {
(c.x.to_bits(), c.y.to_bits())
}
#[must_use]
pub fn build(features: &[GeoFeature]) -> (ArcGraph, Vec<FeatureArcs>) {
let junctions = identify_junctions(features);
let mut graph = ArcGraph::default();
let mut feature_arcs: Vec<FeatureArcs> = Vec::with_capacity(features.len());
for feature in features {
let entry = match &feature.geometry {
Geometry::Point(p) => FeatureArcs::Point(p.0),
Geometry::MultiPoint(mp) => FeatureArcs::MultiPoint(mp.0.iter().map(|p| p.0).collect()),
Geometry::LineString(ls) => FeatureArcs::LineString(linestring_to_arcs(ls, &junctions, &mut graph)),
Geometry::MultiLineString(ml) => FeatureArcs::MultiLineString(
ml.0
.iter()
.map(|ls| linestring_to_arcs(ls, &junctions, &mut graph))
.collect(),
),
Geometry::Polygon(p) => FeatureArcs::Polygon(polygon_to_arcs(p, &junctions, &mut graph)),
Geometry::MultiPolygon(mp) => FeatureArcs::MultiPolygon(
mp.0
.iter()
.map(|p| polygon_to_arcs(p, &junctions, &mut graph))
.collect(),
),
_ => FeatureArcs::Point(Coord { x: 0.0, y: 0.0 }),
};
feature_arcs.push(entry);
}
(graph, feature_arcs)
}
fn identify_junctions(features: &[GeoFeature]) -> HashSet<CoordKey> {
let mut neighbors: HashMap<CoordKey, HashSet<CoordKey>> = HashMap::new();
let mut line_endpoints: HashSet<CoordKey> = HashSet::new();
for feature in features {
match &feature.geometry {
Geometry::LineString(ls) => add_line(ls, &mut neighbors, &mut line_endpoints),
Geometry::MultiLineString(ml) => {
for ls in &ml.0 {
add_line(ls, &mut neighbors, &mut line_endpoints);
}
}
Geometry::Polygon(p) => add_polygon(p, &mut neighbors),
Geometry::MultiPolygon(mp) => {
for p in &mp.0 {
add_polygon(p, &mut neighbors);
}
}
_ => {}
}
}
let mut junctions = line_endpoints;
for (vertex, n_set) in &neighbors {
if n_set.len() != 2 {
junctions.insert(*vertex);
}
}
junctions
}
fn add_polygon(p: &Polygon<f64>, neighbors: &mut HashMap<CoordKey, HashSet<CoordKey>>) {
add_ring(p.exterior(), neighbors);
for interior in p.interiors() {
add_ring(interior, neighbors);
}
}
fn add_ring(ring: &LineString<f64>, neighbors: &mut HashMap<CoordKey, HashSet<CoordKey>>) {
let coords = effective_ring(ring);
let n = coords.len();
if n < 2 {
return;
}
for i in 0..n {
let prev = key(coords[(i + n - 1) % n]);
let curr = key(coords[i]);
let next = key(coords[(i + 1) % n]);
let entry = neighbors.entry(curr).or_default();
entry.insert(prev);
entry.insert(next);
}
}
fn add_line(
line: &LineString<f64>,
neighbors: &mut HashMap<CoordKey, HashSet<CoordKey>>,
endpoints: &mut HashSet<CoordKey>,
) {
let coords = &line.0;
if coords.len() < 2 {
return;
}
for i in 0..coords.len() {
let curr = key(coords[i]);
let entry = neighbors.entry(curr).or_default();
if i > 0 {
entry.insert(key(coords[i - 1]));
}
if i < coords.len() - 1 {
entry.insert(key(coords[i + 1]));
}
}
endpoints.insert(key(coords[0]));
endpoints.insert(key(coords[coords.len() - 1]));
}
fn effective_ring(ring: &LineString<f64>) -> Vec<Coord<f64>> {
let coords = &ring.0;
if coords.len() >= 2 && coords.first() == coords.last() {
coords[..coords.len() - 1].to_vec()
} else {
coords.clone()
}
}
fn polygon_to_arcs(p: &Polygon<f64>, junctions: &HashSet<CoordKey>, graph: &mut ArcGraph) -> PolygonArcs {
PolygonArcs {
exterior: ring_to_arcs(p.exterior(), junctions, graph),
interiors: p
.interiors()
.iter()
.map(|r| ring_to_arcs(r, junctions, graph))
.collect(),
}
}
fn ring_to_arcs(ring: &LineString<f64>, junctions: &HashSet<CoordKey>, graph: &mut ArcGraph) -> Vec<ArcRef> {
let coords = effective_ring(ring);
if coords.len() < 2 {
return Vec::new();
}
let start = (0..coords.len()).find(|&i| junctions.contains(&key(coords[i])));
let Some(start) = start else {
let mut arc_coords = coords.clone();
arc_coords.push(arc_coords[0]);
return vec![graph.insert(arc_coords)];
};
let n = coords.len();
let mut walk: Vec<Coord<f64>> = (0..n).map(|i| coords[(start + i) % n]).collect();
walk.push(walk[0]);
split_walk(&walk, junctions, graph)
}
fn linestring_to_arcs(ls: &LineString<f64>, junctions: &HashSet<CoordKey>, graph: &mut ArcGraph) -> LineStringArcs {
if ls.0.len() < 2 {
return LineStringArcs(Vec::new());
}
LineStringArcs(split_walk(&ls.0, junctions, graph))
}
fn split_walk(walk: &[Coord<f64>], junctions: &HashSet<CoordKey>, graph: &mut ArcGraph) -> Vec<ArcRef> {
let mut refs: Vec<ArcRef> = Vec::new();
if walk.len() < 2 {
return refs;
}
let mut current: Vec<Coord<f64>> = vec![walk[0]];
for i in 1..walk.len() {
current.push(walk[i]);
if i < walk.len() - 1 && junctions.contains(&key(walk[i])) {
let next = vec![walk[i]];
let arc_coords = std::mem::replace(&mut current, next);
refs.push(graph.insert(arc_coords));
}
}
if current.len() >= 2 {
refs.push(graph.insert(current));
}
refs
}
impl ArcGraph {
pub fn insert(&mut self, coords: Vec<Coord<f64>>) -> ArcRef {
let forward: Vec<CoordKey> = coords.iter().map(|c| key(*c)).collect();
let reverse: Vec<CoordKey> = forward.iter().rev().copied().collect();
let input_is_canonical = forward <= reverse;
let canonical_keys = if input_is_canonical { forward } else { reverse };
if let Some(&arc_id) = self.lookup_canonical(&canonical_keys) {
return ArcRef {
arc_id,
reversed: !input_is_canonical,
};
}
let arc_id: ArcId = self.arcs.len();
let canonical_coords: Vec<Coord<f64>> = if input_is_canonical {
coords
} else {
coords.into_iter().rev().collect()
};
self.arcs.push(Arc {
coords: canonical_coords,
});
self.canonical_index.insert(canonical_keys, arc_id);
ArcRef {
arc_id,
reversed: !input_is_canonical,
}
}
fn lookup_canonical(&self, keys: &[CoordKey]) -> Option<&ArcId> {
self.canonical_index.get(keys)
}
}