use crate::source::{TileKey, TileSource};
use std::path::PathBuf;
pub struct FileTileSource {
root: PathBuf,
extension: String,
tile_size: u32,
min_zoom: u8,
max_zoom: u8,
}
impl FileTileSource {
pub fn new(root: impl Into<PathBuf>) -> Self {
Self {
root: root.into(),
extension: "png".to_string(),
tile_size: 256,
min_zoom: 0,
max_zoom: 22,
}
}
pub fn with_extension(mut self, ext: impl Into<String>) -> Self {
self.extension = ext.into();
self
}
pub fn with_tile_size(mut self, size: u32) -> Self {
self.tile_size = size;
self
}
pub fn with_zoom_range(mut self, min: u8, max: u8) -> Self {
self.min_zoom = min;
self.max_zoom = max;
self
}
}
impl TileSource for FileTileSource {
fn tile(&self, key: TileKey) -> Option<slint::Image> {
let path = self
.root
.join(key.z.to_string())
.join(key.x.to_string())
.join(format!("{}.{}", key.y, self.extension));
if !path.exists() {
return None;
}
slint::Image::load_from_path(&path).ok()
}
fn tile_size(&self) -> u32 {
self.tile_size
}
fn min_zoom(&self) -> u8 {
self.min_zoom
}
fn max_zoom(&self) -> u8 {
self.max_zoom
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_tile_returns_none() {
let src = FileTileSource::new("/definitely-does-not-exist-9b3a2c");
assert!(src.tile(TileKey { x: 0, y: 0, z: 0 }).is_none());
}
#[test]
fn sample_bundle_serves_world_tile() {
let src = FileTileSource::new(crate::SAMPLE_TILES_DIR);
assert!(
src.tile(TileKey { x: 0, y: 0, z: 0 }).is_some(),
"sample bundle should serve the (0,0,0) world tile — \
did the bundle move or is the SAMPLE_TILES_DIR path wrong?"
);
}
#[test]
fn sample_bundle_serves_every_zoom_3_tile() {
let src = FileTileSource::new(crate::SAMPLE_TILES_DIR);
for x in 0..8u32 {
for y in 0..8u32 {
assert!(
src.tile(TileKey { x, y, z: 3 }).is_some(),
"missing sample tile (x={x}, y={y}, z=3)"
);
}
}
}
#[test]
fn zoom_range_overridable() {
let src = FileTileSource::new("/tmp").with_zoom_range(5, 14);
assert_eq!(src.min_zoom(), 5);
assert_eq!(src.max_zoom(), 14);
}
}