osm-katana/tests/admin_poly_bz2.rs
//! **The admin boundary comes out of the planet, not off the internet.**
//!
//! `stream_admin_region_polygon` could only read a `.osm.pbf`, while the planet on
//! the machine that needs a clip polygon is a 173 GB `.osm.bz2`. Because of that
//! one gap a Sweden outline was once *downloaded* rather than cut from our own
//! data. These tests pin the wiring that closes it:
//!
//! * a real `.osm.bz2` (many bz2 blocks) yields the country's boundary, and it
//! is the RIGHT country — asserted at named places, the only check that can
//! tell one outline from another;
//! * the bz2 reader and the PBF reader agree **exactly**, proven by converting
//! the same fixture to PBF with this crate's own converter and deriving again;
//! * `admin_level` actually filters, and an unknown name is a fact (`None`),
//! not a silent empty polygon.
//!
//! Fixture: `lbzip2/test_data/liechtenstein.osm.bz2` (5.2 MB → ~60 MB of XML,
//! spanning many bz2 blocks). A country small enough to test in seconds and real
//! enough to have a genuine `admin_level=2` relation with 40 member ways.
use osm_katana::{AdminPolyOptions, Polygon};
/// The bz2 OSM fixture, in the sibling `lbzip2` crate's test data.
fn fixture_bz2() -> String {
concat!(
env!("CARGO_MANIFEST_DIR"),
"/../lbzip2/test_data/liechtenstein.osm.bz2"
)
.to_string()
}
/// Derive Liechtenstein's `admin_level=2` boundary from `path`.
fn derive(path: &str) -> osm_katana::AdminRegionPolygon {
let opts = AdminPolyOptions {
admin_level: Some("2"),
..Default::default()
};
osm_katana::stream_admin_region_polygon_detailed(path, "liechtenstein", &opts)
.expect("the admin scan runs")
.expect("Liechtenstein has a boundary=administrative admin_level=2 relation")
}
/// **The country that comes out is the country that went in.**
///
/// A polygon-shaped result proves nothing: the old bundled Sweden asset was a
/// valid `.poly` that put Visby outside Sweden. So this asserts containment at
/// named places on both sides of three international borders.
#[test]
fn bz2_admin_scan_derives_the_real_country_boundary() {
let d = derive(&fixture_bz2());
assert_eq!(
d.relations.len(),
1,
"exactly one admin_level=2 relation is named Liechtenstein"
);
assert_eq!(
d.relations[0].admin_level, "2",
"the level filter was applied"
);
assert_eq!(d.open_rings, 0, "every member-way chain closed into a ring");
assert_eq!(d.polygon.ring_count(), 1, "Liechtenstein is one ring");
// PINNED, not bounded: the fixture is a committed file, so the derivation is
// deterministic — 2553 vertices, the same figure `clip_mask_equivalence.rs:30`
// documents. The old `> 2_000` could not tell "the real border" from "the real
// border quietly lost a fifth of its vertices".
assert_eq!(
d.polygon.vertex_count(),
2_553,
"Liechtenstein's admin_level=2 border derives from the committed fixture \
with exactly 2553 vertices; a different count means the ring assembly or \
the reader changed behaviour",
);
for (name, lon, lat) in [
("Vaduz", 9.5215, 47.1410),
("Schaan", 9.5090, 47.1660),
("Balzers", 9.5040, 47.0660),
] {
assert!(
d.polygon.contains(lon, lat),
"{name} must be INSIDE Liechtenstein"
);
}
// Neighbours a few km away across the Rhine and the Austrian border — the
// distances that a merely plausible outline gets wrong.
for (name, lon, lat) in [
("Feldkirch, Austria", 9.5959, 47.2380),
("Buchs, Switzerland", 9.4770, 47.1670),
("Sargans, Switzerland", 9.4450, 47.0480),
("Bludenz, Austria", 9.8220, 47.1560),
("Zürich, Switzerland", 8.5417, 47.3769),
] {
assert!(
!d.polygon.contains(lon, lat),
"{name} must be OUTSIDE Liechtenstein"
);
}
}
/// **The bz2 reader and the PBF reader must not drift.**
///
/// Same OSM data, two container formats, two very different readers: the PBF path
/// mmaps and addresses blobs at random; the bz2 path streams bit-aligned blocks
/// through gatling and stitches the elements that straddle a block boundary. The
/// fixture is converted to PBF by this crate's own `xml2pbf`, so the comparison
/// needs nothing from outside the repo.
///
/// Byte-equality of the emitted `.poly` is the strongest available statement: same
/// rings, same vertex order, same coordinates to 7 decimals.
#[test]
fn bz2_and_pbf_readers_derive_the_same_boundary() {
let dir = tempfile::tempdir().expect("tempdir");
let pbf = dir.path().join("liechtenstein.osm.pbf");
let (elems, bytes) = osm_katana::xml_to_pbf::xml_to_pbf_bz2_path(
std::path::Path::new(&fixture_bz2()),
&pbf,
8,
/*skip_to_nodes=*/ false,
)
.expect("bz2 → pbf conversion");
assert!(
elems > 100_000 && bytes > 0,
"the converted PBF holds the fixture ({elems} elements)"
);
let from_bz2 = derive(&fixture_bz2());
let from_pbf = derive(pbf.to_str().expect("utf8 temp path"));
assert_eq!(
from_bz2.relations[0].id, from_pbf.relations[0].id,
"both readers matched the same relation",
);
assert_eq!(
from_bz2.polygon.to_poly_str("liechtenstein"),
from_pbf.polygon.to_poly_str("liechtenstein"),
"the bz2 sweep and the PBF blob scan derived DIFFERENT geometry from the same data",
);
}
/// A derived boundary survives the `.poly` round trip byte-for-byte in geometry:
/// what is written to `korp/assets/sweden.poly` is what was cut from the planet.
#[test]
fn derived_boundary_round_trips_through_the_poly_format() {
let d = derive(&fixture_bz2());
let text = d.polygon.to_poly_str("liechtenstein");
let reparsed = Polygon::from_poly_str(&text).expect("what we write, we can read");
assert_eq!(reparsed.ring_count(), d.polygon.ring_count());
assert_eq!(reparsed.vertex_count(), d.polygon.vertex_count());
assert!(
reparsed.contains(9.5215, 47.1410),
"Vaduz survives the round trip"
);
assert!(
!reparsed.contains(9.5959, 47.2380),
"Feldkirch stays out after the round trip"
);
assert_eq!(
text,
reparsed.to_poly_str("liechtenstein"),
"writing what we parsed reproduces the file",
);
}
/// **The region CATALOG reads the bz2 planet too — and a format it cannot read
/// is an ERROR, never an empty list.**
///
/// `stream_admin_region_names` was PBF-only. Handed a `.osm.bz2` it did not fail:
/// there are no PBF blobs in a bzip2 stream, so the blob scan found none, the
/// fan-out ran over zero units, and it returned `Ok(vec![])` — the same value as
/// "this planet has no administrative regions". The collectors daemon treats an
/// empty catalog as a documented fact, so it went ONLINE with an empty region
/// pick-list and re-swept 173 GB on every tick, forever, with nothing red anywhere.
///
/// So this asserts BOTH halves of the distinction:
/// * a `.osm.bz2` now yields the real catalog;
/// * the PBF reader handed that same bz2 yields a NAMED error
/// ([`osm_katana::UnreadablePlanet`]) — and specifically not `Ok(empty)`.
#[test]
fn catalog_reads_bz2_and_names_an_unreadable_planet_instead_of_going_empty() {
let path = fixture_bz2();
let names = osm_katana::stream_admin_region_names(&path).expect("catalog scan on a .osm.bz2");
assert!(
names.iter().any(|n| n == "Liechtenstein"),
"the bz2 catalog must hold the country itself; got {} names: {:?}",
names.len(),
&names[..names.len().min(8)],
);
assert!(
names.len() > 5,
"a real extract has more than the country in it: {}",
names.len()
);
// The old path, on the same file. It must FAIL, by name — the empty list it
// used to return is the bug this whole wiring exists to kill.
let err = osm_katana::stream_admin_region_names_pbf(&path)
.expect_err("the PBF reader cannot read a bz2 and must say so, not return Ok(empty)");
let named = err
.downcast_ref::<osm_katana::UnreadablePlanet>()
.expect("the failure is downcastable to UnreadablePlanet so a caller can act on it");
assert!(
named.path.ends_with(".osm.bz2"),
"the error names the file: {named}"
);
}
/// **`Ok(vec![])` still has to be reachable, and mean what it says.**
///
/// If "unreadable" and "empty" were merged the other way — everything an error —
/// the distinction would be just as useless. A readable PBF that genuinely holds
/// no administrative relation returns an honest empty catalog, while a file that
/// is not a planet at all returns [`osm_katana::UnreadablePlanet`].
#[test]
fn honest_empty_and_unreadable_are_different_answers() {
let dir = tempfile::tempdir().expect("tempdir");
// A REAL, readable PBF with two nodes and not one relation.
let xml = br#"<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="test">
<node id="1" lat="47.1" lon="9.5" version="1"/>
<node id="2" lat="47.2" lon="9.6" version="1"/>
</osm>
"#;
let empty_pbf = dir.path().join("no-regions.osm.pbf");
osm_katana::xml_to_pbf::xml_to_pbf_raw(&xml[..], &empty_pbf, 2).expect("xml → pbf");
assert_eq!(
osm_katana::planet_format(empty_pbf.to_str().unwrap()).expect("sniff"),
osm_katana::PlanetFormat::Pbf,
"a converted PBF sniffs as a PBF",
);
let names = osm_katana::stream_admin_region_names(empty_pbf.to_str().unwrap())
.expect("a readable PBF with no regions is Ok, not an error");
assert!(names.is_empty(), "an honest empty catalog: {names:?}");
// Files that are NOT planets: gzip magic, and something far too short.
let gz = dir.path().join("planet.osm.gz");
std::fs::write(&gz, [0x1f, 0x8b, 0x08, 0x00, 0, 0, 0, 0]).expect("write gz stub");
let err = osm_katana::stream_admin_region_names(gz.to_str().unwrap())
.expect_err("a .osm.gz is not readable by the admin scan and must say so");
let named = err
.downcast_ref::<osm_katana::UnreadablePlanet>()
.expect("named failure");
assert!(
named.detail.contains("gzip"),
"the error says what it saw: {named}"
);
let stub = dir.path().join("truncated.osm.bz2");
std::fs::write(&stub, b"BZ").expect("write stub");
let err = osm_katana::stream_admin_region_names(stub.to_str().unwrap())
.expect_err("a two-byte file is not a planet");
assert!(
err.downcast_ref::<osm_katana::UnreadablePlanet>().is_some(),
"named failure: {err}"
);
}
/// **Two consumers, one pass.** The catalog and the boundary come out of a single
/// sweep of the relation tail, and each is identical to what its own dedicated
/// scan produces — the whole point of queueing extractions onto one planet pass.
#[test]
fn catalog_and_polygon_ride_one_pass_with_no_loss() {
let path = fixture_bz2();
let opts = AdminPolyOptions {
admin_level: Some("2"),
..Default::default()
};
let (names, poly) =
osm_katana::stream_admin_catalog_and_polygon_bz2(&path, "liechtenstein", &opts)
.expect("shared pass");
let poly = poly.expect("the boundary is derived in the shared pass too");
let names_alone = osm_katana::stream_admin_region_names(&path).expect("catalog alone");
let poly_alone = derive(&path);
assert_eq!(names, names_alone, "the shared pass lost catalog names");
assert_eq!(
poly.polygon.to_poly_str("x"),
poly_alone.polygon.to_poly_str("x"),
"the shared pass derived different geometry than the dedicated one",
);
}
/// `admin_level` is a FILTER, not decoration — pin the wrong level and the
/// country's own relation must not answer. Without the filter the level-2
/// relation is found, so this cannot pass by accident.
#[test]
fn admin_level_filter_and_unknown_name_are_facts() {
let path = fixture_bz2();
let any_level = AdminPolyOptions::default();
assert!(
osm_katana::stream_admin_region_polygon_detailed(&path, "liechtenstein", &any_level)
.expect("scan runs")
.is_some(),
"with no level pinned the country IS found (so the negatives below mean something)",
);
let level_9 = AdminPolyOptions {
admin_level: Some("9"),
..Default::default()
};
assert!(
osm_katana::stream_admin_region_polygon_detailed(&path, "liechtenstein", &level_9)
.expect("scan runs")
.is_none(),
"no relation named Liechtenstein at admin_level=9 — the level filter must bite",
);
let opts = AdminPolyOptions {
admin_level: Some("2"),
..Default::default()
};
assert!(
osm_katana::stream_admin_region_polygon_detailed(&path, "atlantis", &opts)
.expect("scan runs")
.is_none(),
"an unknown region is None (a fact the caller can act on), never an empty polygon",
);
}