aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2026-04-04 19:44:07 +0200
committerDaniel Schadt <kingdread@gmx.de>2026-04-04 19:44:07 +0200
commit38118611513deced3617e3f581920125484923f0 (patch)
treed808aa648e25b93c9fc61340bbb7d2df4cf2c1f6
parenta3b378fd66342ecf9a44f814acf1b18a49a05c42 (diff)
downloadhittekaart-38118611513deced3617e3f581920125484923f0.tar.gz
hittekaart-38118611513deced3617e3f581920125484923f0.tar.bz2
hittekaart-38118611513deced3617e3f581920125484923f0.zip
add osmand sqlite output format (lib & cli)
-rw-r--r--README.adoc27
-rw-r--r--hittekaart-cli/src/main.rs36
-rw-r--r--hittekaart/src/storage.rs110
3 files changed, 128 insertions, 45 deletions
diff --git a/README.adoc b/README.adoc
index bb779b5..fa58a24 100644
--- a/README.adoc
+++ b/README.adoc
@@ -8,7 +8,7 @@ hittekaart - A GPX track heatmap generator.
== SYNOPSIS
----
-hittekaart [--output=...] [--min-zoom=...] [--max-zoom=...] [--threads=...] [--sqlite] FILES...
+hittekaart [--output=...] [--min-zoom=...] [--max-zoom=...] [--threads=...] [--format=...] FILES...
----
== INSTALLATION
@@ -42,8 +42,8 @@ the `--output/-o` option.
In order to overcome storage overhead when saving many small files (see the tip
and table further below), `hittekaart` can instead output a SQLite database
-with the heatmap tile data. To do so, use the `--sqlite` command line option,
-and control where the SQLite file should be placed with `--output`/`-o`.
+with the heatmap tile data. To do so, use the `--format=sqlite` command line
+option, and control where the SQLite file should be placed with `--output`/`-o`.
While this does not allow you to immediately serve the tiles with a HTTP
server, it does cut down on the wasted space on non-optimal file systems.
@@ -61,6 +61,20 @@ CREATE TABLE tiles (
);
----
+=== OSMAND OUTPUT
+
+You can generate a file that can be read directly by
+https://osmand.net/[OsmAnd]. To do so, pass the `--format=osm-and` command line
+option, and control where the file should be placed with `--output`/`-o`.
+
+To use the map in the app, make sure to:
+
+* Give it a `.sqlitedb` extension, and
+* place it under `Android/data/net.osmand.plus/files/tiles/`.
+
+For a reference of the format, see
+https://osmand.net/docs/technical/osmand-file-formats/osmand-sqlite/.
+
=== INPUT FILES
`hittekaart` expects GPX track files with the `.gpx` extension. It will parse
@@ -204,10 +218,9 @@ The following options are supported:
when generating single files, and `tiles.sqlite` when storing the tiles in
a SQLite database.
-`--sqlite`::
- Output a single SQLite file with all tiles instead of saving each tile as a
- separate PNG file. In this case, `-o` can be used to set the location of
- the SQLite database. The schema is described above.
+`--format FORMAT`::
+ Sets the output format (folder, sqlite, osm-and). See the sections above on
+ OUTPUT FORMAT, SQLITE OUTPUT and OSMAND OUTPUT for more information.
`-m MODE`, `--mode=MODE`::
Sets the overlay generation mode (heatmap, marktile, tilehunter). See
diff --git a/hittekaart-cli/src/main.rs b/hittekaart-cli/src/main.rs
index 11133af..880ea61 100644
--- a/hittekaart-cli/src/main.rs
+++ b/hittekaart-cli/src/main.rs
@@ -7,8 +7,8 @@ use color_eyre::{
};
use hittekaart::{
gpx::{self, Compression},
- renderer::{self, heatmap, marktile, tilehunt, Renderer},
- storage::{Folder, Sqlite, Storage},
+ renderer::{self, Renderer, heatmap, marktile, tilehunt},
+ storage::{Folder, Sqlite, Storage, TableFormat},
};
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use is_terminal::IsTerminal;
@@ -25,6 +25,14 @@ enum Mode {
Tilehunter,
}
+/// Output format.
+#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum Format {
+ Folder,
+ Sqlite,
+ OsmAnd,
+}
+
#[derive(Parser, Debug, Clone)]
#[command(author, version, about)]
struct Args {
@@ -49,10 +57,9 @@ struct Args {
#[arg(long, short)]
output: Option<PathBuf>,
- /// Store the tiles in a SQLite database. If given, `--output` will determine the SQLite
- /// filename.
+ /// Output format.
#[arg(long)]
- sqlite: bool,
+ format: Format,
/// Generation mode.
#[arg(value_enum, long, short, default_value_t = Mode::Heatmap)]
@@ -115,12 +122,19 @@ fn run<R: Renderer>(renderer: R, args: Args) -> Result<()> {
let tracks = tracks.into_iter().collect::<Result<Vec<_>>>()?;
bar.finish();
- let mut storage: Box<dyn Storage + Send> = if args.sqlite {
- let output = args.output.unwrap_or_else(|| "tiles.sqlite".into());
- Box::new(Sqlite::connect(output)?)
- } else {
- let output = args.output.unwrap_or_else(|| "tiles".into());
- Box::new(Folder::new(output))
+ let mut storage: Box<dyn Storage + Send> = match args.format {
+ Format::Folder => {
+ let output = args.output.unwrap_or_else(|| "tiles".into());
+ Box::new(Folder::new(output))
+ }
+ Format::Sqlite => {
+ let output = args.output.unwrap_or_else(|| "tiles.sqlite".into());
+ Box::new(Sqlite::connect(output, TableFormat::Simple)?)
+ }
+ Format::OsmAnd => {
+ let output = args.output.unwrap_or_else(|| "tiles.sqlitedb".into());
+ Box::new(Sqlite::connect(output, TableFormat::OsmAnd)?)
+ }
};
storage.prepare()?;
diff --git a/hittekaart/src/storage.rs b/hittekaart/src/storage.rs
index c21627f..1f4931f 100644
--- a/hittekaart/src/storage.rs
+++ b/hittekaart/src/storage.rs
@@ -97,22 +97,88 @@ impl Storage for Folder {
}
}
+/// Describes the SQL table format that hittekaart should use.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum TableFormat {
+ /// A simple format, consisting of a single table:
+ ///
+ /// ```sql
+ /// CREATE TABLE tiles (
+ /// zoom INTEGER,
+ /// x INTEGER,
+ /// y INTEGER,
+ /// data BLOB,
+ /// PRIMARY KEY (zoom, x, y)
+ /// );
+ /// ```
+ Simple,
+ /// Output a SQL file that conforms to the OsmAnd specification and can be loaded as an overlay
+ /// map.
+ ///
+ /// See <https://osmand.net/docs/technical/osmand-file-formats/osmand-sqlite/> for the
+ /// technical reference.
+ ///
+ /// To use the file, give it an `.sqlitedb` extension and place it under
+ /// `Android/data/net.osmand.plus/files/tiles/`
+ OsmAnd,
+}
+
+impl TableFormat {
+ fn initializers(self) -> &'static [&'static str] {
+ match self {
+ TableFormat::Simple => &[
+ "CREATE TABLE tiles (
+ zoom INTEGER,
+ x INTEGER,
+ y INTEGER,
+ data BLOB,
+ PRIMARY KEY (zoom, x, y)
+ );"
+ ],
+ TableFormat::OsmAnd => &[
+ "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
+ );",
+ "INSERT INTO info (inverted_y, tilenumbering, maxzoom) VALUES (0, \"\", 17);",
+ "CREATE TABLE tiles (
+ x INTEGER,
+ y INTEGER,
+ z INTEGER,
+ image BLOB,
+ time INTEGER,
+ PRIMARY KEY (x, y, z)
+ );",
+ ],
+ }
+ }
+
+ fn insert(self) -> &'static str {
+ match self {
+ TableFormat::Simple => "INSERT INTO tiles (zoom, x, y, data) VALUES (?, ?, ?, ?)",
+ TableFormat::OsmAnd => "INSERT INTO tiles (z, x, y, image) VALUES (?, ?, ?, ?)",
+ }
+ }
+}
+
/// 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)
-/// );
-/// ```
+/// This stores tiles in a SQLite database. See [`TableFormat`] to see the table layout.
#[derive(Debug)]
pub struct Sqlite {
connection: Connection,
+ format: TableFormat,
}
impl Sqlite {
@@ -120,28 +186,21 @@ impl Sqlite {
///
/// 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> {
+ pub fn connect<P: AsRef<Path>>(file: P, format: TableFormat) -> 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(Sqlite { connection })
+ Ok(Sqlite { connection, format })
}
}
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)
- );",
- (),
- )?;
+ for stmt in self.format.initializers() {
+ self.connection.execute(stmt, ())?;
+ }
self.connection.execute("BEGIN;", ())?;
Ok(())
}
@@ -151,10 +210,7 @@ impl Storage for Sqlite {
}
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],
- )?;
+ self.connection.execute(self.format.insert(), params![zoom, x, y, data])?;
Ok(())
}