aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock98
-rw-r--r--Cargo.toml2
-rw-r--r--hittekaart-py/Cargo.toml13
-rw-r--r--hittekaart-py/pyproject.toml15
-rw-r--r--hittekaart-py/src/lib.rs155
-rw-r--r--hittekaart/src/gpx.rs4
6 files changed, 284 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c584618..9a97edc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -632,6 +632,14 @@ dependencies = [
]
[[package]]
+name = "hittekaart-py"
+version = "0.1.0"
+dependencies = [
+ "hittekaart",
+ "pyo3",
+]
+
+[[package]]
name = "image"
version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -687,6 +695,12 @@ dependencies = [
]
[[package]]
+name = "indoc"
+version = "2.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
+
+[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -788,6 +802,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1086,6 +1109,69 @@ dependencies = [
]
[[package]]
+name = "pyo3"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "memoffset",
+ "once_cell",
+ "portable-atomic",
+ "pyo3-build-config",
+ "pyo3-ffi",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
+dependencies = [
+ "once_cell",
+ "target-lexicon",
+]
+
+[[package]]
+name = "pyo3-ffi"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
+dependencies = [
+ "libc",
+ "pyo3-build-config",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
+dependencies = [
+ "proc-macro2",
+ "pyo3-macros-backend",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "pyo3-build-config",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1395,6 +1481,12 @@ dependencies = [
]
[[package]]
+name = "target-lexicon"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
+
+[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1510,6 +1602,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
+name = "unindent"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
+
+[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index bf0388e..f5f64c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
resolver = "3"
members = [
- "hittekaart", "hittekaart-cli",
+ "hittekaart", "hittekaart-cli", "hittekaart-py",
]
diff --git a/hittekaart-py/Cargo.toml b/hittekaart-py/Cargo.toml
new file mode 100644
index 0000000..9a44118
--- /dev/null
+++ b/hittekaart-py/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "hittekaart-py"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "hittekaart_py"
+crate-type = ["cdylib"]
+
+[dependencies]
+pyo3 = "0.24.0"
+hittekaart = { path = "../hittekaart" }
diff --git a/hittekaart-py/pyproject.toml b/hittekaart-py/pyproject.toml
new file mode 100644
index 0000000..865cc21
--- /dev/null
+++ b/hittekaart-py/pyproject.toml
@@ -0,0 +1,15 @@
+[build-system]
+requires = ["maturin>=1.8,<2.0"]
+build-backend = "maturin"
+
+[project]
+name = "hittekaart-py"
+requires-python = ">=3.8"
+classifiers = [
+ "Programming Language :: Rust",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+]
+dynamic = ["version"]
+[tool.maturin]
+features = ["pyo3/extension-module"]
diff --git a/hittekaart-py/src/lib.rs b/hittekaart-py/src/lib.rs
new file mode 100644
index 0000000..9f4bd8d
--- /dev/null
+++ b/hittekaart-py/src/lib.rs
@@ -0,0 +1,155 @@
+use hittekaart::gpx::{self, Compression, Coordinates};
+use hittekaart::renderer::{self, Renderer};
+use pyo3::create_exception;
+use pyo3::exceptions::PyTypeError;
+use pyo3::prelude::*;
+use std::error::Error;
+use std::ffi::OsStr;
+use std::fmt::Write as _;
+use std::os::unix::ffi::OsStrExt as _;
+
+create_exception!(hittekaart_py, HitteError, pyo3::exceptions::PyException);
+
+fn err_to_py(mut error: &dyn Error) -> PyErr {
+ let mut text = error.to_string();
+ loop {
+ match error.source() {
+ None => break,
+ Some(e) => error = e,
+ }
+ write!(&mut text, "\ncaused by: {error}").unwrap();
+ }
+ HitteError::new_err(text)
+}
+
+#[pyclass]
+#[derive(Debug, Clone)]
+struct Track {
+ inner: Vec<Coordinates>,
+}
+
+#[pymethods]
+impl Track {
+ #[staticmethod]
+ fn from_file(path: &[u8], compression: Option<&str>) -> PyResult<Track> {
+ let compression = match compression {
+ None | Some("") => Compression::None,
+ Some("gzip") => Compression::Gzip,
+ Some("brotli") => Compression::Brotli,
+ Some(x) => return Err(HitteError::new_err(format!("invalid compression: {x}"))),
+ };
+ let track = gpx::extract_from_file(OsStr::from_bytes(path), compression)
+ .map_err(|e| err_to_py(&e))?;
+ Ok(Track { inner: track })
+ }
+
+ #[staticmethod]
+ fn from_coordinates(coordinates: Vec<(f64, f64)>) -> Track {
+ Track {
+ inner: coordinates
+ .iter()
+ .map(|(lon, lat)| Coordinates::new(*lon, *lat))
+ .collect(),
+ }
+ }
+
+ fn coordinates(&self) -> Vec<(f64, f64)> {
+ self.inner
+ .iter()
+ .map(|c| (c.longitude, c.latitude))
+ .collect()
+ }
+}
+
+#[pyclass]
+struct Storage {
+ inner: Box<dyn hittekaart::storage::Storage + Send + Sync>,
+}
+
+#[pymethods]
+impl Storage {
+ #[staticmethod]
+ #[pyo3(name = "Folder")]
+ fn folder(path: &[u8]) -> Self {
+ let path = OsStr::from_bytes(path);
+ let storage = hittekaart::storage::Folder::new(path.into());
+ Storage {
+ inner: Box::new(storage),
+ }
+ }
+}
+
+#[pyclass]
+struct HeatmapRenderer {
+ inner: renderer::heatmap::Renderer,
+}
+
+#[pymethods]
+impl HeatmapRenderer {
+ #[new]
+ fn new() -> HeatmapRenderer {
+ HeatmapRenderer {
+ inner: renderer::heatmap::Renderer,
+ }
+ }
+}
+
+#[pyfunction]
+fn generate(
+ items: &Bound<'_, PyAny>,
+ renderer: &Bound<'_, PyAny>,
+ storage: &Bound<'_, Storage>,
+) -> PyResult<()> {
+ let mut tracks = vec![];
+
+ for item in items.try_iter()? {
+ let item = item?;
+ tracks.push(item.extract::<Track>()?.inner);
+ }
+
+ if let Ok(r) = renderer.downcast::<HeatmapRenderer>() {
+ do_generate(tracks, &r.borrow().inner, &mut storage.borrow_mut())
+ } else {
+ Err(PyTypeError::new_err("Expected a HeatmapRenderer"))
+ }
+}
+
+fn do_generate<R: Renderer>(
+ tracks: Vec<Vec<Coordinates>>,
+ renderer: &R,
+ storage: &mut Storage,
+) -> PyResult<()> {
+ storage.inner.prepare().map_err(|e| err_to_py(&e))?;
+
+ for zoom in 0..=19 {
+ let counter =
+ renderer::prepare(renderer, zoom, &tracks, || Ok(())).map_err(|e| err_to_py(&e))?;
+
+ storage
+ .inner
+ .prepare_zoom(zoom)
+ .map_err(|e| err_to_py(&e))?;
+
+ renderer::colorize(renderer, counter, |rendered_tile| {
+ storage
+ .inner
+ .store(zoom, rendered_tile.x, rendered_tile.y, &rendered_tile.data)?;
+ Ok(())
+ })
+ .map_err(|e| err_to_py(&e))?;
+ }
+ storage.inner.finish().map_err(|e| err_to_py(&e))?;
+
+ Ok(())
+}
+
+/// A Python module implemented in Rust.
+#[pymodule]
+fn hittekaart_py(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
+ m.add_class::<Track>()?;
+ m.add_class::<HeatmapRenderer>()?;
+ m.add_class::<Storage>()?;
+ m.add_function(wrap_pyfunction!(generate, m)?)?;
+ m.add("HitteError", py.get_type::<HitteError>())?;
+ Ok(())
+}
diff --git a/hittekaart/src/gpx.rs b/hittekaart/src/gpx.rs
index 98019c3..68bd6fb 100644
--- a/hittekaart/src/gpx.rs
+++ b/hittekaart/src/gpx.rs
@@ -22,8 +22,8 @@ use super::error::{Error, Result};
/// World coordinates.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Coordinates {
- longitude: f64,
- latitude: f64,
+ pub longitude: f64,
+ pub latitude: f64,
}
impl Coordinates {