leptos_leaflet/components/
quad_tile_layer.rs1use leptos::logging::{warn, error};
2use leptos::prelude::*;
3use wasm_bindgen::prelude::*;
4
5use crate::core::JsStoredValue;
6
7use super::LeafletMapContext;
8
9fn tile_to_quadkey(x: u32, y: u32, z: u32) -> String {
14 let mut quadkey = String::new();
15
16 let safe_z = if z >= 32 {
18 warn!("Zoom level {} is too high for quadkey calculation, clamping to 31", z);
19 31
20 } else {
21 z
22 };
23
24 for i in (1..=safe_z).rev() {
25 let mut digit = 0;
26 let mask = 1 << (i - 1);
27
28 if (x & mask) != 0 {
29 digit += 1;
30 }
31 if (y & mask) != 0 {
32 digit += 2;
33 }
34
35 quadkey.push(char::from_digit(digit, 10).unwrap_or('0'));
37 }
38
39 quadkey
40}
41
42#[component(transparent)]
68pub fn QuadTileLayer(
69 #[prop(into)] url: String,
70 #[prop(into, optional)] attribution: String,
71 #[prop(optional)] bring_to_front: bool,
72 #[prop(optional)] bring_to_back: bool,
73 #[prop(default = 0.0)] min_zoom: f64,
74 #[prop(default = 18.0)] max_zoom: f64,
75) -> impl IntoView {
76 let map_context = use_context::<LeafletMapContext>().expect("map context not found");
77
78 let get_tile_url_closure: JsStoredValue<Option<Closure<dyn Fn(JsValue) -> String>>> =
80 JsStoredValue::new_local(None);
81
82 Effect::new(move |_| {
83 if let Some(map) = map_context.map() {
84 let options = leaflet::TileLayerOptions::default();
86 if !attribution.is_empty() {
87 options.set_attribution(attribution.to_string());
88 }
89 options.set_min_zoom(min_zoom);
90 options.set_max_zoom(max_zoom);
91
92 let map_layer = leaflet::TileLayer::new_options(&url, &options);
94
95 let url_pattern = url.clone();
97 let closure = Closure::wrap(Box::new(move |coords: JsValue| -> String {
98 let x = js_sys::Reflect::get(&coords, &JsValue::from_str("x"))
100 .unwrap_or(JsValue::from(0))
101 .as_f64()
102 .unwrap_or(0.0) as u32;
103 let y = js_sys::Reflect::get(&coords, &JsValue::from_str("y"))
104 .unwrap_or(JsValue::from(0))
105 .as_f64()
106 .unwrap_or(0.0) as u32;
107 let z = js_sys::Reflect::get(&coords, &JsValue::from_str("z"))
108 .unwrap_or(JsValue::from(0))
109 .as_f64()
110 .unwrap_or(0.0) as u32;
111
112 let quadkey = tile_to_quadkey(x, y, z);
113 url_pattern.replace("{q}", &quadkey)
114 }) as Box<dyn Fn(JsValue) -> String>);
115
116 if let Err(e) = js_sys::Reflect::set(
118 &map_layer,
119 &JsValue::from_str("getTileUrl"),
120 closure.as_ref().unchecked_ref(),
121 ) {
122 error!("Failed to set getTileUrl method: {:?}", e);
123 return;
124 }
125
126 get_tile_url_closure.set_value(Some(closure));
128
129 map_layer.add_to(&map);
130
131 match (bring_to_front, bring_to_back) {
132 (true, true) => warn!("The parameters are set to bring the layer to front and back at the same time. Ignoring these parameters..."),
133 (true, false) => {map_layer.bring_to_front();}
134 (false, true) => {map_layer.bring_to_back();}
135 (false, false) => (),
136 }
137
138 let map_layer = JsStoredValue::new_local(map_layer);
139
140 on_cleanup(move || {
141 map_layer.with_value(|v| v.remove());
142 });
143 }
144 });
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_tile_to_quadkey() {
153 assert_eq!(tile_to_quadkey(0, 0, 1), "0");
158 assert_eq!(tile_to_quadkey(1, 0, 1), "1");
159 assert_eq!(tile_to_quadkey(0, 1, 1), "2");
160 assert_eq!(tile_to_quadkey(1, 1, 1), "3");
161
162 assert_eq!(tile_to_quadkey(2, 1, 2), "12");
164 assert_eq!(tile_to_quadkey(0, 2, 2), "20");
165
166 assert_eq!(tile_to_quadkey(3, 5, 3), "213");
168
169 assert_eq!(tile_to_quadkey(0, 0, 0), "");
171
172 let result_32 = tile_to_quadkey(0, 0, 32);
174 let result_31 = tile_to_quadkey(0, 0, 31);
175 assert_eq!(result_32, result_31);
176 assert!(!result_32.is_empty()); let result_50 = tile_to_quadkey(1, 1, 50);
179 let result_31_same_coords = tile_to_quadkey(1, 1, 31);
180 assert_eq!(result_50, result_31_same_coords);
181 assert!(!result_50.is_empty()); }
183}