Coverage for gws-app/gws/base/ows/server/layer_caps.py: 74%
100 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Utilities to deal with LayerCaps objects."""
3from typing import Optional
5import gws
6import gws.lib.extent
7import gws.lib.uom
8import gws.lib.xmlx as xmlx
10from . import core
13def for_layer(layer: gws.Layer, user: gws.User, service: Optional[gws.OwsService] = None) -> core.LayerCaps:
14 """Create ``LayerCaps`` for a layer."""
16 lc = core.LayerCaps(leaves=[], children=[])
18 lc.layer = layer
19 lc.title = layer.title
20 lc.model = layer.root.app.modelMgr.find_model(layer.ows, layer, user=user, access=gws.Access.read)
22 geom_name = layer.ows.geometryName
23 if not geom_name and lc.model:
24 geom_name = lc.model.geometryName
25 if not geom_name:
26 geom_name = 'geometry'
28 lc.layerName = layer.ows.layerName
29 lc.featureName = layer.ows.featureName
30 lc.geometryName = geom_name
32 lc.xmlNamespace = layer.ows.xmlNamespace
34 if lc.xmlNamespace:
35 lc.layerNameQ = xmlx.namespace.qualify_name(lc.layerName, lc.xmlNamespace)
36 lc.featureNameQ = xmlx.namespace.qualify_name(lc.featureName, lc.xmlNamespace)
37 lc.geometryNameQ = xmlx.namespace.qualify_name(lc.geometryName, lc.xmlNamespace)
38 else:
39 lc.layerNameQ = lc.layerName
40 lc.featureNameQ = lc.featureName
41 lc.geometryNameQ = lc.geometryName
43 lc.hasLegend = layer.hasLegend
44 lc.isSearchable = layer.isSearchable
46 scales = [gws.lib.uom.res_to_scale(r) for r in layer.resolutions]
47 lc.minScale = int(min(scales))
48 lc.maxScale = int(max(scales))
50 lc.bounds = []
51 if service:
52 lc.bounds = [
53 gws.Bounds(
54 crs=b.crs,
55 extent=gws.lib.extent.transform_from_wgs(layer.wgsExtent, b.crs),
56 )
57 for b in service.supportedBounds
58 ]
60 return lc
63def layer_name_matches(lc: core.LayerCaps, name: str) -> bool:
64 """Check if the layer name in the caps matches the given name."""
66 if ':' in name:
67 return name == lc.layerNameQ
68 else:
69 return name == lc.layerName
72def feature_name_matches(lc: core.LayerCaps, name: str, xmlns_replacements: dict) -> bool:
73 """Check if the feature name in the caps matches the given name."""
75 if name == lc.featureNameQ:
76 return True
78 if ':' not in name:
79 return name == lc.featureName
81 custom_xmlns, _, name = xmlx.namespace.split_name(name)
82 if name == lc.featureName and lc.xmlNamespace and lc.xmlNamespace.uid in xmlns_replacements:
83 return xmlns_replacements[lc.xmlNamespace.uid] == custom_xmlns
85 return False
88def xml_schema(lcs: list[core.LayerCaps], user: gws.User) -> tuple[gws.XmlElement, gws.XmlOptions]:
89 """Create an ad-hoc XML Schema for a list of `LayerCaps`."""
91 ns = None
93 for lc in lcs:
94 if not lc.xmlNamespace:
95 raise gws.NotFoundError(f'xml_schema: {lc.layer.uid}: no xmlns')
96 if not lc.model:
97 raise gws.NotFoundError(f'xml_schema: {lc.layer.uid}: no model')
98 if not ns:
99 ns = lc.xmlNamespace
100 elif lc.xmlNamespace.xmlns != ns.xmlns:
101 raise gws.NotFoundError(f'xml_schema: {lc.layer.uid}: wrong xmlns: {ns.xmlns=} {lc.xmlNamespace.xmlns=}')
103 if not ns:
104 raise gws.NotFoundError('xml_schema: no xmlns found')
106 opts = gws.XmlOptions()
107 opts.namespaces = {}
108 opts.namespaces[ns.xmlns] = ns
110 tag = [
111 'xsd:schema',
112 {
113 'targetNamespace': ns.uri,
114 'elementFormDefault': 'qualified',
115 },
116 ]
118 if ns.extendsGml:
119 gml = xmlx.namespace.require('gml')
120 opts.namespaces[gml.xmlns] = gml
121 tag.append(['xsd:import', {'namespace': gml.uri, 'schemaLocation': gml.schemaLocation}])
123 seen = set()
125 for lc in lcs:
126 if lc.featureName in seen:
127 continue
128 seen.add(lc.featureName)
130 elements = []
132 for f in gws.u.require(lc.model).fields:
133 if user.can_read(f):
134 elements.append(
135 [
136 'xsd:element',
137 {
138 'maxOccurs': '1',
139 'minOccurs': '0',
140 'nillable': 'false' if f.isRequired else 'true',
141 'name': f.name,
142 'type': _xsd_type(f),
143 },
144 ]
145 )
147 type_name = f'{lc.featureName}Type'
149 type_def = []
150 type_def.append('xsd:complexContent')
151 if ns.extendsGml:
152 type_def.append(
153 ['xsd:extension', {'base': 'gml:AbstractFeatureType'}, ['xsd:sequence', elements]],
154 )
155 else:
156 type_def.append(['xsd:sequence', elements])
158 tag.append(['xsd:complexType', {'name': type_name}, type_def])
160 atts = {
161 'name': lc.featureName,
162 'type': ns.xmlns + ':' + type_name,
163 }
164 if ns.extendsGml:
165 atts['substitutionGroup'] = 'gml:AbstractFeature'
167 tag.append(['xsd:element', atts])
169 return xmlx.tag(*tag), opts
172def _xsd_type(f: gws.ModelField) -> str:
173 """Get the XSD type for a model field."""
175 if f.attributeType != gws.AttributeType.geometry:
176 return _ATTR_TO_XSD.get(f.attributeType, 'xsd:string')
178 typ = gws.u.get(f, 'geometryType')
179 if not typ:
180 return 'gml:GeometryPropertyType'
181 return _GEOM_TO_XSD.get(typ, 'gml:GeometryPropertyType')
184# map attributes types to XSD
185# https://www.w3.org/TR/xmlschema11-2/#built-in-primitive-datatypes
187_ATTR_TO_XSD = {
188 gws.AttributeType.bool: 'xsd:boolean',
189 gws.AttributeType.bytes: 'xsd:hexBinary',
190 gws.AttributeType.date: 'xsd:date',
191 gws.AttributeType.datetime: 'xsd:dateTime',
192 gws.AttributeType.feature: '',
193 gws.AttributeType.featurelist: '',
194 gws.AttributeType.file: '',
195 gws.AttributeType.float: 'xsd:float',
196 gws.AttributeType.floatlist: '',
197 gws.AttributeType.geometry: 'gml:GeometryPropertyType',
198 gws.AttributeType.int: 'xsd:decimal',
199 gws.AttributeType.intlist: '',
200 gws.AttributeType.str: 'xsd:string',
201 gws.AttributeType.strlist: '',
202 gws.AttributeType.time: 'xsd:time',
203}
205_GEOM_TO_XSD = {
206 gws.GeometryType.point: 'gml:PointPropertyType',
207 gws.GeometryType.linestring: 'gml:CurvePropertyType',
208 gws.GeometryType.polygon: 'gml:SurfacePropertyType',
209 gws.GeometryType.multipoint: 'gml:MultiPointPropertyType',
210 gws.GeometryType.multilinestring: 'gml:MultiCurvePropertyType',
211 gws.GeometryType.multipolygon: 'gml:MultiSurfacePropertyType',
212 gws.GeometryType.multicurve: 'gml:MultiCurvePropertyType',
213 gws.GeometryType.multisurface: 'gml:MultiSurfacePropertyType',
214 gws.GeometryType.linearring: 'gml:LinearRingPropertyType',
215 gws.GeometryType.tin: 'gml:TriangulatedSurfacePropertyType',
216 gws.GeometryType.surface: 'gml:SurfacePropertyType',
217}