Struct for zoom level (#118)

* add struct for zoom level

* fix linter errors

* fix build errors

* fixed for PR comments

* Simplify stencil_reference_value, the z does not need to be taken into account

Co-authored-by: Maximilian Ammann <max@maxammann.org>
This commit is contained in:
Yaroslav Biletskyi 2022-06-01 14:26:58 +03:00 committed by GitHub
parent e59dacdb45
commit 05dbdac924
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 70 deletions

View File

@ -1,4 +1,4 @@
use crate::coords::{Zoom, TILE_SIZE}; use crate::coords::{Zoom, ZoomLevel, TILE_SIZE};
use crate::io::shared_thread_state::SharedThreadState; use crate::io::shared_thread_state::SharedThreadState;
use crate::io::tile_cache::TileCache; use crate::io::tile_cache::TileCache;
use crate::io::TessellateMessage; use crate::io::TessellateMessage;
@ -43,7 +43,7 @@ impl ViewState {
self.camera.calc_view_proj(&self.perspective) self.camera.calc_view_proj(&self.perspective)
} }
pub fn visible_level(&self) -> u8 { pub fn visible_level(&self) -> ZoomLevel {
self.zoom.level() self.zoom.level()
} }

View File

@ -33,12 +33,12 @@ const fn create_zoom_bounds<const DIM: usize>() -> [u32; DIM] {
/// ///
/// TODO: We can optimize the quadkey and store the keys on 2 bits instead of 8 /// TODO: We can optimize the quadkey and store the keys on 2 bits instead of 8
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Copy)] #[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Copy)]
pub struct Quadkey([u8; MAX_ZOOM]); pub struct Quadkey([ZoomLevel; MAX_ZOOM]);
impl Quadkey { impl Quadkey {
pub fn new(quad_encoded: &[u8]) -> Self { pub fn new(quad_encoded: &[ZoomLevel]) -> Self {
let mut key = [0u8; MAX_ZOOM]; let mut key = [ZoomLevel::default(); MAX_ZOOM];
key[0] = quad_encoded.len() as u8; key[0] = (quad_encoded.len() as u8).into();
for (i, part) in quad_encoded.iter().enumerate() { for (i, part) in quad_encoded.iter().enumerate() {
key[i + 1] = *part; key[i + 1] = *part;
} }
@ -48,7 +48,9 @@ impl Quadkey {
impl fmt::Debug for Quadkey { impl fmt::Debug for Quadkey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let len = self.0[0] as usize; let key = self.0;
let ZoomLevel(level) = key[0];
let len = level as usize;
for part in &self.0[0..len] { for part in &self.0[0..len] {
write!(f, "{:?}", part)?; write!(f, "{:?}", part)?;
} }
@ -56,6 +58,57 @@ impl fmt::Debug for Quadkey {
} }
} }
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone, Debug)]
pub struct ZoomLevel(u8);
impl ZoomLevel {
pub fn is_root(self) -> bool {
return self.0 == 0;
}
}
impl Default for ZoomLevel {
fn default() -> Self {
ZoomLevel(0)
}
}
impl std::ops::Add<u8> for ZoomLevel {
type Output = ZoomLevel;
fn add(self, rhs: u8) -> Self::Output {
let zoom_level = self.0.checked_add(rhs).unwrap();
ZoomLevel(zoom_level)
}
}
impl std::ops::Sub<u8> for ZoomLevel {
type Output = ZoomLevel;
fn sub(self, rhs: u8) -> Self::Output {
let zoom_level = self.0.checked_sub(rhs).unwrap();
ZoomLevel(zoom_level)
}
}
impl fmt::Display for ZoomLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u8> for ZoomLevel {
fn from(zoom_level: u8) -> Self {
ZoomLevel(zoom_level)
}
}
impl Into<u8> for ZoomLevel {
fn into(self) -> u8 {
self.0
}
}
/// `Zoom` is an exponential scale that defines the zoom of the camera on the map. /// `Zoom` is an exponential scale that defines the zoom of the camera on the map.
/// We can derive the `ZoomLevel` from `Zoom` by using the `[crate::coords::ZOOM_BOUNDS]`. /// We can derive the `ZoomLevel` from `Zoom` by using the `[crate::coords::ZOOM_BOUNDS]`.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -67,6 +120,12 @@ impl Zoom {
} }
} }
impl Zoom {
pub fn from(zoom_level: ZoomLevel) -> Self {
Zoom(zoom_level.0 as f64)
}
}
impl Default for Zoom { impl Default for Zoom {
fn default() -> Self { fn default() -> Self {
Zoom(0.0) Zoom(0.0)
@ -97,19 +156,19 @@ impl std::ops::Sub for Zoom {
impl Zoom { impl Zoom {
pub fn scale_to_tile(&self, coords: &WorldTileCoords) -> f64 { pub fn scale_to_tile(&self, coords: &WorldTileCoords) -> f64 {
2.0_f64.powf(coords.z as f64 - self.0) 2.0_f64.powf(coords.z.0 as f64 - self.0)
} }
pub fn scale_to_zoom_level(&self, z: u8) -> f64 { pub fn scale_to_zoom_level(&self, z: ZoomLevel) -> f64 {
2.0_f64.powf(z as f64 - self.0) 2.0_f64.powf(z.0 as f64 - self.0)
} }
pub fn scale_delta(&self, zoom: &Zoom) -> f64 { pub fn scale_delta(&self, zoom: &Zoom) -> f64 {
2.0_f64.powf(zoom.0 - self.0) 2.0_f64.powf(zoom.0 - self.0)
} }
pub fn level(&self) -> u8 { pub fn level(&self) -> ZoomLevel {
self.0.floor() as u8 ZoomLevel::from(self.0.floor() as u8)
} }
} }
@ -144,7 +203,7 @@ pub struct InnerCoords {
pub struct TileCoords { pub struct TileCoords {
pub x: u32, pub x: u32,
pub y: u32, pub y: u32,
pub z: u8, pub z: ZoomLevel,
} }
impl TileCoords { impl TileCoords {
@ -158,7 +217,7 @@ impl TileCoords {
pub fn into_world_tile(self, scheme: TileAddressingScheme) -> Option<WorldTileCoords> { pub fn into_world_tile(self, scheme: TileAddressingScheme) -> Option<WorldTileCoords> {
// FIXME: MAX_ZOOM is 32, which means max bound is 2^32, which wouldn't fit in u32 or i32 // FIXME: MAX_ZOOM is 32, which means max bound is 2^32, which wouldn't fit in u32 or i32
// Note that unlike WorldTileCoords, values are signed (no idea why) // Note that unlike WorldTileCoords, values are signed (no idea why)
let bounds = ZOOM_BOUNDS[self.z as usize] as i32; let bounds = ZOOM_BOUNDS[self.z.0 as usize] as i32;
let x = self.x as i32; let x = self.x as i32;
let y = self.y as i32; let y = self.y as i32;
@ -182,7 +241,7 @@ impl From<(u32, u32, u8)> for TileCoords {
TileCoords { TileCoords {
x: tuple.0, x: tuple.0,
y: tuple.1, y: tuple.1,
z: tuple.2, z: ZoomLevel::from(tuple.2),
} }
} }
} }
@ -198,7 +257,7 @@ impl From<(u32, u32, u8)> for TileCoords {
pub struct WorldTileCoords { pub struct WorldTileCoords {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
pub z: u8, pub z: ZoomLevel,
} }
impl WorldTileCoords { impl WorldTileCoords {
@ -211,7 +270,7 @@ impl WorldTileCoords {
/// `x=5,y=5` at zoom level `z=0`. /// `x=5,y=5` at zoom level `z=0`.
pub fn into_tile(self, scheme: TileAddressingScheme) -> Option<TileCoords> { pub fn into_tile(self, scheme: TileAddressingScheme) -> Option<TileCoords> {
// FIXME: MAX_ZOOM is 32, which means max bound is 2^32, which wouldn't fit in u32 or i32 // FIXME: MAX_ZOOM is 32, which means max bound is 2^32, which wouldn't fit in u32 or i32
let bounds = ZOOM_BOUNDS[self.z as usize]; let bounds = ZOOM_BOUNDS[self.z.0 as usize];
let x = self.x as u32; let x = self.x as u32;
let y = self.y as u32; let y = self.y as u32;
@ -239,7 +298,7 @@ impl WorldTileCoords {
If tile.z > zoom: If tile.z > zoom:
=> scale < 512 => scale < 512
*/ */
let tile_scale = TILE_SIZE * Zoom::new(self.z as f64).scale_delta(&zoom); let tile_scale = TILE_SIZE * Zoom::from(self.z).scale_delta(&zoom);
let translate = Matrix4::from_translation(Vector3::new( let translate = Matrix4::from_translation(Vector3::new(
self.x as f64 * tile_scale, self.x as f64 * tile_scale,
@ -264,7 +323,7 @@ impl WorldTileCoords {
/// Adopted from [tilebelt](https://github.com/mapbox/tilebelt) /// Adopted from [tilebelt](https://github.com/mapbox/tilebelt)
pub fn build_quad_key(&self) -> Option<Quadkey> { pub fn build_quad_key(&self) -> Option<Quadkey> {
let bounds = ZOOM_BOUNDS[self.z as usize]; let bounds = ZOOM_BOUNDS[self.z.0 as usize];
let x = self.x as u32; let x = self.x as u32;
let y = self.y as u32; let y = self.y as u32;
@ -272,11 +331,11 @@ impl WorldTileCoords {
return None; return None;
} }
let mut key = [0u8; MAX_ZOOM]; let mut key = [ZoomLevel::default(); MAX_ZOOM];
key[0] = self.z; key[0] = self.z;
for z in 1..self.z + 1 { for z in 1..self.z.0 + 1 {
let mut b = 0; let mut b = 0;
let mask: i32 = 1 << (z - 1); let mask: i32 = 1 << (z - 1);
if (self.x & mask) != 0 { if (self.x & mask) != 0 {
@ -285,7 +344,7 @@ impl WorldTileCoords {
if (self.y & mask) != 0 { if (self.y & mask) != 0 {
b += 2u8; b += 2u8;
} }
key[z as usize] = b; key[z as usize] = ZoomLevel::from(b);
} }
Some(Quadkey(key)) Some(Quadkey(key))
} }
@ -318,7 +377,7 @@ impl WorldTileCoords {
/// Get the tile which is one zoom level lower and contains this one /// Get the tile which is one zoom level lower and contains this one
pub fn get_parent(&self) -> Option<WorldTileCoords> { pub fn get_parent(&self) -> Option<WorldTileCoords> {
if self.z == 0 { if self.z.is_root() {
return None; return None;
} }
@ -330,8 +389,8 @@ impl WorldTileCoords {
} }
} }
impl From<(i32, i32, u8)> for WorldTileCoords { impl From<(i32, i32, ZoomLevel)> for WorldTileCoords {
fn from(tuple: (i32, i32, u8)) -> Self { fn from(tuple: (i32, i32, ZoomLevel)) -> Self {
WorldTileCoords { WorldTileCoords {
x: tuple.0, x: tuple.0,
y: tuple.1, y: tuple.1,
@ -402,7 +461,7 @@ impl WorldCoords {
Self { x, y } Self { x, y }
} }
pub fn into_world_tile(self, z: u8, zoom: Zoom) -> WorldTileCoords { pub fn into_world_tile(self, z: ZoomLevel, zoom: Zoom) -> WorldTileCoords {
let tile_scale = zoom.scale_to_zoom_level(z) / TILE_SIZE; // TODO: Deduplicate let tile_scale = zoom.scale_to_zoom_level(z) / TILE_SIZE; // TODO: Deduplicate
let x = self.x * tile_scale; let x = self.x * tile_scale;
let y = self.y * tile_scale; let y = self.y * tile_scale;
@ -447,12 +506,12 @@ impl From<Point3<f64>> for WorldCoords {
pub struct ViewRegion { pub struct ViewRegion {
min_tile: WorldTileCoords, min_tile: WorldTileCoords,
max_tile: WorldTileCoords, max_tile: WorldTileCoords,
z: u8, z: ZoomLevel,
padding: i32, padding: i32,
} }
impl ViewRegion { impl ViewRegion {
pub fn new(view_region: Aabb2<f64>, padding: i32, zoom: Zoom, z: u8) -> Self { pub fn new(view_region: Aabb2<f64>, padding: i32, zoom: Zoom, z: ZoomLevel) -> Self {
let min_world: WorldCoords = WorldCoords::at_ground(view_region.min.x, view_region.min.y); let min_world: WorldCoords = WorldCoords::at_ground(view_region.min.x, view_region.min.y);
let min_world_tile: WorldTileCoords = min_world.into_world_tile(z, zoom); let min_world_tile: WorldTileCoords = min_world.into_world_tile(z, zoom);
let max_world: WorldCoords = WorldCoords::at_ground(view_region.max.x, view_region.max.y); let max_world: WorldCoords = WorldCoords::at_ground(view_region.max.x, view_region.max.y);
@ -466,7 +525,7 @@ impl ViewRegion {
} }
} }
pub fn zoom_level(&self) -> u8 { pub fn zoom_level(&self) -> ZoomLevel {
self.z self.z
} }
@ -481,7 +540,7 @@ impl ViewRegion {
pub fn iter(&self) -> impl Iterator<Item = WorldTileCoords> + '_ { pub fn iter(&self) -> impl Iterator<Item = WorldTileCoords> + '_ {
(self.min_tile.x - self.padding..self.max_tile.x + 1 + self.padding).flat_map(move |x| { (self.min_tile.x - self.padding..self.max_tile.x + 1 + self.padding).flat_map(move |x| {
(self.min_tile.y - self.padding..self.max_tile.y + 1 + self.padding).map(move |y| { (self.min_tile.y - self.padding..self.max_tile.y + 1 + self.padding).map(move |y| {
let tile_coord: WorldTileCoords = (x, y, self.z as u8).into(); let tile_coord: WorldTileCoords = (x, y, self.z).into();
tile_coord tile_coord
}) })
}) })
@ -489,7 +548,7 @@ impl ViewRegion {
} }
impl fmt::Display for TileCoords { impl fmt::Display for TileCoords {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"T(x={x},y={y},z={z})", "T(x={x},y={y},z={z})",
@ -524,14 +583,14 @@ mod tests {
use crate::style::source::TileAddressingScheme; use crate::style::source::TileAddressingScheme;
use crate::coords::{ use crate::coords::{
Quadkey, TileCoords, ViewRegion, WorldCoords, WorldTileCoords, Zoom, EXTENT, Quadkey, TileCoords, ViewRegion, WorldCoords, WorldTileCoords, Zoom, ZoomLevel, EXTENT,
}; };
use crate::util::math::Aabb2; use crate::util::math::Aabb2;
const TOP_LEFT: Vector4<f64> = Vector4::new(0.0, 0.0, 0.0, 1.0); const TOP_LEFT: Vector4<f64> = Vector4::new(0.0, 0.0, 0.0, 1.0);
const BOTTOM_RIGHT: Vector4<f64> = Vector4::new(EXTENT, EXTENT, 0.0, 1.0); const BOTTOM_RIGHT: Vector4<f64> = Vector4::new(EXTENT, EXTENT, 0.0, 1.0);
fn to_from_world(tile: (i32, i32, u8), zoom: Zoom) { fn to_from_world(tile: (i32, i32, ZoomLevel), zoom: Zoom) {
let tile = WorldTileCoords::from(tile); let tile = WorldTileCoords::from(tile);
let p1 = tile.transform_for_zoom(zoom) * TOP_LEFT; let p1 = tile.transform_for_zoom(zoom) * TOP_LEFT;
let p2 = tile.transform_for_zoom(zoom) * BOTTOM_RIGHT; let p2 = tile.transform_for_zoom(zoom) * BOTTOM_RIGHT;
@ -545,40 +604,56 @@ mod tests {
#[test] #[test]
fn world_coords_tests() { fn world_coords_tests() {
to_from_world((1, 0, 1), Zoom::new(1.0)); to_from_world((1, 0, ZoomLevel::from(1)), Zoom::new(1.0));
to_from_world((67, 42, 7), Zoom::new(7.0)); to_from_world((67, 42, ZoomLevel::from(7)), Zoom::new(7.0));
to_from_world((17421, 11360, 15), Zoom::new(15.0)); to_from_world((17421, 11360, ZoomLevel::from(15)), Zoom::new(15.0));
} }
#[test] #[test]
fn test_quad_key() { fn test_quad_key() {
assert_eq!( assert_eq!(
TileCoords { x: 0, y: 0, z: 1 } TileCoords {
.into_world_tile(TileAddressingScheme::TMS) x: 0,
.unwrap() y: 0,
.build_quad_key(), z: ZoomLevel::from(1)
Some(Quadkey::new(&[2])) }
.into_world_tile(TileAddressingScheme::TMS)
.unwrap()
.build_quad_key(),
Some(Quadkey::new(&[ZoomLevel::from(2)]))
); );
assert_eq!( assert_eq!(
TileCoords { x: 0, y: 1, z: 1 } TileCoords {
.into_world_tile(TileAddressingScheme::TMS) x: 0,
.unwrap() y: 1,
.build_quad_key(), z: ZoomLevel::from(1)
Some(Quadkey::new(&[0])) }
.into_world_tile(TileAddressingScheme::TMS)
.unwrap()
.build_quad_key(),
Some(Quadkey::new(&[ZoomLevel::from(0)]))
); );
assert_eq!( assert_eq!(
TileCoords { x: 1, y: 1, z: 1 } TileCoords {
.into_world_tile(TileAddressingScheme::TMS) x: 1,
.unwrap() y: 1,
.build_quad_key(), z: ZoomLevel::from(1)
Some(Quadkey::new(&[1])) }
.into_world_tile(TileAddressingScheme::TMS)
.unwrap()
.build_quad_key(),
Some(Quadkey::new(&[ZoomLevel::from(1)]))
); );
assert_eq!( assert_eq!(
TileCoords { x: 1, y: 0, z: 1 } TileCoords {
.into_world_tile(TileAddressingScheme::TMS) x: 1,
.unwrap() y: 0,
.build_quad_key(), z: ZoomLevel::from(1)
Some(Quadkey::new(&[3])) }
.into_world_tile(TileAddressingScheme::TMS)
.unwrap()
.build_quad_key(),
Some(Quadkey::new(&[ZoomLevel::from(3)]))
); );
} }
@ -588,7 +663,7 @@ mod tests {
Aabb2::new(Point2::new(0.0, 0.0), Point2::new(2000.0, 2000.0)), Aabb2::new(Point2::new(0.0, 0.0), Point2::new(2000.0, 2000.0)),
1, 1,
Zoom::default(), Zoom::default(),
0, ZoomLevel::default(),
) )
.iter() .iter()
{ {

View File

@ -11,7 +11,9 @@ use geozero::geo_types::GeoWriter;
use geozero::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor}; use geozero::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor};
use rstar::{Envelope, PointDistance, RTree, RTreeObject, AABB}; use rstar::{Envelope, PointDistance, RTree, RTreeObject, AABB};
use crate::coords::{InnerCoords, Quadkey, WorldCoords, WorldTileCoords, Zoom, EXTENT, TILE_SIZE}; use crate::coords::{
InnerCoords, Quadkey, WorldCoords, WorldTileCoords, Zoom, ZoomLevel, EXTENT, TILE_SIZE,
};
use crate::util::math::bounds_from_points; use crate::util::math::bounds_from_points;
/// A quad tree storing the currently loaded tiles. /// A quad tree storing the currently loaded tiles.
@ -35,7 +37,7 @@ impl GeometryIndex {
pub fn query_point( pub fn query_point(
&self, &self,
world_coords: &WorldCoords, world_coords: &WorldCoords,
z: u8, z: ZoomLevel,
zoom: Zoom, zoom: Zoom,
) -> Option<Vec<&IndexedGeometry<f64>>> { ) -> Option<Vec<&IndexedGeometry<f64>>> {
let world_tile_coords = world_coords.into_world_tile(z, zoom); let world_tile_coords = world_coords.into_world_tile(z, zoom);
@ -44,7 +46,7 @@ impl GeometryIndex {
.build_quad_key() .build_quad_key()
.and_then(|key| self.index.get(&key)) .and_then(|key| self.index.get(&key))
{ {
let scale = zoom.scale_delta(&Zoom::new(z as f64)); // FIXME: can be wrong, if tiles of different z are visible let scale = zoom.scale_delta(&Zoom::from(z)); // FIXME: can be wrong, if tiles of different z are visible
let delta_x = world_coords.x / TILE_SIZE * scale - world_tile_coords.x as f64; let delta_x = world_coords.x / TILE_SIZE * scale - world_tile_coords.x as f64;
let delta_y = world_coords.y / TILE_SIZE * scale - world_tile_coords.y as f64; let delta_y = world_coords.y / TILE_SIZE * scale - world_tile_coords.y as f64;

View File

@ -1,6 +1,6 @@
//! Shared thread state. //! Shared thread state.
use crate::coords::{WorldCoords, WorldTileCoords, Zoom}; use crate::coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel};
use crate::error::Error; use crate::error::Error;
use crate::io::geometry_index::{GeometryIndex, IndexProcessor, IndexedGeometry, TileIndex}; use crate::io::geometry_index::{GeometryIndex, IndexProcessor, IndexedGeometry, TileIndex};
use crate::io::tile_request_state::TileRequestState; use crate::io::tile_request_state::TileRequestState;
@ -150,7 +150,7 @@ impl SharedThreadState {
pub fn query_point( pub fn query_point(
&self, &self,
world_coords: &WorldCoords, world_coords: &WorldCoords,
z: u8, z: ZoomLevel,
zoom: Zoom, zoom: Zoom,
) -> Option<Vec<IndexedGeometry<f64>>> { ) -> Option<Vec<IndexedGeometry<f64>>> {
if let Ok(geometry_index) = self.geometry_index.lock() { if let Ok(geometry_index) = self.geometry_index.lock() {

View File

@ -167,13 +167,12 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
} }
pub fn stencil_reference_value(&self, world_coords: &WorldTileCoords) -> u8 { pub fn stencil_reference_value(&self, world_coords: &WorldTileCoords) -> u8 {
world_coords.z * 5 match (world_coords.x, world_coords.y) {
+ match (world_coords.x, world_coords.y) { (x, y) if x % 2 == 0 && y % 2 == 0 => 2,
(x, y) if x % 2 == 0 && y % 2 == 0 => 2, (x, y) if x % 2 == 0 && y % 2 != 0 => 1,
(x, y) if x % 2 == 0 && y % 2 != 0 => 1, (x, y) if x % 2 != 0 && y % 2 == 0 => 4,
(x, y) if x % 2 != 0 && y % 2 == 0 => 4, (x, y) if x % 2 != 0 && y % 2 != 0 => 3,
(x, y) if x % 2 != 0 && y % 2 != 0 => 3, _ => unreachable!(),
_ => unreachable!(), }
}
} }
} }