use leptos::logging::{warn, error};
use leptos::prelude::*;
use wasm_bindgen::prelude::*;
use crate::core::JsStoredValue;
use super::LeafletMapContext;
fn tile_to_quadkey(x: u32, y: u32, z: u32) -> String {
let mut quadkey = String::new();
let safe_z = if z >= 32 {
warn!("Zoom level {} is too high for quadkey calculation, clamping to 31", z);
31
} else {
z
};
for i in (1..=safe_z).rev() {
let mut digit = 0;
let mask = 1 << (i - 1);
if (x & mask) != 0 {
digit += 1;
}
if (y & mask) != 0 {
digit += 2;
}
quadkey.push(char::from_digit(digit, 10).unwrap_or('0'));
}
quadkey
}
#[component(transparent)]
pub fn QuadTileLayer(
#[prop(into)] url: String,
#[prop(into, optional)] attribution: String,
#[prop(optional)] bring_to_front: bool,
#[prop(optional)] bring_to_back: bool,
#[prop(default = 0.0)] min_zoom: f64,
#[prop(default = 18.0)] max_zoom: f64,
) -> impl IntoView {
let map_context = use_context::<LeafletMapContext>().expect("map context not found");
let get_tile_url_closure: JsStoredValue<Option<Closure<dyn Fn(JsValue) -> String>>> =
JsStoredValue::new_local(None);
Effect::new(move |_| {
if let Some(map) = map_context.map() {
let options = leaflet::TileLayerOptions::default();
if !attribution.is_empty() {
options.set_attribution(attribution.to_string());
}
options.set_min_zoom(min_zoom);
options.set_max_zoom(max_zoom);
let map_layer = leaflet::TileLayer::new_options(&url, &options);
let url_pattern = url.clone();
let closure = Closure::wrap(Box::new(move |coords: JsValue| -> String {
let x = js_sys::Reflect::get(&coords, &JsValue::from_str("x"))
.unwrap_or(JsValue::from(0))
.as_f64()
.unwrap_or(0.0) as u32;
let y = js_sys::Reflect::get(&coords, &JsValue::from_str("y"))
.unwrap_or(JsValue::from(0))
.as_f64()
.unwrap_or(0.0) as u32;
let z = js_sys::Reflect::get(&coords, &JsValue::from_str("z"))
.unwrap_or(JsValue::from(0))
.as_f64()
.unwrap_or(0.0) as u32;
let quadkey = tile_to_quadkey(x, y, z);
url_pattern.replace("{q}", &quadkey)
}) as Box<dyn Fn(JsValue) -> String>);
if let Err(e) = js_sys::Reflect::set(
&map_layer,
&JsValue::from_str("getTileUrl"),
closure.as_ref().unchecked_ref(),
) {
error!("Failed to set getTileUrl method: {:?}", e);
return;
}
get_tile_url_closure.set_value(Some(closure));
map_layer.add_to(&map);
match (bring_to_front, bring_to_back) {
(true, true) => warn!("The parameters are set to bring the layer to front and back at the same time. Ignoring these parameters..."),
(true, false) => {map_layer.bring_to_front();}
(false, true) => {map_layer.bring_to_back();}
(false, false) => (),
}
let map_layer = JsStoredValue::new_local(map_layer);
on_cleanup(move || {
map_layer.with_value(|v| v.remove());
});
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tile_to_quadkey() {
assert_eq!(tile_to_quadkey(0, 0, 1), "0");
assert_eq!(tile_to_quadkey(1, 0, 1), "1");
assert_eq!(tile_to_quadkey(0, 1, 1), "2");
assert_eq!(tile_to_quadkey(1, 1, 1), "3");
assert_eq!(tile_to_quadkey(2, 1, 2), "12");
assert_eq!(tile_to_quadkey(0, 2, 2), "20");
assert_eq!(tile_to_quadkey(3, 5, 3), "213");
assert_eq!(tile_to_quadkey(0, 0, 0), "");
let result_32 = tile_to_quadkey(0, 0, 32);
let result_31 = tile_to_quadkey(0, 0, 31);
assert_eq!(result_32, result_31);
assert!(!result_32.is_empty());
let result_50 = tile_to_quadkey(1, 1, 50);
let result_31_same_coords = tile_to_quadkey(1, 1, 31);
assert_eq!(result_50, result_31_same_coords);
assert!(!result_50.is_empty()); }
}