Coverage for gws-app/gws/plugin/ows_client/wms/caps.py: 100%
50 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""WMS capabilities parser."""
3from typing import Optional
5import gws
6import gws.base.ows.client
7import gws.lib.crs
8import gws.gis.source
9import gws.lib.xmlx as xmlx
10import gws.base.ows.client.parseutil as u
14def parse(xml: str, bottom_first: bool=False) -> gws.OwsCapabilities:
15 """Read WMS capabilities from the GetCapabilities XML.
17 Child layers inherit styles, CRSs, the extent, the attribution and the scale
18 range from their parents, as described in OGC 06-042, 7.2.4.8. Layers without
19 a name are neither queryable nor rendered as images.
21 Args:
22 xml: GetCapabilities XML.
23 bottom_first: ``True`` if the service lists layers bottom-first; the
24 layer lists are then reversed to be top-first.
26 Returns:
27 The parsed capabilities.
28 """
30 caps_el = xmlx.from_string(xml, gws.XmlOptions(compactWhitespace=True, removeNamespaces=True))
31 source_layers = gws.gis.source.check_layers(
32 [_layer(el) for el in caps_el.findall('Capability/Layer')],
33 revert=bottom_first
34 )
35 return gws.OwsCapabilities(
36 metadata=u.service_metadata(caps_el),
37 operations=u.service_operations(caps_el),
38 sourceLayers=source_layers,
39 version=caps_el.get('version'))
42def _layer(layer_el: gws.XmlElement, parent: Optional[gws.SourceLayer] = None) -> gws.SourceLayer:
43 """Create a source layer and its children from a ``Layer`` element."""
44 sl = gws.SourceLayer()
46 sl.isQueryable = layer_el.get('queryable') == '1'
47 sl.isVisible = True
48 sl.isExpanded = False
49 sl.metadata = u.element_metadata(layer_el)
50 sl.name = sl.metadata.get('name', '')
51 sl.styles = [u.parse_style(e) for e in layer_el.findall('Style')]
52 sl.title = sl.metadata.get('title', '')
54 # @TODO: support ScaleHint (WMS 1.1)
56 smin = layer_el.textof('MinScaleDenominator')
57 smax = layer_el.textof('MaxScaleDenominator')
58 if smax:
59 sl.scaleRange = [u.to_int(smin), u.to_int(smax)]
61 wgs_extent = u.wgs_extent(layer_el)
62 crs_list = u.supported_crs(layer_el)
64 if not parent:
65 sl.supportedCrs = crs_list or [gws.lib.crs.WGS84]
66 sl.wgsExtent = wgs_extent
68 else:
69 # OGC 06-042, 7.2.4.8 Inheritance of layer properties
71 # Style -> add
72 m = {s.name: s for s in sl.styles}
73 for s in parent.styles:
74 m[s.name] = s
75 sl.styles = list(m.values())
77 # CRS -> add
78 sl.supportedCrs = list(parent.supportedCrs)
79 for crs in crs_list:
80 if crs not in sl.supportedCrs:
81 sl.supportedCrs.append(crs)
83 # EX_GeographicBoundingBox -> replace
84 sl.wgsExtent = wgs_extent or parent.wgsExtent
86 # Dimension -> replace
87 # @TODO
89 # Attribution -> replace
90 sl.metadata.attribution = sl.metadata.attribution or parent.metadata.attribution
92 # AuthorityURL -> add
93 # @TODO
95 # MinScaleDenominator -> replace
96 sl.scaleRange = sl.scaleRange or parent.scaleRange
98 sl.defaultStyle = u.default_style(sl.styles)
99 if sl.defaultStyle:
100 sl.legendUrl = sl.defaultStyle.legendUrl
102 sl.layers = [_layer(e, sl) for e in layer_el.findall('Layer')]
103 sl.isGroup = len(sl.layers) > 0
104 sl.isImage = len(sl.layers) == 0
106 if not sl.name:
107 # some folks have unnamed layers in their caps
108 # we can't render or query them
109 sl.isQueryable = False
110 sl.isImage = False
112 return sl