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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""XYZ tile service provider."""
3from typing import Optional, cast
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
18class Config(gws.Config):
19 """XYZ tile service."""
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)"""
31class Object(gws.Node):
32 """XYZ tile service."""
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)."""
43 def configure(self):
44 self.url = self.cfg('url')
45 self.maxLevel = self.cfg('maxLevel')
46 self.maxRequests = self.cfg('maxRequests')
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)
58 def cache_hash(self):
59 """Compute a hash of the provider settings.
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 ])
72 def get_tile(self, x: int, y: int, z: int) -> bytes:
73 """Fetch a tile from the source.
75 Args:
76 x: Tile column.
77 y: Tile row, counted from the top.
78 z: Zoom level.
80 Returns:
81 Image bytes.
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))
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