diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2025-06-26 22:10:31 +0200 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2025-06-26 22:10:31 +0200 | 
| commit | 99150875308e0cac89f4de2996cfd1954305dcfe (patch) | |
| tree | f19224064543aed367522b05778a992d7385c712 /hittekaart-cli | |
| parent | 6adcd94a6747fe7ec6f1ad1073453636847a0bff (diff) | |
| download | hittekaart-99150875308e0cac89f4de2996cfd1954305dcfe.tar.gz hittekaart-99150875308e0cac89f4de2996cfd1954305dcfe.tar.bz2 hittekaart-99150875308e0cac89f4de2996cfd1954305dcfe.zip  | |
split crate into core and cli
Diffstat (limited to 'hittekaart-cli')
| -rw-r--r-- | hittekaart-cli/Cargo.toml | 16 | ||||
| -rw-r--r-- | hittekaart-cli/src/main.rs | 164 | 
2 files changed, 180 insertions, 0 deletions
diff --git a/hittekaart-cli/Cargo.toml b/hittekaart-cli/Cargo.toml new file mode 100644 index 0000000..1962b87 --- /dev/null +++ b/hittekaart-cli/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hittekaart-cli" +version = "0.1.0" +edition = "2024" + +[[bin]] +path = "src/main.rs" +name = "hittekaart" + +[dependencies] +hittekaart = { path = "../hittekaart" } +clap = { version = "4.2.7", features = ["derive"] } +color-eyre = "0.6.2" +indicatif = "0.17.3" +is-terminal = "0.4.7" +rayon = "1.10.0" diff --git a/hittekaart-cli/src/main.rs b/hittekaart-cli/src/main.rs new file mode 100644 index 0000000..11133af --- /dev/null +++ b/hittekaart-cli/src/main.rs @@ -0,0 +1,164 @@ +use std::{io, path::PathBuf}; + +use clap::{Parser, ValueEnum}; +use color_eyre::{ +    eyre::{bail, eyre, Result}, +    Report, +}; +use hittekaart::{ +    gpx::{self, Compression}, +    renderer::{self, heatmap, marktile, tilehunt, Renderer}, +    storage::{Folder, Sqlite, Storage}, +}; +use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle}; +use is_terminal::IsTerminal; +use rayon::{ +    iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}, +    ThreadPoolBuilder, +}; + +/// Tile generation mode. +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum Mode { +    Heatmap, +    Marktile, +    Tilehunter, +} + +#[derive(Parser, Debug, Clone)] +#[command(author, version, about)] +struct Args { +    /// The GPX files to parse. +    #[arg(required = true)] +    files: Vec<PathBuf>, + +    /// Minimum zoom level to generate tiles for. +    #[arg(long, default_value_t = 0)] +    min_zoom: u32, + +    /// Maximum zoom level to generate tiles for. +    #[arg(long, default_value_t = 19)] +    max_zoom: u32, + +    /// Number of threads to use. Set to 0 to use all available CPU cores. +    #[arg(long, short, default_value_t = 0)] +    threads: usize, + +    /// The output directory. Will be created if it does not exist. Defaults to "tiles" for the +    /// folder-based storage, and "tiles.sqlite" for the SQLite-based storage. +    #[arg(long, short)] +    output: Option<PathBuf>, + +    /// Store the tiles in a SQLite database. If given, `--output` will determine the SQLite +    /// filename. +    #[arg(long)] +    sqlite: bool, + +    /// Generation mode. +    #[arg(value_enum, long, short, default_value_t = Mode::Heatmap)] +    mode: Mode, + +    /// Zoom level for the tilehunter mode. +    #[arg(long, default_value_t = 14)] +    tilehunter_zoom: u32, +} + +fn main() -> Result<()> { +    color_eyre::install()?; + +    let args = Args::parse(); + +    if args.max_zoom < args.min_zoom { +        bail!("Max zoom cannot be smaller than min zoom!"); +    } + +    ThreadPoolBuilder::new() +        .num_threads(args.threads) +        .build_global()?; + +    match args.mode { +        Mode::Heatmap => run(heatmap::Renderer, args), +        Mode::Marktile => run(marktile::Renderer, args), +        Mode::Tilehunter => run(tilehunt::Renderer::new(args.tilehunter_zoom), args), +    } +} + +fn run<R: Renderer>(renderer: R, args: Args) -> Result<()> { +    let progress_style = +        ProgressStyle::with_template("[{elapsed}] {prefix:.cyan} {wide_bar} {pos:.green}/{len}")?; +    let zoom_style = +        ProgressStyle::with_template("[{elapsed}] {prefix:.yellow} {wide_bar} {pos:.green}/{len}")?; + +    let use_progress_bars = io::stdout().is_terminal(); +    let make_bar = |len| { +        if use_progress_bars { +            ProgressBar::new(len) +        } else { +            ProgressBar::hidden() +        } +    }; + +    let bar = make_bar(args.files.len().try_into().unwrap()).with_style(progress_style.clone()); +    bar.set_prefix("Reading GPX files"); + +    let mut tracks = Vec::new(); +    args.files +        .par_iter() +        .map(|file| { +            let compression = Compression::suggest_from_path(file) +                .ok_or_else(|| eyre!("Could not determine format for {file:?}"))?; +            let data = gpx::extract_from_file(file, compression)?; +            bar.inc(1); +            Ok::<_, Report>(data) +        }) +        .collect_into_vec(&mut tracks); +    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)) +    }; +    storage.prepare()?; + +    let multibar = MultiProgress::new(); +    if !use_progress_bars { +        multibar.set_draw_target(ProgressDrawTarget::hidden()) +    } +    let zoom_bar = make_bar((args.max_zoom - args.min_zoom + 1).into()).with_style(zoom_style); +    multibar.add(zoom_bar.clone()); +    zoom_bar.set_prefix("Zoom levels"); + +    for zoom in args.min_zoom..=args.max_zoom { +        let bar = make_bar(tracks.len().try_into().unwrap()).with_style(progress_style.clone()); +        multibar.insert_from_back(1, bar.clone()); +        bar.set_prefix("Rendering heat zones"); +        let counter = renderer::prepare(&renderer, zoom, &tracks, || { +            bar.inc(1); +            Ok(()) +        })?; +        bar.finish(); +        multibar.remove(&bar); + +        storage.prepare_zoom(zoom)?; + +        let bar = make_bar(renderer.tile_count(&counter)?).with_style(progress_style.clone()); +        multibar.insert_from_back(1, bar.clone()); +        bar.set_prefix("Saving heat tiles"); +        renderer::colorize(&renderer, counter, |rendered_tile| { +            storage.store(zoom, rendered_tile.x, rendered_tile.y, &rendered_tile.data)?; +            bar.inc(1); +            Ok(()) +        })?; +        bar.finish(); +        multibar.remove(&bar); +        zoom_bar.inc(1); +    } +    storage.finish()?; +    zoom_bar.finish(); + +    Ok(()) +}  | 
