Coverage for gws-app/gws/plugin/ows_client/wmts/layer.py: 0%

108 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-24 12:46 +0200

1from typing import Optional 

2 

3import gws 

4import gws.base.layer 

5import gws.config.util 

6import gws.lib.bounds 

7import gws.lib.crs 

8import gws.gis.source 

9import gws.gis.zoom 

10 

11from . import provider 

12 

13gws.ext.new.layer('wmts') 

14 

15 

16class Config(gws.base.layer.Config): 

17 """WMTS layer""" 

18 

19 provider: provider.Config 

20 """WMTS provider.""" 

21 display: gws.LayerDisplayMode = gws.LayerDisplayMode.tile 

22 """Layer display mode.""" 

23 sourceLayers: Optional[gws.gis.source.LayerFilter] 

24 """Source layer filter.""" 

25 style: Optional[str] 

26 """WMTS style name.""" 

27 

28 

29class Object(gws.base.layer.image.Object): 

30 serviceProvider: provider.Object 

31 sourceLayers: list[gws.SourceLayer] 

32 

33 activeLayer: gws.SourceLayer 

34 activeStyle: gws.SourceStyle 

35 activeTms: gws.TileMatrixSet 

36 

37 def configure(self): 

38 self.configure_layer() 

39 

40 def configure_provider(self): 

41 return gws.config.util.configure_service_provider_for(self, provider.Object) 

42 

43 def configure_sources(self): 

44 if super().configure_sources(): 

45 return True 

46 

47 gws.u.require(self.serviceProvider, 'failed to configure service provider') 

48 

49 self.configure_source_layers() 

50 if not self.sourceLayers: 

51 raise gws.Error('no source layers found') 

52 

53 self.activeLayer = self.sourceLayers[0] 

54 self.configure_tms() 

55 self.configure_style() 

56 

57 def configure_source_layers(self): 

58 return gws.config.util.configure_source_layers_for(self, self.serviceProvider.sourceLayers, is_image=True) 

59 

60 def configure_tms(self): 

61 crs = self.serviceProvider.forceCrs 

62 if not crs: 

63 crs = gws.lib.crs.best_match(self.mapCrs, [tms.crs for tms in self.activeLayer.tileMatrixSets]) 

64 tms_list = [tms for tms in self.activeLayer.tileMatrixSets if tms.crs == crs] 

65 if not tms_list: 

66 raise gws.Error(f'no TMS for {crs} in {self.serviceProvider.url}') 

67 self.activeTms = tms_list[0] 

68 

69 def configure_style(self): 

70 p = self.cfg('styleName') 

71 if p: 

72 for style in self.activeLayer.styles: 

73 if style.name == p: 

74 self.activeStyle = style 

75 return True 

76 raise gws.Error(f'style {p!r} not found') 

77 

78 for style in self.activeLayer.styles: 

79 if style.isDefault: 

80 self.activeStyle = style 

81 return True 

82 

83 self.activeStyle = gws.SourceStyle(name='default') 

84 return True 

85 

86 # 

87 # reprojecting the world doesn't make sense, just use the map extent here 

88 # @TODO maybe look for more sensible grid alignment 

89 # 

90 # def configure_bounds(self): 

91 # if super().configure_bounds(): 

92 # return True 

93 # src_bounds = gws.Bounds(crs=self.activeTms.crs, extent=self.activeTms.matrices[0].extent) 

94 # self.bounds = gws.lib.bounds.transform(src_bounds, self.mapCrs) 

95 # return True 

96 

97 def configure_resolutions(self): 

98 if super().configure_resolutions(): 

99 return True 

100 res = [gws.lib.uom.scale_to_res(m.scale) for m in self.activeTms.matrices] 

101 self.resolutions = sorted(res, reverse=True) 

102 return True 

103 

104 def configure_grid(self): 

105 p = self.cfg('grid', default=gws.Config()) 

106 self.grid = gws.TileGrid( 

107 origin=p.origin or gws.Origin.nw, 

108 tileSize=p.tileSize or self.activeTms.matrices[0].tileWidth, 

109 ) 

110 if p.extent: 

111 self.grid.bounds = gws.Bounds(crs=self.mapCrs, extent=p.extent) 

112 elif self.activeTms.crs == self.mapCrs: 

113 self.grid.bounds = gws.Bounds(crs=self.mapCrs, extent=self.activeTms.matrices[0].extent) 

114 else: 

115 self.grid.bounds = self.bounds 

116 

117 if p.resolutions: 

118 self.grid.resolutions = p.resolutions 

119 else: 

120 self.grid.resolutions = gws.gis.zoom.resolutions_from_bounds(self.grid.bounds, self.grid.tileSize) 

121 

122 def configure_legend(self): 

123 if super().configure_legend(): 

124 return True 

125 url = self.activeStyle.legendUrl 

126 if url: 

127 self.legend = self.create_child(gws.ext.object.legend, type='remote', urls=[url]) 

128 return True 

129 

130 def configure_metadata(self): 

131 if super().configure_metadata(): 

132 return True 

133 self.metadata = self.serviceProvider.metadata 

134 return True 

135 

136 def mapproxy_config(self, mc): 

137 url = self.serviceProvider.tile_url_template(self.activeLayer, self.activeTms, self.activeStyle) 

138 

139 # mapproxy encoding 

140 

141 url = url.replace('{TileMatrix}', '%(z)02d') 

142 url = url.replace('{TileCol}', '%(x)d') 

143 url = url.replace('{TileRow}', '%(y)d') 

144 

145 source_grid = self.serviceProvider.grid_for_tms(self.activeTms) 

146 

147 if source_grid.origin == gws.Origin.nw: 

148 origin = 'nw' 

149 elif source_grid.origin == gws.Origin.sw: 

150 origin = 'sw' 

151 else: 

152 raise gws.Error(f'invalid grid origin {source_grid.origin!r}') 

153 

154 source_grid_uid = mc.grid( 

155 gws.u.compact( 

156 { 

157 'origin': origin, 

158 'srs': source_grid.bounds.crs.epsg, 

159 'bbox': source_grid.bounds.extent, 

160 'res': source_grid.resolutions, 

161 'tile_size': [source_grid.tileSize, source_grid.tileSize], 

162 } 

163 ) 

164 ) 

165 

166 src_uid = gws.base.layer.util.mapproxy_back_cache_config(self, mc, url, source_grid_uid) 

167 gws.base.layer.util.mapproxy_layer_config(self, mc, src_uid) 

168 

169 ## 

170 

171 def render(self, lri): 

172 return gws.base.layer.util.mpx_raster_render(self, lri)