#![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::{SlicerAll, 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 mut slicer = SlicerAll::<Coord<i32>, Attrs>::new(EXTENT, 0).expect("valid config");
for input in &inputs {
slicer
.add_feature_with(&input.line, input.attrs.clone())
.expect("valid geometry");
}
let tiled = tile_features(&slicer, 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(),
slicer.len(),
);
}
fn tile_features(slicer: &SlicerAll<Coord<i32>, Attrs>, extent: u32) -> Vec<TiledFeature> {
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(TiledFeature {
tile: id,
attrs: view.attr().clone(),
runs,
});
}
}
out
}
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)]),
},
]
}