Coverage for gws-app/gws/plugin/ows_client/wms/caps.py: 0%
50 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"""WMS Capabilities parser."""
3from typing import Optional
5import gws
6import gws.base.ows.client
7import gws.gis.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 Args:
18 xml: GetCapabilities XML
19 bottom_first: True if layers are listed bottom-first
21 Returns:
22 The Capabilities object.
23 """
25 caps_el = xmlx.from_string(xml, compact_whitespace=True, remove_namespaces=True)
26 source_layers = gws.gis.source.check_layers(
27 [_layer(el) for el in caps_el.findall('Capability/Layer')],
28 revert=bottom_first
29 )
30 return gws.OwsCapabilities(
31 metadata=u.service_metadata(caps_el),
32 operations=u.service_operations(caps_el),
33 sourceLayers=source_layers,
34 version=caps_el.get('version'))
37def _layer(layer_el: gws.XmlElement, parent: Optional[gws.SourceLayer] = None) -> gws.SourceLayer:
38 sl = gws.SourceLayer()
40 sl.isQueryable = layer_el.get('queryable') == '1'
41 sl.isVisible = True
42 sl.isExpanded = False
43 sl.metadata = u.element_metadata(layer_el)
44 sl.name = sl.metadata.get('name', '')
45 sl.styles = [u.parse_style(e) for e in layer_el.findall('Style')]
46 sl.title = sl.metadata.get('title', '')
48 # @TODO: support ScaleHint (WMS 1.1)
50 smin = layer_el.textof('MinScaleDenominator')
51 smax = layer_el.textof('MaxScaleDenominator')
52 if smax:
53 sl.scaleRange = [u.to_int(smin), u.to_int(smax)]
55 wgs_extent = u.wgs_extent(layer_el)
56 crs_list = u.supported_crs(layer_el)
58 if not parent:
59 sl.supportedCrs = crs_list or [gws.gis.crs.WGS84]
60 sl.wgsExtent = wgs_extent
62 else:
63 # OGC 06-042, 7.2.4.8 Inheritance of layer properties
65 # Style -> add
66 m = {s.name: s for s in sl.styles}
67 for s in parent.styles:
68 m[s.name] = s
69 sl.styles = list(m.values())
71 # CRS -> add
72 sl.supportedCrs = list(parent.supportedCrs)
73 for crs in crs_list:
74 if crs not in sl.supportedCrs:
75 sl.supportedCrs.append(crs)
77 # EX_GeographicBoundingBox -> replace
78 sl.wgsExtent = wgs_extent or parent.wgsExtent
80 # Dimension -> replace
81 # @TODO
83 # Attribution -> replace
84 sl.metadata.attribution = sl.metadata.attribution or parent.metadata.attribution
86 # AuthorityURL -> add
87 # @TODO
89 # MinScaleDenominator -> replace
90 sl.scaleRange = sl.scaleRange or parent.scaleRange
92 sl.defaultStyle = u.default_style(sl.styles)
93 if sl.defaultStyle:
94 sl.legendUrl = sl.defaultStyle.legendUrl
96 sl.layers = [_layer(e, sl) for e in layer_el.findall('Layer')]
97 sl.isGroup = len(sl.layers) > 0
98 sl.isImage = len(sl.layers) == 0
100 if not sl.name:
101 # some folks have unnamed layers in their caps
102 # we can't render or query them
103 sl.isQueryable = False
104 sl.isImage = False
106 return sl