Last active
August 13, 2023 21:01
-
-
Save Nemo157/354120646fbfdd465404c6abbdc23a7a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! ```cargo | |
//! package.edition = "2021" | |
//! [dependencies] | |
//! serde-xml-rs = "0.6.0" | |
//! quick-xml = { version = "0.30.0", features = ["serialize"] } | |
//! serde = { version = "=1.0.171", features = ["derive"] } | |
//! clap = { version = "4.3.21", features = ["derive"] } | |
//! anyhow = "1.0.72" | |
//! culpa = "1.0.1" | |
//! ``` | |
use anyhow::Error; | |
use clap::Parser; | |
use serde::{Deserialize, Serialize}; | |
use std::{ | |
collections::BTreeSet, | |
f64::consts::PI, | |
path::{Path, PathBuf}, | |
}; | |
#[derive(Clone, Debug, Serialize, Deserialize)] | |
struct Gpx { | |
#[serde(rename = "@version")] | |
version: String, | |
#[serde(rename = "@creator")] | |
creator: String, | |
metadata: Metadata, | |
trk: Track, | |
} | |
#[derive(Clone, Debug, Serialize, Deserialize)] | |
struct Metadata { | |
name: String, | |
author: String, | |
} | |
#[derive(Clone, Debug, Serialize, Deserialize)] | |
struct Track { | |
name: String, | |
trkseg: TrackSegment, | |
} | |
#[derive(Clone, Debug, Serialize, Deserialize)] | |
struct TrackSegment { | |
trkpt: Vec<TrackPoint>, | |
} | |
#[derive(Clone, Debug, Serialize, Deserialize)] | |
struct TrackPoint { | |
#[serde(rename = "@lat")] | |
lat: f64, | |
#[serde(rename = "@lon")] | |
lon: f64, | |
} | |
#[derive(Clone, Debug, Parser)] | |
enum Args { | |
GetBounds { | |
path: PathBuf, | |
}, | |
Copy { | |
input: PathBuf, | |
output: PathBuf, | |
}, | |
GridCovering { | |
input: PathBuf, | |
output: PathBuf, | |
#[clap(long, default_value_t = 17)] | |
zoom: i32, | |
}, | |
Expanded { | |
input: PathBuf, | |
output: PathBuf, | |
#[clap(long, default_value_t = 17)] | |
zoom: i32, | |
#[clap(long, default_value_t = 3)] | |
expansion: i32, | |
}, | |
} | |
#[culpa::throws] | |
fn read(path: &Path) -> Gpx { | |
quick_xml::de::from_str(&std::fs::read_to_string(path)?)? | |
} | |
#[culpa::throws] | |
fn write(path: &Path, gpx: &Gpx) { | |
let mut text = String::new(); | |
let mut serializer = quick_xml::se::Serializer::with_root(&mut text, Some("gpx"))?; | |
serializer.indent(' ', 4); | |
gpx.serialize(serializer)?; | |
std::fs::write(path, text)?; | |
} | |
fn bounds(gpx: &Gpx) -> (TrackPoint, TrackPoint) { | |
let mut min = TrackPoint { | |
lat: f64::INFINITY, | |
lon: f64::INFINITY, | |
}; | |
let mut max = TrackPoint { | |
lat: f64::NEG_INFINITY, | |
lon: f64::NEG_INFINITY, | |
}; | |
for point in &gpx.trk.trkseg.trkpt { | |
min.lat = min.lat.min(point.lat); | |
min.lon = min.lon.min(point.lon); | |
max.lat = max.lat.max(point.lat); | |
max.lon = max.lon.max(point.lon); | |
} | |
(min, max) | |
} | |
fn point_to_tile(TrackPoint { lat, lon }: TrackPoint, zoom: i32) -> (i32, i32) { | |
let n = (2.0f64).powi(zoom); | |
let x = (lon + 180.0) / 360.0 * n; | |
let y = (1.0 - (lat * PI / 180.0).tan().asinh() / PI) / 2.0 * n; | |
(x.floor() as i32, y.floor() as i32) | |
} | |
fn tile_to_point((x, y): (i32, i32), zoom: i32) -> TrackPoint { | |
let (x, y) = (x as f64 + 0.5, y as f64 + 0.5); | |
let n = (2.0f64).powi(zoom); | |
let lat = ((1.0 - 2.0 * y / n) * PI).sinh().atan() * 180.0 / PI; | |
let lon = x / n * 360.0 - 180.0; | |
TrackPoint { lat, lon } | |
} | |
#[culpa::throws] | |
fn main() { | |
match Args::parse() { | |
Args::GetBounds { path } => { | |
let gpx = read(&path)?; | |
let (min, max) = bounds(&gpx); | |
dbg!(min, max); | |
} | |
Args::Copy { input, output } => { | |
write(&output, &read(&input)?)?; | |
} | |
Args::GridCovering { | |
input, | |
output, | |
zoom, | |
} => { | |
let gpx = read(&input)?; | |
let name = format!("Grid covering {}", gpx.metadata.name); | |
let (min, max) = bounds(&gpx); | |
let (min, max) = (point_to_tile(min, zoom), point_to_tile(max, zoom)); | |
let gpx = Gpx { | |
version: "1.0".into(), | |
creator: "tool.rs".into(), | |
metadata: Metadata { | |
name: name.clone(), | |
author: "tool.rs".into(), | |
}, | |
trk: Track { | |
name, | |
trkseg: TrackSegment { | |
trkpt: Vec::from_iter((min.0..=max.0).flat_map(|x| { | |
(max.1..=min.1).map(move |y| tile_to_point((x, y), zoom)) | |
})), | |
}, | |
}, | |
}; | |
write(&output, &gpx)?; | |
} | |
Args::Expanded { | |
input, | |
output, | |
zoom, | |
expansion, | |
} => { | |
let gpx = read(&input)?; | |
let name = format!("Expanded Width {}", gpx.metadata.name); | |
let tiles = BTreeSet::from_iter( | |
gpx.trk | |
.trkseg | |
.trkpt | |
.into_iter() | |
.map(|point| point_to_tile(point, zoom)), | |
); | |
let expanded = BTreeSet::from_iter(tiles.into_iter().flat_map(|(x, y)| { | |
(0..=expansion / 2).flat_map(move |o| { | |
[ | |
(x - o, y - o), | |
(x, y - o), | |
(x + o, y - o), | |
(x - o, y), | |
(x, y), | |
(x + o, y), | |
(x - o, y + o), | |
(x, y + o), | |
(x + o, y + o), | |
] | |
}) | |
})); | |
let gpx = Gpx { | |
version: "1.0".into(), | |
creator: "tool.rs".into(), | |
metadata: Metadata { | |
name: name.clone(), | |
author: "tool.rs".into(), | |
}, | |
trk: Track { | |
name, | |
trkseg: TrackSegment { | |
trkpt: Vec::from_iter( | |
expanded.into_iter().map(|tile| tile_to_point(tile, zoom)), | |
), | |
}, | |
}, | |
}; | |
write(&output, &gpx)?; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment