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
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1"""WMTS Service.
3Implements WMTS 1.0.0.
4This implementation only supports ``GET`` requests with ``KVP`` encoding.
6References:
7 - OGC 07-057r7 (https://portal.ogc.org/files/?artifact_id=35326)
8"""
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
21gws.ext.new.owsService('wmts')
24class Config(server.service.Config):
25 """WMTS Service configuration"""
26 pass
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]
39_DEFAULT_METADATA = gws.Metadata(
40 inspireDegreeOfConformity='notEvaluated',
41 inspireMandatoryKeyword='infoMapAccessService',
42 inspireResourceType='service',
43 inspireSpatialDataServiceType='view',
44 isoScope='dataset',
45 isoSpatialRepresentationType='vector',
46)
49class Object(server.service.Object):
50 protocol = gws.OwsProtocol.WMTS
51 supportedVersions = ['1.0.0']
52 isRasterService = True
53 isOwsCommon = True
55 tileMatrixSets: list[gws.TileMatrixSet]
57 def configure(self):
58 gws.config.util.configure_templates_for(self, extra=_DEFAULT_TEMPLATES)
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 ]
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 ]
90 def make_tile_matrices(self, extent, min_zoom, max_zoom, tile_size=256):
91 ms = []
93 # north origin
94 extent = extent[0], extent[3], extent[2], extent[1]
96 w, h = gws.gis.extent.size(extent)
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 ))
113 return ms
115 ##
117 def layer_is_suitable(self, layer: gws.Layer):
118 return not layer.isGroup and layer.canRenderBox
120 ##
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 )
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')
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')
140 bounds = self.bounds_for_tile(matrix_set_uid, matrix_uid, row, col)
141 if not bounds:
142 raise server.error.TileOutOfRange()
144 mime = sr.requested_format('FORMAT')
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 )
157 mro = gws.gis.render.render_map(mri)
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()
164 return self.image_response(sr, mro.planes[0].image, mime)
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'))
170 ##
172 def requested_layer_caps(self, sr: server.request.Object):
173 lcs = []
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)
181 if not lcs:
182 raise server.error.LayerNotDefined()
184 return gws.u.uniq(lcs)
186 def bounds_for_tile(self, matrix_set_uid, matrix_uid, row, col):
187 tms = None
188 tm = None
190 for m in self.tileMatrixSets:
191 if m.uid == matrix_set_uid:
192 tms = m
194 if not tms:
195 return
197 for m in tms.matrices:
198 if m.uid == matrix_uid:
199 tm = m
201 if not tm:
202 return
204 w, h = gws.gis.extent.size(tm.extent)
205 span = w / tm.width
207 x = tm.x + col * span
208 y = tm.y - row * span
210 bbox = x, y - span, x + span, y
211 return gws.Bounds(crs=tms.crs, extent=bbox)