use rusqlite::{params, Connection};
use std::{
fs,
io::ErrorKind,
path::{Path, PathBuf},
};
use super::error::{Error, Result};
pub trait Storage {
fn prepare(&mut self) -> Result<()>;
fn prepare_zoom(&mut self, zoom: u32) -> Result<()>;
fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()>;
fn finish(&mut self) -> Result<()>;
}
#[derive(Debug)]
pub struct Folder {
base_dir: PathBuf,
}
impl Folder {
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 => {
fs::create_dir(path)
.map_err(|e| Error::Io("creating the output directory", e))?
}
Err(e) => Err(Error::Io("checking the output direectory", e))?,
Ok(m) if m.is_dir() => (),
Ok(_) => Err(Error::InvalidOutputDirectory)?,
}
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)
.map_err(|e| Error::Io("creating the zoom directory", e))?;
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)
.map_err(|e| Error::Io("creating the output directory", e))?,
Ok(m) if !m.is_dir() => Err(Error::InvalidOutputDirectory)?,
_ => {}
}
let file = folder.join(format!("{y}.png"));
fs::write(file, data)
.map_err(|e| Error::Io("writing the output file", e))?;
Ok(())
}
fn finish(&mut self) -> Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub struct OsmAnd {
connection: Connection,
min_zoom: u32,
max_zoom: u32,
}
impl OsmAnd {
pub fn open<P: AsRef<Path>>(file: P) -> Result<Self> {
let path = file.as_ref();
if fs::metadata(path).is_ok() {
return Err(Error::OutputAlreadyExists(path.to_path_buf()));
}
let connection = Connection::open(path)?;
Ok(OsmAnd {
connection,
min_zoom: u32::MAX,
max_zoom: u32::MIN,
})
}
}
impl Storage for OsmAnd {
fn prepare(&mut self) -> Result<()> {
self.connection.execute(
"CREATE TABLE info (
url TEXT,
randoms TEXT,
referer TEXT,
rule TEXT,
useragent TEXT,
minzoom INTEGER,
maxzoom INTEGER,
ellipsoid INTEGER,
inverted_y INTEGER,
timecolumn TEXT,
expireminutes INTEGER,
tilenumbering TEXT,
tilesize INTEGER
);",
(),
)?;
self.connection.execute(
"CREATE TABLE tiles (
x INTEGER,
y INTEGER,
z INTEGER,
image BLOB,
time INTEGER,
PRIMARY KEY (x, y, z)
);",
(),
)?;
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<()> {
let x: i64 = x.try_into().expect("x coordinate too large for SQLite");
let y: i64 = y.try_into().expect("y coordinate too large for SQLite");
self.connection.execute(
"INSERT INTO tiles (z, x, y, image) VALUES (?, ?, ?, ?)",
params![zoom, x, y, data],
)?;
self.min_zoom = self.min_zoom.min(zoom);
self.max_zoom = self.max_zoom.max(zoom);
Ok(())
}
fn finish(&mut self) -> Result<()> {
self.connection.execute(
"INSERT INTO info (inverted_y, tilenumbering, minzoom, maxzoom) VALUES (?, ?, ?, ?);",
params![0, "", self.min_zoom, self.max_zoom],
)?;
self.connection.execute("COMMIT;", ())?;
Ok(())
}
}
#[derive(Debug)]
pub struct MbTiles {
connection: Connection,
}
impl MbTiles {
pub fn open<P: AsRef<Path>>(file: P) -> Result<Self> {
let path = file.as_ref();
if fs::metadata(path).is_ok() {
return Err(Error::OutputAlreadyExists(path.to_path_buf()));
}
let connection = Connection::open(path)?;
Ok(MbTiles { connection })
}
}
impl Storage for MbTiles {
fn prepare(&mut self) -> Result<()> {
self.connection.execute(
"PRAGMA application_id = 0x4d504258;",
(),
)?;
self.connection.execute(
"CREATE TABLE metadata (name TEXT, value TEXT);",
(),
)?;
self.connection.execute(
"CREATE TABLE tiles (
zoom_level INTEGER,
tile_column INTEGER,
tile_row INTEGER,
tile_data BLOB,
PRIMARY KEY (zoom_level, tile_column, tile_row)
);",
(),
)?;
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<()> {
let inverted_y = 2u64.pow(zoom) - 1 - y;
let x: i64 = x.try_into().expect("x coordinate too large for SQLite");
let inverted_y: i64 = inverted_y.try_into().expect("inverted_y coordinate too large for SQLite");
self.connection.execute(
"INSERT INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)",
params![zoom, x, inverted_y, data],
)?;
Ok(())
}
fn finish(&mut self) -> Result<()> {
self.connection.execute(
"INSERT INTO metadata (name, value) VALUES (?, ?);",
params!["name", "Heatmap"],
)?;
self.connection.execute(
"INSERT INTO metadata (name, value) VALUES (?, ?);",
params!["format", "png"],
)?;
self.connection.execute("COMMIT;", ())?;
Ok(())
}
}