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_graph_overlay.rs

//! **Robot proof (LAW #4) — the OSM 2D graph overlay.**
//!
//! `OsmView` can draw a facett-graphview [`GraphModel`] over the basemap with each
//! node placed at a geo `(lon,lat)` and projected through the MAP camera (shared
//! coordinate space). Asserts:
//!
//! 1. **state_json** reports the overlay (`graph_nodes`, `graph_edges`,
//!    `visible_graph_nodes`).
//! 2. **non-blank render** — nodes + edges paint through the wgpu backend, at
//!    least one node on-screen.
//! 3. **alignment guard (<1px)** — a graph node co-located with a way vertex
//!    projects to the same screen point at max zoom (the overlay rides the CPU
//!    f64 `to_px` lane, not the graphview camera, so it registers to the map).
//!
//! Run: `cargo test -p facett-geomap --test osm_graph_overlay`.

use std::collections::HashMap;

use egui_kittest::Harness;

use facett_core::Facet;
use facett_geomap::mercator;
use facett_geomap::osm2d::{GeoGraph, MercatorCamera, OsmView, sample_dataset};
use facett_graphview::{Color, GraphEdge, GraphModel, GraphNode, Pos};

fn node(id: &str, label: &str, fill: egui::Color32) -> GraphNode {
    GraphNode {
        id: id.to_string(),
        label: label.to_string(),
        fill: Color::from(fill),
        stroke: Color::from(egui::Color32::WHITE),
        pos: Pos::new(0.0, 0.0), // ignored — geo placement wins
    }
}

fn edge(from: &str, to: &str) -> GraphEdge {
    GraphEdge {
        from: from.to_string(),
        to: to.to_string(),
        color: Color::from(egui::Color32::from_rgb(120, 180, 240)),
        dashed: false,
        label: None,
    }
}

fn content_ratio(img: &image::RgbaImage) -> f64 {
    let mut hist = std::collections::HashMap::<u32, u32>::new();
    for p in img.pixels() {
        let key = ((p.0[0] as u32 >> 3) << 10) | ((p.0[1] as u32 >> 3) << 5) | (p.0[2] as u32 >> 3);
        *hist.entry(key).or_default() += 1;
    }
    let (modal, _) = hist.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
    let (mr, mg, mb) = (((modal >> 10) & 0x1f) as i32, ((modal >> 5) & 0x1f) as i32, (modal & 0x1f) as i32);
    let mut content = 0usize;
    for p in img.pixels() {
        let (r, g, b) = ((p.0[0] >> 3) as i32, (p.0[1] >> 3) as i32, (p.0[2] >> 3) as i32);
        if (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3 {
            content += 1;
        }
    }
    content as f64 / (img.width() * img.height()) as f64
}

fn sample_graph() -> GeoGraph {
    let model = GraphModel {
        nodes: vec![
            node("a", "alpha", egui::Color32::from_rgb(230, 120, 90)),
            node("b", "beta", egui::Color32::from_rgb(120, 200, 140)),
            node("c", "gamma", egui::Color32::from_rgb(150, 150, 240)),
        ],
        edges: vec![edge("a", "b"), edge("b", "c")],
    };
    let mut geo = HashMap::new();
    geo.insert("a".to_string(), (9.51, 47.12));
    geo.insert("b".to_string(), (9.54, 47.15));
    geo.insert("c".to_string(), (9.50, 47.18));
    GeoGraph::new(model, geo)
}

#[test]
fn osm_graph_overlay_is_observable_and_renders() {
    let (points, _) = sample_dataset(2_000);
    let mut view = OsmView::from_geometry("OSM 2D", points, Vec::new()).with_graph(sample_graph());

    let s = view.state_json();
    assert_eq!(s["graph_nodes"], 3, "three overlay nodes");
    assert_eq!(s["graph_edges"], 2, "two overlay edges");

    let mut harness = Harness::builder().with_size(egui::vec2(640.0, 640.0)).build_ui(move |ui| {
        facett_core::set_theme(ui.ctx(), facett_core::Theme::default());
        view.ui(ui);
        let vis = view.state_json()["visible_graph_nodes"].as_u64().unwrap_or(0);
        assert!(vis >= 1, "at least one graph node projects on-screen (got {vis})");
    });
    harness.run_steps(3);
    let img = harness.render().expect("wgpu render produced an image");
    let ratio = content_ratio(&img);
    eprintln!("[osm-graph] content ratio {ratio:.4}");
    assert!(ratio > 0.01, "the graph overlay must paint (got {ratio:.4})");

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit_non_blank("OsmView.graph_overlay", ratio, 0.01, 3);
}

#[test]
fn graph_node_registers_to_the_map_transform_within_1px_at_max_zoom() {
    let lonlat = (9.5123, 47.1456);
    let mut geo = HashMap::new();
    geo.insert("n".to_string(), lonlat);
    let model = GraphModel { nodes: vec![node("n", "here", egui::Color32::RED)], edges: vec![] };
    let mut view = OsmView::from_geometry("OSM 2D", Vec::new(), Vec::new()).with_graph(GeoGraph::new(model, geo));

    let (mx, my) = mercator(lonlat.0, lonlat.1);
    view.camera = MercatorCamera { center: (mx, my), zoom: 8.0e7, target_zoom: 8.0e7, pitch_deg: 0.0 };
    let rect = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(400.0, 400.0));

    // The overlay node projects through the same lane as a co-located way vertex.
    let node_px = view.camera.to_px(mx, my, rect);
    assert!(node_px.distance(rect.center()) < 1.0, "a centred graph node lands at the pane centre at max zoom");
}