A simple map viewer

orthografic_view.rs 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. use cgmath::{Matrix3, Point3, Transform, vec3};
  2. use coord::{LatLonRad, ScreenCoord, TextureRect, TileCoord};
  3. use map_view::MapView;
  4. use std::collections::HashSet;
  5. use std::f64::consts::{PI, FRAC_1_PI};
  6. use std::f64;
  7. #[derive(Clone, Debug)]
  8. pub struct VisibleTile {
  9. pub tile: TileCoord,
  10. }
  11. impl From<TileCoord> for VisibleTile {
  12. fn from(tc: TileCoord) -> Self {
  13. VisibleTile {
  14. tile: tc,
  15. }
  16. }
  17. }
  18. #[derive(Clone, Debug)]
  19. pub struct TexturedVisibleTile {
  20. pub tile_coord: TileCoord,
  21. pub tex_rect: TextureRect,
  22. pub tex_minmax: TextureRect,
  23. }
  24. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
  25. pub enum TileNeighbor {
  26. Coord(TileCoord),
  27. NorthPole,
  28. SouthPole,
  29. }
  30. /// Tile neighbors using sphere topology
  31. pub fn tile_neighbors(origin: TileCoord, result: &mut Vec<TileNeighbor>) {
  32. result.clear();
  33. let zoom_level_tiles = TileCoord::get_zoom_level_tiles(origin.zoom);
  34. if origin.y < 0 || origin.y >= zoom_level_tiles {
  35. // Tile is out of bounds
  36. return;
  37. }
  38. // Normalize x coordinate
  39. let origin = TileCoord {
  40. zoom: origin.zoom,
  41. x: ((origin.x % zoom_level_tiles) + zoom_level_tiles) % zoom_level_tiles,
  42. y: origin.y,
  43. };
  44. match (origin.zoom, origin.y) {
  45. (0, _) => {},
  46. (1, _) => {
  47. result.extend(&[
  48. TileNeighbor::Coord(TileCoord::new(
  49. origin.zoom,
  50. (origin.x + 1) % zoom_level_tiles,
  51. origin.y)
  52. ),
  53. TileNeighbor::Coord(TileCoord::new(
  54. origin.zoom,
  55. origin.x,
  56. (origin.y + 1) % zoom_level_tiles),
  57. ),
  58. ]);
  59. },
  60. (_, 0) => {
  61. result.extend(&[
  62. TileNeighbor::NorthPole,
  63. TileNeighbor::Coord(TileCoord::new(
  64. origin.zoom,
  65. origin.x,
  66. origin.y + 1,
  67. )),
  68. TileNeighbor::Coord(TileCoord::new(
  69. origin.zoom,
  70. (origin.x + 1) % zoom_level_tiles,
  71. origin.y,
  72. )),
  73. TileNeighbor::Coord(TileCoord::new(
  74. origin.zoom,
  75. (origin.x + zoom_level_tiles - 1) % zoom_level_tiles,
  76. origin.y,
  77. )),
  78. ]);
  79. },
  80. (_, y) if y == zoom_level_tiles - 1 => {
  81. result.extend(&[
  82. TileNeighbor::SouthPole,
  83. TileNeighbor::Coord(TileCoord::new(
  84. origin.zoom,
  85. origin.x,
  86. origin.y - 1,
  87. )),
  88. TileNeighbor::Coord(TileCoord::new(
  89. origin.zoom,
  90. (origin.x + 1) % zoom_level_tiles,
  91. origin.y,
  92. )),
  93. TileNeighbor::Coord(TileCoord::new(
  94. origin.zoom,
  95. (origin.x + zoom_level_tiles - 1) % zoom_level_tiles,
  96. origin.y,
  97. )),
  98. ]);
  99. },
  100. _ => {
  101. result.extend(&[
  102. TileNeighbor::Coord(TileCoord::new(
  103. origin.zoom,
  104. origin.x,
  105. origin.y + 1,
  106. )),
  107. TileNeighbor::Coord(TileCoord::new(
  108. origin.zoom,
  109. origin.x,
  110. origin.y - 1,
  111. )),
  112. TileNeighbor::Coord(TileCoord::new(
  113. origin.zoom,
  114. (origin.x + 1) % zoom_level_tiles,
  115. origin.y,
  116. )),
  117. TileNeighbor::Coord(TileCoord::new(
  118. origin.zoom,
  119. (origin.x + zoom_level_tiles - 1) % zoom_level_tiles,
  120. origin.y,
  121. )),
  122. ]);
  123. },
  124. }
  125. }
  126. #[derive(Clone, Debug)]
  127. pub struct OrthograficView {
  128. }
  129. impl OrthograficView {
  130. /// Returns true if the rendering covers the whole viewport.
  131. pub fn covers_viewport(map_view: &MapView) -> bool {
  132. let sphere_diameter = 2.0f64.powf(map_view.zoom) *
  133. (f64::consts::FRAC_1_PI * f64::from(map_view.tile_size));
  134. // Add a little safety margin (the constant factor) since the rendered globe is not a
  135. // perfect sphere and its screen area is underestimated by the tesselation.
  136. map_view.width.hypot(map_view.height) < sphere_diameter * 0.9
  137. }
  138. /// Returns the tile zoom value that is used for rendering with the current zoom.
  139. //TODO Insert real implementation. Add TileCoord parameter -> lower resolution at the poles
  140. pub fn tile_zoom(map_view: &MapView) -> u32 {
  141. (map_view.zoom + map_view.tile_zoom_offset).floor().max(0.0) as u32
  142. }
  143. //TODO Return the transformation matrix that is used here to avoid redundant calculation.
  144. /// Returns a `Vec` of all tiles that are visible in the current viewport.
  145. pub fn visible_tiles(map_view: &MapView) -> Vec<VisibleTile> {
  146. let uzoom = Self::tile_zoom(map_view);
  147. match uzoom {
  148. 0 => return vec![TileCoord::new(0, 0, 0).into()],
  149. 1 => {
  150. // return every tile
  151. return vec![
  152. TileCoord::new(1, 0, 0).into(),
  153. TileCoord::new(1, 0, 1).into(),
  154. TileCoord::new(1, 1, 0).into(),
  155. TileCoord::new(1, 1, 1).into(),
  156. ]},
  157. _ => {},
  158. }
  159. let center_tile = map_view.center.on_tile_at_zoom(uzoom).nearest_valid();
  160. let transform = Self::transformation_matrix(map_view);
  161. let tile_is_visible = |tc: TileCoord| -> bool {
  162. let nw = tc.latlon_rad_north_west();
  163. let se = tc.latlon_rad_south_east();
  164. let vertices = [
  165. transform.transform_point(nw.to_sphere_point3()),
  166. transform.transform_point(se.to_sphere_point3()),
  167. transform.transform_point(LatLonRad::new(nw.lat, se.lon).to_sphere_point3()),
  168. transform.transform_point(LatLonRad::new(se.lat, nw.lon).to_sphere_point3()),
  169. ];
  170. if vertices.iter().all(|v| v.z > 0.0) {
  171. // Tile is on the backside of the sphere
  172. false
  173. } else {
  174. // Check bounding box of vertices against screen.
  175. //TODO Create true bounding box of tile that also accounts for curved borders.
  176. vertices.iter().fold(false, |acc, v| acc || v.x >= -1.0) &&
  177. vertices.iter().fold(false, |acc, v| acc || v.x <= 1.0) &&
  178. vertices.iter().fold(false, |acc, v| acc || v.y >= -1.0) &&
  179. vertices.iter().fold(false, |acc, v| acc || v.y <= 1.0)
  180. }
  181. };
  182. let mut tiles = vec![center_tile.into()];
  183. let mut stack: Vec<TileNeighbor> = vec![];
  184. tile_neighbors(center_tile, &mut stack);
  185. let mut visited: HashSet<TileNeighbor> = HashSet::new();
  186. visited.insert(TileNeighbor::Coord(center_tile));
  187. visited.extend(stack.iter());
  188. let mut neighbors = Vec::with_capacity(4);
  189. while let Some(tn) = stack.pop() {
  190. if let TileNeighbor::Coord(tc) = tn {
  191. if tile_is_visible(tc) {
  192. tiles.push(tc.into());
  193. tile_neighbors(tc, &mut neighbors);
  194. for tn in &neighbors {
  195. if !visited.contains(tn) {
  196. visited.insert(*tn);
  197. stack.push(*tn);
  198. }
  199. }
  200. }
  201. }
  202. }
  203. tiles
  204. }
  205. pub fn diameter_physical_pixels(map_view: &MapView) -> f64 {
  206. 2.0f64.powf(map_view.zoom) * (FRAC_1_PI * f64::from(map_view.tile_size))
  207. }
  208. pub fn transformation_matrix(map_view: &MapView) -> Matrix3<f64> {
  209. let (scale_x, scale_y) = {
  210. let diam = Self::diameter_physical_pixels(map_view);
  211. (diam / map_view.width, diam / map_view.height)
  212. };
  213. let scale_mat: Matrix3<f64> = Matrix3::from_cols(
  214. vec3(scale_x, 0.0, 0.0),
  215. vec3(0.0, scale_y, 0.0),
  216. vec3(0.0, 0.0, 1.0),
  217. );
  218. let center_latlon = map_view.center.to_latlon_rad();
  219. let rot_mat_x: Matrix3<f64> = {
  220. let alpha = center_latlon.lon + (PI * 0.5);
  221. let cosa = alpha.cos();
  222. let sina = alpha.sin();
  223. Matrix3::from_cols(
  224. vec3(cosa, 0.0, -sina),
  225. vec3(0.0, 1.0, 0.0),
  226. vec3(sina, 0.0, cosa),
  227. )
  228. };
  229. let rot_mat_y: Matrix3<f64> = {
  230. let alpha = -center_latlon.lat;
  231. let cosa = alpha.cos();
  232. let sina = alpha.sin();
  233. Matrix3::from_cols(
  234. vec3(1.0, 0.0, 0.0),
  235. vec3(0.0, cosa, sina),
  236. vec3(0.0, -sina, cosa),
  237. )
  238. };
  239. Transform::<Point3<f64>>::concat(
  240. &scale_mat,
  241. &Transform::<Point3<f64>>::concat(&rot_mat_y, &rot_mat_x)
  242. )
  243. }
  244. // Returns the inverse rotation matrix of the given view.
  245. pub fn inv_rotation_matrix(map_view: &MapView) -> Matrix3<f64> {
  246. let center_latlon = map_view.center.to_latlon_rad();
  247. let rot_mat_x: Matrix3<f64> = {
  248. let alpha = -center_latlon.lon - (PI * 0.5);
  249. let cosa = alpha.cos();
  250. let sina = alpha.sin();
  251. Matrix3::from_cols(
  252. vec3(cosa, 0.0, -sina),
  253. vec3(0.0, 1.0, 0.0),
  254. vec3(sina, 0.0, cosa),
  255. )
  256. };
  257. let rot_mat_y: Matrix3<f64> = {
  258. let alpha = center_latlon.lat;
  259. let cosa = alpha.cos();
  260. let sina = alpha.sin();
  261. Matrix3::from_cols(
  262. vec3(1.0, 0.0, 0.0),
  263. vec3(0.0, cosa, sina),
  264. vec3(0.0, -sina, cosa),
  265. )
  266. };
  267. Transform::<Point3<f64>>::concat(&rot_mat_x, &rot_mat_y)
  268. }
  269. // Returns the coordinates of the location that is nearest to the given `ScreenCoord`.
  270. pub fn screen_coord_to_sphere_point(map_view: &MapView, screen_coord: ScreenCoord) -> Point3<f64> {
  271. // Point on unit sphere
  272. let sphere_point = {
  273. let recip_radius = 2.0 * Self::diameter_physical_pixels(map_view).recip();
  274. let sx = (screen_coord.x - map_view.width * 0.5) * recip_radius;
  275. let sy = (screen_coord.y - map_view.height * 0.5) * -recip_radius;
  276. let t = 1.0 - sx * sx - sy * sy;
  277. if t >= 0.0 {
  278. // screen_coord is on the sphere
  279. Point3::new(
  280. sx,
  281. sy,
  282. -(t.sqrt()),
  283. )
  284. } else {
  285. // screen_coord is outside of sphere -> pick nearest.
  286. let scale = sx.hypot(sy).recip();
  287. Point3::new(
  288. sx * scale,
  289. sy * scale,
  290. 0.0,
  291. )
  292. }
  293. };
  294. // Rotate
  295. let inv_trans = Self::inv_rotation_matrix(map_view);
  296. inv_trans.transform_point(sphere_point)
  297. }
  298. // Returns the coordinates of the location that is nearest to the given `ScreenCoord`.
  299. pub fn screen_coord_to_latlonrad(map_view: &MapView, screen_coord: ScreenCoord) -> LatLonRad {
  300. let p = Self::screen_coord_to_sphere_point(map_view, screen_coord);
  301. // Transform to latitude, longitude
  302. LatLonRad::new(p.y.asin(), p.z.atan2(p.x))
  303. }
  304. /// Change zoom value by `zoom_delta` and zoom to a position given in screen coordinates.
  305. pub fn zoom_at(map_view: &mut MapView, pos: ScreenCoord, zoom_delta: f64) {
  306. //TODO Do something sophisticated: Increase zoom and rotate slightly so that the given
  307. // ScreenCoord points to the same geographical location.
  308. /*
  309. let latlon = Self::screen_coord_to_latlonrad(map_view, pos);
  310. let delta_x = pos.x - map_view.width * 0.5;
  311. let delta_y = pos.y - map_view.height * 0.5;
  312. map_view.center = latlon.into();
  313. */
  314. map_view.zoom += zoom_delta;
  315. }
  316. /// Change zoom value by `zoom_delta` and zoom to a position given in screen coordinates.
  317. pub fn set_zoom_at(map_view: &mut MapView, pos: ScreenCoord, zoom: f64) {
  318. //TODO Do something sophisticated, just like with Self::zoom_at
  319. map_view.zoom = zoom;
  320. }
  321. }
  322. #[cfg(test)]
  323. mod tests {
  324. use orthografic_view::*;
  325. #[test]
  326. fn tilecoord_neighbors() {
  327. let mut result = vec![];
  328. tile_neighbors(TileCoord::new(0, 0, 0), &mut result);
  329. assert!(result.is_empty());
  330. tile_neighbors(TileCoord::new(0, 0, -1), &mut result);
  331. assert!(result.is_empty());
  332. tile_neighbors(TileCoord::new(3, 0, -1), &mut result);
  333. assert!(result.is_empty());
  334. tile_neighbors(TileCoord::new(1, 0, 0), &mut result);
  335. assert_eq!(result.len(), 2);
  336. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(1, 1, 0))).is_some());
  337. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(1, 0, 1))).is_some());
  338. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(1, 1, 1))).is_none());
  339. tile_neighbors(TileCoord::new(2, 0, 0), &mut result);
  340. assert_eq!(result.len(), 4);
  341. assert!(result.iter().find(|&&x| x == TileNeighbor::NorthPole).is_some());
  342. tile_neighbors(TileCoord::new(2, 0, 3), &mut result);
  343. assert_eq!(result.len(), 4);
  344. assert!(result.iter().find(|&&x| x == TileNeighbor::SouthPole).is_some());
  345. tile_neighbors(TileCoord::new(2, 3, 1), &mut result);
  346. assert_eq!(result.len(), 4);
  347. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 2, 1))).is_some());
  348. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 0, 1))).is_some());
  349. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 3, 0))).is_some());
  350. assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 3, 2))).is_some());
  351. }
  352. }