use anyhow::Result;
use crate::EventCtx;
use geom::{GPSBounds, LonLat, Pt2D};
pub struct URLManager;
impl URLManager {
pub fn update_url_free_param(free_param: String) {
must_update_url(Box::new(move |url| change_url_free_param(url, &free_param)))
}
pub fn update_url_param(key: String, value: String) {
must_update_url(Box::new(move |url| change_url_param(url, &key, &value)))
}
pub fn get_cam_param(ctx: &EventCtx, gps_bounds: &GPSBounds) -> String {
let center = ctx.canvas.center_to_map_pt().to_gps(gps_bounds);
let earth_circumference_equator = 40_075_016.686;
let log_arg =
earth_circumference_equator * center.y().to_radians().cos() * ctx.canvas.cam_zoom;
let zoom_lvl = log_arg.log2() - 8.0;
format!("{:.2}/{:.5}/{:.5}", zoom_lvl, center.y(), center.x())
}
pub fn update_url_cam(ctx: &EventCtx, gps_bounds: &GPSBounds) {
let cam = URLManager::get_cam_param(ctx, gps_bounds);
must_update_url(Box::new(move |url| change_url_param(url, "--cam", &cam)))
}
pub fn change_camera(ctx: &mut EventCtx, raw: Option<&String>, gps_bounds: &GPSBounds) -> bool {
if let Some((pt, zoom)) =
raw.and_then(|raw| URLManager::parse_center_camera(raw, gps_bounds))
{
ctx.canvas.cam_zoom = zoom;
ctx.canvas.center_on_map_pt(pt);
true
} else {
false
}
}
fn parse_center_camera(raw: &str, gps_bounds: &GPSBounds) -> Option<(Pt2D, f64)> {
let parts: Vec<&str> = raw.split('/').collect();
if parts.len() != 3 {
return None;
}
let zoom_lvl = parts[0].parse::<f64>().ok()?;
let lat = parts[1].parse::<f64>().ok()?;
let lon = parts[2].parse::<f64>().ok()?;
let gps = LonLat::new(lon, lat);
if !gps_bounds.contains(gps) {
return None;
}
let pt = gps.to_pt(gps_bounds);
let earth_circumference_equator = 40_075_016.686;
let horiz_meters_per_pixel =
earth_circumference_equator * gps.y().to_radians().cos() / 2.0_f64.powf(zoom_lvl + 8.0);
let cam_zoom = 1.0 / horiz_meters_per_pixel;
Some((pt, cam_zoom))
}
}
fn must_update_url(transform: Box<dyn Fn(String) -> String>) {
if let Err(err) = update_url(transform) {
warn!("Couldn't update URL: {}", err);
}
}
#[allow(unused_variables)]
fn update_url(transform: Box<dyn Fn(String) -> String>) -> Result<()> {
#[cfg(target_arch = "wasm32")]
{
let window = web_sys::window().ok_or(anyhow!("no window?"))?;
let url = window.location().href().map_err(|err| {
anyhow!(err
.as_string()
.unwrap_or("window.location.href failed".to_string()))
})?;
let new_url = (transform)(url);
let history = window.history().map_err(|err| {
anyhow!(err
.as_string()
.unwrap_or("window.history failed".to_string()))
})?;
history
.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(&new_url))
.map_err(|err| {
anyhow!(err
.as_string()
.unwrap_or("window.history.replace_state failed".to_string()))
})?;
}
Ok(())
}
fn change_url_free_param(url: String, free_param: &str) -> String {
let url_parts = url.split('?').collect::<Vec<_>>();
if url_parts.len() == 1 {
return format!("{}?{}", url, free_param);
}
let mut query_params = String::new();
let mut found_free = false;
let mut first = true;
for x in url_parts[1].split('&') {
if !first {
query_params.push('&');
}
first = false;
if x.starts_with("--") {
query_params.push_str(x);
} else if !found_free {
query_params.push_str(free_param);
found_free = true;
} else {
query_params.push_str(x);
}
}
if !found_free {
if !first {
query_params.push('&');
}
query_params.push_str(free_param);
}
format!("{}?{}", url_parts[0], query_params)
}
fn change_url_param(url: String, key: &str, value: &str) -> String {
let url_parts = url.split('?').collect::<Vec<_>>();
if url_parts.len() == 1 {
return format!("{}?{}={}", url, key, value);
}
let mut query_params = String::new();
let mut found_key = false;
let mut first = true;
for x in url_parts[1].split('&') {
if !first {
query_params.push('&');
}
first = false;
if x.starts_with(key) {
query_params.push_str(&format!("{}={}", key, value));
found_key = true;
} else {
query_params.push_str(x);
}
}
if !found_key {
if !first {
query_params.push('&');
}
query_params.push_str(&format!("{}={}", key, value));
}
format!("{}?{}", url_parts[0], query_params)
}
#[cfg(test)]
mod tests {
#[test]
fn test_change_url_free_param() {
use super::change_url_free_param;
assert_eq!(
"http://0.0.0.0:8000/?--dev&seattle/maps/montlake.bin",
change_url_free_param(
"http://0.0.0.0:8000/?--dev".to_string(),
"seattle/maps/montlake.bin"
)
);
assert_eq!(
"http://0.0.0.0:8000/?--dev&seattle/maps/qa.bin",
change_url_free_param(
"http://0.0.0.0:8000/?--dev&seattle/maps/montlake.bin".to_string(),
"seattle/maps/qa.bin"
)
);
assert_eq!(
"http://0.0.0.0:8000?seattle/maps/montlake.bin",
change_url_free_param(
"http://0.0.0.0:8000".to_string(),
"seattle/maps/montlake.bin"
)
);
}
#[test]
fn test_change_url_param() {
use super::change_url_param;
assert_eq!(
"http://0.0.0.0:8000/?--dev&seattle/maps/montlake.bin&--cam=16.6/53.78449/-1.70701",
change_url_param(
"http://0.0.0.0:8000/?--dev&seattle/maps/montlake.bin".to_string(),
"--cam",
"16.6/53.78449/-1.70701"
)
);
assert_eq!(
"http://0.0.0.0:8000?--cam=16.6/53.78449/-1.70701",
change_url_param(
"http://0.0.0.0:8000".to_string(),
"--cam",
"16.6/53.78449/-1.70701"
)
);
}
}