Coverage for gws-app/gws/base/layer/util.py: 31%
104 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
1from typing import Callable
3import math
5import gws
6import gws.base.model
7import gws.base.search
8import gws.lib.crs
9import gws.lib.extent
10import gws.gis.mpx
11import gws.gis.source
12import gws.gis.zoom
13import gws.lib.image
14import gws.base.metadata
15import gws.lib.style
16import gws.lib.svg
20def mapproxy_layer_config(layer: gws.Layer, mc, source_uid):
21 mc.layer({
22 'name': layer.uid + '_NOCACHE',
23 'sources': [source_uid]
24 })
26 tg = layer.grid
28 tg.uid = mc.grid(gws.u.compact({
29 'origin': tg.origin,
30 'tile_size': [tg.tileSize, tg.tileSize],
31 'res': tg.resolutions,
32 'srs': tg.bounds.crs.epsg,
33 'bbox': tg.bounds.extent,
34 }))
36 front_cache_config = {
37 'sources': [source_uid],
38 'grids': [tg.uid],
39 'cache': {
40 'type': 'file',
41 'directory_layout': 'mp'
42 },
43 'meta_size': [1, 1],
44 'meta_buffer': 0,
45 'disable_storage': True,
46 'minimize_meta_requests': True,
47 'format': layer.imageFormat.name or 'png8',
48 }
50 cache = getattr(layer, 'cache', None)
51 if cache:
52 front_cache_config['disable_storage'] = False
53 if cache.requestTiles:
54 front_cache_config['meta_size'] = [cache.requestTiles, cache.requestTiles]
55 if cache.requestBuffer:
56 front_cache_config['meta_buffer'] = cache.requestBuffer
58 layer.mpxCacheUid = mc.cache(front_cache_config)
60 mc.layer({
61 'name': layer.uid,
62 'sources': [layer.mpxCacheUid]
63 })
66def mapproxy_back_cache_config(layer: gws.Layer, mc, url, grid_uid):
67 source_uid = mc.source({
68 'type': 'tile',
69 'url': url,
70 'grid': grid_uid,
71 'concurrent_requests': layer.cfg('maxRequests', default=0)
72 })
74 return mc.cache(gws.u.compact({
75 'sources': [source_uid],
76 'grids': [grid_uid],
77 'cache': {
78 'type': 'file',
79 'directory_layout': 'mp'
80 },
81 'disable_storage': True,
82 'format': layer.imageFormat.name or 'png8',
84 }))
87##
89_DEFAULT_BOX_SIZE = 1000
90_DEFAULT_BOX_BUFFER = 200
92_GetBoxFn = Callable[[gws.Bounds, float, float], bytes]
95def mpx_raster_render(layer: gws.Layer, lri: gws.LayerRenderInput):
96 if lri.type == gws.LayerRenderInputType.box:
98 uid = layer.uid
99 if not layer.cache:
100 uid += '_NOCACHE'
102 def get_box(bounds, width, height):
103 return gws.gis.mpx.wms_request(uid, bounds, width, height, forward=lri.extraParams)
105 content = generic_render_box(layer, lri, get_box)
106 return gws.LayerRenderOutput(content=content)
108 if lri.type == gws.LayerRenderInputType.xyz:
109 content = gws.gis.mpx.wmts_request(
110 layer.uid,
111 lri.x,
112 lri.y,
113 lri.z,
114 tile_matrix=layer.grid.uid,
115 tile_size=layer.grid.tileSize)
117 annotate = layer.root.app.developer_option('map.annotate_render')
118 if annotate:
119 content = _annotate(content, f'{lri.x} {lri.y} {lri.z}')
121 return gws.LayerRenderOutput(content=content)
124def generic_render_box(layer: gws.Layer, lri: gws.LayerRenderInput, get_box: _GetBoxFn, box_size: int = 0, box_buffer: int = 0) -> bytes:
125 annotate = layer.root.app.developer_option('map.annotate_render')
127 box_size = box_size or _DEFAULT_BOX_SIZE
128 box_buffer = box_buffer or _DEFAULT_BOX_BUFFER
130 w, h = lri.view.pxSize
132 if not lri.view.rotation and w < box_size and h < box_size:
133 # fast path: no rotation, small box
134 content = get_box(lri.view.bounds, w, h)
135 if annotate:
136 content = _annotate(content, 'fast')
137 return content
139 if not lri.view.rotation:
140 # no rotation, big box
141 img = _box_to_image(lri.view.bounds, w, h, box_size, box_buffer, annotate, get_box)
142 return img.to_bytes()
144 # rotation: render a circumsquare around the wanted extent
146 circ = gws.lib.extent.circumsquare(lri.view.bounds.extent)
147 d = gws.lib.extent.diagonal((0, 0, w, h))
148 b = gws.Bounds(crs=lri.view.bounds.crs, extent=circ)
150 img = _box_to_image(b, d, d, box_size, box_buffer, annotate, get_box)
152 # rotate the square (NB: PIL rotations are counter-clockwise)
153 # and crop the square back to the wanted extent
155 img.rotate(-lri.view.rotation).crop((
156 d / 2 - w / 2,
157 d / 2 - h / 2,
158 d / 2 + w / 2,
159 d / 2 + h / 2,
160 ))
162 return img.to_bytes()
165def _box_to_image(bounds: gws.Bounds, width: float, height: float, max_size: int, buffer: int, annotate: bool, get_box: _GetBoxFn) -> gws.lib.image.Image:
167 if width < max_size and height < max_size:
168 content = get_box(bounds, width, height)
169 img = gws.lib.image.from_bytes(content)
170 if annotate:
171 img = _annotate_image(img, 'small')
172 return img
174 xcount = math.ceil(width / max_size)
175 ycount = math.ceil(height / max_size)
177 ext = bounds.extent
179 xres = (ext[2] - ext[0]) / width
180 yres = (ext[3] - ext[1]) / height
182 gws.log.debug(f'_box_to_image (BIG): {xcount=} {ycount=} {xres=} {yres=}')
184 ext_w = xres * max_size
185 ext_h = yres * max_size
187 grid = []
189 for ny in range(ycount):
190 for nx in range(xcount):
191 e = (
192 ext[0] + ext_w * (nx + 0) - buffer * xres,
193 ext[3] - ext_h * (ny + 1) - buffer * yres,
194 ext[0] + ext_w * (nx + 1) + buffer * xres,
195 ext[3] - ext_h * (ny + 0) + buffer * yres,
196 )
197 bounds = gws.Bounds(crs=bounds.crs, extent=e)
198 content = get_box(bounds, max_size + buffer * 2, max_size + buffer * 2)
199 gws.log.debug(f'_box_to_image (BIG): {nx=}/{xcount} {ny=}/{ycount} {len(content)=}')
200 grid.append([nx, ny, content])
202 img = gws.lib.image.from_size((max_size * xcount, max_size * ycount))
204 for nx, ny, content in grid:
205 tile = gws.lib.image.from_bytes(content)
206 tile.crop((buffer, buffer, tile.size()[0] - buffer, tile.size()[1] - buffer))
207 if annotate:
208 _annotate_image(tile, f'{nx} {ny}')
209 img.paste(tile, (nx * max_size, ny * max_size))
211 img.crop((0, 0, gws.u.to_rounded_int(width), gws.u.to_rounded_int(height)))
212 return img
215def _annotate(content, text):
216 return _annotate_image(gws.lib.image.from_bytes(content), text).to_bytes()
219def _annotate_image(img, text):
220 return img.add_text(text, x=5, y=5).add_box()