@@ -30,7 +30,7 @@ enum Lods {
3030}
3131
3232/// Errors that can occur during building a [`TileSchema`].
33- #[ derive( Debug , thiserror:: Error ) ]
33+ #[ derive( Debug , thiserror:: Error , PartialEq , Copy , Clone ) ]
3434pub enum TileSchemaError {
3535 /// No zoom levels provided
3636 #[ error( "no zoom levels provided" ) ]
@@ -69,11 +69,30 @@ pub enum TileSchemaError {
6969 /// Resolution of the `lower_level`
7070 lower_resolution : f64 ,
7171 } ,
72+
73+ /// Tile bounds have invalid value
74+ #[ error( "tile bounds have zero size or not finite: {0:?}" ) ]
75+ InvalidTileBounds ( Rect ) ,
76+
77+ /// World bounds have invalid value
78+ #[ error( "world bounds have zero size or not finite: {0:?}" ) ]
79+ InvalidWorldBounds ( Rect ) ,
7280}
7381
7482impl TileSchemaBuilder {
7583 /// Create a new builder with default parameters.
7684 pub fn build ( self ) -> Result < TileSchema , TileSchemaError > {
85+ if !self . tile_bounds . width ( ) . is_normal ( ) || !self . tile_bounds . height ( ) . is_normal ( ) {
86+ return Err ( TileSchemaError :: InvalidTileBounds ( self . tile_bounds ) ) ;
87+ }
88+
89+ if self . wrap_x
90+ && matches ! ( self . lods, Lods :: Logarithmic ( _) )
91+ && ( !self . world_bounds . width ( ) . is_normal ( ) || !self . world_bounds . height ( ) . is_normal ( ) )
92+ {
93+ return Err ( TileSchemaError :: InvalidWorldBounds ( self . tile_bounds ) ) ;
94+ }
95+
7796 // Resolution is bound by the maximum tile index that can be represented
7897 let min_resolution = f64:: min (
7998 self . world_bounds . width ( ) / self . tile_width as f64 / u64:: MAX as f64 ,
@@ -162,7 +181,8 @@ impl TileSchemaBuilder {
162181
163182 Ok ( TileSchema {
164183 origin : self . origin ,
165- bounds : self . tile_bounds ,
184+ tile_bounds : self . tile_bounds ,
185+ world_bounds : self . world_bounds ,
166186 lods : Arc :: new ( lods) ,
167187 tile_width : self . tile_width ,
168188 tile_height : self . tile_height ,
@@ -236,7 +256,7 @@ impl TileSchemaBuilder {
236256 /// or increasing x coordinate by the whole number of bounding box widths. This produces an effect of
237257 /// horizontally infinite map, where a user can pan as log as they want to the right or left.
238258 ///
239- /// Note, that for wrapping to work property, bounds of the tile schema should cover the whole globe.
259+ /// Note, that for wrapping to work property, world bounds of the tile schema should cover the whole globe.
240260 /// This is not enforced in `.build()` method validatation since tile schema is agnostic to the CRS
241261 /// it will be used for.
242262 pub fn wrap_x ( mut self , shall_wrap : bool ) -> Self {
@@ -262,40 +282,69 @@ impl TileSchemaBuilder {
262282 /// .expect("tile schema is properly defined");
263283 /// ```
264284 ///
265- /// Note that origin point doesn't have to be inside the schema bounds. For example, the origin may point to
285+ /// Note that origin point doesn't have to be inside the tile bounds. For example, the origin may point to
266286 /// the top left angle of the world map, but tiles might only be available for a specific region, and the
267287 /// bounds will only contain that region. In this case tiles may have indices starting not from 0.
268288 pub fn origin ( mut self , origin : Point2 ) -> Self {
269289 self . origin = origin;
270290 self
271291 }
272292
273- /// Sets a rectangle in projected coordinates for which tiles are available.
293+ /// Sets the rectangle in projected coordinates for which tiles are available.
274294 ///
275- /// Tiles that lies outside of the bounds will not be requested from the source.
295+ /// Tiles that lie outside of the bounds will not be requested from the source.
276296 ///
277297 /// ```
278298 /// # use galileo::tile_schema::TileSchemaBuilder;
279299 /// # use galileo::galileo_types::cartesian::Rect;
280300 /// let tile_schema = TileSchemaBuilder::web_mercator(0..23)
281301 /// // only show tiles for Angola
282- /// .bounds (Rect::new(1282761., -1975899., 2674573., -590691.))
302+ /// .tile_bounds (Rect::new(1282761., -1975899., 2674573., -590691.))
283303 /// .build()
284304 /// .expect("tile schema is properly defined");
285305 /// ```
286306 ///
287307 /// # Errors
288308 ///
289309 /// If either width or height of the bounds rectangle is `0`, `NaN` or `Infinity`, building the tile schema
290- /// will return an error `TileSchemaError::InvalidBounds` .
291- pub fn bounds ( mut self , bounds : Rect ) -> Self {
310+ /// will return an error [ `TileSchemaError::InvalidTileBounds`] .
311+ pub fn tile_bounds ( mut self , bounds : Rect ) -> Self {
292312 self . tile_bounds = bounds;
293313 self
294314 }
315+
316+ /// Sets the rectangle in projected coordinates, which includes the whole globe as defined by the target
317+ /// projection.
318+ ///
319+ /// World bounds are used to calculate x coordinate of tiles when wrapping around 180 parallel, and to
320+ /// calculate resolution levels for logarithmic z-levels. If wrapping is not used and z-levels are set
321+ /// manually, this parameter is not required for correct calculations of the tile indices.
322+ ///
323+ /// ```
324+ /// # use galileo::tile_schema::TileSchemaBuilder;
325+ /// # use galileo::galileo_types::cartesian::Rect;
326+ /// let tile_schema = TileSchemaBuilder::web_mercator(0..23)
327+ /// // square WebMercator projetion bounds
328+ /// .world_bounds(Rect::new(-20037508.342787, -20037508.342787, 20037508.342787, 20037508.342787))
329+ /// .build()
330+ /// .expect("tile schema is properly defined");
331+ /// ```
332+ ///
333+ /// # Errors
334+ ///
335+ /// If either width or height of the bounds rectangle is `0`, `NaN` or `Infinity`, building the tile schema
336+ /// will return an error `TileSchemaError::InvalidWorldBounds`. This check is skipped if neither wrapping nor
337+ /// logarithmic z-levels are used for the schema.
338+ pub fn world_bounds ( mut self , bounds : Rect ) -> Self {
339+ self . world_bounds = bounds;
340+ self
341+ }
295342}
296343
297344#[ cfg( test) ]
298345mod tests {
346+ use core:: f64;
347+
299348 use approx:: assert_abs_diff_eq;
300349
301350 use super :: * ;
@@ -322,7 +371,7 @@ mod tests {
322371 Point2 :: new( -20037508.342787 , 20037508.342787 )
323372 ) ;
324373 assert_eq ! (
325- schema. bounds ,
374+ schema. tile_bounds ,
326375 Rect :: new(
327376 -20037508.342787 ,
328377 -20037508.342787 ,
@@ -507,4 +556,69 @@ mod tests {
507556 "Unexpected schema build result: {result:?}"
508557 )
509558 }
559+
560+ #[ test]
561+ fn invalid_tile_bounds_return_error ( ) {
562+ let to_check = [
563+ Rect :: new ( 0.0 , 0.0 , 0.0 , 1000.0 ) ,
564+ Rect :: new ( 0.0 , 0.0 , 1000.0 , 0.0 ) ,
565+ Rect :: new ( 0.0 , 0.0 , f64:: NAN , 1000.0 ) ,
566+ Rect :: new ( 0.0 , 0.0 , 0.0 , f64:: INFINITY ) ,
567+ Rect :: new ( f64:: NEG_INFINITY , 0.0 , 1000.0 , 1000.0 ) ,
568+ ] ;
569+
570+ for bounds in to_check {
571+ let result = TileSchemaBuilder :: web_mercator ( 0 ..18 )
572+ . tile_bounds ( bounds)
573+ . build ( ) ;
574+ assert ! (
575+ matches!( result, Err ( TileSchemaError :: InvalidTileBounds ( _) ) ) ,
576+ "Error not returned for tile bounds: {bounds:?}"
577+ ) ;
578+ }
579+ }
580+
581+ #[ test]
582+ fn invalid_world_bounds_return_error ( ) {
583+ let to_check = [
584+ Rect :: new ( 0.0 , 0.0 , 0.0 , 1000.0 ) ,
585+ Rect :: new ( 0.0 , 0.0 , 1000.0 , 0.0 ) ,
586+ Rect :: new ( 0.0 , 0.0 , f64:: NAN , 1000.0 ) ,
587+ Rect :: new ( 0.0 , 0.0 , 0.0 , f64:: INFINITY ) ,
588+ Rect :: new ( f64:: NEG_INFINITY , 0.0 , 1000.0 , 1000.0 ) ,
589+ ] ;
590+
591+ for bounds in to_check {
592+ let result = TileSchemaBuilder :: web_mercator ( 0 ..18 )
593+ . world_bounds ( bounds)
594+ . build ( ) ;
595+ assert ! (
596+ matches!( result, Err ( TileSchemaError :: InvalidWorldBounds ( _) ) ) ,
597+ "Error not returned for world bounds: {bounds:?}"
598+ ) ;
599+ }
600+ }
601+
602+ #[ test]
603+ fn invalid_world_bounds_skipped_if_not_needed ( ) {
604+ let to_check = [
605+ Rect :: new ( 0.0 , 0.0 , 0.0 , 1000.0 ) ,
606+ Rect :: new ( 0.0 , 0.0 , 1000.0 , 0.0 ) ,
607+ Rect :: new ( 0.0 , 0.0 , f64:: NAN , 1000.0 ) ,
608+ Rect :: new ( 0.0 , 0.0 , 0.0 , f64:: INFINITY ) ,
609+ Rect :: new ( f64:: NEG_INFINITY , 0.0 , 1000.0 , 1000.0 ) ,
610+ ] ;
611+
612+ for bounds in to_check {
613+ let result = TileSchemaBuilder :: web_mercator ( 0 ..18 )
614+ . world_bounds ( bounds)
615+ . wrap_x ( false )
616+ . with_z_levels ( [ ( 0 , 1000.0 ) , ( 1 , 500.0 ) ] )
617+ . build ( ) ;
618+ assert ! (
619+ result. is_ok( ) ,
620+ "Error returned for world bounds: {bounds:?}"
621+ ) ;
622+ }
623+ }
510624}
0 commit comments