use super::cache::TileCache;
use super::protocol::{TileCoordinate, TileProtocol, TileRequest, TileResponse};
use crate::error::{Result, StreamingError};
use async_trait::async_trait;
use std::sync::Arc;
use tracing::{debug, info};
#[async_trait]
pub trait TileProvider: Send + Sync {
async fn get_tile(&self, request: &TileRequest) -> Result<TileResponse>;
async fn prefetch_tiles(&self, requests: Vec<TileRequest>) -> Result<Vec<TileResponse>>;
}
#[derive(Debug, Clone)]
pub enum TileSource {
Http {
url_template: String,
min_zoom: u8,
max_zoom: u8,
},
FileSystem {
base_path: std::path::PathBuf,
format: super::protocol::TileFormat,
},
Generator {
min_zoom: u8,
max_zoom: u8,
},
}
pub struct StandardTileProvider {
source: TileSource,
cache: Option<Arc<TileCache>>,
protocol: Arc<dyn TileProtocol>,
}
impl StandardTileProvider {
pub fn new(source: TileSource, protocol: Arc<dyn TileProtocol>) -> Self {
Self {
source,
cache: None,
protocol,
}
}
pub fn with_cache(mut self, cache: Arc<TileCache>) -> Self {
self.cache = Some(cache);
self
}
async fn fetch_tile(&self, request: &TileRequest) -> Result<TileResponse> {
self.protocol.get_tile(request).await
}
}
#[async_trait]
impl TileProvider for StandardTileProvider {
async fn get_tile(&self, request: &TileRequest) -> Result<TileResponse> {
if let Some(cache) = &self.cache {
if let Some(response) = cache.get(&request.coord).await {
debug!("Cache hit for tile {}", request.coord);
return Ok(response);
}
}
let response = self.fetch_tile(request).await?;
if let Some(cache) = &self.cache {
cache.put(response.clone()).await.ok();
}
Ok(response)
}
async fn prefetch_tiles(&self, requests: Vec<TileRequest>) -> Result<Vec<TileResponse>> {
let mut responses = Vec::with_capacity(requests.len());
for request in requests {
match self.get_tile(&request).await {
Ok(response) => responses.push(response),
Err(e) => {
debug!("Failed to prefetch tile {}: {}", request.coord, e);
}
}
}
Ok(responses)
}
}
pub struct MultiSourceTileProvider {
providers: Vec<Arc<dyn TileProvider>>,
}
impl MultiSourceTileProvider {
pub fn new(providers: Vec<Arc<dyn TileProvider>>) -> Self {
Self { providers }
}
pub fn add_provider(&mut self, provider: Arc<dyn TileProvider>) {
self.providers.push(provider);
}
}
#[async_trait]
impl TileProvider for MultiSourceTileProvider {
async fn get_tile(&self, request: &TileRequest) -> Result<TileResponse> {
for (i, provider) in self.providers.iter().enumerate() {
match provider.get_tile(request).await {
Ok(response) => {
if i > 0 {
info!("Fallback to provider {} for tile {}", i, request.coord);
}
return Ok(response);
}
Err(e) => {
debug!("Provider {} failed for tile {}: {}", i, request.coord, e);
continue;
}
}
}
Err(StreamingError::Other(
format!("All providers failed for tile {}", request.coord)
))
}
async fn prefetch_tiles(&self, requests: Vec<TileRequest>) -> Result<Vec<TileResponse>> {
if let Some(provider) = self.providers.first() {
provider.prefetch_tiles(requests).await
} else {
Ok(Vec::new())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::protocol::{TileFormat, XyzProtocol};
#[test]
fn test_tile_source() {
let source = TileSource::Http {
url_template: "https://tile.openstreetmap.org/{z}/{x}/{y}.png".to_string(),
min_zoom: 0,
max_zoom: 18,
};
match source {
TileSource::Http { min_zoom, max_zoom, .. } => {
assert_eq!(min_zoom, 0);
assert_eq!(max_zoom, 18);
}
_ => panic!("Wrong variant"),
}
}
}