Coverage for gws-app/gws/base/layer/image.py: 69%
64 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
1"""Base raster layer."""
3from typing import Optional, cast
5import gws
6import gws.base.grabber
7import gws.gis.cache
8import gws.lib.crs
9import gws.lib.extent
11from . import core
13CACHE_NAME_LENGTH = 12
16class Object(core.Object):
17 """Base raster layer.
19 Renders boxes and tiles through grabbers (see ``gws.base.grabber``), one
20 per CRS supported by the application. Subclasses provide the grabber with
21 ``create_grabber`` and the default cache name with ``create_cache_name``.
22 """
24 canRenderBox = True
25 canRenderTile = True
27 def post_configure(self):
28 self.post_configure_grabbers()
30 def post_configure_grabbers(self):
31 """Create a grabber for each CRS supported by the application.
33 The cache settings come from the ``cache`` configuration. The cache
34 name defaults to ``create_cache_name`` and gets the CRS code appended.
35 Caching is disabled (``maxAge`` 0) when ``withCache`` is off, and for
36 CRSs not listed in ``cache.crs`` if that list is given. CRSs that are
37 incompatible with the layer extent are skipped with a configuration
38 warning.
39 """
40 if not (self.canRenderBox or self.canRenderTile):
41 return
43 p = cast(
44 gws.gis.cache.LayerConfig,
45 self.cfg('cache') or self.root.specs.read({}, 'gws.gis.cache.core.LayerConfig'),
46 )
47 cache_proto = gws.MapCache(
48 name=p.name,
49 maxAge=p.maxAge,
50 maxLevel=p.maxLevel,
51 requestBuffer=p.requestBuffer,
52 requestTiles=p.requestTiles,
53 )
54 if not self.cfg('withCache'):
55 cache_proto.maxAge = 0
56 if not cache_proto.name:
57 cache_proto.name = self.create_cache_name(cache_proto)
59 cache_srids = []
60 if p.crs:
61 cache_srids = [gws.lib.crs.require(c).srid for c in p.crs]
63 for crs in self.root.app.supported_crs():
64 ext = crs.clip_wgs_extent(self.wgsExtent)
65 if not ext:
66 self.root.config_warning(f'extent {self.wgsExtent} is incompatible with {crs!r}')
67 continue
68 cache = gws.MapCache(**vars(cache_proto))
69 if cache_srids and crs.srid not in cache_srids:
70 cache.maxAge = 0
71 cache.name += f'_{crs.srid}'
73 opts = gws.base.grabber.Options(
74 crs=crs,
75 cache=cache,
76 extent=gws.lib.extent.transform_from_wgs(ext, crs),
77 imageFormat=self.imageFormat,
78 )
80 gr = self.create_grabber(opts)
81 if gr:
82 self.grabbers[crs.srid] = gr
84 def create_cache_name(self, cache: gws.MapCache) -> str:
85 """Create a default cache name for the layer.
87 Subclasses must implement this.
89 Args:
90 cache: Cache settings.
92 Returns:
93 Cache name.
95 Raises:
96 ``NotImplementedError``: In the base class.
97 """
98 raise NotImplementedError(f'create_cache_name not implemented in {self!r}')
100 def create_grabber(self, opts: gws.base.grabber.Options) -> Optional[gws.Grabber]:
101 """Create a grabber for one CRS. Returns nothing in the base class.
103 Args:
104 opts: Grabber options: CRS, cache, extent and image format.
106 Returns:
107 A grabber, or ``None`` if the layer cannot serve this CRS.
108 """
109 pass
111 ##
113 def render_tile(self, lri):
114 grabber = self.grabber_for(lri)
115 if not grabber:
116 return
118 blob = grabber.get_tile_as_bytes((lri.x, lri.y, lri.z), lri.renderParams)
119 return gws.LayerRenderOutput(content=blob)
121 def render_box(self, lri):
122 grabber = self.grabber_for(lri)
123 if not grabber:
124 return
126 params = lri.renderParams
127 w, h = lri.view.pxSize
129 if not lri.view.rotation:
130 blob = grabber.get_box_as_bytes(lri.view.bounds.extent, w, h, params)
131 return gws.LayerRenderOutput(content=blob)
133 circ = gws.lib.extent.circumsquare(lri.view.bounds.extent)
134 d = gws.u.to_rounded_int(gws.lib.extent.diagonal((0, 0, w, h)))
136 img = grabber.get_box_as_image(circ, d, d, params)
137 img.rotate(-lri.view.rotation)
138 img.crop(
139 (
140 d / 2 - w / 2,
141 d / 2 - h / 2,
142 d / 2 + w / 2,
143 d / 2 + h / 2,
144 )
145 )
146 blob = img.to_bytes(self.imageFormat.mimeTypes[0], self.imageFormat.options)
147 return gws.LayerRenderOutput(content=blob)