1use anyhow::Result;
2use clap::Parser;
3use pmtiles::AsyncPmTilesReader;
4use std::{fs::File, path::PathBuf};
5use tokio::fs;
6
7mod filtering;
8mod metadata;
9mod processing;
10mod transform;
11
12#[derive(Parser)]
13#[command(author, version, about)]
14pub struct Args {
15 pub input: PathBuf,
17
18 pub output: PathBuf,
20
21 #[arg(short, long)]
24 pub filter: Option<PathBuf>,
25
26 #[arg(long, short = 'n')]
28 pub name: Option<String>,
29
30 #[arg(long, short = 'N')]
32 pub description: Option<String>,
33
34 #[arg(long, short = 'A')]
36 pub attribution: Option<String>,
37}
38
39pub async fn run(args: Args) -> Result<()> {
40 if args.output.exists() {
42 fs::remove_file(&args.output).await?;
43 }
44
45 let pmtiles_path = args.input;
46 if !pmtiles_path.exists() {
47 panic!("Input file does not exist: {}", pmtiles_path.display());
48 }
49
50 let mut fc = None;
52 if let Some(filter_path) = &args.filter {
53 if !filter_path.exists() {
54 panic!("Filter file does not exist: {}", filter_path.display());
55 }
56 let filter_str = fs::read_to_string(filter_path).await?;
57 let filter_json: filtering::data::FilterCollection = serde_json::from_str(&filter_str)?;
58 let compiled = filter_json.compile()?;
59 fc = Some(compiled);
60 }
61
62 if args.output.extension().and_then(|s| s.to_str()) != Some("pmtiles") {
64 panic!("Output file must have .pmtiles extension");
65 }
66
67 let in_pmt = AsyncPmTilesReader::new_with_path(&pmtiles_path).await?;
69 let out_pmt_f = File::create(&args.output)?;
70 let header = in_pmt.get_header();
71 let in_metadata_str = in_pmt.get_metadata().await?;
72 if header.tile_type != pmtiles::TileType::Mvt {
73 panic!("Unsupported tile type: {:?}", header.tile_type);
74 }
75 let out_metadata_str = metadata::apply_overrides(
77 &in_metadata_str,
78 args.name.as_deref(),
79 args.description.as_deref(),
80 args.attribution.as_deref(),
81 )?;
82 let out_pmt = pmtiles::PmTilesWriter::new(header.tile_type)
83 .tile_compression(header.tile_compression)
84 .min_zoom(header.min_zoom)
85 .max_zoom(header.max_zoom)
86 .bounds(
87 header.min_longitude,
88 header.min_latitude,
89 header.max_longitude,
90 header.max_latitude,
91 )
92 .center_zoom(header.center_zoom)
93 .center(header.center_longitude, header.center_latitude)
94 .metadata(&out_metadata_str)
95 .create(out_pmt_f)?;
96
97 processing::process_tiles(&pmtiles_path, out_pmt, header.tile_compression, fc).await?;
98
99 println!("✅ Wrote transformed tiles to {}", args.output.display());
100 Ok(())
101}