#![allow(clippy::pedantic, reason = "illustrative example, not library code")]
use std::collections::BTreeMap;
use geo_types::Coord;
use geojson::feature::Id;
use geojson::{Feature, FeatureCollection, GeometryValue, Position};
use map_tile_toolkit::{Slicer, TileId};
use serde_json::{Map, json};
#[derive(Clone, Debug, PartialEq, Eq)]
struct Attrs {
id: Option<u64>,
props: BTreeMap<&'static str, &'static str>,
}
struct Input {
attrs: Attrs,
line: Vec<Coord<i32>>,
}
struct TiledFeature {
tile: TileId,
attrs: Attrs,
runs: Vec<Vec<Coord<i32>>>,
}
fn main() {
const EXTENT: u32 = 25;
let inputs = sample_inputs();
let slicer = Slicer::<Coord<i32>>::new(EXTENT, 0).expect("valid config");
let tiled = tile_features(slicer, &inputs, EXTENT);
let features: Vec<Feature> = tiled.iter().map(to_geojson).collect();
let fc = FeatureCollection {
bbox: None,
features,
foreign_members: None,
};
println!("{}", serde_json::to_string_pretty(&fc).expect("serializes"));
eprintln!(
"{} input features → {} tiled pieces across {} tiles",
inputs.len(),
tiled.len(),
tiled
.iter()
.map(|t| t.tile)
.collect::<std::collections::BTreeSet<_>>()
.len(),
);
}
fn tile_features(slicer: Slicer<Coord<i32>>, inputs: &[Input], extent: u32) -> Vec<TiledFeature> {
let mut pieces: BTreeMap<(TileId, usize), TiledFeature> = BTreeMap::new();
for (index, input) in inputs.iter().enumerate() {
for slice in slicer.line(&input.line).expect("valid geometry") {
let origin = slice.tile().origin(extent).expect("tile in range");
pieces
.entry((slice.tile(), index))
.or_insert_with(|| TiledFeature {
tile: slice.tile(),
attrs: input.attrs.clone(),
runs: Vec::new(),
})
.runs
.push(slice.run().iter().map(|c| c + origin).collect());
}
}
pieces.into_values().collect()
}
fn to_geojson(tf: &TiledFeature) -> Feature {
let position = |c: &Coord<i32>| Position::from([f64::from(c.x), f64::from(c.y)]);
let run = |r: &Vec<Coord<i32>>| r.iter().map(position).collect::<Vec<_>>();
let geometry = if tf.runs.len() == 1 {
GeometryValue::LineString {
coordinates: run(&tf.runs[0]),
}
} else {
GeometryValue::MultiLineString {
coordinates: tf.runs.iter().map(run).collect(),
}
};
let mut props = Map::new();
props.insert(
"tile".to_string(),
json!(format!("{},{}", tf.tile.x, tf.tile.y)),
);
for (k, v) in &tf.attrs.props {
props.insert((*k).to_string(), json!(v));
}
Feature {
bbox: None,
geometry: Some(geojson::Geometry::new(geometry)),
id: tf.attrs.id.map(|n| Id::Number(n.into())),
properties: Some(props),
foreign_members: None,
}
}
fn sample_inputs() -> Vec<Input> {
let line = |pts: &[(i32, i32)]| pts.iter().map(|&(x, y)| Coord { x, y }).collect();
vec![
Input {
attrs: Attrs {
id: Some(1),
props: BTreeMap::from([("name", "Main St"), ("kind", "primary")]),
},
line: line(&[(5, 5), (30, 20), (55, 30), (80, 45)]),
},
Input {
attrs: Attrs {
id: None,
props: BTreeMap::from([("name", "River")]),
},
line: line(&[(8, 15), (8, 40), (13, 40), (18, 40), (18, 15)]),
},
Input {
attrs: Attrs {
id: Some(7),
props: BTreeMap::from([("name", "Pier"), ("kind", "path")]),
},
line: line(&[(3, 3), (18, 8)]),
},
]
}