diff options
Diffstat (limited to 'hittekaart-py')
| -rw-r--r-- | hittekaart-py/Cargo.toml | 13 | ||||
| -rw-r--r-- | hittekaart-py/pyproject.toml | 15 | ||||
| -rw-r--r-- | hittekaart-py/src/lib.rs | 155 | 
3 files changed, 183 insertions, 0 deletions
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(()) +}  | 
