use std::env;
use plozone::{
EnuConverter, OctreeNode, ScanMode, Zone, ZoneEntry, ZoneStore, run_scan,
};
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
usage();
return;
}
match args[1].as_str() {
"query" => cmd_query(&args),
"zones" => cmd_zones(&args),
"scan" => cmd_scan(&args),
"bench" => cmd_bench(),
_ => usage(),
}
}
fn usage() {
eprintln!("plozone — 3D spatial zone query tool");
eprintln!();
eprintln!("Usage:");
eprintln!(" plozone query <lat> <lon> <alt> Query zones at a geodetic point");
eprintln!(" plozone zones list List all zone IDs in the store");
eprintln!(" plozone scan <zone_id> [--depth D] Scan a zone for unsampled holes");
eprintln!(" plozone bench Quick zone query benchmark");
eprintln!();
eprintln!("Examples:");
eprintln!(" plozone query 10.7626 106.6601 5.0");
eprintln!(" plozone scan 1 --depth 7");
}
fn default_store() -> (EnuConverter, ZoneStore) {
let conv = EnuConverter::new(10.7626, 106.6601, 0.0);
let store = ZoneStore::from_entries(
&[
ZoneEntry::new(
1,
Zone::Cylinder {
center: [10.7626, 106.6601],
radius_m: 50.0,
z_min: 0.0,
z_max: 20.0,
},
),
ZoneEntry::new(
2,
Zone::Aabb {
min: [10.7620, 106.6595, 0.0],
max: [10.7632, 106.6607, 10.0],
},
),
],
&conv,
);
(conv, store)
}
fn cmd_query(args: &[String]) {
if args.len() < 5 {
eprintln!("Usage: plozone query <lat> <lon> <alt>");
return;
}
let lat: f64 = args[2].parse().expect("invalid lat");
let lon: f64 = args[3].parse().expect("invalid lon");
let alt: f64 = args[4].parse().expect("invalid alt");
let (conv, store) = default_store();
let hits = store.query_geodetic(lat, lon, alt, &conv);
if hits.is_empty() {
println!("no zones at ({lat}, {lon}, {alt})");
} else {
for id in hits {
let zone_aabb = store.zone_aabb(id);
let msg = zone_aabb
.map(|a| format!("[{:.1}, {:.1}, {:.1}] – [{:.1}, {:.1}, {:.1}]", a[0], a[1], a[2], a[3], a[4], a[5]))
.unwrap_or_else(|| "N/A".to_string());
println!("zone {id:>4} ENU AABB {msg}");
}
}
}
fn cmd_zones(args: &[String]) {
let (_, store) = default_store();
let ids = store.ids();
if ids.is_empty() {
println!("no zones loaded");
} else {
for id in ids {
println!("{id}");
}
}
let _ = args;
}
fn cmd_scan(args: &[String]) {
if args.len() < 3 {
eprintln!("Usage: plozone scan <zone_id> [--depth D]");
return;
}
let zone_id: u32 = args[2].parse().expect("invalid zone id");
let mut depth = 4u8;
for w in args.windows(2) {
if w[0] == "--depth" {
depth = w[1].parse().expect("invalid depth");
}
}
let (conv, store) = default_store();
if !store.ids().contains(&zone_id) {
eprintln!("zone {} not found", zone_id);
return;
}
let octree = OctreeNode::new([0.0; 3], 64.0);
let result = run_scan(&octree, &store, zone_id, &ScanMode::Coarse { depth });
println!("zone {zone_id} scan (depth {depth})");
println!(" coverage: {:.1}%", result.coverage_pct);
println!(" holes: {}", result.holes.len());
for h in &result.holes {
println!(
" [{:.2}, {:.2}, {:.2}] size={:.2}m depth={}",
h.center[0], h.center[1], h.center[2], h.size_m, h.depth
);
}
let _ = conv;
}
fn cmd_bench() {
let (conv, store) = default_store();
let n = 1_000_000u64;
let start = std::time::Instant::now();
for _ in 0..n {
let _ = store.query_geodetic(10.7626, 106.6601, 5.0, &conv);
}
let elapsed = start.elapsed();
let ns_per_query = elapsed.as_nanos() as f64 / n as f64;
println!(
"{n} queries in {:.3}s ({:.0} ns/query)",
elapsed.as_secs_f64(),
ns_per_query
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_store_has_two_zones() {
let (_, s) = default_store();
assert_eq!(s.len(), 2);
}
}