use geo_types::Coord;
use map_tile_toolkit::{SlicerAll, SlicerOne, TileError, TileId};
fn line(coords: Vec<(i32, i32)>) -> Vec<Coord<i32>> {
coords.into_iter().map(|(x, y)| Coord { x, y }).collect()
}
fn all(extent: u32, buffer: u16) -> Result<SlicerAll<Coord<i32>>, TileError> {
SlicerAll::new(extent, buffer)
}
fn one(extent: u32, buffer: u16, tile: TileId) -> SlicerOne<Coord<i32>> {
SlicerOne::new(extent, buffer, tile).expect("valid config")
}
fn snapshot(s: &SlicerAll<Coord<i32>>) -> Vec<(TileId, Vec<Vec<Coord<i32>>>)> {
s.iter_tiles()
.map(|t| {
let runs = t
.iter_features()
.flat_map(|f| f.iter_polylines().map(<[_]>::to_vec))
.collect();
(t.tile_id(), runs)
})
.collect()
}
fn errors_after_emitting() -> Vec<Coord<i32>> {
line(vec![
(i32::MIN + 8200, 0), (i32::MIN + 8250, 0),
(i32::MIN + 8, 0), ])
}
#[test]
fn invalid_extent() {
assert_eq!(all(0, 0).err(), Some(TileError::InvalidExtent));
assert_eq!(all(u32::MAX, 0).err(), Some(TileError::InvalidExtent));
assert!(all(1, 0).is_ok());
assert!(all(i32::MAX as u32, u16::MAX).is_ok());
assert_eq!(
SlicerOne::<Coord<i32>>::new(0, 0, TileId::new(0, 0)).err(),
Some(TileError::InvalidExtent)
);
}
#[test]
fn buffer_too_large() {
assert_eq!(all(10, 5).err(), Some(TileError::BufferTooLarge)); assert_eq!(all(10, 6).err(), Some(TileError::BufferTooLarge));
assert!(all(10, 4).is_ok()); assert!(all(1, 0).is_ok());
assert_eq!(all(2, 1).err(), Some(TileError::BufferTooLarge));
assert_eq!(
SlicerOne::<Coord<i32>>::new(10, 5, TileId::new(0, 0)).err(),
Some(TileError::BufferTooLarge)
);
}
#[test]
fn extreme_tile_errors_instead_of_panicking() {
let l = line(vec![(0, 0), (10, 10)]);
assert_eq!(
one(4096, 0, TileId::new(i32::MAX, i32::MAX))
.add_feature(&l)
.err(),
Some(TileError::Overflow)
);
assert_eq!(
one(4096, 0, TileId::new(i32::MIN, 0)).add_feature(&l).err(),
Some(TileError::Overflow)
);
let mut far = one(4096, 0, TileId::new(1000, 1000));
far.add_feature(&l).expect("no error for a far tile");
assert!(far.is_empty());
}
#[test]
fn spanning_too_many_tiles_errors() {
let mut s = all(1, 0).expect("valid config"); assert_eq!(
s.add_feature(line(vec![(0, 0), (40_000, 0)])).err(),
Some(TileError::TooManyTiles)
);
}
#[test]
fn coordinate_overflow_errors() {
let mut s = all(4096, 8).expect("valid config");
assert_eq!(
s.add_feature(line(vec![(i32::MAX, 0), (i32::MAX, 10)]))
.err(),
Some(TileError::Overflow)
);
}
#[test]
fn too_many_vertices_errors() {
let mut s = all(1_000_000, 0).expect("valid config");
let coords: Vec<Coord<i32>> = (0..=(i32::from(u16::MAX) + 1))
.map(|i| Coord { x: i % 8, y: 0 })
.collect();
assert_eq!(
s.add_feature(&coords).err(),
Some(TileError::PolylineTooLarge)
);
}
#[test]
fn add_feature_is_atomic_on_error() {
let mut probe = all(4096, 8).expect("valid config");
probe
.add_feature(errors_after_emitting())
.expect_err("must error mid-walk");
assert!(
probe.is_empty(),
"a feature that errors before any other data must leave the slicer empty"
);
let mut s = all(4096, 8).expect("valid config");
s.add_feature(line(vec![(i32::MIN + 8200, 0), (i32::MIN + 8300, 0)]))
.expect("clean feature");
let before = snapshot(&s);
assert_eq!(
s.add_feature(errors_after_emitting()).err(),
Some(TileError::Overflow)
);
assert_eq!(snapshot(&s), before, "errored feature must roll back fully");
s.add_feature(line(vec![(i32::MIN + 8210, 0), (i32::MIN + 8260, 0)]))
.expect("still usable after a rolled-back error");
let mut clean = all(4096, 8).expect("valid config");
clean
.add_feature(line(vec![(i32::MIN + 8200, 0), (i32::MIN + 8300, 0)]))
.expect("clean feature");
clean
.add_feature(line(vec![(i32::MIN + 8210, 0), (i32::MIN + 8260, 0)]))
.expect("clean feature");
assert_eq!(
snapshot(&s),
snapshot(&clean),
"skipping a failed feature matches never adding it"
);
}
#[test]
fn clear_resets_for_reuse() {
let mut s = all(25, 0).expect("valid config");
s.add_feature(line(vec![(5, 5), (60, 40)])).expect("slice");
assert!(!s.is_empty());
s.clear();
assert!(s.is_empty());
assert_eq!(s.len(), 0);
let mut fresh = all(25, 0).expect("valid config");
s.add_feature(line(vec![(1, 1), (30, 30)])).expect("slice");
fresh
.add_feature(line(vec![(1, 1), (30, 30)]))
.expect("slice");
assert_eq!(snapshot(&s), snapshot(&fresh));
let mut o = one(25, 0, TileId::new(0, 0));
o.add_feature(line(vec![(5, 5), (20, 20)])).expect("slice");
assert!(!o.is_empty());
o.clear();
assert!(o.is_empty());
assert_eq!(o.tile(), TileId::new(0, 0), "clear keeps the bound tile");
}
#[test]
fn empty_and_degenerate_inputs_are_ok() {
let mut s = all(25, 0).expect("valid config");
s.add_feature(Vec::<Coord<i32>>::new())
.expect("empty polyline is ok");
assert!(s.is_empty());
let dot = line(vec![(5, 5)]);
s.add_feature(&dot).expect("single-point polyline is ok");
assert!(s.is_empty());
let mut one = one(25, 0, TileId::new(0, 0));
one.add_feature(&dot).expect("single-point polyline is ok");
assert!(one.is_empty());
}