oxigdal_streaming/tile/
provider.rs1use super::cache::TileCache;
4use super::protocol::{TileProtocol, TileRequest, TileResponse};
5use crate::error::{Result, StreamingError};
6use async_trait::async_trait;
7use std::sync::Arc;
8use tracing::{debug, info};
9
10#[async_trait]
12pub trait TileProvider: Send + Sync {
13 async fn get_tile(&self, request: &TileRequest) -> Result<TileResponse>;
15
16 async fn prefetch_tiles(&self, requests: Vec<TileRequest>) -> Result<Vec<TileResponse>>;
18}
19
20#[derive(Debug, Clone)]
22pub enum TileSource {
23 Http {
25 url_template: String,
27 min_zoom: u8,
29 max_zoom: u8,
31 },
32
33 FileSystem {
35 base_path: std::path::PathBuf,
37 format: super::protocol::TileFormat,
39 },
40
41 Generator {
43 min_zoom: u8,
45 max_zoom: u8,
47 },
48}
49
50pub struct StandardTileProvider {
52 #[allow(dead_code)]
54 source: TileSource,
55 cache: Option<Arc<TileCache>>,
56 protocol: Arc<dyn TileProtocol>,
57}
58
59impl StandardTileProvider {
60 pub fn new(source: TileSource, protocol: Arc<dyn TileProtocol>) -> Self {
62 Self {
63 source,
64 cache: None,
65 protocol,
66 }
67 }
68
69 pub fn with_cache(mut self, cache: Arc<TileCache>) -> Self {
71 self.cache = Some(cache);
72 self
73 }
74
75 async fn fetch_tile(&self, request: &TileRequest) -> Result<TileResponse> {
77 self.protocol.get_tile(request).await
78 }
79}
80
81#[async_trait]
82impl TileProvider for StandardTileProvider {
83 async fn get_tile(&self, request: &TileRequest) -> Result<TileResponse> {
84 if let Some(cache) = &self.cache
86 && let Some(response) = cache.get(&request.coord).await
87 {
88 debug!("Cache hit for tile {}", request.coord);
89 return Ok(response);
90 }
91
92 let response = self.fetch_tile(request).await?;
94
95 if let Some(cache) = &self.cache {
97 cache.put(response.clone()).await.ok();
98 }
99
100 Ok(response)
101 }
102
103 async fn prefetch_tiles(&self, requests: Vec<TileRequest>) -> Result<Vec<TileResponse>> {
104 let mut responses = Vec::with_capacity(requests.len());
105
106 for request in requests {
107 match self.get_tile(&request).await {
108 Ok(response) => responses.push(response),
109 Err(e) => {
110 debug!("Failed to prefetch tile {}: {}", request.coord, e);
111 }
112 }
113 }
114
115 Ok(responses)
116 }
117}
118
119pub struct MultiSourceTileProvider {
121 providers: Vec<Arc<dyn TileProvider>>,
122}
123
124impl MultiSourceTileProvider {
125 pub fn new(providers: Vec<Arc<dyn TileProvider>>) -> Self {
127 Self { providers }
128 }
129
130 pub fn add_provider(&mut self, provider: Arc<dyn TileProvider>) {
132 self.providers.push(provider);
133 }
134}
135
136#[async_trait]
137impl TileProvider for MultiSourceTileProvider {
138 async fn get_tile(&self, request: &TileRequest) -> Result<TileResponse> {
139 for (i, provider) in self.providers.iter().enumerate() {
140 match provider.get_tile(request).await {
141 Ok(response) => {
142 if i > 0 {
143 info!("Fallback to provider {} for tile {}", i, request.coord);
144 }
145 return Ok(response);
146 }
147 Err(e) => {
148 debug!("Provider {} failed for tile {}: {}", i, request.coord, e);
149 continue;
150 }
151 }
152 }
153
154 Err(StreamingError::Other(format!(
155 "All providers failed for tile {}",
156 request.coord
157 )))
158 }
159
160 async fn prefetch_tiles(&self, requests: Vec<TileRequest>) -> Result<Vec<TileResponse>> {
161 if let Some(provider) = self.providers.first() {
163 provider.prefetch_tiles(requests).await
164 } else {
165 Ok(Vec::new())
166 }
167 }
168}
169
170#[cfg(test)]
171#[allow(clippy::panic)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_tile_source() {
177 let source = TileSource::Http {
178 url_template: "https://tile.openstreetmap.org/{z}/{x}/{y}.png".to_string(),
179 min_zoom: 0,
180 max_zoom: 18,
181 };
182
183 match source {
184 TileSource::Http {
185 min_zoom, max_zoom, ..
186 } => {
187 assert_eq!(min_zoom, 0);
188 assert_eq!(max_zoom, 18);
189 }
190 _ => panic!("Wrong variant"),
191 }
192 }
193}