Coverage for gws-app/gws/base/layer/util.py: 0%

104 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +0200

1from typing import Callable 

2 

3import math 

4 

5import gws 

6import gws.base.model 

7import gws.base.search 

8import gws.gis.crs 

9import gws.gis.extent 

10import gws.gis.mpx 

11import gws.gis.source 

12import gws.gis.zoom 

13import gws.lib.image 

14import gws.lib.metadata 

15import gws.lib.style 

16import gws.lib.svg 

17 

18 

19 

20def mapproxy_layer_config(layer: gws.Layer, mc, source_uid): 

21 mc.layer({ 

22 'name': layer.uid + '_NOCACHE', 

23 'sources': [source_uid] 

24 }) 

25 

26 tg = layer.grid 

27 

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 })) 

35 

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, 

48 } 

49 

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 

57 

58 layer.mpxCacheUid = mc.cache(front_cache_config) 

59 

60 mc.layer({ 

61 'name': layer.uid, 

62 'sources': [layer.mpxCacheUid] 

63 }) 

64 

65 

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 }) 

73 

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, 

83 })) 

84 

85 

86## 

87 

88_BOX_SIZE = 1000 

89_BOX_BUFFER = 200 

90 

91_GetBoxFn = Callable[[gws.Bounds, float, float], bytes] 

92 

93 

94def mpx_raster_render(layer: gws.Layer, lri: gws.LayerRenderInput): 

95 if lri.type == gws.LayerRenderInputType.box: 

96 

97 uid = layer.uid 

98 if not layer.cache: 

99 uid += '_NOCACHE' 

100 

101 def get_box(bounds, width, height): 

102 return gws.gis.mpx.wms_request(uid, bounds, width, height, forward=lri.extraParams) 

103 

104 content = generic_render_box(layer, lri, get_box) 

105 return gws.LayerRenderOutput(content=content) 

106 

107 if lri.type == gws.LayerRenderInputType.xyz: 

108 content = gws.gis.mpx.wmts_request( 

109 layer.uid, 

110 lri.x, 

111 lri.y, 

112 lri.z, 

113 tile_matrix=layer.grid.uid, 

114 tile_size=layer.grid.tileSize) 

115 

116 annotate = layer.root.app.developer_option('map.annotate_render') 

117 if annotate: 

118 content = _annotate(content, f'{lri.x} {lri.y} {lri.z}') 

119 

120 return gws.LayerRenderOutput(content=content) 

121 

122 

123def generic_render_box(layer: gws.Layer, lri: gws.LayerRenderInput, get_box: _GetBoxFn) -> bytes: 

124 annotate = layer.root.app.developer_option('map.annotate_render') 

125 

126 max_box_size = lri.boxSize or _BOX_SIZE 

127 box_buffer = lri.boxBuffer or _BOX_BUFFER 

128 

129 w, h = lri.view.pxSize 

130 

131 if not lri.view.rotation and w < max_box_size and h < max_box_size: 

132 # fast path: no rotation, small box 

133 content = get_box(lri.view.bounds, w, h) 

134 if annotate: 

135 content = _annotate(content, 'fast') 

136 return content 

137 

138 if not lri.view.rotation: 

139 # no rotation, big box 

140 img = _box_to_image(lri.view.bounds, w, h, max_box_size, box_buffer, annotate, get_box) 

141 return img.to_bytes() 

142 

143 # rotation: render a circumsquare around the wanted extent 

144 

145 circ = gws.gis.extent.circumsquare(lri.view.bounds.extent) 

146 d = gws.gis.extent.diagonal((0, 0, w, h)) 

147 b = gws.Bounds(crs=lri.view.bounds.crs, extent=circ) 

148 

149 img = _box_to_image(b, d, d, max_box_size, box_buffer, annotate, get_box) 

150 

151 # rotate the square (NB: PIL rotations are counter-clockwise) 

152 # and crop the square back to the wanted extent 

153 

154 img.rotate(-lri.view.rotation).crop(( 

155 d / 2 - w / 2, 

156 d / 2 - h / 2, 

157 d / 2 + w / 2, 

158 d / 2 + h / 2, 

159 )) 

160 

161 return img.to_bytes() 

162 

163 

164def _box_to_image(bounds: gws.Bounds, width: float, height: float, max_size: int, buf: int, annotate: bool, get_box: _GetBoxFn) -> gws.lib.image.Image: 

165 

166 if width < max_size and height < max_size: 

167 content = get_box(bounds, width, height) 

168 img = gws.lib.image.from_bytes(content) 

169 if annotate: 

170 img = _annotate_image(img, 'small') 

171 return img 

172 

173 xcount = math.ceil(width / max_size) 

174 ycount = math.ceil(height / max_size) 

175 

176 ext = bounds.extent 

177 

178 xres = (ext[2] - ext[0]) / width 

179 yres = (ext[3] - ext[1]) / height 

180 

181 gws.log.debug(f'_box_to_image (BIG): {xcount=} {ycount=} {xres=} {yres=}') 

182 

183 ext_w = xres * max_size 

184 ext_h = yres * max_size 

185 

186 grid = [] 

187 

188 for ny in range(ycount): 

189 for nx in range(xcount): 

190 e = ( 

191 ext[0] + ext_w * (nx + 0) - buf * xres, 

192 ext[3] - ext_h * (ny + 1) - buf * yres, 

193 ext[0] + ext_w * (nx + 1) + buf * xres, 

194 ext[3] - ext_h * (ny + 0) + buf * yres, 

195 ) 

196 bounds = gws.Bounds(crs=bounds.crs, extent=e) 

197 content = get_box(bounds, max_size + buf * 2, max_size + buf * 2) 

198 gws.log.debug(f'_box_to_image (BIG): {nx=}/{xcount} {ny=}/{ycount} {len(content)=}') 

199 grid.append([nx, ny, content]) 

200 

201 img = gws.lib.image.from_size((max_size * xcount, max_size * ycount)) 

202 

203 for nx, ny, content in grid: 

204 tile = gws.lib.image.from_bytes(content) 

205 tile.crop((buf, buf, tile.size()[0] - buf, tile.size()[1] - buf)) 

206 if annotate: 

207 _annotate_image(tile, f'{nx} {ny}') 

208 img.paste(tile, (nx * max_size, ny * max_size)) 

209 

210 img.crop((0, 0, gws.u.to_rounded_int(width), gws.u.to_rounded_int(height))) 

211 return img 

212 

213 

214def _annotate(content, text): 

215 return _annotate_image(gws.lib.image.from_bytes(content), text).to_bytes() 

216 

217 

218def _annotate_image(img, text): 

219 return img.add_text(text, x=5, y=5).add_box()