use crate::source::{TileKey, TileSource};
use crate::sources::util::{decode_png_to_buffer, format_url};
use lru::LruCache;
use send_wrapper::SendWrapper;
use slint::{Image, Rgba8Pixel, SharedPixelBuffer};
use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex};
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
pub const OSM_TILE_URL: &str = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
const TILE_CACHE_CAPACITY: usize = 256;
pub struct WasmOsmTileSource {
memory: Arc<Mutex<LruCache<TileKey, SharedPixelBuffer<Rgba8Pixel>>>>,
in_flight: Arc<Mutex<HashSet<TileKey>>>,
failed: Arc<Mutex<HashSet<TileKey>>>,
url_template: String,
on_tile_ready: Arc<Mutex<Option<SendWrapper<Arc<dyn Fn()>>>>>,
tile_size: u32,
min_zoom: u8,
max_zoom: u8,
}
impl WasmOsmTileSource {
pub fn new() -> Self {
Self::with_url(OSM_TILE_URL)
}
pub fn with_url(url_template: impl Into<String>) -> Self {
let capacity = NonZeroUsize::new(TILE_CACHE_CAPACITY)
.expect("TILE_CACHE_CAPACITY is a const, never zero");
Self {
memory: Arc::new(Mutex::new(LruCache::new(capacity))),
in_flight: Arc::new(Mutex::new(HashSet::new())),
failed: Arc::new(Mutex::new(HashSet::new())),
url_template: url_template.into(),
on_tile_ready: Arc::new(Mutex::new(None)),
tile_size: 256,
min_zoom: 0,
max_zoom: 19,
}
}
pub fn with_zoom_range(mut self, min: u8, max: u8) -> Self {
self.min_zoom = min;
self.max_zoom = max;
self
}
pub fn on_tile_ready(&self, cb: impl Fn() + 'static) {
*self.on_tile_ready.lock().unwrap() = Some(SendWrapper::new(Arc::new(cb)));
}
}
impl Default for WasmOsmTileSource {
fn default() -> Self {
Self::new()
}
}
impl TileSource for WasmOsmTileSource {
fn tile(&self, key: TileKey) -> Option<Image> {
if let Some(buf) = self.memory.lock().unwrap().get(&key).cloned() {
return Some(Image::from_rgba8(buf));
}
if self.failed.lock().unwrap().contains(&key) {
return None;
}
if !self.in_flight.lock().unwrap().insert(key) {
return None;
}
let url = format_url(&self.url_template, key);
let memory = Arc::clone(&self.memory);
let in_flight_cb = Arc::clone(&self.in_flight);
let failed_cb = Arc::clone(&self.failed);
let on_ready = Arc::clone(&self.on_tile_ready);
let in_flight_err = Arc::clone(&self.in_flight);
let failed_err = Arc::clone(&self.failed);
let xhr = web_sys::XmlHttpRequest::new()
.expect("XmlHttpRequest::new should succeed in a browser context");
if xhr.open_with_async("GET", &url, true).is_err() {
failed_err.lock().unwrap().insert(key);
in_flight_err.lock().unwrap().remove(&key);
return None;
}
xhr.set_response_type(web_sys::XmlHttpRequestResponseType::Arraybuffer);
let xhr_for_handler = xhr.clone();
let handler_js = Closure::once_into_js(move || {
let status = xhr_for_handler.status().unwrap_or(0);
let success = (200..300).contains(&status);
let buf = if success {
xhr_for_handler
.response()
.ok()
.and_then(|resp| resp.dyn_into::<js_sys::ArrayBuffer>().ok())
.and_then(|ab| {
let view = js_sys::Uint8Array::new(&ab);
let mut bytes = vec![0u8; view.length() as usize];
view.copy_to(&mut bytes);
decode_png_to_buffer(&bytes)
})
} else {
None
};
match buf {
Some(b) => {
memory.lock().unwrap().put(key, b);
}
None => {
failed_cb.lock().unwrap().insert(key);
}
}
in_flight_cb.lock().unwrap().remove(&key);
let cb = on_ready.lock().unwrap().as_ref().map(|w| Arc::clone(&**w));
if let Some(cb) = cb {
cb();
}
});
xhr.set_onloadend(Some(handler_js.unchecked_ref()));
if xhr.send().is_err() {
failed_err.lock().unwrap().insert(key);
in_flight_err.lock().unwrap().remove(&key);
}
None
}
fn tile_size(&self) -> u32 {
self.tile_size
}
fn min_zoom(&self) -> u8 {
self.min_zoom
}
fn max_zoom(&self) -> u8 {
self.max_zoom
}
fn cancel_all_except(&self, _keep: &HashSet<TileKey>) {
}
}