egui_map_view/
projection.rs1use egui::{Pos2, Rect, vec2};
4use serde::{Deserialize, Serialize};
5
6use crate::{TILE_SIZE, lat_to_y, lon_to_x, x_to_lon, y_to_lat};
7
8pub struct MapProjection {
10 zoom: u8,
11 center_lon: f64,
12 center_lat: f64,
13 widget_rect: Rect,
14}
15
16impl MapProjection {
17 pub(crate) fn new(zoom: u8, center: GeoPos, widget_rect: Rect) -> Self {
19 Self {
20 zoom,
21 center_lon: center.lon,
22 center_lat: center.lat,
23 widget_rect,
24 }
25 }
26
27 pub fn project(&self, geo_pos: GeoPos) -> Pos2 {
29 let center_x = lon_to_x(self.center_lon, self.zoom);
30 let center_y = lat_to_y(self.center_lat, self.zoom);
31
32 let tile_x = lon_to_x(geo_pos.lon, self.zoom);
33 let tile_y = lat_to_y(geo_pos.lat, self.zoom);
34
35 let dx = (tile_x - center_x) * TILE_SIZE as f64;
36 let dy = (tile_y - center_y) * TILE_SIZE as f64;
37
38 let widget_center = self.widget_rect.center();
39 widget_center + vec2(dx as f32, dy as f32)
40 }
41
42 pub fn unproject(&self, screen_pos: Pos2) -> GeoPos {
44 let rel_pos = screen_pos - self.widget_rect.min;
45 let widget_center_x = self.widget_rect.width() as f64 / 2.0;
46 let widget_center_y = self.widget_rect.height() as f64 / 2.0;
47
48 let center_x = lon_to_x(self.center_lon, self.zoom);
49 let center_y = lat_to_y(self.center_lat, self.zoom);
50
51 let target_x = center_x + (rel_pos.x as f64 - widget_center_x) / TILE_SIZE as f64;
52 let target_y = center_y + (rel_pos.y as f64 - widget_center_y) / TILE_SIZE as f64;
53
54 GeoPos {
55 lon: x_to_lon(target_x, self.zoom),
56 lat: y_to_lat(target_y, self.zoom),
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
63pub struct GeoPos {
64 pub lon: f64,
66
67 pub lat: f64,
69}
70
71impl From<(f64, f64)> for GeoPos {
72 fn from((lon, lat): (f64, f64)) -> Self {
73 Self { lon, lat }
74 }
75}
76
77impl From<GeoPos> for (f64, f64) {
78 fn from(pos: GeoPos) -> Self {
79 (pos.lon, pos.lat)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use egui::{pos2, vec2};
87
88 const EPSILON: f64 = 1e-9;
89
90 fn create_projection() -> MapProjection {
91 MapProjection::new(
92 10,
93 GeoPos::from((24.93545, 60.16952)), Rect::from_min_size(pos2(100.0, 200.0), vec2(800.0, 600.0)),
95 )
96 }
97
98 #[test]
99 fn project_center() {
100 let projection = create_projection();
101 let center_geo = GeoPos::from((projection.center_lon, projection.center_lat));
102 let projected_center = projection.project(center_geo);
103 assert_eq!(projected_center, projection.widget_rect.center());
104 }
105
106 #[test]
107 fn unproject_center() {
108 let projection = create_projection();
109 let center_screen = projection.widget_rect.center();
110 let (lon, lat) = projection.unproject(center_screen).into();
111 assert!((lon - projection.center_lon).abs() < EPSILON);
112 assert!((lat - projection.center_lat).abs() < EPSILON);
113 }
114
115 #[test]
116 fn project_unproject_roundtrip() {
117 let projection = create_projection();
118 let geo_pos_in = GeoPos::from((24.93545, 60.16952)); let screen_pos = projection.project(geo_pos_in);
121 let geo_pos_out = projection.unproject(screen_pos);
122
123 assert!((geo_pos_in.lon - geo_pos_out.lon).abs() < EPSILON);
124 assert!((geo_pos_in.lat - geo_pos_out.lat).abs() < EPSILON);
125 }
126
127 #[test]
128 fn unproject_project_roundtrip() {
129 let projection = create_projection();
130 let screen_pos_in = pos2(150.0, 250.0); let geo_pos = projection.unproject(screen_pos_in);
133 let screen_pos_out = projection.project(geo_pos);
134
135 assert!((screen_pos_in.x - screen_pos_out.x).abs() < 1e-3); assert!((screen_pos_in.y - screen_pos_out.y).abs() < 1e-3);
137 }
138}