@@ -420,6 +420,33 @@ impl TileSource for PMTilesReader {
420420 ) )
421421 }
422422
423+ #[ context( "streaming tile sizes for bbox {:?}" , bbox) ]
424+ async fn get_tile_size_stream ( & self , bbox : TileBBox ) -> Result < TileStream < ' static , u32 > > {
425+ let mut tile_sizes: Vec < ( TileCoord , u32 ) > = Vec :: new ( ) ;
426+
427+ let coords: Vec < TileCoord > = bbox. iter_coords ( ) . collect ( ) ;
428+ for coord in coords {
429+ let Ok ( tile_id) = coord. get_hilbert_index ( ) else {
430+ continue ;
431+ } ;
432+ if let Some ( range) = Self :: resolve_tile_range (
433+ tile_id,
434+ Arc :: clone ( & self . root_entries ) ,
435+ & self . leaves_cache ,
436+ & self . leaves_bytes ,
437+ self . header . tile_data . offset ,
438+ self . internal_compression ,
439+ )
440+ . await ?
441+ && let Ok ( size) = u32:: try_from ( range. length )
442+ {
443+ tile_sizes. push ( ( coord, size) ) ;
444+ }
445+ }
446+
447+ Ok ( TileStream :: from_vec ( tile_sizes) )
448+ }
449+
423450 // deep probe of container meta
424451 #[ cfg( feature = "cli" ) ]
425452 /// Adds PMTiles‑specific container metadata (the v3 header) to the CLI probe output.
@@ -489,6 +516,60 @@ mod tests {
489516 Ok ( ( ) )
490517 }
491518
519+ #[ tokio:: test]
520+ async fn tile_size_stream_matches_tile_reads ( ) -> Result < ( ) > {
521+ let reader = PMTilesReader :: open_path ( & PATH , TilesRuntime :: default ( ) ) . await ?;
522+
523+ let bbox = TileBBox :: from_min_and_max ( 9 , 274 , 167 , 275 , 168 ) ?;
524+ let compression = reader. metadata ( ) . tile_compression ;
525+
526+ let mut sizes: Vec < ( TileCoord , u32 ) > = reader. get_tile_size_stream ( bbox) . await ?. to_vec ( ) . await ;
527+ sizes. sort_by_key ( |( c, _) | ( c. y , c. x ) ) ;
528+
529+ assert_eq ! ( sizes. len( ) , 4 ) ;
530+
531+ for ( coord, size) in & sizes {
532+ let blob = reader
533+ . get_tile ( coord)
534+ . await ?
535+ . expect ( "tile should exist" )
536+ . into_blob ( compression) ?;
537+ assert_eq ! (
538+ * size,
539+ blob. len( ) as u32 ,
540+ "size mismatch at {coord:?}"
541+ ) ;
542+ }
543+
544+ Ok ( ( ) )
545+ }
546+
547+ #[ tokio:: test]
548+ async fn tile_size_stream_single_tile ( ) -> Result < ( ) > {
549+ let reader = PMTilesReader :: open_path ( & PATH , TilesRuntime :: default ( ) ) . await ?;
550+
551+ let bbox = TileBBox :: from_min_and_max ( 0 , 0 , 0 , 0 , 0 ) ?;
552+ let sizes: Vec < ( TileCoord , u32 ) > = reader. get_tile_size_stream ( bbox) . await ?. to_vec ( ) . await ;
553+
554+ assert_eq ! ( sizes. len( ) , 1 ) ;
555+ assert_eq ! ( sizes[ 0 ] . 0 , TileCoord :: new( 0 , 0 , 0 ) ?) ;
556+ assert_eq ! ( sizes[ 0 ] . 1 , 20 ) ;
557+
558+ Ok ( ( ) )
559+ }
560+
561+ #[ tokio:: test]
562+ async fn tile_size_stream_empty_for_missing_zoom ( ) -> Result < ( ) > {
563+ let reader = PMTilesReader :: open_path ( & PATH , TilesRuntime :: default ( ) ) . await ?;
564+
565+ let bbox = TileBBox :: from_min_and_max ( 20 , 0 , 0 , 3 , 3 ) ?;
566+ let sizes: Vec < ( TileCoord , u32 ) > = reader. get_tile_size_stream ( bbox) . await ?. to_vec ( ) . await ;
567+
568+ assert ! ( sizes. is_empty( ) ) ;
569+
570+ Ok ( ( ) )
571+ }
572+
492573 #[ tokio:: test]
493574 async fn tile_stream_matches_individual_reads ( ) -> Result < ( ) > {
494575 let reader = PMTilesReader :: open_path ( & PATH , TilesRuntime :: default ( ) ) . await ?;
0 commit comments