Coverage for gws-app/gws/plugin/ows_server/wmts/__init__.py: 0%

90 statements  

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

1"""WMTS Service. 

2 

3Implements WMTS 1.0.0. 

4This implementation only supports ``GET`` requests with ``KVP`` encoding. 

5 

6References: 

7 - OGC 07-057r7 (https://portal.ogc.org/files/?artifact_id=35326) 

8""" 

9 

10import gws 

11import gws.config.util 

12import gws.base.ows.server as server 

13import gws.base.web 

14import gws.gis.crs 

15import gws.gis.extent 

16import gws.lib.image 

17import gws.lib.mime 

18import gws.gis.render 

19import gws.lib.uom as units 

20 

21gws.ext.new.owsService('wmts') 

22 

23 

24class Config(server.service.Config): 

25 """WMTS Service configuration""" 

26 pass 

27 

28 

29_DEFAULT_TEMPLATES = [ 

30 gws.Config( 

31 type='py', 

32 path=gws.u.dirname(__file__) + '/templates/getCapabilities.cx.py', 

33 subject='ows.GetCapabilities', 

34 mimeTypes=[gws.lib.mime.XML], 

35 access=gws.c.PUBLIC, 

36 ), 

37] 

38 

39_DEFAULT_METADATA = gws.Metadata( 

40 inspireDegreeOfConformity='notEvaluated', 

41 inspireMandatoryKeyword='infoMapAccessService', 

42 inspireResourceType='service', 

43 inspireSpatialDataServiceType='view', 

44 isoScope='dataset', 

45 isoSpatialRepresentationType='vector', 

46) 

47 

48 

49class Object(server.service.Object): 

50 protocol = gws.OwsProtocol.WMTS 

51 supportedVersions = ['1.0.0'] 

52 isRasterService = True 

53 isOwsCommon = True 

54 

55 tileMatrixSets: list[gws.TileMatrixSet] 

56 

57 def configure(self): 

58 gws.config.util.configure_templates_for(self, extra=_DEFAULT_TEMPLATES) 

59 

60 # @TODO different matrix sets per layer 

61 self.tileMatrixSets = [ 

62 # see https://docs.opengeospatial.org/is/13-082r2/13-082r2.html#29 

63 gws.TileMatrixSet( 

64 uid=f'TMS_{b.crs.srid}', 

65 crs=b.crs, 

66 matrices=self.make_tile_matrices(b.extent, 0, 16), 

67 ) 

68 for b in self.supportedBounds 

69 ] 

70 

71 def configure_operations(self): 

72 self.supportedOperations = [ 

73 gws.OwsOperation( 

74 verb=gws.OwsVerb.GetCapabilities, 

75 formats=self.available_formats(gws.OwsVerb.GetCapabilities), 

76 handlerName='handle_get_capabilities', 

77 ), 

78 gws.OwsOperation( 

79 verb=gws.OwsVerb.GetLegendGraphic, 

80 formats=self.available_formats(gws.OwsVerb.GetLegendGraphic), 

81 handlerName='handle_get_legend_graphic', 

82 ), 

83 gws.OwsOperation( 

84 verb=gws.OwsVerb.GetTile, 

85 formats=self.available_formats(gws.OwsVerb.GetTile), 

86 handlerName='handle_get_tile', 

87 ), 

88 ] 

89 

90 def make_tile_matrices(self, extent, min_zoom, max_zoom, tile_size=256): 

91 ms = [] 

92 

93 # north origin 

94 extent = extent[0], extent[3], extent[2], extent[1] 

95 

96 w, h = gws.gis.extent.size(extent) 

97 

98 for z in range(min_zoom, max_zoom + 1): 

99 size = 1 << z 

100 res = w / (tile_size * size) 

101 ms.append(gws.TileMatrix( 

102 uid=f'{z:02d}', 

103 scale=gws.lib.uom.res_to_scale(res), 

104 x=extent[0], 

105 y=extent[1], 

106 tileWidth=tile_size, 

107 tileHeight=tile_size, 

108 width=size, 

109 height=size, 

110 extent=extent, 

111 )) 

112 

113 return ms 

114 

115 ## 

116 

117 def layer_is_suitable(self, layer: gws.Layer): 

118 return not layer.isGroup and layer.canRenderBox 

119 

120 ## 

121 

122 def handle_get_capabilities(self, sr: server.request.Object): 

123 return self.template_response( 

124 sr, 

125 sr.requested_format('FORMAT'), 

126 layerCapsList=sr.layerCapsList, 

127 tileMatrixSets=self.tileMatrixSets, 

128 ) 

129 

130 def handle_get_tile(self, sr: server.request.Object): 

131 lcs = self.requested_layer_caps(sr) 

132 if len(lcs) != 1: 

133 raise server.error.InvalidParameterValue('LAYER') 

134 

135 matrix_set_uid = sr.string_param('TILEMATRIXSET') 

136 matrix_uid = sr.string_param('TILEMATRIX') 

137 row = sr.int_param('TILEROW') 

138 col = sr.int_param('TILECOL') 

139 

140 bounds = self.bounds_for_tile(matrix_set_uid, matrix_uid, row, col) 

141 if not bounds: 

142 raise server.error.TileOutOfRange() 

143 

144 mime = sr.requested_format('FORMAT') 

145 

146 mri = gws.MapRenderInput( 

147 backgroundColor=None, 

148 bbox=bounds.extent, 

149 crs=bounds.crs, 

150 mapSize=(256, 256, gws.Uom.px), 

151 planes=[ 

152 gws.MapRenderInputPlane(type=gws.MapRenderInputPlaneType.imageLayer, layer=lc.layer) 

153 for lc in lcs 

154 ] 

155 ) 

156 

157 mro = gws.gis.render.render_map(mri) 

158 

159 if self.root.app.developer_option('ows.annotate_wmts'): 

160 e = bounds.extent 

161 text = f"{matrix_uid} {row} {col}\n{e[0]}\n{e[1]}\n{e[2]}\n{e[3]}" 

162 mro.planes[0].image = mro.planes[0].image.add_text(text, x=10, y=10).add_box() 

163 

164 return self.image_response(sr, mro.planes[0].image, mime) 

165 

166 def handle_get_legend_graphic(self, sr: server.request.Object): 

167 lcs = self.requested_layer_caps(sr) 

168 return self.render_legend(sr, lcs, sr.requested_format('FORMAT')) 

169 

170 ## 

171 

172 def requested_layer_caps(self, sr: server.request.Object): 

173 lcs = [] 

174 

175 for name in sr.list_param('LAYER'): 

176 for lc in sr.layerCapsList: 

177 if not server.layer_caps.layer_name_matches(lc, name): 

178 continue 

179 lcs.append(lc) 

180 

181 if not lcs: 

182 raise server.error.LayerNotDefined() 

183 

184 return gws.u.uniq(lcs) 

185 

186 def bounds_for_tile(self, matrix_set_uid, matrix_uid, row, col): 

187 tms = None 

188 tm = None 

189 

190 for m in self.tileMatrixSets: 

191 if m.uid == matrix_set_uid: 

192 tms = m 

193 

194 if not tms: 

195 return 

196 

197 for m in tms.matrices: 

198 if m.uid == matrix_uid: 

199 tm = m 

200 

201 if not tm: 

202 return 

203 

204 w, h = gws.gis.extent.size(tm.extent) 

205 span = w / tm.width 

206 

207 x = tm.x + col * span 

208 y = tm.y - row * span 

209 

210 bbox = x, y - span, x + span, y 

211 return gws.Bounds(crs=tms.crs, extent=bbox)