#![allow(clippy::pedantic, reason = "test tool")]
use std::collections::BTreeMap;
use geo_types::Coord;
use map_tile_toolkit::{Measured, SlicerAll, SlicerOne, TileId};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Attrs {
id: Option<u64>,
props: BTreeMap<&'static str, &'static str>,
}
impl Attrs {
fn new(id: Option<u64>, props: &[(&'static str, &'static str)]) -> Self {
Self {
id,
props: props.iter().copied().collect(),
}
}
}
type Input = (Attrs, Vec<Coord<i32>>);
#[derive(Debug, Clone, PartialEq)]
struct Tiled {
tile: TileId,
attrs: Attrs,
runs: Vec<Vec<Coord<i32>>>,
}
fn coords(pts: &[(i32, i32)]) -> Vec<Coord<i32>> {
pts.iter().map(|&(x, y)| Coord { x, y }).collect()
}
fn slice(extent: u32, inputs: &[Input]) -> Vec<Tiled> {
let mut slicer = SlicerAll::<Coord<i32>, Attrs>::new(extent, 0).expect("valid config");
for (attrs, line) in inputs {
slicer.add_feature_with(line, attrs.clone()).expect("slice");
}
let mut out = Vec::new();
for tile in slicer.iter_tiles() {
let id = tile.tile_id();
let origin = id.origin(extent).expect("tile in range");
for view in tile.iter_features() {
let runs = view
.iter_polylines()
.map(|run| run.iter().map(|&c| c + origin).collect())
.collect();
out.push(Tiled {
tile: id,
attrs: view.attr().clone(),
runs,
});
}
}
out
}
fn all_vertices(tiled: &[Tiled]) -> std::collections::BTreeSet<(i32, i32)> {
tiled
.iter()
.flat_map(|t| t.runs.iter().flatten().map(|c| (c.x, c.y)))
.collect()
}
#[test]
fn attributes_duplicate_across_tiles() {
let attrs = Attrs::new(Some(42), &[("name", "Main St"), ("kind", "primary")]);
let line = coords(&[(5, 5), (30, 20), (55, 30)]);
let tiled = slice(25, &[(attrs.clone(), line.clone())]);
assert!(tiled.len() >= 2, "feature should span multiple tiles");
assert!(
tiled.iter().all(|t| t.attrs == attrs),
"every tiled piece keeps the original id + properties"
);
let seen = all_vertices(&tiled);
for c in &line {
assert!(
seen.contains(&(c.x, c.y)),
"original vertex {c:?} preserved"
);
}
}
#[test]
fn split_within_a_tile_becomes_one_multi_run_feature() {
let attrs = Attrs::new(None, &[("name", "River")]);
let line = coords(&[(8, 15), (8, 40), (13, 40), (18, 40), (18, 15)]);
let tiled = slice(25, &[(attrs.clone(), line)]);
let in_00: Vec<&Tiled> = tiled
.iter()
.filter(|t| t.tile == TileId::new(0, 0))
.collect();
assert_eq!(in_00.len(), 1, "one feature (not two) in tile (0,0)");
let piece = in_00[0];
assert_eq!(piece.attrs, attrs, "id: None and props preserved");
assert_eq!(
piece.runs.len(),
2,
"the split yields two runs → MultiLineString"
);
}
#[test]
fn feature_attrs_stay_correct_when_features_interleave() {
let p = Attrs::new(Some(1), &[("name", "P")]);
let q = Attrs::new(Some(2), &[("name", "Q")]);
let tiled = slice(
25,
&[
(p.clone(), coords(&[(2, 2), (30, 8), (55, 12)])),
(q.clone(), coords(&[(2, 6), (30, 12), (55, 18)])),
],
);
assert!(tiled.iter().all(|t| t.attrs == p || t.attrs == q));
assert!(tiled.iter().any(|t| t.attrs == p));
assert!(tiled.iter().any(|t| t.attrs == q));
assert!(
tiled.iter().filter(|t| t.attrs == p).count() >= 2,
"P should appear in several tiles, each with P's attrs"
);
}
#[test]
fn no_attribute_channel_is_still_usable() {
let mut slicer = SlicerAll::new(25, 0).expect("valid config");
slicer
.add_feature(coords(&[(5, 5), (60, 40)]))
.expect("slice");
for tile in slicer.iter_tiles() {
for f in tile.iter_features() {
assert_eq!(f.attr(), &());
}
}
let mut one = SlicerOne::<Coord<i32>, Attrs>::new(25, 0, TileId::new(0, 0)).expect("cfg");
one.add_feature_with(coords(&[(5, 5), (20, 20)]), Attrs::new(Some(9), &[]))
.expect("slice");
let got: Vec<Attrs> = one.iter_features().map(|f| f.attr().clone()).collect();
assert_eq!(got, vec![Attrs::new(Some(9), &[])]);
}
#[test]
fn per_vertex_m_and_per_feature_attrs_compose() {
let attrs = Attrs::new(Some(100), &[("name", "trail")]);
let mut slicer = SlicerAll::<Measured<i32>, Attrs>::new(25, 0).expect("cfg");
slicer
.add_feature_with(
[
Measured::new(5, 5, 1000),
Measured::new(30, 20, 2000),
Measured::new(55, 30, 3000),
],
attrs.clone(),
)
.expect("slice");
let mut pieces = 0;
for tile in slicer.iter_tiles() {
for f in tile.iter_features() {
assert_eq!(f.attr(), &attrs);
for run in f.iter_polylines() {
assert!(run.iter().all(|v| [1000, 2000, 3000].contains(&v.m)));
}
pieces += 1;
}
}
assert!(pieces >= 2, "the trail should span multiple tiles");
}