1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
//! Abstractions over different storage backends.
//!
//! The main trait to use here is [`Storage`], which provides the necessary interface to store
//! tiles. Usually you want to have a `dyn Storage`, and then instantiate it with a concrete
//! implementation (either [`Folder`] or [`Sqlite`]), depending on the command line flags or
//! similar.
use color_eyre::{
eyre::{bail, WrapErr},
Result,
};
use rusqlite::{params, Connection};
use std::{
fs,
io::ErrorKind,
path::{Path, PathBuf},
};
/// The trait that provides the interface for storing tiles.
pub trait Storage {
/// Prepare the storage.
///
/// This can be used to e.g. ensure the directory exists, or to create the database.
fn prepare(&mut self) -> Result<()>;
/// Prepare for a given zoom level.
///
/// This function is called once per zoom, and can be used e.g. to set up the inner folder for
/// the level. This can avoid unnecesary syscalls if this setup would be done in
/// [`Storage::store`] instead.
fn prepare_zoom(&mut self, zoom: u32) -> Result<()>;
/// Store the given data for the tile.
fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()>;
/// Finish the storing operation.
///
/// This can flush any buffers, commit database changes, and so on.
fn finish(&mut self) -> Result<()>;
}
/// Folder-based storage.
///
/// This stores the tiles according to the [slippy map
/// tilenames](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames).
#[derive(Debug)]
pub struct Folder {
base_dir: PathBuf,
}
impl Folder {
/// Create a new folder based storage.
///
/// The given directory is the "root" directory, so a tile would be saved as
/// `base_dir/{zoom}/{x}/{y}.png`.
pub fn new(base_dir: PathBuf) -> Self {
Folder { base_dir }
}
}
impl Storage for Folder {
fn prepare(&mut self) -> Result<()> {
let path = &self.base_dir;
let metadata = fs::metadata(path);
match metadata {
Err(e) if e.kind() == ErrorKind::NotFound => {
let parent = path.parent().unwrap_or_else(|| Path::new("/"));
fs::create_dir(path)
.context(format!("Could not create output directory at {parent:?}"))?
}
Err(e) => Err(e).context("Error while checking output directory")?,
Ok(m) if m.is_dir() => (),
Ok(_) => bail!("Output directory is not a directory"),
}
Ok(())
}
fn prepare_zoom(&mut self, zoom: u32) -> Result<()> {
let target = [&self.base_dir, &zoom.to_string().into()]
.iter()
.collect::<PathBuf>();
fs::create_dir(target)?;
Ok(())
}
fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()> {
let folder = self.base_dir.join(zoom.to_string()).join(x.to_string());
let metadata = folder.metadata();
match metadata {
Err(_) => fs::create_dir(&folder)?,
Ok(m) if !m.is_dir() => bail!("Output path is not a directory"),
_ => {}
}
let file = folder.join(format!("{y}.png"));
fs::write(file, data)?;
Ok(())
}
fn finish(&mut self) -> Result<()> {
Ok(())
}
}
/// SQLite based storage.
///
/// This stores tiles in a SQLite database. The database will have a single table:
///
/// ```sql
/// CREATE TABLE tiles (
/// zoom INTEGER,
/// x INTEGER,
/// y INTEGER,
/// data BLOB,
/// PRIMARY KEY (zoom, x, y)
/// );
/// ```
#[derive(Debug)]
pub struct Sqlite {
connection: Connection,
}
impl Sqlite {
/// Create a new SQLite backed tile store.
///
/// The database will be saved at the given location. Note that the database must not yet
/// exist.
pub fn connect<P: AsRef<Path>>(file: P) -> Result<Self> {
let path = file.as_ref();
if fs::metadata(path).is_ok() {
bail!("Path {path:?} already exists, refusing to open")
}
let connection = Connection::open(path)?;
Ok(Sqlite { connection })
}
}
impl Storage for Sqlite {
fn prepare(&mut self) -> Result<()> {
self.connection.execute(
"CREATE TABLE tiles (
zoom INTEGER,
x INTEGER,
y INTEGER,
data BLOB,
PRIMARY KEY (zoom, x, y)
);",
(),
)?;
self.connection.execute("BEGIN;", ())?;
Ok(())
}
fn prepare_zoom(&mut self, _zoom: u32) -> Result<()> {
Ok(())
}
fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()> {
self.connection.execute(
"INSERT INTO tiles (zoom, x, y, data) VALUES (?, ?, ?, ?)",
params![zoom, x, y, data],
)?;
Ok(())
}
fn finish(&mut self) -> Result<()> {
self.connection.execute("COMMIT;", ())?;
Ok(())
}
}
|