martin_core/tiles/mbtiles/
source.rs

1//! `MBTiles` tile source implementation.
2
3use std::fmt::{Debug, Formatter};
4use std::io;
5use std::path::PathBuf;
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use log::trace;
10use martin_tile_utils::{TileCoord, TileData, TileInfo};
11use mbtiles::MbtilesPool;
12use tilejson::TileJSON;
13
14use crate::tiles::mbtiles::MbtilesError;
15use crate::tiles::{BoxedSource, MartinCoreResult, Source, UrlQuery};
16
17/// Tile source that reads from `MBTiles` files.
18#[derive(Clone)]
19pub struct MbtSource {
20    id: String,
21    mbtiles: Arc<MbtilesPool>,
22    tilejson: TileJSON,
23    tile_info: TileInfo,
24}
25
26impl Debug for MbtSource {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        write!(
29            f,
30            "MbtSource {{ id: {}, path: {:?} }}",
31            self.id,
32            self.mbtiles.as_ref()
33        )
34    }
35}
36
37impl MbtSource {
38    /// Creates a new `MBTiles` source from the given file path.
39    pub async fn new(id: String, path: PathBuf) -> Result<Self, MbtilesError> {
40        let mbt = MbtilesPool::open_readonly(&path)
41            .await
42            .map_err(|e| io::Error::other(format!("{e:?}: Cannot open file {}", path.display())))
43            .map_err(|e| MbtilesError::IoError(e, path.clone()))?;
44
45        let meta = mbt
46            .get_metadata()
47            .await
48            .map_err(|e| MbtilesError::InvalidMetadata(e.to_string(), path))?;
49
50        Ok(Self {
51            id,
52            mbtiles: Arc::new(mbt),
53            tilejson: meta.tilejson,
54            tile_info: meta.tile_info,
55        })
56    }
57}
58
59#[async_trait]
60impl Source for MbtSource {
61    fn get_id(&self) -> &str {
62        &self.id
63    }
64
65    fn get_tilejson(&self) -> &TileJSON {
66        &self.tilejson
67    }
68
69    fn get_tile_info(&self) -> TileInfo {
70        self.tile_info
71    }
72
73    fn clone_source(&self) -> BoxedSource {
74        Box::new(self.clone())
75    }
76
77    fn get_version(&self) -> Option<String> {
78        self.tilejson.version.clone()
79    }
80
81    fn benefits_from_concurrent_scraping(&self) -> bool {
82        // If we copy from one local file to another, we are likely not bottlenecked by CPU
83        false
84    }
85
86    async fn get_tile(
87        &self,
88        xyz: TileCoord,
89        _url_query: Option<&UrlQuery>,
90    ) -> MartinCoreResult<TileData> {
91        if let Some(tile) = self
92            .mbtiles
93            .get_tile(xyz.z, xyz.x, xyz.y)
94            .await
95            .map_err(|_| MbtilesError::AcquireConnError(self.id.clone()))?
96        {
97            Ok(tile)
98        } else {
99            trace!(
100                "Couldn't find tile data in {}/{}/{} of {}",
101                xyz.z, xyz.x, xyz.y, &self.id
102            );
103            Ok(Vec::new())
104        }
105    }
106}