Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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()
            ),
        );
    }
}