diff --git a/README.md b/README.md index b8bf3fe..f266e47 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,12 @@ use approx::assert_relative_eq; let longitude = -3.7038; let latitude = 40.4168; let resolution = 10_u8; -let qb = Cell::from_point(longitude, latitude, resolution); +let qb = Cell::from_point(latitude, longitude, resolution); assert_eq!(qb, Cell::new(5234261499580514303_u64)); // Get a point from a Quadbin cell let coords = Cell::new(5209574053332910079_u64).to_point(); -assert_eq!(coords, (33.75, -11.178401873711776)); +assert_eq!(coords, (-11.178401873711776, 33.75)); // Quadbin resolution at equator in m² let area = Cell::from_point(0.0, 0.0, 26).area_m2(); diff --git a/src/cells.rs b/src/cells.rs index abe10ed..2ea13b5 100644 --- a/src/cells.rs +++ b/src/cells.rs @@ -49,7 +49,7 @@ pub(crate) fn tile_to_cell(tile: Tile) -> Cell { } /// Convert Quadbin cell into a tile -pub(crate) fn cell_to_tile(cell: Cell) -> Tile { +pub(crate) fn cell_to_tile(cell: &Cell) -> Tile { assert!(cell.is_valid(), "Quadbin cell index is not valid"); let cell64 = cell.get(); @@ -83,38 +83,38 @@ pub(crate) fn cell_to_tile(cell: Cell) -> Tile { } /// Convert a geographic point into a cell. -pub(crate) fn point_to_cell(longitude: f64, latitude: f64, resolution: u8) -> Cell { - let long = clip_longitude(longitude); - let lat = clip_latitude(latitude); +pub(crate) fn point_to_cell(lat: f64, lng: f64, res: u8) -> Cell { + let lng = clip_longitude(lng); + let lat = clip_latitude(lat); - let tile = Tile::from_point(long, lat, resolution); + let tile = Tile::from_point(lat, lng, res); tile.to_cell() } /// Convert cell into point -pub(crate) fn cell_to_point(cell: Cell) -> (f64, f64) { +pub(crate) fn cell_to_point(cell: &Cell) -> (f64, f64) { assert!(cell.is_valid(), "Quadbin cell index is not valid"); let tile = cell.to_tile(); let lat = tile.to_latitude(0.5); let lon = tile.to_longitude(0.5); - (lon, lat) + (lat, lon) } /// Compute the parent cell for a specific resolution. -pub(crate) fn cell_to_parent(cell: Cell, parent_resolution: u8) -> Cell { +pub(crate) fn cell_to_parent(cell: &Cell, parent_res: u8) -> Cell { // Check resolution let resolution = cell.resolution(); assert!( - parent_resolution < resolution, + parent_res < resolution, "parent resolution should be greater than current resolution" ); let result = (cell.get() & !(0x1F << 52)) - | ((parent_resolution as u64) << 52) - | (FOOTER >> ((parent_resolution as u64) << 1)); + | ((parent_res as u64) << 52) + | (FOOTER >> ((parent_res as u64) << 1)); Cell::new(result) } diff --git a/src/lib.rs b/src/lib.rs index f86cfdd..0240015 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ mod constants; pub mod utils; mod types; -pub use crate::types::{Cell, Tile}; +pub use crate::types::Cell; #[cfg(test)] mod test; diff --git a/src/test/cells.rs b/src/test/cells.rs index 449ca6d..09ddfe8 100644 --- a/src/test/cells.rs +++ b/src/test/cells.rs @@ -70,7 +70,7 @@ fn test_point_to_cell() { ]; for (x, y, res, cell) in cases.iter() { - assert_eq!(Cell::from_point(*x, *y, *res), Cell::new(*cell)); + assert_eq!(Cell::from_point(*y, *x, *res), Cell::new(*cell)); } } @@ -79,15 +79,19 @@ fn test_point_to_cell() { fn test_cell_to_point() { assert_eq!( Cell::new(5209574053332910079_u64).to_point(), - (33.75, -11.178401873711776) - ) + (-11.178401873711776, 33.75) + ); + + let coords = Cell::new(5309133744805926483_u64).to_point(); + assert_relative_eq!(coords.0, -41.28303708488909, epsilon = 1e-6); + assert_relative_eq!(coords.1, 174.77727502584457, epsilon = 1e-6) } // Get cell resolution #[test] fn test_get_cell_resolution() { let qb_cell = Cell::new(5209574053332910079_u64); - assert_eq!(Cell::resolution(qb_cell), 4_u8) + assert_eq!(qb_cell.resolution(), 4_u8) } // Get parent cell diff --git a/src/test/hashing.rs b/src/test/hashing.rs index 42d22ca..149aeb5 100644 --- a/src/test/hashing.rs +++ b/src/test/hashing.rs @@ -16,7 +16,7 @@ fn test_tile_hashing() { for (tile, hash) in cases.iter() { // Tile to hash - assert_eq!(Tile::to_hash(*tile), *hash); + assert_eq!(Tile::to_hash(&tile), *hash); // Hash to tile assert_eq!(Tile::from_hash(*hash), *tile); } @@ -31,6 +31,6 @@ fn test_point_hashing() { ]; for (coords, res, hash) in cases.iter() { - assert_eq!(point_cover(coords.0, coords.1, *res), *hash); + assert_eq!(point_cover(coords.1, coords.0, *res), *hash); } } diff --git a/src/test/utils.rs b/src/test/utils.rs index 5de793a..3b0638a 100644 --- a/src/test/utils.rs +++ b/src/test/utils.rs @@ -9,7 +9,7 @@ const ACC: f64 = 1e-10; // See https://github.com/CartoDB/quadbin-py/blob/master/tests/unit/test_utils.py #[test] fn test_point_to_tile_fraction() { - let tile = point_to_tile_fraction(-95.93965530395508_f64, 41.26000108568697_f64, 9_u8); + let tile = point_to_tile_fraction(41.26000108568697_f64, -95.93965530395508_f64, 9_u8); assert_relative_eq!(tile.0, 119.552490234375_f64, epsilon = ACC); assert_relative_eq!(tile.1, 191.47119140625_f64, epsilon = ACC); assert_eq!(tile.2, 9_u8); @@ -18,15 +18,15 @@ fn test_point_to_tile_fraction() { #[test] fn test_point_to_tile() { // X axis - assert_eq!(Tile::from_point(-180.0, 0.0, 0), Tile::new(0, 0, 0)); - assert_eq!(Tile::from_point(-180.0, 85.0, 2), Tile::new(0, 0, 2)); - assert_eq!(Tile::from_point(180.0, 85.0, 2), Tile::new(0, 0, 2)); - assert_eq!(Tile::from_point(-185.0, 85.0, 2), Tile::new(3, 0, 2)); - assert_eq!(Tile::from_point(185.0, 85.0, 2), Tile::new(0, 0, 2)); + assert_eq!(Tile::from_point(0.0, -180.0, 0), Tile::new(0, 0, 0)); + assert_eq!(Tile::from_point(85.0, -180.0, 2), Tile::new(0, 0, 2)); + assert_eq!(Tile::from_point(85.0, 180.0, 2), Tile::new(0, 0, 2)); + assert_eq!(Tile::from_point(85.0, -185.0, 2), Tile::new(3, 0, 2)); + assert_eq!(Tile::from_point(85.0, 185.0, 2), Tile::new(0, 0, 2)); // Y-axis - assert_eq!(Tile::from_point(-175.0, -95.0, 2), Tile::new(0, 3, 2)); - assert_eq!(Tile::from_point(-175.0, 95.0, 2), Tile::new(0, 0, 2)); + assert_eq!(Tile::from_point(-95.0, -175.0, 2), Tile::new(0, 3, 2)); + assert_eq!(Tile::from_point(95.0, -175.0, 2), Tile::new(0, 0, 2)); } // Estimate tile's area @@ -60,7 +60,7 @@ fn test_tile_conversion() { let lon = -45.0_f64; let lat = 45.0_f64; - let tile = Tile::from_point(lon, lat, 10); + let tile = Tile::from_point(lat, lon, 10); // Check Tile conversion assert_eq!(tile.x, 384_u32); @@ -85,17 +85,17 @@ fn test_tile_conversion() { #[test] fn test_tile_scalefactor() { assert_relative_eq!( - tile_scalefactor(Tile::new(384, 368, 10)), + tile_scalefactor(&Tile::new(384, 368, 10)), 0.7075410884638627_f64, epsilon = ACC ); assert_relative_eq!( - tile_scalefactor(Tile::new(384, 368, 26)), + tile_scalefactor(&Tile::new(384, 368, 26)), 0.08626970361752928_f64, epsilon = ACC ); assert_relative_eq!( - tile_scalefactor(Tile::new(100, 100, 10)), + tile_scalefactor(&Tile::new(100, 100, 10)), 0.15910754230624527_f64, epsilon = ACC ); diff --git a/src/types.rs b/src/types.rs index 252594c..72a39d9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,8 +3,10 @@ use crate::utils::*; use core::num::NonZeroU64; /// A single tile coordinates +/// +/// _Internal struct_ #[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct Tile { +pub(crate) struct Tile { pub x: u32, pub y: u32, pub z: u8, @@ -12,12 +14,6 @@ pub struct Tile { impl Tile { /// Create a new tile. - /// - /// # Examples - /// - /// ``` - /// let tile = quadbin::Tile::new(8108, 14336, 14); - /// ``` pub fn new(x: u32, y: u32, z: u8) -> Tile { Tile { x, y, z } } @@ -28,32 +24,12 @@ impl Tile { } /// Compute the tile for a longitude and latitude in a specific resolution. - /// - /// # Examples - /// ``` - /// use quadbin::Tile; - /// // Create a tile from geographic coordinates: - /// let tile = Tile::from_point(-175.0, 95.0, 2); - /// assert_eq!(tile, Tile::new(0, 0, 2)); - /// ``` - pub fn from_point(longitude: f64, latitude: f64, resolution: u8) -> Self { - point_to_tile(longitude, latitude, resolution) + pub fn from_point(lat: f64, lng: f64, res: u8) -> Self { + point_to_tile(lat, lng, res) } /// Approximate tile area in square meters. - /// - /// # Examples - /// ``` - /// use quadbin::Tile; - /// use approx::assert_relative_eq; - /// - /// // Create new tile - /// let tile = Tile::new(8108, 14336, 14); - /// // Estimate tile's area in m2 - /// let area = Tile::area(tile); - /// assert_relative_eq!(area, 210619.87609208928_f64, epsilon = 1e-10); - /// ``` - pub fn area(self) -> f64 { + pub fn area(&self) -> f64 { tile_area(self) } @@ -61,18 +37,7 @@ impl Tile { /// /// See also [Tile::to_longitude]. /// - /// # Examples - /// ``` - /// use quadbin::Tile; - /// use approx::assert_relative_eq; - /// - /// // Create new tile - /// let tile = Tile::new(8108, 14336, 14); - /// // Retrieve tile's latitude - /// let lat = tile.to_latitude(0.0); - /// assert_relative_eq!(lat, -79.17133464081944_f64, epsilon = 1e-10); - /// ``` - pub fn to_latitude(self, offset: f64) -> f64 { + pub fn to_latitude(&self, offset: f64) -> f64 { tile_to_latitude(self, offset) } @@ -80,30 +45,19 @@ impl Tile { /// /// See also [Tile::to_latitude]. /// - /// # Examples - /// ``` - /// use quadbin::Tile; - /// use approx::assert_relative_eq; - /// - /// // Create new tile - /// let tile = Tile::new(8108, 14336, 14); - /// // Retrieve tile's latitude - /// let lat = tile.to_longitude(0.0); - /// assert_relative_eq!(lat, -1.845703125_f64, epsilon = 1e-10); - /// ``` - pub fn to_longitude(self, offset: f64) -> f64 { + pub fn to_longitude(&self, offset: f64) -> f64 { tile_to_longitude(self, offset) } /// Get tile's siblings. // TODO: // Add examples. See how to properly document direction - pub fn get_sibling(self, direction: u8) -> Option { + pub fn get_sibling(&self, direction: u8) -> Option { tile_sibling(self, direction) } /// Compute a hash from the tile. - pub fn to_hash(self) -> u64 { + pub fn to_hash(&self) -> u64 { to_tile_hash(self) } @@ -164,7 +118,7 @@ impl Cell { /// let qb_cell = quadbin::Cell::new(5234261499580514303); /// assert_eq!(qb_cell.is_valid(), true) /// ``` - pub fn is_valid(self) -> bool { + pub fn is_valid(&self) -> bool { is_valid_cell(self.get()) } @@ -176,7 +130,7 @@ impl Cell { /// let res = qb_cell.resolution(); /// assert_eq!(res, 10) /// ``` - pub fn resolution(self) -> u8 { + pub fn resolution(&self) -> u8 { ((self.0.get() >> 52) & 0x1F) as u8 } @@ -188,8 +142,8 @@ impl Cell { /// let parent = qb_cell.parent(2_u8); /// assert_eq!(parent, quadbin::Cell::new(5200813144682790911)) /// ``` - pub fn parent(self, parent_resolution: u8) -> Cell { - cell_to_parent(self, parent_resolution) + pub fn parent(&self, parent_res: u8) -> Cell { + cell_to_parent(self, parent_res) } /// Computes the area of this Quadbin cell, in m². @@ -204,7 +158,7 @@ impl Cell { /// assert_relative_eq!(area, 888546364.7859862, epsilon = 1e-6) /// /// ``` - pub fn area_m2(self) -> f64 { + pub fn area_m2(&self) -> f64 { self.to_tile().area() } @@ -220,22 +174,32 @@ impl Cell { /// assert_relative_eq!(area, 888.5463647859862, epsilon = 1e-6) /// /// ``` - pub fn area_km2(self) -> f64 { + pub fn area_km2(&self) -> f64 { self.area_m2() / 1_000_000_f64 } /// Convert a Quadbin cell into geographic point. - pub fn to_point(self) -> (f64, f64) { + /// + /// Returns a tuple with latitude and longitude in degrees. + /// + pub fn to_point(&self) -> (f64, f64) { cell_to_point(self) } /// Convert a geographic point into a Quadbin cell. - pub fn from_point(longitude: f64, latitude: f64, resolution: u8) -> Cell { - point_to_cell(longitude, latitude, resolution) + /// + /// # Example + /// + /// ``` + /// let cell = quadbin::Cell::from_point(-41.28303675124842, 174.77727344223067, 26); + /// assert_eq!(cell.get(), 5309133744805926483_u64) + /// ``` + pub fn from_point(lat: f64, lng: f64, res: u8) -> Cell { + point_to_cell(lat, lng, res) } /// Convert a Quadbin cell into a tile. - pub fn to_tile(self) -> Tile { + pub(crate) fn to_tile(&self) -> Tile { cell_to_tile(self) } } diff --git a/src/utils.rs b/src/utils.rs index 2b3b06e..4649016 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,32 +8,28 @@ pub(crate) fn clip_number(num: f64, lower: f64, upper: f64) -> f64 { } /// Limit longitude bounds. -pub(crate) fn clip_longitude(longitude: f64) -> f64 { - clip_number(longitude, MIN_LONGITUDE, MAX_LONGITUDE) +pub(crate) fn clip_longitude(lng: f64) -> f64 { + clip_number(lng, MIN_LONGITUDE, MAX_LONGITUDE) } /// Limit latitude bounds. -pub(crate) fn clip_latitude(latitude: f64) -> f64 { - clip_number(latitude, MIN_LATITUDE, MAX_LATITUDE) +pub(crate) fn clip_latitude(lat: f64) -> f64 { + clip_number(lat, MIN_LATITUDE, MAX_LATITUDE) } /// Compute the tile in fractions for a longitude and latitude in a /// specific resolution. -pub(crate) fn point_to_tile_fraction( - longitude: f64, - latitude: f64, - resolution: u8, -) -> (f64, f64, u8) { +pub(crate) fn point_to_tile_fraction(lat: f64, lng: f64, res: u8) -> (f64, f64, u8) { // Check resolution to avoid overflow assert!( - (resolution <= MAX_RESOLUTION), + (res <= MAX_RESOLUTION), "Resolution should be between 0 and 26" ); // Compute tile coordinates - let z2: f64 = (1 << resolution) as f64; - let sinlat = f64::sin(latitude * PI / 180.0); - let x = z2 * (longitude / 360.0 + 0.5); + let z2: f64 = (1 << res) as f64; + let sinlat = f64::sin(lat * PI / 180.0); + let x = z2 * (lng / 360.0 + 0.5); let yfraction = 0.5 - 0.25 * ((1.0 + sinlat) / (1.0 - sinlat)).ln() / PI; let y = clip_number(z2 * yfraction, 0.0, z2 - 1.0); @@ -41,19 +37,19 @@ pub(crate) fn point_to_tile_fraction( let x = if x < 0.0 { x + z2 } else { x }; // Return the tile coordinates - (x, y, resolution) + (x, y, res) } /// Compute the tile for a longitude and latitude in a specific resolution. -pub(crate) fn point_to_tile(longitude: f64, latitude: f64, resolution: u8) -> Tile { - let (x, y, z) = point_to_tile_fraction(longitude, latitude, resolution); +pub(crate) fn point_to_tile(lat: f64, lng: f64, res: u8) -> Tile { + let (x, y, z) = point_to_tile_fraction(lat, lng, res); let x: u32 = x.floor() as u32; let y: u32 = y.floor() as u32; Tile::new(x, y, z) } /// Compute the latitude for a tile with an offset. -pub(crate) fn tile_to_latitude(tile: Tile, offset: f64) -> f64 { +pub(crate) fn tile_to_latitude(tile: &Tile, offset: f64) -> f64 { // Check if offset is between 0 and 1 assert!( (0.0..=1.0).contains(&offset), @@ -70,7 +66,7 @@ pub(crate) fn tile_to_latitude(tile: Tile, offset: f64) -> f64 { } /// Compute the longitude for a tile with an offset. -pub(crate) fn tile_to_longitude(tile: Tile, offset: f64) -> f64 { +pub(crate) fn tile_to_longitude(tile: &Tile, offset: f64) -> f64 { // Check if offset is between 0 and 1 assert!( (0.0..=1.0).contains(&offset), @@ -86,7 +82,7 @@ pub(crate) fn tile_to_longitude(tile: Tile, offset: f64) -> f64 { } /// Inverse of the scale factor at the tile center. -pub(crate) fn tile_scalefactor(tile: Tile) -> f64 { +pub(crate) fn tile_scalefactor(tile: &Tile) -> f64 { // Get Tile coords let y = tile.y as f64; let z2 = (1 << tile.z) as f64; @@ -97,9 +93,9 @@ pub(crate) fn tile_scalefactor(tile: Tile) -> f64 { } /// Approximate area of a tile in square meters. -pub(crate) fn tile_area(tile: Tile) -> f64 { +pub(crate) fn tile_area(tile: &Tile) -> f64 { // Get Tile coords - let x = tile.x; + let x = &tile.x; let y = tile.y as f64; let z = tile.z as usize; @@ -119,8 +115,8 @@ pub(crate) fn tile_area(tile: Tile) -> f64 { if y < center_y - 1.0 || y > center_y { let z_factor = |y_val: f64| -> f64 { // Create a new tile with the same x and z but different y - let temp_tile = Tile::new(x, y_val as u32, z as u8); - tile_scalefactor(temp_tile).powf(2.0) + let temp_tile = Tile::new(*x, y_val as u32, z as u8); + tile_scalefactor(&temp_tile).powf(2.0) }; area *= z_factor(y) / z_factor(center_y); @@ -130,7 +126,7 @@ pub(crate) fn tile_area(tile: Tile) -> f64 { } /// Compute the sibling (neighbour) tile in a specific direction. -pub(crate) fn tile_sibling(tile: Tile, direction: u8) -> Option { +pub(crate) fn tile_sibling(tile: &Tile, direction: u8) -> Option { // Early return for a low level == no neighbors // TODO: Think about what should one return instead of None if tile.z == 0_u8 { @@ -184,7 +180,7 @@ pub(crate) fn tile_sibling(tile: Tile, direction: u8) -> Option { } /// Compute a hash from the tile. -pub(crate) fn to_tile_hash(tile: Tile) -> u64 { +pub(crate) fn to_tile_hash(tile: &Tile) -> u64 { let x = tile.x as u64; let y = tile.y as u64; let z = tile.z as u64; @@ -211,6 +207,6 @@ pub(crate) fn from_tile_hash(tile_hash: u64) -> Tile { /// Return the tiles hashes that cover a point. /// /// _For internal use._ -pub fn point_cover(longitude: f64, latitude: f64, resolution: u8) -> u64 { - Tile::from_point(longitude, latitude, resolution).to_hash() +pub fn point_cover(lat: f64, lng: f64, res: u8) -> u64 { + Tile::from_point(lat, lng, res).to_hash() }