A simple map viewer

map_view.rs 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. use coord::{MapCoord, ScreenCoord, ScreenRect, TileCoord};
  2. /// A view of a tiled map with a rectangular viewport and a zoom.
  3. #[derive(Clone, Debug)]
  4. pub struct MapView {
  5. /// Width of the viewport.
  6. pub width: f64,
  7. /// Height of the viewport.
  8. pub height: f64,
  9. /// Size of each square tile in the same unit as the viewport dimensions (usually pixels).
  10. pub tile_size: u32,
  11. /// The `MapCoord` that corresponds to the center of the viewport.
  12. pub center: MapCoord,
  13. /// The zoom value. The zoom factor is given by 2.0.powf(zoom);
  14. pub zoom: f64,
  15. /// Tiles only exist for integer zoom values. The tile zoom value that is used for rendering
  16. /// is computed by the `tile_zoom` method. Increasing `tile_zoom_offset` increases the number
  17. /// of visible tiles for a given zoom value.
  18. pub tile_zoom_offset: f64,
  19. }
  20. /// The position and size of a specific tile on the screen.
  21. #[derive(Clone, Debug)]
  22. pub struct VisibleTile {
  23. pub tile: TileCoord,
  24. pub rect: ScreenRect,
  25. }
  26. impl MapView {
  27. /// Constructs a new `MapView`.
  28. pub fn new(width: f64, height: f64, tile_size: u32, center: MapCoord, zoom: f64) -> MapView {
  29. MapView {
  30. width,
  31. height,
  32. tile_size,
  33. center,
  34. zoom,
  35. tile_zoom_offset: 0.0,
  36. }
  37. }
  38. /// Constructs a new `MapView` centered at Null Island with an integer zoom that fills a screen
  39. /// with the given dimensions.
  40. pub fn with_filling_zoom(width: f64, height: f64, tile_size: u32) -> MapView {
  41. let min_dimension = width.min(height);
  42. let zoom = (min_dimension / f64::from(tile_size)).log2().ceil();
  43. MapView {
  44. width,
  45. height,
  46. tile_size,
  47. center: MapCoord::new(0.5, 0.5),
  48. zoom,
  49. tile_zoom_offset: 0.0,
  50. }
  51. }
  52. /// Returns the map coordinate that corresponds to the top-left corner of the viewport.
  53. pub fn top_left_coord(&self) -> MapCoord {
  54. let scale = f64::powf(2.0, -self.zoom) / f64::from(self.tile_size);
  55. let x = self.center.x + -0.5 * self.width * scale;
  56. let y = self.center.y + -0.5 * self.height * scale;
  57. MapCoord::new(x, y)
  58. }
  59. /// Returns the screen coordinate that corresponds to the given map coordinate.
  60. pub fn map_to_screen_coord(&self, map_coord: MapCoord) -> ScreenCoord {
  61. let scale = f64::powf(2.0, self.zoom) * f64::from(self.tile_size);
  62. let delta_x = map_coord.x - self.center.x;
  63. let delta_y = map_coord.y - self.center.y;
  64. ScreenCoord {
  65. x: 0.5 * self.width + delta_x * scale,
  66. y: 0.5 * self.height + delta_y * scale,
  67. }
  68. }
  69. /// Returns true if the viewport rectangle is fully inside the map.
  70. pub fn viewport_in_map(&self) -> bool {
  71. let scale = f64::powf(2.0, -self.zoom) / f64::from(self.tile_size);
  72. let y_top = self.center.y + -0.5 * self.height * scale;
  73. let y_bottom = self.center.y + 0.5 * self.height * scale;
  74. y_top >= 0.0 && y_bottom <= 1.0
  75. }
  76. /// Returns the screen coordinate of the top-left corner of a tile.
  77. pub fn tile_screen_position(&self, tile: &TileCoord) -> ScreenCoord {
  78. self.map_to_screen_coord(tile.map_coord_north_west())
  79. }
  80. /// Returns a `Vec` of all tiles that are visible in the current viewport.
  81. pub fn visible_tiles(&self, snap_to_pixel: bool) -> Vec<VisibleTile> {
  82. let uzoom = self.tile_zoom();
  83. let top_left_tile = self.top_left_coord().on_tile_at_zoom(uzoom);
  84. let mut top_left_tile_screen_coord = self.tile_screen_position(&top_left_tile);
  85. let tile_screen_size = f64::powf(2.0, self.zoom - f64::from(uzoom)) * f64::from(self.tile_size);
  86. if snap_to_pixel {
  87. // only snap to pixel if apparent zoom is integer
  88. let apparent_zoom = self.zoom + self.tile_zoom_offset;
  89. let apparent_zoom_int = (apparent_zoom + 0.5).floor();
  90. if (apparent_zoom - apparent_zoom_int).abs() < 1e-10 {
  91. top_left_tile_screen_coord.snap_to_pixel();
  92. }
  93. }
  94. let start_tile_x = top_left_tile.x;
  95. let start_tile_y = top_left_tile.y;
  96. let num_tiles_x = ((self.width - top_left_tile_screen_coord.x) / tile_screen_size).ceil().max(0.0) as i32;
  97. let num_tiles_y = ((self.height - top_left_tile_screen_coord.y) / tile_screen_size).ceil().max(0.0) as i32;
  98. let mut visible_tiles = Vec::with_capacity(num_tiles_x as usize * num_tiles_y as usize);
  99. for y in 0..num_tiles_y {
  100. for x in 0..num_tiles_x {
  101. let t = TileCoord::new(uzoom, start_tile_x + x, start_tile_y + y);
  102. if t.is_on_planet() {
  103. visible_tiles.push(
  104. VisibleTile {
  105. tile: t,
  106. rect: ScreenRect {
  107. x: top_left_tile_screen_coord.x + tile_screen_size * f64::from(x),
  108. y: top_left_tile_screen_coord.y + tile_screen_size * f64::from(y),
  109. width: tile_screen_size,
  110. height: tile_screen_size,
  111. }
  112. }
  113. );
  114. }
  115. }
  116. }
  117. visible_tiles
  118. }
  119. /// Returns the tile zoom value that is used for rendering with the current zoom.
  120. pub fn tile_zoom(&self) -> u32 {
  121. (self.zoom + self.tile_zoom_offset).floor().max(0.0) as u32
  122. }
  123. /// Returns the tile zoom offset.
  124. pub fn tile_zoom_offset(&self) -> f64 {
  125. self.tile_zoom_offset
  126. }
  127. /// Set the tile zoom offset.
  128. pub fn set_tile_zoom_offset(&mut self, offset: f64) {
  129. self.tile_zoom_offset = offset;
  130. }
  131. /// Set the viewport size.
  132. pub fn set_size(&mut self, width: f64, height: f64) {
  133. self.width = width;
  134. self.height = height;
  135. }
  136. /// Set the zoom value.
  137. pub fn set_zoom(&mut self, zoom: f64) {
  138. self.zoom = zoom;
  139. }
  140. /// Change zoom value by `zoom_delta`.
  141. pub fn zoom(&mut self, zoom_delta: f64) {
  142. self.zoom += zoom_delta;
  143. }
  144. /// Change zoom value by `zoom_delta` and zoom to a position given in screen coordinates.
  145. pub fn zoom_at(&mut self, pos: ScreenCoord, zoom_delta: f64) {
  146. let delta_x = pos.x - self.width * 0.5;
  147. let delta_y = pos.y - self.height * 0.5;
  148. let scale =
  149. (f64::powf(2.0, -self.zoom) - f64::powf(2.0, -self.zoom - zoom_delta))
  150. / f64::from(self.tile_size);
  151. self.zoom += zoom_delta;
  152. self.center.x += delta_x * scale;
  153. self.center.y += delta_y * scale;
  154. }
  155. /// Set a zoom value and zoom to a `position` given in screen coordinates.
  156. pub fn set_zoom_at(&mut self, pos: ScreenCoord, zoom: f64) {
  157. let delta_x = pos.x - self.width * 0.5;
  158. let delta_y = pos.y - self.height * 0.5;
  159. let scale = (f64::powf(2.0, -self.zoom) - f64::powf(2.0, -zoom)) / f64::from(self.tile_size);
  160. self.zoom = zoom;
  161. self.center.x += delta_x * scale;
  162. self.center.y += delta_y * scale;
  163. }
  164. /// Move the center of the viewport by (`delta_x`, `delta_y`) in screen coordinates.
  165. pub fn move_pixel(&mut self, delta_x: f64, delta_y: f64) {
  166. let scale = f64::powf(2.0, -self.zoom) / f64::from(self.tile_size);
  167. self.center.x += delta_x * scale;
  168. self.center.y += delta_y * scale;
  169. }
  170. }