A simple map viewer

coord.rs 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. use std::f64::consts::{PI, FRAC_1_PI};
  2. use tile_source::TileSourceId;
  3. use cgmath::{Point3};
  4. /// A position in latitude, longitude.
  5. /// Values are in degrees and usually in these intervals:
  6. /// latitude: [-90.0, 90.0]
  7. /// longitude: [-180, 180.0]
  8. #[derive(Copy, Debug, PartialEq, Clone)]
  9. pub struct LatLonDeg {
  10. pub lat: f64,
  11. pub lon: f64,
  12. }
  13. impl LatLonDeg {
  14. pub fn new(lat: f64, lon: f64) -> Self {
  15. LatLonDeg { lat, lon }
  16. }
  17. pub fn to_radians(&self) -> LatLonRad {
  18. let f = PI / 180.0;
  19. LatLonRad {
  20. lat: self.lat * f,
  21. lon: self.lon * f,
  22. }
  23. }
  24. }
  25. /// A position in latitude, longitude.
  26. /// Values are in radians and usually in these intervals:
  27. /// latitude: [-0.5 * π, 0.5 * π]
  28. /// longitude: [-π, π]
  29. #[derive(Copy, Debug, PartialEq, Clone)]
  30. pub struct LatLonRad {
  31. pub lat: f64,
  32. pub lon: f64,
  33. }
  34. impl LatLonRad {
  35. pub fn new(lat: f64, lon: f64) -> Self {
  36. LatLonRad { lat, lon }
  37. }
  38. pub fn to_degrees(&self) -> LatLonDeg {
  39. let f = 180.0 * FRAC_1_PI;
  40. LatLonDeg {
  41. lat: self.lat * f,
  42. lon: self.lon * f,
  43. }
  44. }
  45. pub fn to_sphere_xyz(&self, radius: f64) -> SphereXYZ {
  46. SphereXYZ {
  47. x: radius * self.lat.cos() * self.lon.cos(),
  48. y: radius * self.lat.sin(),
  49. z: radius * self.lat.cos() * self.lon.sin(),
  50. }
  51. }
  52. pub fn to_sphere_point3(&self, radius: f64) -> Point3<f32> {
  53. let p = self.to_sphere_xyz(radius);
  54. Point3::new(
  55. p.x as f32,
  56. p.y as f32,
  57. p.z as f32,
  58. )
  59. }
  60. }
  61. #[derive(Copy, Debug, PartialEq, Clone)]
  62. pub struct SphereXYZ {
  63. pub x: f64,
  64. pub y: f64,
  65. pub z: f64,
  66. }
  67. impl SphereXYZ {
  68. pub fn new(x: f64, y: f64, z: f64) -> Self {
  69. SphereXYZ { x, y, z }
  70. }
  71. }
  72. /// A position in map coordinates.
  73. /// Valid values for x and y lie in the interval [0.0, 1.0].
  74. #[derive(Copy, Debug, PartialEq, Clone)]
  75. pub struct MapCoord {
  76. pub x: f64,
  77. pub y: f64,
  78. }
  79. impl From<LatLonDeg> for MapCoord
  80. {
  81. fn from(pos: LatLonDeg) -> MapCoord {
  82. let x = pos.lon * (1.0 / 360.0) + 0.5;
  83. let pi_lat = pos.lat * (PI / 180.0);
  84. let y = f64::ln(f64::tan(pi_lat) + 1.0 / f64::cos(pi_lat)) * (-0.5 * FRAC_1_PI) + 0.5;
  85. debug_assert!(y.is_finite());
  86. MapCoord { x, y }
  87. }
  88. }
  89. impl From<LatLonRad> for MapCoord
  90. {
  91. fn from(pos: LatLonRad) -> MapCoord {
  92. let x = pos.lon * (0.5 * FRAC_1_PI) + 0.5;
  93. let y = f64::ln(f64::tan(pos.lat) + 1.0 / f64::cos(pos.lat)) * (-0.5 * FRAC_1_PI) + 0.5;
  94. debug_assert!(y.is_finite());
  95. MapCoord { x, y }
  96. }
  97. }
  98. impl MapCoord {
  99. pub fn new(x: f64, y: f64) -> MapCoord {
  100. MapCoord { x, y }
  101. }
  102. //TODO differ between normalized and not normalized tiles
  103. pub fn on_tile_at_zoom(&self, zoom: u32) -> TileCoord {
  104. let zoom_factor = f64::powi(2.0, zoom as i32);
  105. let x = (self.x * zoom_factor).floor() as i32;
  106. let y = (self.y * zoom_factor).floor() as i32;
  107. TileCoord { zoom, x, y }
  108. }
  109. /// Wrap around in x-direction.
  110. /// Do not wrap around in y-direction. The poles don't touch.
  111. pub fn normalize_x(&mut self) {
  112. self.x = (self.x.fract() + 1.0).fract();
  113. }
  114. /// Wrap around in x-direction.
  115. /// Restrict y coordinates to interval [0.0, 1.0]
  116. pub fn normalize_xy(&mut self) {
  117. self.x = (self.x.fract() + 1.0).fract();
  118. self.y = 0.0f64.max(1.0f64.min(self.y));
  119. }
  120. pub fn to_latlon_rad(&self) -> LatLonRad {
  121. LatLonRad {
  122. lat: (PI - self.y * (2.0 * PI)).sinh().atan(),
  123. lon: self.x * (2.0 * PI) - PI,
  124. }
  125. }
  126. pub fn to_latlon_deg(&self) -> LatLonDeg {
  127. LatLonDeg {
  128. lat: (PI - self.y * (2.0 * PI)).sinh().atan() * (180.0 * FRAC_1_PI),
  129. lon: self.x * 360.0 - 180.0,
  130. }
  131. }
  132. }
  133. /// A position on the screen in pixels. Top-left corner is (0.0, 0.0).
  134. #[derive(Copy, Clone, Debug)]
  135. pub struct ScreenCoord {
  136. pub x: f64,
  137. pub y: f64,
  138. }
  139. impl ScreenCoord {
  140. pub fn new(x: f64, y: f64) -> Self {
  141. ScreenCoord { x, y }
  142. }
  143. pub fn snap_to_pixel(&mut self) {
  144. self.x = self.x.floor();
  145. self.y = self.y.floor();
  146. }
  147. pub fn is_inside(&self, rect: &ScreenRect) -> bool {
  148. self.x >= rect.x &&
  149. self.y >= rect.y &&
  150. self.x < rect.x + rect.width &&
  151. self.y < rect.y + rect.height
  152. }
  153. }
  154. /// A rectangle in screen coordinates.
  155. #[derive(Copy, Clone, Debug)]
  156. pub struct ScreenRect {
  157. pub x: f64,
  158. pub y: f64,
  159. pub width: f64,
  160. pub height: f64,
  161. }
  162. impl ScreenRect {
  163. pub fn subdivide(&self, sub_tile: &SubTileCoord) -> ScreenRect {
  164. let scale = 1.0 / f64::from(sub_tile.size);
  165. let w = self.width * scale;
  166. let h = self.height * scale;
  167. ScreenRect {
  168. x: self.x + f64::from(sub_tile.x) * w,
  169. y: self.y + f64::from(sub_tile.y) * h,
  170. width: w,
  171. height: h,
  172. }
  173. }
  174. }
  175. /// A rectangle in texture coordinates.
  176. /// Top-left corner is (0.0, 0.0).
  177. /// Bottom-right corner is (1.0, 1.0).
  178. #[derive(Copy, Clone, Debug)]
  179. pub struct TextureRect {
  180. pub x1: f64,
  181. pub y1: f64,
  182. pub x2: f64,
  183. pub y2: f64,
  184. }
  185. impl TextureRect {
  186. pub fn inset(self, margin_x: f64, margin_y: f64) -> TextureRect {
  187. TextureRect {
  188. x1: self.x1 + margin_x,
  189. y1: self.y1 + margin_y,
  190. x2: self.x2 - margin_x,
  191. y2: self.y2 - margin_y,
  192. }
  193. }
  194. pub fn subdivide(&self, sub_tile: &SubTileCoord) -> TextureRect {
  195. let scale = 1.0 / f64::from(sub_tile.size);
  196. let w = (self.x2 - self.x1) * scale;
  197. let h = (self.y2 - self.y1) * scale;
  198. TextureRect {
  199. x1: self.x1 + f64::from(sub_tile.x) * w,
  200. y1: self.y1 + f64::from(sub_tile.y) * h,
  201. x2: self.x1 + f64::from(sub_tile.x + 1) * w,
  202. y2: self.y1 + f64::from(sub_tile.y + 1) * h,
  203. }
  204. }
  205. }
  206. /// A subdivision of a tile. `x` and `y` are in the interval [0, `size` - 1].
  207. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
  208. pub struct SubTileCoord {
  209. pub size: u32,
  210. pub x: u32,
  211. pub y: u32,
  212. }
  213. impl SubTileCoord {
  214. pub fn subdivide(&self, other: &SubTileCoord) -> SubTileCoord {
  215. SubTileCoord {
  216. x: self.x * other.size + other.x,
  217. y: self.y * other.size + other.y,
  218. size: self.size * other.size,
  219. }
  220. }
  221. }
  222. /// A tile position in a tile pyramid.
  223. /// Each zoom level has 2<sup>zoom</sup> by 2<sup>zoom</sup> tiles.
  224. /// `x` and `y` are allowed to be negative or >= 2<sup>zoom</sup> but then they will not correspond to a tile
  225. /// and `is_on_planet` will return false.
  226. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
  227. pub struct TileCoord {
  228. pub zoom: u32,
  229. pub x: i32,
  230. pub y: i32,
  231. }
  232. impl TileCoord {
  233. pub fn new(zoom: u32, x: i32, y: i32) -> TileCoord {
  234. TileCoord {
  235. zoom,
  236. x: Self::normalize_coord(x, zoom),
  237. y,
  238. }
  239. }
  240. pub fn is_on_planet(&self) -> bool {
  241. let num_tiles = Self::get_zoom_level_tiles(self.zoom);
  242. self.y >= 0 && self.y < num_tiles &&
  243. self.x >= 0 && self.x < num_tiles
  244. }
  245. // Return the MapCoord of the top left corner of the current tile.
  246. pub fn map_coord_north_west(&self) -> MapCoord {
  247. let inv_zoom_factor = f64::powi(2.0, -(self.zoom as i32));
  248. MapCoord::new(f64::from(self.x) * inv_zoom_factor, f64::from(self.y) * inv_zoom_factor)
  249. }
  250. // Return the LatLonRad coordinate of the top left corner of the current tile.
  251. pub fn latlon_rad_north_west(&self) -> LatLonRad {
  252. let factor = f64::powi(2.0, -(self.zoom as i32)) * (2.0 * PI);
  253. if self.y == 0 {
  254. LatLonRad::new(
  255. 0.5 * PI,
  256. f64::from(self.x) * factor - PI,
  257. )
  258. } else if self.y == Self::get_zoom_level_tiles(self.zoom) {
  259. LatLonRad::new(
  260. -0.5 * PI,
  261. f64::from(self.x) * factor - PI,
  262. )
  263. } else {
  264. LatLonRad::new(
  265. (PI - f64::from(self.y) * factor).sinh().atan(),
  266. f64::from(self.x) * factor - PI,
  267. )
  268. }
  269. }
  270. // Return the LatLonRad coordinate of the bottom right corner of the current tile.
  271. pub fn latlon_rad_south_east(&self) -> LatLonRad {
  272. TileCoord { zoom: self.zoom, x: self.x + 1, y: self.y + 1 }.latlon_rad_north_west()
  273. }
  274. // Return the MapCoord of the center of the current tile.
  275. pub fn map_coord_center(&self) -> MapCoord {
  276. let inv_zoom_factor = f64::powi(2.0, -(self.zoom as i32));
  277. MapCoord::new(
  278. (f64::from(self.x) + 0.5) * inv_zoom_factor,
  279. (f64::from(self.y) + 0.5) * inv_zoom_factor,
  280. )
  281. }
  282. pub fn parent(&self, distance: u32) -> Option<(TileCoord, SubTileCoord)> {
  283. if distance > self.zoom {
  284. None
  285. } else {
  286. let scale = u32::pow(2, distance);
  287. Some((
  288. TileCoord {
  289. zoom: self.zoom - distance,
  290. x: self.x / scale as i32,
  291. y: self.y / scale as i32,
  292. },
  293. SubTileCoord {
  294. size: scale,
  295. x: (Self::normalize_coord(self.x, self.zoom) as u32) % scale,
  296. y: (Self::normalize_coord(self.y, self.zoom) as u32) % scale,
  297. },
  298. ))
  299. }
  300. }
  301. pub fn children_iter(&self, zoom_delta: u32) -> TileChildrenIter {
  302. let zoom_level_tiles = Self::get_zoom_level_tiles(zoom_delta);
  303. TileChildrenIter {
  304. zoom: self.zoom + zoom_delta,
  305. tile_base_x: self.x * zoom_level_tiles,
  306. tile_base_y: self.y * zoom_level_tiles,
  307. child_x: -1,
  308. child_y: 0,
  309. zoom_level_tiles,
  310. }
  311. }
  312. pub fn children(&self) -> [(TileCoord, SubTileCoord); 4] {
  313. [
  314. (
  315. TileCoord {
  316. zoom: self.zoom + 1,
  317. x: self.x * 2,
  318. y: self.y * 2,
  319. },
  320. SubTileCoord {
  321. size: 2,
  322. x: 0,
  323. y: 0,
  324. },
  325. ),
  326. (
  327. TileCoord {
  328. zoom: self.zoom + 1,
  329. x: self.x * 2 + 1,
  330. y: self.y * 2,
  331. },
  332. SubTileCoord {
  333. size: 2,
  334. x: 1,
  335. y: 0,
  336. },
  337. ),
  338. (
  339. TileCoord {
  340. zoom: self.zoom + 1,
  341. x: self.x * 2,
  342. y: self.y * 2 + 1,
  343. },
  344. SubTileCoord {
  345. size: 2,
  346. x: 0,
  347. y: 1,
  348. },
  349. ),
  350. (
  351. TileCoord {
  352. zoom: self.zoom + 1,
  353. x: self.x * 2 + 1,
  354. y: self.y * 2 + 1,
  355. },
  356. SubTileCoord {
  357. size: 2,
  358. x: 1,
  359. y: 1,
  360. },
  361. ),
  362. ]
  363. }
  364. #[inline]
  365. fn normalize_coord(coord: i32, zoom: u32) -> i32 {
  366. let max = Self::get_zoom_level_tiles(zoom);
  367. ((coord % max) + max) % max
  368. }
  369. /// Wrap around in x-direction.
  370. /// Values for y that are out-of-bounds "rotate" around the globe and also influence the
  371. /// x-coordinate.
  372. pub fn globe_norm(&self) -> Self {
  373. let max = Self::get_zoom_level_tiles(self.zoom);
  374. let period = max * 2;
  375. let yp = ((self.y % period) + period) % period;
  376. let side = yp / max;
  377. let x = self.x + side * (max / 2);
  378. let x = ((x % max) + max) % max;
  379. let y = (1 - side) * yp + side * (period - 1 - yp);
  380. TileCoord {
  381. zoom: self.zoom,
  382. x: x,
  383. y: y,
  384. }
  385. }
  386. #[inline]
  387. pub fn get_zoom_level_tiles(zoom: u32) -> i32 {
  388. //TODO throw error when zoom too big
  389. i32::pow(2, zoom)
  390. }
  391. pub fn to_quadkey(&self) -> Option<String> {
  392. if self.zoom == 0 || self.zoom > 30 || self.x < 0 || self.y < 0 {
  393. return None;
  394. }
  395. let mut quadkey = String::with_capacity(self.zoom as usize);
  396. let len = self.zoom;
  397. for i in (0..len).rev() {
  398. let mask: u32 = 1 << i;
  399. match ((self.x as u32 & mask) != 0, (self.y as u32 & mask) != 0) {
  400. (false, false) => quadkey.push('0'),
  401. (true, false) => quadkey.push('1'),
  402. (false, true) => quadkey.push('2'),
  403. (true, true) => quadkey.push('3'),
  404. }
  405. }
  406. Some(quadkey)
  407. }
  408. }
  409. pub struct TileChildrenIter {
  410. zoom: u32,
  411. tile_base_x: i32,
  412. tile_base_y: i32,
  413. child_x: i32,
  414. child_y: i32,
  415. zoom_level_tiles: i32,
  416. }
  417. impl Iterator for TileChildrenIter {
  418. type Item = (TileCoord, SubTileCoord);
  419. fn next(&mut self) -> Option<Self::Item> {
  420. self.child_x += 1;
  421. if self.child_x >= self.zoom_level_tiles {
  422. self.child_x = 0;
  423. self.child_y += 1;
  424. }
  425. if self.child_y >= self.zoom_level_tiles {
  426. return None;
  427. }
  428. Some((
  429. TileCoord {
  430. zoom: self.zoom,
  431. x: self.tile_base_x + self.child_x,
  432. y: self.tile_base_y + self.child_y,
  433. },
  434. SubTileCoord {
  435. size: self.zoom_level_tiles as u32,
  436. x: self.child_x as u32,
  437. y: self.child_y as u32,
  438. },
  439. ))
  440. }
  441. }
  442. //TODO include width and height of view rect to determine visibility
  443. #[derive(Copy, Clone, Debug, PartialEq)]
  444. pub struct View {
  445. pub source_id: TileSourceId,
  446. pub zoom: u32,
  447. pub center: MapCoord,
  448. }
  449. #[cfg(test)]
  450. mod tests {
  451. use coord::*;
  452. #[test]
  453. fn normalize_mapcoord() {
  454. {
  455. let a = MapCoord::new(0.0, 0.0);
  456. let mut b = a.clone();
  457. assert_eq!(a, b);
  458. b.normalize_x();
  459. assert_eq!(a, b);
  460. }
  461. {
  462. let mut a = MapCoord::new(1.0, 1.0);
  463. let b = MapCoord::new(0.0, 1.0);
  464. a.normalize_x();
  465. assert_eq!(a, b);
  466. }
  467. }
  468. #[test]
  469. fn quadkey() {
  470. assert_eq!(TileCoord::new(0, 0, 0).to_quadkey(), None);
  471. assert_eq!(TileCoord::new(1, 0, 0).to_quadkey(), Some("0".to_string()));
  472. assert_eq!(TileCoord::new(1, 1, 0).to_quadkey(), Some("1".to_string()));
  473. assert_eq!(TileCoord::new(1, 0, 1).to_quadkey(), Some("2".to_string()));
  474. assert_eq!(TileCoord::new(1, 1, 1).to_quadkey(), Some("3".to_string()));
  475. assert_eq!(TileCoord::new(3, 1, 0).to_quadkey(), Some("001".to_string()));
  476. assert_eq!(TileCoord::new(30, 0, 1).to_quadkey(), Some("000000000000000000000000000002".to_string()));
  477. }
  478. fn approx_eq(a: f64, b: f64) -> bool {
  479. (a - b).abs() < 1e-10
  480. }
  481. #[test]
  482. fn approx_eq_test() {
  483. assert!(approx_eq(1.0, 1.0));
  484. assert!(approx_eq(0.0, 0.0));
  485. assert!(approx_eq(0.0, -0.0));
  486. assert!(approx_eq(1e20, 1e20 + 1.0));
  487. assert!(approx_eq(1e20, 1e20 - 1.0));
  488. assert!(!approx_eq(1000.0, 1000.1));
  489. }
  490. #[test]
  491. fn degree_radians() {
  492. {
  493. let rad = LatLonDeg::new(0.0, 0.0).to_radians();
  494. assert!(approx_eq(rad.lat, 0.0));
  495. assert!(approx_eq(rad.lon, 0.0));
  496. let deg = rad.to_degrees();
  497. assert!(approx_eq(deg.lat, 0.0));
  498. assert!(approx_eq(deg.lon, 0.0));
  499. }
  500. {
  501. let rad = LatLonDeg::new(-45.0, 180.0).to_radians();
  502. assert!(approx_eq(rad.lat, -PI / 4.0));
  503. assert!(approx_eq(rad.lon, PI));
  504. let deg = rad.to_degrees();
  505. assert!(approx_eq(deg.lat, -45.0));
  506. assert!(approx_eq(deg.lon, 180.0));
  507. }
  508. {
  509. let mc = MapCoord::from(LatLonDeg::new(23.45, 123.45));
  510. let deg = mc.to_latlon_rad().to_degrees();
  511. assert!(approx_eq(deg.lat, 23.45));
  512. assert!(approx_eq(deg.lon, 123.45));
  513. }
  514. {
  515. let mc = MapCoord::from(LatLonRad::new(-0.345 * PI, -0.987 * PI));
  516. let rad = mc.to_latlon_deg().to_radians();
  517. assert!(approx_eq(rad.lat, -0.345 * PI));
  518. assert!(approx_eq(rad.lon, -0.987 * PI));
  519. }
  520. }
  521. #[test]
  522. fn tile_to_latlon() {
  523. // Test edge cases at the poles where the longitude is technically undefined.
  524. let t = TileCoord::new(0, 0, 0);
  525. let deg = t.latlon_rad_north_west();
  526. assert!(approx_eq(deg.lat, 0.5 * PI));
  527. assert!(approx_eq(deg.lon, -PI));
  528. let deg = t.latlon_rad_south_east();
  529. assert!(approx_eq(deg.lat, -0.5 * PI));
  530. assert!(approx_eq(deg.lon, PI));
  531. }
  532. #[test]
  533. fn tile_children() {
  534. let t = TileCoord::new(2, 1, 2);
  535. for (&(a1, a2), (b1, b2)) in t.children().iter().zip(t.children_iter(1)) {
  536. assert_eq!(a1, b1);
  537. assert_eq!(a2, b2);
  538. }
  539. assert_eq!(t.children_iter(0).count(), 1);
  540. assert_eq!(t.children_iter(0).next(), Some((t, SubTileCoord{ size: 1, x: 0, y: 0 })));
  541. assert_eq!(t.children_iter(1).count(), 4);
  542. assert_eq!(t.children_iter(2).count(), 16);
  543. }
  544. #[test]
  545. fn globe_norm() {
  546. assert_eq!(TileCoord::new(0, 0, 0).globe_norm(), TileCoord::new(0, 0, 0));
  547. assert_eq!(TileCoord::new(0, -1, 0).globe_norm(), TileCoord::new(0, 0, 0));
  548. assert_eq!(TileCoord::new(0, -1, -1).globe_norm(), TileCoord::new(0, 0, 0));
  549. assert_eq!(TileCoord::new(0, 0, 1).globe_norm(), TileCoord::new(0, 0, 0));
  550. assert_eq!(TileCoord::new(2, 0, 0).globe_norm(), TileCoord::new(2, 0, 0));
  551. assert_eq!(TileCoord::new(2, 0, 3).globe_norm(), TileCoord::new(2, 0, 3));
  552. assert_eq!(TileCoord::new(2, 0, 4).globe_norm(), TileCoord::new(2, 2, 3));
  553. assert_eq!(TileCoord::new(2, 0, 5).globe_norm(), TileCoord::new(2, 2, 2));
  554. assert_eq!(TileCoord::new(2, 0, 8).globe_norm(), TileCoord::new(2, 0, 0));
  555. assert_eq!(TileCoord::new(2, 3, 0).globe_norm(), TileCoord::new(2, 3, 0));
  556. assert_eq!(TileCoord::new(2, 3, 3).globe_norm(), TileCoord::new(2, 3, 3));
  557. assert_eq!(TileCoord::new(2, 3, 4).globe_norm(), TileCoord::new(2, 1, 3));
  558. assert_eq!(TileCoord::new(2, 3, 5).globe_norm(), TileCoord::new(2, 1, 2));
  559. assert_eq!(TileCoord::new(2, 3, 8).globe_norm(), TileCoord::new(2, 3, 0));
  560. assert_eq!(TileCoord::new(2, -1, 0).globe_norm(), TileCoord::new(2, 3, 0));
  561. assert_eq!(TileCoord::new(2, 0, -1).globe_norm(), TileCoord::new(2, 2, 0));
  562. assert_eq!(TileCoord::new(2, 0, -5).globe_norm(), TileCoord::new(2, 0, 3));
  563. }
  564. }