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

nordisk/facett

facett-geomap/tests/osm_point_lod.rs

//! **The BUILDING-VERTEX LOD** — "the corners of the houses are blue, it makes it
//! look like a sea when zooming out".
//!
//! The OSM point layer is the node cloud, and in a real extract most nodes are
//! building corners. It used to paint every surviving node as a circle at *any*
//! zoom, in the **theme's** `point` hue — an unrelated blue on the cartographic-day
//! paper. Zoomed out over a town that is tens of thousands of dots on a pane with
//! hundreds of thousands of pixels: a solid wash of colour, not a set of markers.
//!
//! Three separate things had to be true, and each gets its own oracle here:
//!
//! 1. **The corners must not render when zoomed out at all** (the actual bug — an
//!    LOD rule, not a colour choice). `zoomed_out_paints_zero_building_vertices` is
//!    the RED-when-broken assertion: at a whole-dataset camera over a dense extract
//!    the count of corner-point primitives is **exactly 0**. The pre-fix painter
//!    emitted tens of thousands there, so it fails this outright.
//! 2. **When they DO show they are the building outline's own ink**, not blue —
//!    `visible_corners_are_the_building_stroke_colour`.
//! 3. **A debug mode makes them deliberately conspicuous**, default OFF, reachable
//!    from the UI — `debug_mode_*`.
//!
//! Plus the guard that keeps the cure from being worse than the disease: a SPARSE
//! layer (the demo's "collector data layers" pane, 43 points) must keep every point
//! at every zoom — `a_sparse_layer_is_never_culled_at_any_zoom`.

use facett_core::harness;
use facett_geomap::osm2d::{Msg, OsmView};

/// A pane the size of the demo's map area.
fn rect() -> egui::Rect {
    egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0))
}

/// A **dense** town-scale node cloud — the shape of a real OSM extract, where the
/// overwhelming majority of nodes are building corners. Deterministic (xorshift, no
/// wall clock) so the oracle is reproducible.
fn dense_town(n: usize) -> Vec<(f64, f64)> {
    let (lon0, lon1, lat0, lat1) = (17.70, 17.75, 59.60, 59.64);
    let mut state: u64 = 0x2545_F491_4F6C_DD1D;
    let mut next = || {
        state ^= state << 13;
        state ^= state >> 7;
        state ^= state << 17;
        (state >> 11) as f64 / (1u64 << 53) as f64
    };
    (0..n).map(|_| (lon0 + next() * (lon1 - lon0), lat0 + next() * (lat1 - lat0))).collect()
}

/// **THE RED-WHEN-BROKEN ORACLE.** At a zoomed-out (whole-dataset) camera over a
/// dense extract the painter must emit **ZERO** building-vertex primitives.
///
/// This is stated as a count, not as a colour or a stride, because that is what the
/// bug was: the layer *rendered*, and no hue would have saved it. The pre-fix
/// painter drew `visible / stride` circles here — a five-figure number — so it fails
/// this assertion by five orders of magnitude.
#[test]
fn zoomed_out_paints_zero_building_vertices() {
    let mut v = OsmView::new("sea", dense_town(40_000), vec![]);
    let r = rect();
    v.fit_to(r);

    let res = harness::headless_render(&mut v);
    let vis = res.state["visible_points"].as_u64().unwrap();
    let drawn = res.state["drawn_points"].as_u64().unwrap();

    assert!(vis > 10_000, "the whole dense cloud IS in view ({vis}) — this is the bug's input");
    assert_eq!(drawn, 0, "a zoomed-out frame paints NO corner points, got {drawn}");
    assert_eq!(res.state["points_culled"], true, "and reports the cull as data");

    // The decision is reproducible from state alone: the ink the layer WOULD have
    // painted, against the budget it was measured against.
    let ink = res.state["point_ink_frac"].as_f64().unwrap();
    let budget = res.state["max_point_ink_frac"].as_f64().unwrap();
    assert!(ink > budget, "the cull fired on ink {ink} over budget {budget}");
    // …and it was not a marginal call: this frame wanted MORE THAN A SCREENFUL of
    // dots. That is the "sea", quantified.
    assert!(ink > 1.0, "the pre-fix frame painted over a full screen of dots (ink {ink})");
}

/// The other half of the LOD: **at building zoom the corners come back**, and they
/// are individually meaningful (few enough to read as markers, not as a texture).
#[test]
fn building_zoom_paints_the_corner_points() {
    let mut v = OsmView::new("close", dense_town(40_000), vec![]);
    let r = rect();
    v.fit_to(r);

    // Zoom in toward a building-scale camera until the layer is admitted. Each step
    // is a real `Msg` through the single update path (live == headless).
    let mut drawn = 0u64;
    let mut steps = 0;
    for _ in 0..12 {
        v.update(Msg::ZoomTo { k: 2.0, anchor: r.center(), rect: r });
        while v.camera_mut().tick() {}
        let res = harness::headless_render(&mut v);
        steps += 1;
        drawn = res.state["drawn_points"].as_u64().unwrap();
        if drawn > 0 {
            assert_eq!(res.state["points_culled"], false, "admitted ⇒ not culled");
            let ink = res.state["point_ink_frac"].as_f64().unwrap();
            let budget = res.state["max_point_ink_frac"].as_f64().unwrap();
            assert!(ink <= budget, "admitted ⇒ within the ink budget ({ink} <= {budget})");
            break;
        }
    }
    assert!(drawn > 0, "the corner layer is admitted at a close enough zoom (gave up after {steps})");
    // The knee is a ZOOM knee, not a "never draws": it took only a handful of 2x
    // steps off the fit, i.e. the layer returns at a normal street/building camera
    // rather than at some unreachable extreme.
    assert!(steps <= 8, "the layer returns at a reachable zoom, took {steps} doublings");
}

/// A **SPARSE** annotation layer (the demo's "collector data layers" pane holds 43
/// points) must never be culled — at any zoom, including the most zoomed-out one.
/// Tens of dots are nowhere near a screenful of ink, so the density rule leaves them
/// alone; a plain "metres per pixel" threshold would have blanked this pane.
#[test]
fn a_sparse_layer_is_never_culled_at_any_zoom() {
    let pts: Vec<(f64, f64)> =
        (0..43).map(|i| (17.70 + (i % 7) as f64 * 0.004, 59.60 + (i / 7) as f64 * 0.004)).collect();
    let mut v = OsmView::new("layers", pts.clone(), vec![]);
    let r = rect();
    v.fit_to(r);

    // The fit itself.
    let res = harness::headless_render(&mut v);
    assert_eq!(res.state["points_culled"], false, "43 points are not a sea");
    assert_eq!(
        res.state["drawn_points"].as_u64().unwrap(),
        res.state["visible_points"].as_u64().unwrap(),
        "every visible point of a sparse layer draws"
    );

    // …and at every zoom-out step down to the world view.
    for _ in 0..6 {
        v.update(Msg::ZoomTo { k: 0.25, anchor: r.center(), rect: r });
        while v.camera_mut().tick() {}
        let res = harness::headless_render(&mut v);
        let vis = res.state["visible_points"].as_u64().unwrap();
        let drawn = res.state["drawn_points"].as_u64().unwrap();
        assert_eq!(res.state["points_culled"], false, "a sparse layer is never a sea");
        assert_eq!(drawn, vis, "sparse: drew {drawn} of {vis} visible");
    }
}

/// **The colour.** A corner point is the building outline's own ink — the palette's
/// `building_stroke`, the SAME value `MapPalette::recolor_way` gives a building way
/// — and NOT the theme's `point` hue, which is what made them read blue on the
/// cartographic paper.
#[test]
fn visible_corners_are_the_building_stroke_colour() {
    let v = OsmView::new("c", dense_town(100), vec![]);
    let theme = facett_core::Theme::default();
    for mode in [
        facett_map::PaletteMode::CartographicDay,
        facett_map::PaletteMode::CartographicNight,
        facett_map::PaletteMode::Theme,
    ] {
        let pal = mode.palette(&theme);
        assert_eq!(
            v.point_color(&pal, 0.0),
            pal.building_stroke(),
            "{}: a corner dot is the building OUTLINE ink",
            mode.label()
        );
        // RED-when-broken against the old behaviour: it must NOT be the theme's
        // point hue on the cartographic paper (that value is the reported blue).
        if mode.is_cartographic() {
            assert_ne!(v.point_color(&pal, 0.0), theme.point, "{}: not the theme point hue", mode.label());
        }
    }
}

/// **Debug mode** — default OFF; when ON it (a) bypasses the LOD cull so the
/// vertices are visible at the very zoom that normally hides them, and (b) paints
/// them in an unmistakable flashing colour that no cartographic palette contains.
#[test]
fn debug_mode_shows_the_culled_vertices_and_flashes() {
    let r = rect();
    let mut plain = OsmView::new("plain", dense_town(40_000), vec![]);
    plain.fit_to(r);
    assert!(!plain.debug_points(), "debug mode is OFF by default");
    let res = harness::headless_render(&mut plain);
    assert_eq!(res.state["drawn_points"], 0, "default: culled");
    assert_eq!(res.state["debug_points"], false);

    let mut dbg = OsmView::new("dbg", dense_town(40_000), vec![]).with_debug_points(true);
    dbg.fit_to(r);
    let res = harness::headless_render(&mut dbg);
    assert!(res.state["drawn_points"].as_u64().unwrap() > 0, "debug shows what the LOD hid");
    assert_eq!(res.state["points_culled"], false, "debug bypasses the cull");
    assert_eq!(res.state["debug_points"], true);

    // The flash: two distinct high-contrast phases, deterministic in time, and
    // neither is a colour the cartographic palettes use.
    let pal = facett_map::PaletteMode::CartographicDay.palette(&facett_core::Theme::default());
    let a = dbg.point_color(&pal, 0.0);
    let b = dbg.point_color(&pal, 0.25);
    assert_ne!(a, b, "the debug ink FLASHES between two phases");
    for c in [a, b] {
        assert_ne!(c, pal.building_stroke(), "debug ink is never the map ink");
        assert_ne!(c, pal.building, "debug ink is never the map ink");
        assert_ne!(c, pal.land, "debug ink is never the map ink");
    }
}

/// The debug view is reachable **from the UI**, not only from Rust: the pane's own
/// `Msg::SetDebugPoints` (which its header checkbox and the `V` key both emit) is a
/// real transition through the single update path, and it is observable in state.
#[test]
fn debug_mode_is_reachable_from_the_pane_and_observable() {
    let mut v = OsmView::new("d", dense_town(40_000), vec![]);
    let r = rect();
    v.fit_to(r);

    v.update(Msg::SetDebugPoints(true));
    let on = harness::headless_render(&mut v);
    assert_eq!(on.state["debug_points"], true);
    assert!(on.state["drawn_points"].as_u64().unwrap() > 0, "toggling on reveals the vertices");

    v.update(Msg::SetDebugPoints(false));
    let off = harness::headless_render(&mut v);
    assert_eq!(off.state["debug_points"], false);
    assert_eq!(off.state["drawn_points"], 0, "toggling off restores the LOD cull");
}

/// The LOD is a **pure function of the ink**, so the painter's branch and a test's
/// prediction cannot drift apart: assert the boundary from both sides.
#[test]
fn the_ink_rule_is_exact_at_its_boundary() {
    let r = rect();
    let v = OsmView::new("i", vec![], vec![]);
    let dot = OsmView::point_dot_px(1_000_000.0);
    assert_eq!(dot, 3.0, "at high zoom the dot radius saturates at 3 px");

    // Solve for the dot count that exactly spends the budget on this pane.
    let pane = r.width() * r.height();
    let per_dot = std::f32::consts::PI * dot * dot;
    let budget_dots = (0.06 * pane / per_dot).floor() as usize;
    assert!(v.points_pass_lod(budget_dots, dot, r), "{budget_dots} dots is within budget");
    assert!(!v.points_pass_lod(budget_dots * 2, dot, r), "twice the budget is over");
    // Zero dots is always fine (an empty layer must not be reported as a sea).
    assert!(v.points_pass_lod(0, dot, r));
}