facett-demo/src/bin/osm2d_demo.rs
//! **osm2d_demo** — the standalone "test application" for facett's state-of-the-art
//! 2D OSM viewer ([`facett::geomap::OsmView`]). A self-contained eframe app: it
//! opens the viewer full-window over a real OSM dataset and lets you drive it with
//! the mouse wheel (zoom **to the cursor**), click-drag pan, the keyboard (arrows /
//! WASD pan, `+`/`−` / PageUp/PageDown zoom, `F`/Cmd-0 fit) and the trackpad
//! (macOS pinch + two-finger pan).
//!
//! **Data source (graceful):**
//! - `$FACETT_OSM_PARQUET` — a GeoParquet `nodes.parquet` (points), and
//! - `$FACETT_OSM_WAYS` — a GeoParquet `ways.parquet` (the vector basemap),
//!
//! loaded through [`facett::osm`]. With neither set, it falls back to a synthetic
//! Liechtenstein-ish dataset ([`facett::geomap::osm2d::sample_dataset`]) so the app
//! always runs with no fixture on disk.
//!
//! Run:
//! cargo run -p facett-demo --bin osm2d_demo
//! FACETT_OSM_PARQUET=/tmp/li_geo/nodes.parquet \
//! FACETT_OSM_WAYS=/tmp/li_geo/ways.parquet \
//! cargo run -p facett-demo --bin osm2d_demo
//!
//! This is intentionally separate from facett-demo's shared `main` + tab registry.
// This standalone viewer drives `eframe::run_native`, which only exists on native
// targets. It is intentionally NOT part of the wasm web demo (the web demo is the
// shared `main.rs` deck). Compile it to an empty `main` on wasm so the whole
// `facett-demo` package builds for `wasm32-unknown-unknown`.
#[cfg(target_arch = "wasm32")]
fn main() {}
#[cfg(not(target_arch = "wasm32"))]
use eframe::egui;
#[cfg(not(target_arch = "wasm32"))]
use facett::Facet;
#[cfg(not(target_arch = "wasm32"))]
use facett::LookTheme;
#[cfg(not(target_arch = "wasm32"))]
use facett::geomap::OsmView;
#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> {
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([1100.0, 760.0])
.with_min_inner_size([640.0, 480.0])
.with_title("facett — OSM 2D viewer"),
..Default::default()
};
eframe::run_native(
"facett-osm2d-demo",
native_options,
Box::new(|cc| {
// Match the running OS look preset (drives the per-OS keymap + feel the
// viewer reads for pan/zoom). Linux → Windows-dark (documented default).
LookTheme::from_os(cc.egui_ctx.os()).apply(&cc.egui_ctx);
Ok(Box::new(OsmApp::new()))
}),
)
}
#[cfg(not(target_arch = "wasm32"))]
struct OsmApp {
view: OsmView,
source: String,
}
#[cfg(not(target_arch = "wasm32"))]
impl OsmApp {
fn new() -> Self {
let (view, source) = load_view();
Self { view, source }
}
}
/// Load the viewer from `$FACETT_OSM_PARQUET` / `$FACETT_OSM_WAYS` if present,
/// else the synthetic sample. Returns the view + a human label of the source.
#[cfg(not(target_arch = "wasm32"))]
fn load_view() -> (OsmView, String) {
let pts_path = std::env::var("FACETT_OSM_PARQUET").ok();
let ways_path = std::env::var("FACETT_OSM_WAYS").ok();
if pts_path.is_some() || ways_path.is_some() {
let theme = facett::Theme::default();
let points = pts_path
.as_deref()
.and_then(|p| match facett::osm::read_points(p, 2_000_000) {
Ok(v) => Some(v),
Err(e) => {
eprintln!("osm2d_demo: read_points({p}) failed: {e}");
None
}
})
.unwrap_or_default();
let ways = ways_path
.as_deref()
.and_then(|p| match facett::osm::read_ways(p, 0, &theme) {
Ok(v) => Some(v),
Err(e) => {
eprintln!("osm2d_demo: read_ways({p}) failed: {e}");
None
}
})
.unwrap_or_default();
if !points.is_empty() || !ways.is_empty() {
let label = format!(
"GeoParquet — {} points, {} ways",
points.len(),
ways.len()
);
return (OsmView::new("OSM 2D", points, ways), label);
}
eprintln!("osm2d_demo: no features read from the GeoParquet paths; using the synthetic sample.");
}
let (points, ways) = facett::geomap::osm2d::sample_dataset(20_000);
let label = format!("synthetic sample — {} points, {} ways", points.len(), ways.len());
(OsmView::new("OSM 2D (sample)", points, ways), label)
}
#[cfg(not(target_arch = "wasm32"))]
impl eframe::App for OsmApp {
// This eframe build hands the app a `Ui` directly (same surface the shared
// facett-demo `main` uses — CLI/GUI parity).
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
ui.horizontal_wrapped(|ui| {
ui.label("Source:");
ui.monospace(&self.source);
ui.separator();
ui.label("wheel = zoom-to-cursor · drag = pan · arrows/WASD = pan · +/− & PageUp/Down = zoom · F / Ctrl-0 = fit");
});
ui.separator();
self.view.ui(ui);
}
}