facett-map3d/tests/poi_proof.rs
//! **POI-layer proof** (headless, data-level — no pixels).
//!
//! The Google-Maps-style **POI layer** feeds amenity/shop nodes through the *shared*
//! [`facett_map::LayerCategory::from_osm_tags`] classifier ([`extrude::poi_anchors`]),
//! projecting each POI into the SAME design plane the city extrudes into, and the map
//! gates them by a master toggle + the shared per-category [`facett_map::LayerSet`].
//! This asserts, as DATA (no framebuffer):
//! 1. only **POI** nodes (bar/kiosk/restaurant/shop/amenity) become markers — a road
//! node / a plain building node is skipped (never fabricated into a POI),
//! 2. each POI resolves to the **right category** via the shared classifier,
//! 3. every marker lands inside the `[-1, 1]` design box (aligned with the city),
//! 4. the label is the OSM `name`, else the category label,
//! 5. `poi_count` is gated: **0** when the layer is off, the visible-category count
//! when on, and drops as categories are toggled off (RED-when-broken).
//!
//! Run: `cargo test -p facett-map3d --test poi_proof [--features testmatrix]`.
use facett_map::{LayerCategory, Way};
use facett_core::Theme;
use facett_map3d::{Map3D, PoiNode, extrude};
/// A tiny town: a 3×3 block of building rings around Sigtuna, the footprint the POIs
/// project against.
fn town() -> Vec<Way> {
let mut ways = Vec::new();
let mut id = 1;
for r in 0..3 {
for c in 0..3 {
let bx = 17.70 + c as f64 * 0.0015;
let by = 59.61 + r as f64 * 0.0015;
let ring = [(bx, by), (bx + 0.0008, by), (bx + 0.0008, by + 0.0008), (bx, by + 0.0008), (bx, by)];
let tags = vec![("building".to_string(), "yes".to_string())];
ways.push(Way::from_lonlat(id, &ring, &tags, &Theme::default()).expect("a building way"));
id += 1;
}
}
ways
}
/// The POI feed: a bar (named), a kiosk, a restaurant, a shop, a bare amenity, PLUS two
/// non-POI nodes (a highway node + an untagged node) that must NOT become markers.
fn pois() -> Vec<PoiNode> {
vec![
PoiNode::new(17.7010, 59.6110, &[("amenity", "bar")], "Loki's Pub"),
PoiNode::new(17.7025, 59.6112, &[("shop", "kiosk")], ""),
PoiNode::new(17.7018, 59.6125, &[("amenity", "restaurant")], "Valhalla Grill"),
PoiNode::new(17.7030, 59.6120, &[("shop", "bakery")], ""),
PoiNode::new(17.7022, 59.6118, &[("amenity", "bench")], ""),
// Non-POIs — skipped.
PoiNode::new(17.7015, 59.6115, &[("highway", "traffic_signals")], ""),
PoiNode::new(17.7020, 59.6117, &[("foo", "bar")], ""),
]
}
#[test]
fn poi_anchors_classify_project_and_gate() {
let ways = town();
let anchors = extrude::poi_anchors(&ways, &pois());
// (1) Exactly the five POI nodes survive; the highway + untagged nodes are dropped.
assert_eq!(anchors.len(), 5, "only the 5 POI nodes get markers, got {anchors:?}");
// (2) Categories resolve through the shared classifier.
let cats: Vec<LayerCategory> = anchors.iter().map(|a| a.category).collect();
assert!(cats.contains(&LayerCategory::Bars), "bar → Bars");
assert!(cats.contains(&LayerCategory::Kiosks), "kiosk → Kiosks");
assert!(cats.contains(&LayerCategory::Restaurants), "restaurant → Restaurants");
assert!(cats.contains(&LayerCategory::Shops), "shop=bakery → Shops");
assert!(cats.contains(&LayerCategory::Amenities), "amenity=bench → Amenities");
assert!(cats.iter().all(|c| c.is_poi()), "every anchor is a POI category");
// (3) Every marker lands inside the [-1, 1] design box the city extrudes into.
let in_box = anchors.iter().all(|a| a.pos[0].abs() <= 1.0 && a.pos[2].abs() <= 1.0);
assert!(in_box, "every POI marker sits inside the design box: {anchors:?}");
// (4) Label = OSM name, else the category label.
let bar = anchors.iter().find(|a| a.category == LayerCategory::Bars).unwrap();
assert_eq!(bar.label, "Loki's Pub", "a named POI keeps its name");
let kiosk = anchors.iter().find(|a| a.category == LayerCategory::Kiosks).unwrap();
assert_eq!(kiosk.label, LayerCategory::Kiosks.label(), "an unnamed POI falls back to its category label");
// Empty ways ⇒ no footprint to align to ⇒ no markers.
assert!(extrude::poi_anchors(&[], &pois()).is_empty(), "no footprint ⇒ no POI markers");
// Determinism.
assert_eq!(extrude::poi_anchors(&ways, &pois()), anchors, "poi_anchors is a pure function");
// (5) `poi_count` gating on the live map: off ⇒ 0, on ⇒ 5, toggling Bars off ⇒ 4.
let mut map = Map3D::from_geometry("navig", &ways).with_pois(&ways, &pois());
assert_eq!(map.poi_total(), 5, "all 5 markers attached");
assert_eq!(map.poi_count(), 0, "layer OFF ⇒ 0 drawn (RED-when-broken)");
map.set_show_pois(true);
let on = map.poi_count();
assert_eq!(on, 5, "layer ON ⇒ all visible categories drawn");
map.set_poi_layer(LayerCategory::Bars, false);
let hidden_bars = map.poi_count();
assert_eq!(hidden_bars, 4, "hiding Bars drops that marker ({on} → {hidden_bars})");
#[cfg(feature = "testmatrix")]
{
let ok = anchors.len() == 5
&& cats.iter().all(|c| c.is_poi())
&& in_box
&& bar.label == "Loki's Pub"
&& map.poi_total() == 5
&& on == 5
&& hidden_bars == 4;
facett_core::testmatrix::emit(
"facett-map3d",
"poi_layer_classify_project_and_gate",
ok,
&format!(
"anchors={} in_box={in_box} off=0 on={on} hidden_bars={hidden_bars} total={}",
anchors.len(),
map.poi_total()
),
);
}
}