77 lines
2.4 KiB
Rust
77 lines
2.4 KiB
Rust
|
|
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);
|
||
|
|
}
|
||
|
|
}
|