33use core:: f64;
44use std:: sync:: Arc ;
55
6+ use ahash:: HashMap ;
67use galileo_types:: cartesian:: { Point2 , Rect } ;
78
89use super :: schema:: { TileSchema , VerticalDirection } ;
@@ -23,17 +24,18 @@ pub struct TileSchemaBuilder {
2324#[ derive( Debug ) ]
2425enum Lods {
2526 Logarithmic ( Vec < u32 > ) ,
27+ Custom ( HashMap < u32 , f64 > ) ,
2628}
2729
2830/// Errors that can occur during building a [`TileSchema`].
2931#[ derive( Debug , thiserror:: Error ) ]
3032pub enum TileSchemaError {
3133 /// No zoom levels provided
32- #[ error( "No zoom levels provided" ) ]
34+ #[ error( "no zoom levels provided" ) ]
3335 NoZLevelsProvided ,
3436
3537 /// Invalid tile size
36- #[ error( "Invalid tile size: {width}x{height}" ) ]
38+ #[ error( "invalid tile size: {width}x{height}" ) ]
3739 InvalidTileSize {
3840 /// Tile width
3941 width : u32 ,
@@ -45,18 +47,37 @@ pub enum TileSchemaError {
4547 ///
4648 /// If the resolution is too small, it means that the tile indices would exceed the maximum
4749 /// representable value (u64::MAX).
48- #[ error( "Resolution too small at z-level {z_level}: {resolution}" ) ]
50+ #[ error( "resolution too small at z-level {z_level}: {resolution}" ) ]
4951 ResolutionTooSmall {
5052 /// Z-level where resolution is too small
5153 z_level : u32 ,
5254 /// The resolution value that is too small
5355 resolution : f64 ,
5456 } ,
57+
58+ /// Z-level resolutions are not decreasing
59+ #[ error( "resolution at z-level {upper_level} ({upper_resolution}) cannot be smaller than resolution at z-level {lower_level} ({lower_resolution})" ) ]
60+ NotSortedZLevels {
61+ /// Smaller z-level value
62+ upper_level : u32 ,
63+ /// Resolution of the `upper_level`
64+ upper_resolution : f64 ,
65+ /// Larger z-level value
66+ lower_level : u32 ,
67+ /// Resolution of the `lower_level`
68+ lower_resolution : f64 ,
69+ } ,
5570}
5671
5772impl TileSchemaBuilder {
5873 /// Create a new builder with default parameters.
5974 pub fn build ( self ) -> Result < TileSchema , TileSchemaError > {
75+ // Resolution is bound by the maximum tile index that can be represented
76+ let min_resolution = f64:: min (
77+ self . bounds . width ( ) / self . tile_width as f64 / u64:: MAX as f64 ,
78+ self . bounds . height ( ) / self . tile_height as f64 / u64:: MAX as f64 ,
79+ ) ;
80+
6081 let lods = match self . lods {
6182 Lods :: Logarithmic ( z_levels) => {
6283 if z_levels. is_empty ( ) {
@@ -65,12 +86,6 @@ impl TileSchemaBuilder {
6586
6687 let top_resolution = self . bounds . width ( ) / self . tile_width as f64 ;
6788
68- // Resolution is bound by the maximum tile index that can be represented
69- let min_resolution = f64:: min (
70- self . bounds . width ( ) / self . tile_width as f64 / u64:: MAX as f64 ,
71- self . bounds . height ( ) / self . tile_height as f64 / u64:: MAX as f64 ,
72- ) ;
73-
7489 let max_z_level = * z_levels. iter ( ) . max ( ) . unwrap_or ( & 0 ) ;
7590 let mut lods = vec ! [ f64 :: MAX ; max_z_level as usize + 1 ] ;
7691
@@ -93,6 +108,45 @@ impl TileSchemaBuilder {
93108 }
94109 }
95110
111+ lods
112+ }
113+ Lods :: Custom ( z_levels) => {
114+ if z_levels. is_empty ( ) {
115+ return Err ( TileSchemaError :: NoZLevelsProvided ) ;
116+ }
117+
118+ let max_z_level = * z_levels. keys ( ) . max ( ) . unwrap_or ( & 0 ) ;
119+ let mut lods = vec ! [ f64 :: MAX ; max_z_level as usize + 1 ] ;
120+
121+ for i in 0 ..lods. len ( ) {
122+ match z_levels. get ( & ( i as u32 ) ) {
123+ Some ( & resolution) => {
124+ if resolution < min_resolution {
125+ return Err ( TileSchemaError :: ResolutionTooSmall {
126+ z_level : i as u32 ,
127+ resolution,
128+ } ) ;
129+ }
130+
131+ if i > 0 && lods[ i - 1 ] < resolution {
132+ return Err ( TileSchemaError :: NotSortedZLevels {
133+ upper_level : i as u32 - 1 ,
134+ upper_resolution : lods[ i - 1 ] ,
135+ lower_level : i as u32 ,
136+ lower_resolution : resolution,
137+ } ) ;
138+ }
139+
140+ lods[ i] = resolution
141+ }
142+ None => {
143+ if i > 0 {
144+ lods[ i] = lods[ i - 1 ] ;
145+ }
146+ }
147+ }
148+ }
149+
96150 lods
97151 }
98152 } ;
@@ -154,6 +208,16 @@ impl TileSchemaBuilder {
154208
155209 self
156210 }
211+
212+ /// Sets the z-levels with specified resolution from the iterator.
213+ ///
214+ /// Z-levels are given as tuples of `(z-index, resolution)`. Smaller z-indexes must correspond
215+ /// to larger resolution values. If z-levels are not sorted correctly, building the tile schema
216+ /// would result in [`TileSchemaError::NotSortedZLevels`] error.
217+ pub fn with_z_levels ( mut self , z_levels : impl IntoIterator < Item = ( u32 , f64 ) > ) -> Self {
218+ self . lods = Lods :: Custom ( z_levels. into_iter ( ) . collect ( ) ) ;
219+ self
220+ }
157221}
158222
159223#[ cfg( test) ]
@@ -163,15 +227,17 @@ mod tests {
163227 use super :: * ;
164228 use crate :: tile_schema:: VerticalDirection ;
165229
230+ const TOP_RESOLUTION : f64 = 156543.03392802345 ;
231+
166232 #[ test]
167233 fn schema_builder_normal_web_mercator ( ) {
168234 let schema = TileSchemaBuilder :: web_mercator ( 0 ..=20 ) . build ( ) . unwrap ( ) ;
169235 assert_eq ! ( schema. lods. len( ) , 21 ) ;
170236
171- assert_abs_diff_eq ! ( schema. lods[ 0 ] , 156543.03392802345 ) ;
237+ assert_abs_diff_eq ! ( schema. lods[ 0 ] , TOP_RESOLUTION ) ;
172238
173239 for z in 1 ..=20 {
174- let expected = 156543.03392802345 / 2f64 . powi ( z) ;
240+ let expected = TOP_RESOLUTION / 2f64 . powi ( z) ;
175241 assert_abs_diff_eq ! ( schema. lods[ z as usize ] , expected) ;
176242 }
177243
@@ -208,8 +274,8 @@ mod tests {
208274 let schema = TileSchemaBuilder :: web_mercator ( 5 ..=10 ) . build ( ) . unwrap ( ) ;
209275 assert_eq ! ( schema. lods. len( ) , 11 ) ;
210276
211- assert_abs_diff_eq ! ( schema. lods[ 5 ] , 156543.03392802345 / 2f64 . powi( 5 ) ) ;
212- assert_abs_diff_eq ! ( schema. lods[ 10 ] , 156543.03392802345 / 2f64 . powi( 10 ) ) ;
277+ assert_abs_diff_eq ! ( schema. lods[ 5 ] , TOP_RESOLUTION / 2f64 . powi( 5 ) ) ;
278+ assert_abs_diff_eq ! ( schema. lods[ 10 ] , TOP_RESOLUTION / 2f64 . powi( 10 ) ) ;
213279 }
214280
215281 #[ test]
@@ -254,30 +320,30 @@ mod tests {
254320
255321 assert_eq ! ( schema. lods[ 0 ] , f64 :: MAX ) ;
256322
257- assert_abs_diff_eq ! ( schema. lods[ 1 ] , 156543.03392802345 / 2f64 . powi( 1 ) ) ;
258- assert_abs_diff_eq ! ( schema. lods[ 2 ] , 156543.03392802345 / 2f64 . powi( 2 ) ) ;
259- assert_abs_diff_eq ! ( schema. lods[ 3 ] , 156543.03392802345 / 2f64 . powi( 3 ) ) ;
323+ assert_abs_diff_eq ! ( schema. lods[ 1 ] , TOP_RESOLUTION / 2f64 . powi( 1 ) ) ;
324+ assert_abs_diff_eq ! ( schema. lods[ 2 ] , TOP_RESOLUTION / 2f64 . powi( 2 ) ) ;
325+ assert_abs_diff_eq ! ( schema. lods[ 3 ] , TOP_RESOLUTION / 2f64 . powi( 3 ) ) ;
260326
261- let expected_level_3 = 156543.03392802345 / 2f64 . powi ( 3 ) ;
327+ let expected_level_3 = TOP_RESOLUTION / 2f64 . powi ( 3 ) ;
262328 assert_abs_diff_eq ! ( schema. lods[ 4 ] , expected_level_3) ;
263329
264- assert_abs_diff_eq ! ( schema. lods[ 5 ] , 156543.03392802345 / 2f64 . powi( 5 ) ) ;
330+ assert_abs_diff_eq ! ( schema. lods[ 5 ] , TOP_RESOLUTION / 2f64 . powi( 5 ) ) ;
265331 }
266332
267333 #[ test]
268334 fn skipped_multiple_middle_z_levels_use_previous_value ( ) {
269335 let schema = TileSchemaBuilder :: web_mercator ( [ 0 , 1 , 5 ] ) . build ( ) . unwrap ( ) ;
270336 assert_eq ! ( schema. lods. len( ) , 6 ) ;
271337
272- assert_abs_diff_eq ! ( schema. lods[ 0 ] , 156543.03392802345 / 2f64 . powi( 0 ) ) ;
273- assert_abs_diff_eq ! ( schema. lods[ 1 ] , 156543.03392802345 / 2f64 . powi( 1 ) ) ;
338+ assert_abs_diff_eq ! ( schema. lods[ 0 ] , TOP_RESOLUTION / 2f64 . powi( 0 ) ) ;
339+ assert_abs_diff_eq ! ( schema. lods[ 1 ] , TOP_RESOLUTION / 2f64 . powi( 1 ) ) ;
274340
275- let expected_level_1 = 156543.03392802345 / 2f64 . powi ( 1 ) ;
341+ let expected_level_1 = TOP_RESOLUTION / 2f64 . powi ( 1 ) ;
276342 for z in 2 ..5 {
277343 assert_abs_diff_eq ! ( schema. lods[ z] , expected_level_1) ;
278344 }
279345
280- assert_abs_diff_eq ! ( schema. lods[ 5 ] , 156543.03392802345 / 2f64 . powi( 5 ) ) ;
346+ assert_abs_diff_eq ! ( schema. lods[ 5 ] , TOP_RESOLUTION / 2f64 . powi( 5 ) ) ;
281347 }
282348
283349 #[ test]
@@ -300,4 +366,71 @@ mod tests {
300366 result
301367 ) ;
302368 }
369+
370+ #[ test]
371+ fn custom_z_levels_equivalent_to_logarithmic ( ) {
372+ let mut lods = vec ! [ ] ;
373+ const LEVELS : u32 = 32 ;
374+
375+ for i in 0 ..=LEVELS {
376+ lods. push ( ( i, TOP_RESOLUTION / 2f64 . powi ( i as i32 ) ) ) ;
377+ }
378+
379+ let tile_schema = TileSchemaBuilder :: web_mercator ( 0 ..0 )
380+ . with_z_levels ( lods)
381+ . build ( )
382+ . unwrap ( ) ;
383+
384+ assert_eq ! (
385+ tile_schema. lods,
386+ TileSchemaBuilder :: web_mercator( 0 ..=LEVELS )
387+ . build( )
388+ . unwrap( )
389+ . lods,
390+ ) ;
391+ }
392+
393+ #[ test]
394+ fn custom_z_levels_check_for_min_resolution ( ) {
395+ let result = TileSchemaBuilder :: web_mercator ( 0 ..0 )
396+ . with_z_levels ( [ ( 0 , TOP_RESOLUTION ) , ( 1 , TOP_RESOLUTION / 2f64 . powi ( 65 ) ) ] )
397+ . build ( ) ;
398+ assert ! (
399+ matches!(
400+ result,
401+ Err ( TileSchemaError :: ResolutionTooSmall { z_level: 1 , .. } )
402+ ) ,
403+ "Unexpected schema build result: {result:?}"
404+ ) ;
405+ }
406+
407+ #[ test]
408+ fn custom_z_levels_must_be_sorted ( ) {
409+ let mut lods = vec ! [ ] ;
410+ const LEVELS : u32 = 32 ;
411+
412+ for i in 0 ..=LEVELS {
413+ lods. push ( ( i, TOP_RESOLUTION / 2f64 . powi ( i as i32 ) ) ) ;
414+ }
415+
416+ lods. swap ( 1 , 2 ) ;
417+ lods[ 1 ] . 0 = 1 ;
418+ lods[ 2 ] . 0 = 2 ;
419+
420+ let result = TileSchemaBuilder :: web_mercator ( 0 ..0 )
421+ . with_z_levels ( lods)
422+ . build ( ) ;
423+
424+ assert ! (
425+ matches!(
426+ result,
427+ Err ( TileSchemaError :: NotSortedZLevels {
428+ upper_level: 1 ,
429+ lower_level: 2 ,
430+ ..
431+ } )
432+ ) ,
433+ "Unexpected schema build result: {result:?}"
434+ )
435+ }
303436}
0 commit comments