Coverage for gws-app/gws/base/grabber/tile.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Base grabber for tiled sources.""" 

2 

3import gws 

4import gws.lib.extent 

5import gws.lib.grid 

6import gws.lib.image 

7 

8from . import core 

9 

10 

11class Object(core.Object): 

12 """Base grabber for sources addressed as tile pyramids. 

13 

14 Composes boxes and tiles from source tiles, mosaicked and warped into the 

15 target grid. Provides subclasses with the composition and the caching of 

16 source tiles. 

17 

18 Subclasses set ``sourceTms`` and implement ``fetch_tile_as_bytes``. 

19 """ 

20 

21 sourceTms: gws.TileMatrixSet 

22 """Source tile matrix set.""" 

23 

24 def compose_box_as_image(self, extent, w, h, params=None): 

25 w = gws.u.to_rounded_int(w) 

26 h = gws.u.to_rounded_int(h) 

27 

28 if self.targetCrs == self.sourceCrs: 

29 src_extent = extent 

30 else: 

31 src_extent = self.extent_to_source_crs(extent) 

32 if not src_extent: 

33 gws.log.debug(f'grabber {self.cache.name!r}: empty image: box {extent!r} outside the source CRS area') 

34 return self.empty_image(w, h) 

35 

36 src_res = gws.lib.extent.w(src_extent) / w 

37 tm = gws.lib.grid.matrix_for_resolution(self.sourceTms, src_res) 

38 if self.targetCrs != self.sourceCrs: 

39 src_extent = gws.lib.extent.buffer(src_extent, tm.resolution * 2) 

40 

41 mtr = gws.lib.grid.matrix_range_for_extent(tm, src_extent) 

42 if not mtr: 

43 gws.log.debug(f'grabber {self.cache.name!r}: empty image: box {extent!r} outside the source matrix {tm.identifier!r}') 

44 return self.empty_image(w, h) 

45 

46 x0, y0, x1, y1, _ = mtr 

47 mw = (x1 - x0 + 1) * tm.tileWidth 

48 mh = (y1 - y0 + 1) * tm.tileHeight 

49 mosaic = gws.lib.image.from_size((mw, mh)) 

50 

51 for x, y, _ in gws.lib.grid.enum_tiles((x0, y0, x1, y1, 0)): 

52 blob = self._fetch_and_cache_source_tile(tm, x, y) 

53 img = self.to_image(blob) 

54 px = (x - x0) * tm.tileWidth 

55 py = (y - y0) * tm.tileHeight 

56 mosaic.paste(img, (px, py)) 

57 

58 src_extent = gws.lib.grid.matrix_extent_for_range(tm, mtr) 

59 return self.warp_image( 

60 mosaic, 

61 gws.Bounds(crs=self.sourceCrs, extent=src_extent), 

62 gws.Bounds(crs=self.targetCrs, extent=extent), 

63 w, 

64 h, 

65 ) 

66 

67 def fetch_tile_as_bytes(self, tm: gws.TileMatrix, col: int, row: int) -> bytes: 

68 """Fetch a source tile with exactly one request, as encoded bytes. 

69 

70 Subclasses must implement this. 

71 

72 Args: 

73 tm: Source tile matrix. 

74 col: Tile column. 

75 row: Tile row. 

76 

77 Returns: 

78 The encoded tile image. 

79 

80 Raises: 

81 ``NotImplementedError``: In the base class. 

82 """ 

83 

84 raise NotImplementedError(f'fetch_tile_as_bytes not implemented in {self!r}') 

85 

86 ## 

87 

88 def _fetch_and_cache_source_tile(self, tm: gws.TileMatrix, col: int, row: int) -> bytes: 

89 """Return a source tile, fetched once and kept in the ephemeral store for neighbouring requests.""" 

90 

91 key = gws.u.sha256([self.cache.name, tm.identifier, col, row]) 

92 return gws.u.get_ephemeral_content( 

93 f'source_tile_{key}', 

94 lambda: self.fetch_tile_as_bytes(tm, col, row), 

95 )