use eframe::egui; pub trait MapProjection: Send + Sync { fn project(&self, lon: f64, lat: f64, rect: egui::Rect) -> egui::Pos2; fn unproject(&self, pos: egui::Pos2, rect: egui::Rect) -> (f64, f64); fn name(&self) -> &'static str; } pub struct Equirectangular; impl MapProjection for Equirectangular { fn project(&self, lon: f64, lat: f64, rect: egui::Rect) -> egui::Pos2 { egui::pos2( rect.left() + ((lon + 180.0) / 360.0) as f32 * rect.width(), rect.top() + ((90.0 - lat) / 180.0) as f32 * rect.height(), ) } fn unproject(&self, pos: egui::Pos2, rect: egui::Rect) -> (f64, f64) { let lon = (pos.x - rect.left()) as f64 / rect.width() as f64 * 360.0 - 180.0; let lat = 90.0 - (pos.y - rect.top()) as f64 / rect.height() as f64 * 180.0; (lon, lat) } fn name(&self) -> &'static str { "Equirectangular" } } #[cfg(test)] mod tests { use super::*; fn unit_rect() -> egui::Rect { egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(360.0, 180.0)) } #[test] fn roundtrip_center() { let proj = Equirectangular; let r = unit_rect(); let pos = proj.project(0.0, 0.0, r); let (lon, lat) = proj.unproject(pos, r); assert!((lon).abs() < 1e-4); assert!((lat).abs() < 1e-4); } #[test] fn roundtrip_corners() { let proj = Equirectangular; let r = unit_rect(); for (lon, lat) in [(-180.0_f64, 90.0_f64), (180.0, -90.0), (-180.0, -90.0), (180.0, 90.0)] { let pos = proj.project(lon, lat, r); let (lon2, lat2) = proj.unproject(pos, r); assert!((lon - lon2).abs() < 1e-4, "lon {lon} → {lon2}"); assert!((lat - lat2).abs() < 1e-4, "lat {lat} → {lat2}"); } } #[test] fn project_prime_meridian_equator() { let proj = Equirectangular; let r = unit_rect(); let pos = proj.project(0.0, 0.0, r); assert!((pos.x - 180.0).abs() < 1e-3, "x={}", pos.x); assert!((pos.y - 90.0).abs() < 1e-3, "y={}", pos.y); } #[test] fn rect_scaling() { let proj = Equirectangular; let r = egui::Rect::from_min_size(egui::pos2(10.0, 20.0), egui::vec2(720.0, 360.0)); let pos = proj.project(0.0, 0.0, r); assert!((pos.x - 370.0).abs() < 1e-3, "x={}", pos.x); assert!((pos.y - 200.0).abs() < 1e-3, "y={}", pos.y); } }