use super::{Arc, ArcRef, FeatureArcs, LineStringArcs, PolygonArcs};
use crate::geo::GeoFeature;
use geo_types::{Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
#[must_use]
pub fn reassemble_features(arcs: &[Arc], feature_arcs: &[FeatureArcs], template: &[GeoFeature]) -> Vec<GeoFeature> {
feature_arcs
.iter()
.zip(template.iter())
.map(|(fa, original)| GeoFeature {
id: original.id.clone(),
geometry: reassemble_geometry(arcs, fa),
properties: original.properties.clone(),
})
.collect()
}
#[must_use]
pub fn reassemble_geometry(arcs: &[Arc], fa: &FeatureArcs) -> Geometry<f64> {
match fa {
FeatureArcs::Point(c) => Geometry::Point(Point(*c)),
FeatureArcs::MultiPoint(coords) => Geometry::MultiPoint(MultiPoint(coords.iter().map(|c| Point(*c)).collect())),
FeatureArcs::LineString(la) => Geometry::LineString(reassemble_line_string(arcs, la)),
FeatureArcs::MultiLineString(mls) => Geometry::MultiLineString(MultiLineString(
mls.iter().map(|la| reassemble_line_string(arcs, la)).collect(),
)),
FeatureArcs::Polygon(pa) => Geometry::Polygon(reassemble_polygon(arcs, pa)),
FeatureArcs::MultiPolygon(mpa) => Geometry::MultiPolygon(MultiPolygon(
mpa.iter().map(|pa| reassemble_polygon(arcs, pa)).collect(),
)),
}
}
fn reassemble_line_string(arcs: &[Arc], la: &LineStringArcs) -> LineString<f64> {
LineString::new(concat_arc_refs(arcs, &la.0))
}
fn reassemble_polygon(arcs: &[Arc], pa: &PolygonArcs) -> Polygon<f64> {
let exterior = reassemble_ring(arcs, &pa.exterior);
let interiors: Vec<LineString<f64>> = pa.interiors.iter().map(|r| reassemble_ring(arcs, r)).collect();
Polygon::new(exterior, interiors)
}
fn reassemble_ring(arcs: &[Arc], refs: &[ArcRef]) -> LineString<f64> {
let mut coords = concat_arc_refs(arcs, refs);
if !coords.is_empty() && coords.first() != coords.last() {
let first = coords[0];
coords.push(first);
}
LineString::new(coords)
}
fn concat_arc_refs(arcs: &[Arc], refs: &[ArcRef]) -> Vec<Coord<f64>> {
let mut out: Vec<Coord<f64>> = Vec::new();
for (i, arc_ref) in refs.iter().enumerate() {
let coords = &arcs[arc_ref.arc_id].coords;
let skip = usize::from(i > 0);
if arc_ref.reversed {
out.extend(coords.iter().rev().skip(skip).copied());
} else {
out.extend(coords.iter().skip(skip).copied());
}
}
out
}
#[cfg(test)]
mod tests {
use super::super::build;
use super::*;
use crate::geo::GeoFeature;
use geo_types::{Geometry, LineString, Polygon};
fn poly_feat(rings: &[Vec<[f64; 2]>]) -> GeoFeature {
let mut iter = rings.iter().map(|r| LineString::from(r.clone()));
let exterior = iter.next().unwrap();
let interiors = iter.collect();
GeoFeature::new(Geometry::Polygon(Polygon::new(exterior, interiors)))
}
#[test]
fn isolated_polygon_round_trips() {
let original = poly_feat(&[vec![[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]);
let (graph, fa) = build(std::slice::from_ref(&original));
let arcs: Vec<Arc> = graph.arcs().to_vec();
let rebuilt = reassemble_features(&arcs, &fa, std::slice::from_ref(&original));
match (&original.geometry, &rebuilt[0].geometry) {
(Geometry::Polygon(o), Geometry::Polygon(r)) => {
assert_eq!(o.exterior().0.len(), r.exterior().0.len());
assert_eq!(r.exterior().0.first(), r.exterior().0.last());
}
_ => panic!(),
}
}
fn line_feat(coords: Vec<[f64; 2]>) -> GeoFeature {
GeoFeature::new(Geometry::LineString(LineString::from(coords)))
}
#[test]
fn shared_border_byte_identical_at_every_tolerance() {
use super::super::simplify::simplify_arcs;
let shared_edge: Vec<[f64; 2]> = vec![
[1.0, 0.0],
[1.001, 0.15],
[0.999, 0.30],
[1.001, 0.45],
[0.999, 0.60],
[1.001, 0.75],
[0.999, 0.90],
[1.0, 1.0],
];
let mut a_ring: Vec<[f64; 2]> = vec![[0.0, 0.0]];
a_ring.extend(shared_edge.iter().copied());
a_ring.push([0.0, 1.0]);
a_ring.push([0.0, 0.0]);
let mut b_ring: Vec<[f64; 2]> = vec![[1.0, 0.0], [2.0, 0.0], [2.0, 1.0], [1.0, 1.0]];
b_ring.extend(shared_edge.iter().rev().skip(1).copied()); let a = poly_feat(&[a_ring]);
let b = poly_feat(&[b_ring]);
let template = vec![a, b];
let (graph, fa) = build(&template);
for &tolerance in &[0.0, 0.0001, 0.005, 0.05, 0.5] {
let simplified = simplify_arcs(graph.arcs(), tolerance);
let rebuilt = reassemble_features(&simplified, &fa, &template);
let a_border = vertices_at_x(&rebuilt[0].geometry, 1.0);
let b_border = vertices_at_x(&rebuilt[1].geometry, 1.0);
let mut a_sorted = a_border.clone();
a_sorted.sort_by(|p, q| p.y.partial_cmp(&q.y).unwrap());
let mut b_sorted = b_border.clone();
b_sorted.sort_by(|p, q| p.y.partial_cmp(&q.y).unwrap());
assert_eq!(
a_sorted, b_sorted,
"shared border must be byte-identical at tolerance {tolerance}"
);
}
}
#[test]
fn road_junction_vertex_preserved_at_every_tolerance() {
use super::super::simplify::simplify_arcs;
let a = line_feat(vec![[0.0, 0.0], [0.5, 0.0001], [1.0, 0.0], [1.5, -0.0001], [2.0, 0.0]]);
let b = line_feat(vec![[1.0, -1.0], [1.0001, -0.5], [1.0, 0.0], [0.9999, 0.5], [1.0, 1.0]]);
let template = vec![a, b];
let (graph, fa) = build(&template);
let junction = Coord { x: 1.0, y: 0.0 };
for &tolerance in &[0.0, 0.001, 0.01, 0.1] {
let simplified = simplify_arcs(graph.arcs(), tolerance);
let rebuilt = reassemble_features(&simplified, &fa, &template);
let a_coords = line_string_coords(&rebuilt[0].geometry);
let b_coords = line_string_coords(&rebuilt[1].geometry);
assert!(
a_coords.contains(&junction),
"line A must keep the junction vertex at tolerance {tolerance}"
);
assert!(
b_coords.contains(&junction),
"line B must keep the junction vertex at tolerance {tolerance}"
);
}
}
fn vertices_at_x(g: &Geometry<f64>, x: f64) -> Vec<Coord<f64>> {
match g {
Geometry::Polygon(p) => p
.exterior()
.0
.iter()
.filter(|c| (c.x - x).abs() < 1e-9)
.copied()
.collect(),
_ => panic!("expected Polygon"),
}
}
fn line_string_coords(g: &Geometry<f64>) -> Vec<Coord<f64>> {
match g {
Geometry::LineString(ls) => ls.0.clone(),
_ => panic!("expected LineString"),
}
}
#[test]
fn shared_border_simplifies_consistently() {
use super::super::simplify::simplify_arcs;
let a = poly_feat(&[vec![
[0.0, 0.0],
[1.0, 0.0],
[1.0, 0.5001],
[1.0, 1.0],
[0.0, 1.0],
[0.0, 0.0],
]]);
let b = poly_feat(&[vec![
[1.0, 0.0],
[2.0, 0.0],
[2.0, 1.0],
[1.0, 1.0],
[1.0, 0.5001],
[1.0, 0.0],
]]);
let template = vec![a.clone(), b.clone()];
let (graph, fa) = build(&template);
let simplified = simplify_arcs(graph.arcs(), 0.01);
let rebuilt = reassemble_features(&simplified, &fa, &template);
fn border_at_x_eq_1(g: &Geometry<f64>) -> Vec<Coord<f64>> {
match g {
Geometry::Polygon(p) => p
.exterior()
.0
.iter()
.filter(|c| (c.x - 1.0).abs() < 1e-9)
.copied()
.collect(),
_ => panic!(),
}
}
let a_border = border_at_x_eq_1(&rebuilt[0].geometry);
let b_border = border_at_x_eq_1(&rebuilt[1].geometry);
let mut a_sorted = a_border.clone();
a_sorted.sort_by(|p, q| p.y.partial_cmp(&q.y).unwrap());
let mut b_sorted = b_border.clone();
b_sorted.sort_by(|p, q| p.y.partial_cmp(&q.y).unwrap());
assert_eq!(a_sorted, b_sorted, "shared border must match byte-for-byte");
}
}