Skip to content

Instantly share code, notes, and snippets.

@Nemo157
Last active August 13, 2023 21:01
Show Gist options
  • Save Nemo157/354120646fbfdd465404c6abbdc23a7a to your computer and use it in GitHub Desktop.
Save Nemo157/354120646fbfdd465404c6abbdc23a7a to your computer and use it in GitHub Desktop.
//! ```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