osm-katana/examples/relmeasure.rs
//! **How many relations reach the pyramid, before and after the exclusion fix.**
//!
//! Measured against a REAL `relations.resolved.parquet`, with the REAL predicate
//! (`osm_katana::network::carried_by_a_network`) — not a fixture and not a replica.
//!
//! `cargo run --release --example relmeasure -- <relations.resolved.parquet>`
use arrow::array::{Array, BinaryArray, StringArray};
use osm_katana::network::{carried_by_a_network, Source};
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
use std::collections::BTreeMap;
fn wkb_type(b: &[u8]) -> Option<u32> {
if b.len() < 5 || b[0] != 1 {
return None;
}
Some(u32::from_le_bytes([b[1], b[2], b[3], b[4]]))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).expect("usage: relmeasure <relations.resolved.parquet>");
let f = std::fs::File::open(&path)?;
let rdr = ParquetRecordBatchReaderBuilder::try_new(f)?.with_batch_size(4096).build()?;
let (mut rows, mut with_geom, mut matched, mut matched_with_geom, mut mp) = (0u64, 0u64, 0u64, 0u64, 0u64);
let mut classes: BTreeMap<String, u64> = BTreeMap::new();
for batch in rdr {
let batch = batch?;
let geom = batch.column_by_name("geometry").and_then(|c| c.as_any().downcast_ref::<BinaryArray>());
let tags = batch.column_by_name("tags").and_then(|c| c.as_any().downcast_ref::<StringArray>());
for i in 0..batch.num_rows() {
rows += 1;
let has_geom = geom.map(|g| !g.is_null(i) && !g.value(i).is_empty()).unwrap_or(false);
if has_geom {
with_geom += 1;
}
let t = match tags {
Some(a) if !a.is_null(i) && !a.value(i).is_empty() => a.value(i),
_ => continue,
};
// **The OLD rule.** `is_way` was true for a relation, so the tiler asked
// the ways predicate — the one ROADS and RAILS are cut with.
if !carried_by_a_network(Source::Ways, t) {
continue;
}
matched += 1;
if !has_geom {
continue;
}
matched_with_geom += 1;
if wkb_type(geom.unwrap().value(i)) == Some(6) {
mp += 1;
}
for kv in t.split('\u{1}') {
if let Some((k, v)) = kv.split_once('\u{2}').or_else(|| kv.split_once('=')) {
if k == "highway" || k == "railway" {
*classes.entry(format!("{k}={v}")).or_default() += 1;
}
}
}
}
}
// The NEW rule: `SrcKind::Relation::network_source()` is `None`, so the predicate
// is not consulted at all and nothing is excluded on a relation.
println!("file {path}");
println!("relation rows {rows}");
println!(" with assembled geometry {with_geom}");
println!("match the ways predicate {matched}");
println!(" of those, with geometry {matched_with_geom} <- what the OLD rule dropped");
println!(" of those, MultiPolygon {mp}");
println!();
println!("REACHING THE PYRAMID");
println!(" before (Relation => Some(Ways)) {}", with_geom - matched_with_geom);
println!(" after (Relation => None) {with_geom}");
let mut v: Vec<_> = classes.into_iter().collect();
v.sort_by_key(|(_, n)| std::cmp::Reverse(*n));
for (k, n) in v.into_iter().take(8) {
println!(" {k:32} {n}");
}
Ok(())
}