250 lines
8.8 KiB
Rust
250 lines
8.8 KiB
Rust
use crate::components::messages::MonitorInputMsg;
|
|
use crate::components::{MonitorCommand, MonitorModel};
|
|
use crate::coords::Range;
|
|
use crate::map_tile_utils::lat_lon_to_zoom;
|
|
use crate::pipeline::{Target, TargetType};
|
|
use dirs::cache_dir;
|
|
use femtovg::ImageSource;
|
|
use futures::future::BoxFuture;
|
|
use gtk::ffi::gtk_about_dialog_add_credit_section;
|
|
use once_cell::sync::Lazy;
|
|
use quick_cache::sync::Cache;
|
|
use relm4::{ComponentSender, Sender};
|
|
use reqwest::{Client, Error, Url};
|
|
use slippy_map_tiles::{merc_location_to_tile_coords, BBox, Tile};
|
|
use smallvec::SmallVec;
|
|
use sorted_vec::SortedSet;
|
|
use std::cell::{Cell, RefCell};
|
|
use std::collections::HashSet;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use tokio::sync::Mutex;
|
|
use tokio::task;
|
|
use tracing::{debug, info};
|
|
|
|
static TILE_CACHE_PATH: Lazy<std::sync::Mutex<PathBuf>> = Lazy::new(|| {
|
|
let new_path = cache_dir()
|
|
.unwrap_or(PathBuf::from("./"))
|
|
.join("radar-g/tiles");
|
|
|
|
if !new_path.exists() {
|
|
info!("Create cache dir: {:?}", new_path);
|
|
std::fs::create_dir_all(&new_path).unwrap();
|
|
}
|
|
std::sync::Mutex::new(new_path)
|
|
});
|
|
|
|
type TileCache = Cache<Tile, Arc<std::sync::Mutex<Target>>>;
|
|
|
|
pub struct MapTile {
|
|
server: String,
|
|
api_key: String,
|
|
style: String,
|
|
client: Client,
|
|
onloading: Arc<std::sync::Mutex<HashSet<Tile>>>,
|
|
cache: Arc<std::sync::Mutex<TileCache>>,
|
|
zoom_level: Cell<u8>,
|
|
}
|
|
|
|
impl Default for MapTile {
|
|
fn default() -> Self {
|
|
Self {
|
|
server: "https://tiles.stadiamaps.com/tiles/".to_string(),
|
|
api_key: "06f1aeed-5d91-48e3-9ce5-1e99063f7f73".to_string(),
|
|
// style: "stamen_toner".to_string(),
|
|
style: "alidade_smooth_dark".to_string(),
|
|
client: Client::new(),
|
|
onloading: Arc::new(std::sync::Mutex::new(HashSet::new())),
|
|
cache: Arc::new(std::sync::Mutex::new(Cache::new(32))),
|
|
zoom_level: Cell::new(4),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MapTile {
|
|
pub fn new_tiles(
|
|
&self,
|
|
zoom: u8,
|
|
lat_range: (f32, f32),
|
|
lon_range: (f32, f32),
|
|
) -> impl Iterator<Item = Tile> + Sized {
|
|
let bbox = BBox::new(lat_range.1, lon_range.0, lat_range.0, lon_range.1).unwrap();
|
|
let tiles = bbox.tiles_for_zoom(zoom);
|
|
tiles
|
|
}
|
|
|
|
fn get_tile_task(&self, tile: &Tile) -> (bool, BoxFuture<'static, Result<Vec<u8>, Error>>) {
|
|
let _cache_path = TILE_CACHE_PATH.lock().unwrap().clone().join(format!(
|
|
"{}-{}-{}.png",
|
|
tile.zoom(),
|
|
tile.x(),
|
|
tile.y()
|
|
));
|
|
let exists_cache = _cache_path.exists();
|
|
if exists_cache {
|
|
let client = self.client.clone();
|
|
let key = self.api_key.clone();
|
|
(
|
|
false,
|
|
Box::pin(async move {
|
|
info!("Read from cache: {:?}", _cache_path);
|
|
let result = tokio::fs::read(_cache_path).await.unwrap();
|
|
Ok(result)
|
|
}),
|
|
)
|
|
} else {
|
|
let base_url = Url::parse(&self.server).unwrap();
|
|
let mut request_url = base_url
|
|
.join(&format!("{}/", self.style))
|
|
.unwrap()
|
|
.join(&format!("{}/{}/{}@2x.png", tile.zoom(), tile.x(), tile.y()))
|
|
.unwrap();
|
|
let client = self.client.clone();
|
|
let key = self.api_key.clone();
|
|
(
|
|
true,
|
|
Box::pin(async move {
|
|
let result = client
|
|
.get(request_url)
|
|
.query(&[("api_key", &key)])
|
|
.send()
|
|
.await?;
|
|
let bytes = result.bytes().await?;
|
|
Ok(bytes.to_vec())
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
pub async fn get_tile(&self, tile: &Tile) -> Result<Vec<u8>, Error> {
|
|
let base_url = Url::parse(&self.server).unwrap();
|
|
let mut request_url = base_url
|
|
.join(&self.style)
|
|
.unwrap()
|
|
.join(&format!("{}/{}/{}@2x.png", tile.zoom(), tile.x(), tile.y()))
|
|
.unwrap();
|
|
|
|
let result = self
|
|
.client
|
|
.get(request_url)
|
|
.query(&[("api_key", &self.api_key)])
|
|
.send()
|
|
.await?;
|
|
let bytes = result.bytes().await?;
|
|
Ok(bytes.to_vec())
|
|
}
|
|
|
|
pub fn set_zoom(&self, zoom: u8) {
|
|
self.zoom_level.set(zoom);
|
|
}
|
|
|
|
fn insert_to_cache(cache: Arc<std::sync::Mutex<TileCache>>, result: Vec<u8>, tile: Tile) {
|
|
let origin = tile.nw_corner();
|
|
let rb = tile.se_corner();
|
|
let bounds = (
|
|
(origin.lon() as f64..rb.lon() as f64).into(),
|
|
(origin.lat() as f64..rb.lat() as f64).into(),
|
|
);
|
|
let result = Target::new(TargetType::Mem(result), 256.0, 256.0, bounds, None);
|
|
let cache = cache.lock().unwrap();
|
|
cache.insert(tile, Arc::new(std::sync::Mutex::new(result)));
|
|
}
|
|
|
|
async fn save_to_cache_path(result: &Vec<u8>, tile: Tile) -> Result<(), std::io::Error> {
|
|
let _cache_path = TILE_CACHE_PATH.lock().unwrap().clone();
|
|
let path = _cache_path.join(format!("{}-{}-{}.png", tile.zoom(), tile.x(), tile.y()));
|
|
tokio::fs::write(path, result).await
|
|
}
|
|
|
|
pub fn load_tiles(
|
|
&self,
|
|
range: ((f32, f32), (f32, f32)),
|
|
sender: ComponentSender<MonitorModel>,
|
|
) -> BoxFuture<'static, ()> {
|
|
let zoom = self.zoom_level.get();
|
|
let new_tiles = self.new_tiles(zoom, range.0, range.1);
|
|
let cache = (*self.cache).lock().unwrap();
|
|
let mut bindings = self.onloading.lock().unwrap();
|
|
let new_tiles = new_tiles
|
|
.filter(|x| cache.peek(x).is_none() && !bindings.contains(x))
|
|
.collect::<Vec<_>>();
|
|
|
|
if new_tiles.len() > 0 {
|
|
bindings.extend(new_tiles.iter().cloned());
|
|
info!("Load new tiles");
|
|
let tasks = new_tiles
|
|
.into_iter()
|
|
.map(move |tile| {
|
|
let (need_save, _task) = self.get_tile_task(&tile);
|
|
let sender = sender.clone();
|
|
let cache = self.cache.clone();
|
|
let onloading = self.onloading.clone();
|
|
task::spawn(async move {
|
|
let result = _task.await;
|
|
if let Ok(result) = result {
|
|
if let Err(e) = Self::save_to_cache_path(&result, tile).await {
|
|
info!("Error saving to cache: {}", e);
|
|
}
|
|
if need_save {
|
|
Self::save_to_cache_path(&result, tile);
|
|
}
|
|
Self::insert_to_cache(cache, result, tile);
|
|
onloading.lock().unwrap().remove(&tile);
|
|
sender.command_sender().emit(MonitorCommand::LoadedTile);
|
|
}
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
return Box::pin(async move {
|
|
for task in tasks {
|
|
task.await.unwrap();
|
|
}
|
|
});
|
|
} else {
|
|
info!("No new tiles need to download");
|
|
return Box::pin(async move {
|
|
sender.command_sender().emit(MonitorCommand::LoadedTile);
|
|
});
|
|
}
|
|
}
|
|
|
|
pub fn current_tiles(
|
|
&self,
|
|
range: ((f32, f32), (f32, f32)),
|
|
) -> Vec<Arc<std::sync::Mutex<Target>>> {
|
|
let zoom = self.zoom_level.get();
|
|
let current = self.new_tiles(zoom, range.0, range.1);
|
|
let mut total_len = 0;
|
|
let mut results = Vec::new();
|
|
let mut cached = Vec::new();
|
|
let cache = self.cache.lock().unwrap();
|
|
for tile in current {
|
|
total_len += 1;
|
|
if let Some(target) = cache.get(&tile) {
|
|
results.push(target);
|
|
} else {
|
|
let center = tile.center_point();
|
|
let mut start_zoom = zoom - 1;
|
|
while start_zoom > 0 {
|
|
let (tile, (x, y)) = merc_location_to_tile_coords(
|
|
center.lon() as f64,
|
|
center.lat() as f64,
|
|
start_zoom,
|
|
);
|
|
let tile = Tile::new(start_zoom, tile.0, tile.1).unwrap();
|
|
if cached.contains(&tile) {
|
|
break;
|
|
} else {
|
|
if let Some(target) = cache.get(&tile) {
|
|
results.insert(0, target);
|
|
cached.push(tile);
|
|
break;
|
|
}
|
|
}
|
|
start_zoom -= 1;
|
|
}
|
|
}
|
|
}
|
|
results
|
|
}
|
|
}
|