-
-
Notifications
You must be signed in to change notification settings - Fork 221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix bbox_to_xyz #1070
Fix bbox_to_xyz #1070
Changes from all commits
ebe8303
d140f4e
2581bff
e33c787
1ba2d92
c3b4c42
ae8b98d
56846a8
7945f2c
2dfff7d
1bb848e
2d69b1a
2719626
0fd5e16
4bc205b
8bde903
8f9c619
93d9f27
a83ccd7
eea4a86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,18 +6,10 @@ | |
use std::f64::consts::PI; | ||
use std::fmt::Display; | ||
|
||
use tile_grid::{tms, Tms, Xyz}; | ||
|
||
pub const EARTH_CIRCUMFERENCE: f64 = 40_075_016.685_578_5; | ||
pub const EARTH_RADIUS: f64 = EARTH_CIRCUMFERENCE / 2.0 / PI; | ||
|
||
pub const MAX_ZOOM: u8 = 24; | ||
use std::sync::OnceLock; | ||
|
||
fn web_merc() -> &'static Tms { | ||
static TMS: OnceLock<Tms> = OnceLock::new(); | ||
TMS.get_or_init(|| tms().lookup("WebMercatorQuad").unwrap()) | ||
} | ||
pub const MAX_ZOOM: u8 = 30; | ||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub enum Format { | ||
|
@@ -196,37 +188,46 @@ impl Display for TileInfo { | |
|
||
/// Convert longitude and latitude to a tile (x,y) coordinates for a given zoom | ||
#[must_use] | ||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] | ||
pub fn tile_index(lon: f64, lat: f64, zoom: u8) -> (u32, u32) { | ||
assert!(zoom <= MAX_ZOOM, "zoom {zoom} must be <= {MAX_ZOOM}"); | ||
let tile = web_merc().tile(lon, lat, zoom).unwrap(); | ||
let max_value = (1_u64 << zoom) - 1; | ||
(tile.x.min(max_value) as u32, tile.y.min(max_value) as u32) | ||
#[allow(clippy::cast_possible_truncation)] | ||
#[allow(clippy::cast_sign_loss)] | ||
pub fn tile_index(lng: f64, lat: f64, zoom: u8) -> (u32, u32) { | ||
let tile_size = EARTH_CIRCUMFERENCE / f64::from(1_u32 << zoom); | ||
let (x, y) = wgs84_to_webmercator(lng, lat); | ||
let col = (((x - (EARTH_CIRCUMFERENCE * -0.5)).abs() / tile_size) as u32).min((1 << zoom) - 1); | ||
let row = ((((EARTH_CIRCUMFERENCE * 0.5) - y).abs() / tile_size) as u32).min((1 << zoom) - 1); | ||
(col, row) | ||
} | ||
|
||
/// Convert min/max XYZ tile coordinates to a bounding box values. | ||
/// The result is `[min_lng, min_lat, max_lng, max_lat]` | ||
#[must_use] | ||
pub fn xyz_to_bbox(zoom: u8, min_x: u32, min_y: u32, max_x: u32, max_y: u32) -> [f64; 4] { | ||
assert!(zoom <= MAX_ZOOM, "zoom {zoom} must be <= {MAX_ZOOM}"); | ||
assert!(min_x <= max_x, "min_x {min_x} must be <= max_x {max_x}"); | ||
assert!(min_y <= max_y, "min_y {min_y} must be <= max_y {max_y}"); | ||
let left_top_bounds = web_merc().xy_bounds(&Xyz::new(u64::from(min_x), u64::from(min_y), zoom)); | ||
let right_bottom_bounds = | ||
web_merc().xy_bounds(&Xyz::new(u64::from(max_x), u64::from(max_y), zoom)); | ||
let (min_lng, min_lat) = webmercator_to_wgs84(left_top_bounds.left, right_bottom_bounds.bottom); | ||
let (max_lng, max_lat) = webmercator_to_wgs84(right_bottom_bounds.right, left_top_bounds.top); | ||
|
||
let tile_length = EARTH_CIRCUMFERENCE / f64::from(1_u32 << zoom); | ||
|
||
let left_down_bbox = tile_bbox(min_x, max_y, tile_length); | ||
let right_top_bbox = tile_bbox(max_x, min_y, tile_length); | ||
|
||
let (min_lng, min_lat) = webmercator_to_wgs84(left_down_bbox[0], left_down_bbox[1]); | ||
let (max_lng, max_lat) = webmercator_to_wgs84(right_top_bbox[2], right_top_bbox[3]); | ||
[min_lng, min_lat, max_lng, max_lat] | ||
} | ||
|
||
#[allow(clippy::cast_lossless)] | ||
fn tile_bbox(x: u32, y: u32, tile_length: f64) -> [f64; 4] { | ||
let min_x = EARTH_CIRCUMFERENCE * -0.5 + x as f64 * tile_length; | ||
let max_y = EARTH_CIRCUMFERENCE * 0.5 - y as f64 * tile_length; | ||
|
||
[min_x, max_y - tile_length, min_x + tile_length, max_y] | ||
} | ||
|
||
/// Convert bounding box to a tile box `(min_x, min_y, max_x, max_y)` for a given zoom | ||
#[must_use] | ||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] | ||
pub fn bbox_to_xyz(left: f64, bottom: f64, right: f64, top: f64, zoom: u8) -> (u32, u32, u32, u32) { | ||
let (min_x, min_y) = tile_index(left, top, zoom); | ||
let (max_x, max_y) = tile_index(right, bottom, zoom); | ||
(min_x, min_y, max_x, max_y) | ||
let (min_col, min_row) = tile_index(left, top, zoom); | ||
let (max_col, max_row) = tile_index(right, bottom, zoom); | ||
(min_col, min_row, max_col, max_row) | ||
} | ||
|
||
/// Compute precision of a zoom level, i.e. how many decimal digits of the longitude and latitude are relevant | ||
|
@@ -250,13 +251,27 @@ pub fn webmercator_to_wgs84(x: f64, y: f64) -> (f64, f64) { | |
(lng, lat) | ||
} | ||
|
||
/// transform WGS84 to `WebMercator` | ||
// from https://github.com/Esri/arcgis-osm-editor/blob/e4b9905c264aa22f8eeb657efd52b12cdebea69a/src/OSMWeb10_1/Utils/WebMercator.cs | ||
#[must_use] | ||
pub fn wgs84_to_webmercator(lon: f64, lat: f64) -> (f64, f64) { | ||
let x = lon * PI / 180.0 * EARTH_RADIUS; | ||
|
||
let rad = lat * PI / 180.0; | ||
let sin = rad.sin(); | ||
let y = EARTH_RADIUS / 2.0 * ((1.0 + sin) / (1.0 - sin)).ln(); | ||
|
||
(x, y) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#![allow(clippy::unreadable_literal)] | ||
|
||
use std::fs::read; | ||
|
||
use approx::assert_relative_eq; | ||
use insta::assert_snapshot; | ||
use Encoding::{Internal, Uncompressed}; | ||
use Format::{Jpeg, Json, Png, Webp}; | ||
|
||
|
@@ -296,61 +311,75 @@ mod tests { | |
} | ||
|
||
#[test] | ||
fn test_tile_index() { | ||
fn test_tile_colrow() { | ||
assert_eq!((0, 0), tile_index(-180.0, 85.0511, 0)); | ||
} | ||
|
||
#[test] | ||
fn test_xyz_to_bbox() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Due to the inherent precision limitations of floating point numbers, performing back-and-forth transformations between XYZ and bounding box coordinates within a single unit test seems not a good idea? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or maybe we should reserve |
||
// you could easily get test cases from maptiler: https://www.maptiler.com/google-maps-coordinates-tile-bounds-projection/#4/-118.82/71.02 | ||
let bbox = xyz_to_bbox(0, 0, 0, 0, 0); | ||
assert_relative_eq!(bbox[0], -179.99999999999955, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[0], -180.0, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], -85.0511287798066, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[2], 179.99999999999986, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 85.05112877980655, epsilon = f64::EPSILON * 2.0); | ||
|
||
let xyz = bbox_to_xyz(bbox[0], bbox[1], bbox[2], bbox[3], 0); | ||
assert_eq!(xyz, (0, 0, 0, 0)); | ||
assert_relative_eq!(bbox[2], 180.0, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 85.0511287798066, epsilon = f64::EPSILON * 2.0); | ||
|
||
let bbox = xyz_to_bbox(1, 0, 0, 0, 0); | ||
assert_relative_eq!(bbox[0], -179.99999999999955, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], 2.007891127734306e-13, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!( | ||
bbox[2], | ||
-2.007891127734306e-13, | ||
epsilon = f64::EPSILON * 2.0 | ||
); | ||
assert_relative_eq!(bbox[3], 85.05112877980655, epsilon = f64::EPSILON * 2.0); | ||
|
||
let xyz = bbox_to_xyz(bbox[0], bbox[1], bbox[2], bbox[3], 1); | ||
assert!(xyz.0 == 0 || xyz.0 == 1); | ||
assert!(xyz.1 == 0); | ||
assert!(xyz.2 == 0 || xyz.2 == 1); | ||
assert!(xyz.3 == 0 || xyz.3 == 1); | ||
assert_relative_eq!(bbox[0], -180.0, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], 0.0, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[2], 0.0, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 85.0511287798066, epsilon = f64::EPSILON * 2.0); | ||
|
||
let bbox = xyz_to_bbox(5, 1, 1, 2, 2); | ||
assert_relative_eq!(bbox[0], -168.74999999999955, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], 81.09321385260832, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[2], -146.2499999999996, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 83.979259498862, epsilon = f64::EPSILON * 2.0); | ||
|
||
let xyz = bbox_to_xyz(bbox[0], bbox[1], bbox[2], bbox[3], 5); | ||
assert!(xyz.0 == 1 || xyz.0 == 2); | ||
assert!(xyz.1 == 0 || xyz.1 == 1); | ||
assert!(xyz.2 == 2 || xyz.2 == 3); | ||
assert!(xyz.3 == 2 || xyz.3 == 3); | ||
assert_relative_eq!(bbox[0], -168.75, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], 81.09321385260837, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[2], -146.25, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 83.97925949886205, epsilon = f64::EPSILON * 2.0); | ||
|
||
let bbox = xyz_to_bbox(5, 1, 3, 2, 5); | ||
assert_relative_eq!(bbox[0], -168.74999999999955, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], 74.01954331150218, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[2], -146.2499999999996, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 81.09321385260832, epsilon = f64::EPSILON * 2.0); | ||
|
||
let xyz = bbox_to_xyz(bbox[0], bbox[1], bbox[2], bbox[3], 5); | ||
assert!(xyz.0 == 1 || xyz.0 == 2); | ||
assert!(xyz.1 == 2 || xyz.1 == 3); | ||
assert!(xyz.2 == 2 || xyz.2 == 3); | ||
assert!(xyz.3 == 5 || xyz.3 == 6); | ||
assert_relative_eq!(bbox[0], -168.75, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[1], 74.01954331150226, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[2], -146.25, epsilon = f64::EPSILON * 2.0); | ||
assert_relative_eq!(bbox[3], 81.09321385260837, epsilon = f64::EPSILON * 2.0); | ||
} | ||
|
||
#[test] | ||
fn test_box_to_xyz() { | ||
fn tst(left: f64, bottom: f64, right: f64, top: f64, zoom: u8) -> String { | ||
let (x0, y0, x1, y1) = bbox_to_xyz(left, bottom, right, top, zoom); | ||
format!("({x0}, {y0}, {x1}, {y1})") | ||
} | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 0), @"(0, 0, 0, 0)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 1), @"(0, 1, 0, 1)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 2), @"(0, 3, 0, 3)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 3), @"(0, 7, 0, 7)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 4), @"(0, 14, 1, 15)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 5), @"(0, 29, 2, 31)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 6), @"(0, 58, 5, 63)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 7), @"(0, 116, 11, 126)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 8), @"(0, 233, 23, 253)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 9), @"(0, 466, 47, 507)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 10), @"(1, 933, 94, 1014)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 11), @"(3, 1866, 188, 2029)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 12), @"(6, 3732, 377, 4059)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 13), @"(12, 7465, 755, 8119)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 14), @"(25, 14931, 1510, 16239)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 15), @"(51, 29863, 3020, 32479)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 16), @"(102, 59727, 6041, 64958)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 17), @"(204, 119455, 12083, 129917)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 18), @"(409, 238911, 24166, 259834)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 19), @"(819, 477823, 48332, 519669)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 20), @"(1638, 955647, 96665, 1039339)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 21), @"(3276, 1911295, 193331, 2078678)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 22), @"(6553, 3822590, 386662, 4157356)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 23), @"(13107, 7645181, 773324, 8314713)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 24), @"(26214, 15290363, 1546649, 16629427)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 25), @"(52428, 30580726, 3093299, 33258855)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 26), @"(104857, 61161453, 6186598, 66517711)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 27), @"(209715, 122322907, 12373196, 133035423)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 28), @"(419430, 244645814, 24746393, 266070846)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 29), @"(838860, 489291628, 49492787, 532141692)"); | ||
assert_snapshot!(tst(-179.43749999999955,-84.76987877980656,-146.8124999999996,-81.37446385260833, 30), @"(1677721, 978583256, 98985574, 1064283385)"); | ||
} | ||
|
||
#[test] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's from esri.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this code is actually from the OSM wiki that has all this math