#![allow(clippy::pedantic, reason = "test/inspection tool")]
use std::collections::BTreeMap;
use std::path::Path;
use geo_types::Coord;
use insta::assert_binary_snapshot;
use map_tile_toolkit::TileId;
mod support;
use support::{feature, load_fixture_geoms};
use crate::support::{EXTENT, feature_line};
fn slicers() -> [(support::Cfg, &'static str); 2] {
[
(support::grid(), "polylines/snapshots"),
(support::grid_buffered(), "polylines/snapshots-5"),
]
}
mod files {
use test_each_file::test_each_path;
test_each_path! { for ["geojson"] in "./tests/polylines/fixtures" => super::slice_one_fixture }
}
fn padded_tile_span(polylines: &[Vec<Coord<i32>>]) -> (TileId, TileId) {
let mut lo = TileId::new(i32::MAX, i32::MAX);
let mut hi = TileId::new(i32::MIN, i32::MIN);
for line in polylines {
for &c in line {
let (tx, ty) = (c.x.div_euclid(EXTENT as i32), c.y.div_euclid(EXTENT as i32));
lo = TileId::new(lo.x.min(tx), lo.y.min(ty));
hi = TileId::new(hi.x.max(tx), hi.y.max(ty));
}
}
(
TileId::new(lo.x - 1, lo.y - 1),
TileId::new(hi.x + 1, hi.y + 1),
)
}
fn duplicate_vertices(polylines: &[Vec<Coord<i32>>]) -> Vec<Vec<Coord<i32>>> {
polylines
.iter()
.map(|line| line.iter().flat_map(|&c| [c, c]).collect())
.collect()
}
fn globalize(tile: TileId, runs: &[Vec<Coord<i32>>], extent: i32) -> Vec<Vec<Coord<i32>>> {
let origin = tile.origin(extent.cast_unsigned()).expect("tile in range");
runs.iter()
.map(|run| run.iter().map(|&c| c + origin).collect())
.collect()
}
fn build_features(
input: &[Vec<Coord<i32>>],
tiles: &BTreeMap<TileId, Vec<Vec<Coord<i32>>>>,
) -> Vec<geojson::Feature> {
let mut features: Vec<_> = input
.iter()
.map(|line| support::input_feature(line))
.collect();
for (tile, runs) in tiles {
for run in runs {
features.push(feature_line(run, &format!("tile {}/{}", tile.x, tile.y)));
}
}
features
}
fn slice_one_fixture([path]: [&Path; 1]) {
let stem = path.file_stem().and_then(|s| s.to_str()).expect("stem");
let polylines = load_fixture_geoms(path);
for (slicer, snapshot_dir) in &slicers() {
slice_at_buffer(slicer, stem, &polylines, snapshot_dir);
}
}
#[test]
#[ignore = "manually save big geometry"]
fn save_big_geometry() {
let bytes = support::feature_collection_bytes(vec![feature(
&support::to_f64(&support::big_polyline()),
vec![],
)]);
std::fs::write("tests/polylines/fixtures/big-geometry.geojson", bytes).expect("writes");
}
#[test]
fn big_config_tile_counts() {
let geom = support::polylines_of(&support::big_polyline());
for (name, slicer) in support::big_configs() {
let n = support::slice_all_runs(&slicer, &geom).len();
match name {
"single" => assert_eq!(n, 1, "`single` should keep the whole polyline in one tile"),
"few" => assert_eq!(n, 4, "`few` should produce a 2×2 grid of tiles"),
"multi" => assert!(n > 100, "`multi` should produce many tiles, got {n}"),
other => panic!("unexpected config {other}"),
}
}
}
#[test]
fn long_diagonal_slices_at_z14() {
const N: i32 = 1 << 14;
let cfg = support::slicer(support::DIAGONAL_EXTENT as u32, 64);
let line = vec![support::long_diagonal(N)];
let all: BTreeMap<TileId, Vec<Vec<Coord<i32>>>> =
support::slice_all_runs(&cfg, &line).into_iter().collect();
assert!(
all.len() > 2 * N as usize && all.len() < 4 * N as usize,
"{}",
all.len()
);
assert!(all.contains_key(&TileId::new(0, 0)) && all.contains_key(&TileId::new(N - 1, N - 1)));
for t in all.keys().step_by(97) {
for (dx, dy) in (-1..=1).flat_map(|dx| (-1..=1).map(move |dy| (dx, dy))) {
let tile = TileId::new(t.x + dx, t.y + dy);
let expected = support::slice_tile_runs(&cfg, &line, tile);
assert_eq!(
all.get(&tile).cloned().unwrap_or_default(),
expected,
"{tile:?}"
);
}
}
}
fn slice_at_buffer(
slicer: &support::Cfg,
stem: &str,
geom: &[Vec<Coord<i32>>],
snapshot_dir: &str,
) {
let all: BTreeMap<TileId, Vec<Vec<Coord<i32>>>> =
support::slice_all_runs(slicer, geom).into_iter().collect();
let (lo, hi) = padded_tile_span(geom);
let mut one = BTreeMap::new();
for y in lo.y..=hi.y {
for x in lo.x..=hi.x {
let tile = TileId::new(x, y);
let piece = support::slice_tile_runs(slicer, geom, tile);
if !piece.is_empty() {
one.insert(tile, piece);
}
}
}
assert_eq!(
all, one,
"batch and per-tile slicing disagree for {stem} (buffer {})",
slicer.buffer
);
let duped = duplicate_vertices(geom);
let all_duped: BTreeMap<TileId, Vec<Vec<Coord<i32>>>> = support::slice_all_runs(slicer, &duped)
.into_iter()
.collect();
assert_eq!(
all_duped, all,
"duplicating every vertex changed the batch result for {stem} (buffer {})",
slicer.buffer
);
for (&tile, piece) in &all {
let piece_dup = support::slice_tile_runs(slicer, &duped, tile);
assert!(
!piece_dup.is_empty(),
"tile {tile:?} vanished after vertex duplication for {stem}"
);
assert_eq!(
&piece_dup, piece,
"duplicated-vertex per-tile differs at {tile:?} for {stem}"
);
}
let extent = slicer.extent as i32;
let global =
|m: &BTreeMap<TileId, Vec<Vec<Coord<i32>>>>| -> BTreeMap<TileId, Vec<Vec<Coord<i32>>>> {
m.iter()
.map(|(&t, runs)| (t, globalize(t, runs, extent)))
.collect()
};
let all_bytes = support::feature_collection_bytes(build_features(geom, &global(&all)));
let one_bytes = support::feature_collection_bytes(build_features(geom, &global(&one)));
assert_eq!(
all_bytes, one_bytes,
"batch and per-tile snapshots differ for {stem} (buffer {})",
slicer.buffer
);
insta::with_settings!({
snapshot_path => snapshot_dir,
prepend_module_to_snapshot => false,
}, {
let name = if slicer.buffer > 0 {
format!("{stem}-{}.geojson", slicer.buffer)
} else {
format!("{stem}.geojson")
};
assert_binary_snapshot!(&name, all_bytes);
});
}