Coverage for gws-app/gws/plugin/tile_layer/provider.py: 0%

44 statements  

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

1"""XYZ tile service provider.""" 

2 

3from typing import Optional, cast 

4 

5import gws 

6import gws.base.grabber 

7import gws.base.layer 

8import gws.base.layer 

9import gws.lib.grid 

10import gws.base.metadata 

11import gws.config.util 

12import gws.gis.zoom 

13import gws.lib.crs 

14import gws.lib.extent 

15import gws.lib.net 

16 

17 

18class Config(gws.Config): 

19 """XYZ tile service.""" 

20 

21 maxLevel: int = 19 

22 """Finest zoom level the source provides. (added in 8.5)""" 

23 maxRequests: int = 0 

24 """Max. concurrent requests to this source.""" 

25 url: gws.Url 

26 """REST URL with placeholders {x}, {y} and {z}.""" 

27 grid: Optional[gws.lib.grid.Config] 

28 """Tile grid of the source. (changed in 8.5)""" 

29 

30 

31class Object(gws.Node): 

32 """XYZ tile service.""" 

33 

34 url: gws.Url 

35 """Tile URL with the placeholders ``{x}``, ``{y}`` and ``{z}``.""" 

36 grid: gws.MapGrid 

37 """Tile grid of the source, web mercator by default.""" 

38 maxLevel: int 

39 """Finest zoom level the source provides.""" 

40 maxRequests: int 

41 """Max. concurrent requests to the source (not used).""" 

42 

43 def configure(self): 

44 self.url = self.cfg('url') 

45 self.maxLevel = self.cfg('maxLevel') 

46 self.maxRequests = self.cfg('maxRequests') 

47 

48 p = cast(gws.lib.grid.Config, self.cfg('grid', default=gws.Config())) 

49 opts = gws.lib.grid.Options( 

50 crs=gws.lib.crs.require(p.crs) if p.crs else gws.lib.crs.WEBMERCATOR, 

51 extent=p.extent, 

52 baseResolution=p.baseResolution, 

53 tileSize=p.tileSize, 

54 withSnap=False, 

55 ) 

56 self.grid = gws.lib.grid.new(opts) 

57 

58 def cache_hash(self): 

59 """Compute a hash of the provider settings. 

60 

61 Returns: 

62 Hash string, built from the URL and the grid. 

63 """ 

64 return gws.u.sha256([ 

65 self.url, 

66 self.grid.crs.srid, 

67 list(self.grid.extent), 

68 self.grid.baseResolution, 

69 self.grid.tileSize, 

70 ]) 

71 

72 def get_tile(self, x: int, y: int, z: int) -> bytes: 

73 """Fetch a tile from the source. 

74 

75 Args: 

76 x: Tile column. 

77 y: Tile row, counted from the top. 

78 z: Zoom level. 

79 

80 Returns: 

81 Image bytes. 

82 

83 Raises: 

84 ``gws.ExternalServiceError``: If the request fails or the response is not an image. 

85 """ 

86 url = self.url 

87 url = url.replace('{x}', str(x)) 

88 url = url.replace('{y}', str(y)) 

89 url = url.replace('{z}', str(z)) 

90 

91 res = gws.lib.net.http_request(url) 

92 if not res.ok: 

93 raise gws.ExternalServiceError(f'tile request failed: status={res.status_code} url={url!r}') 

94 if not res.content_type.startswith('image/'): 

95 raise gws.ExternalServiceError(f'tile request failed: content type {res.content_type!r} url={url!r}') 

96 return res.content