Coverage for gws-app/gws/base/ows/server/layer_caps.py: 0%

88 statements  

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

1"""Utilities to deal with LayerCaps objects.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.gis.extent 

7import gws.lib.uom 

8import gws.lib.xmlx as xmlx 

9 

10from . import core 

11 

12 

13def for_layer(layer: gws.Layer, user: gws.User, service: Optional[gws.OwsService] = None) -> core.LayerCaps: 

14 """Create ``LayerCaps`` for a layer.""" 

15 

16 lc = core.LayerCaps(leaves=[], children=[]) 

17 

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) 

21 

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' 

27 

28 lc.layerName = layer.ows.layerName 

29 lc.featureName = layer.ows.featureName 

30 lc.geometryName = geom_name 

31 

32 lc.xmlNamespace = layer.ows.xmlNamespace 

33 

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 

42 

43 lc.hasLegend = layer.hasLegend 

44 lc.isSearchable = layer.isSearchable 

45 

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)) 

49 

50 lc.bounds = [] 

51 if service: 

52 lc.bounds = [ 

53 gws.Bounds( 

54 crs=b.crs, 

55 extent=gws.gis.extent.transform_from_wgs(layer.wgsExtent, b.crs) 

56 ) 

57 for b in service.supportedBounds 

58 ] 

59 

60 return lc 

61 

62 

63def layer_name_matches(lc: core.LayerCaps, name: str) -> bool: 

64 """Check if the layer name in the caps matches the given name.""" 

65 

66 if ':' in name: 

67 return name == lc.layerNameQ 

68 else: 

69 return name == lc.layerName 

70 

71 

72def feature_name_matches(lc: core.LayerCaps, name: str) -> bool: 

73 """Check if the feature name in the caps matches the given name.""" 

74 

75 if ':' in name: 

76 return name == lc.featureNameQ 

77 else: 

78 return name == lc.featureName 

79 

80 

81def xml_schema(lcs: list[core.LayerCaps], user: gws.User) -> gws.XmlElement: 

82 """Create an ad-hoc XML Schema for a list of `LayerCaps`.""" 

83 

84 ns = None 

85 

86 for lc in lcs: 

87 if not lc.xmlNamespace: 

88 gws.log.debug(f'xml_schema: skip {lc.layer.uid}: no xmlns') 

89 continue 

90 if not lc.model: 

91 gws.log.debug(f'xml_schema: skip {lc.layer.uid}: no model') 

92 continue 

93 if not ns: 

94 ns = lc.xmlNamespace 

95 continue 

96 if lc.xmlNamespace.xmlns != ns.xmlns: 

97 gws.log.debug(f'xml_schema: skip {lc.layer.uid}: wrong xmlns') 

98 continue 

99 

100 if not ns: 

101 raise gws.NotFoundError('xml_schema: no xmlns found') 

102 

103 tag = [ 

104 'xsd:schema', 

105 { 

106 f'xmlns': ns.uri, 

107 'targetNamespace': ns.uri, 

108 'elementFormDefault': 'qualified', 

109 } 

110 ] 

111 

112 if ns.extendsGml: 

113 gml = xmlx.namespace.get('gml3') 

114 tag[1][f'xmlns:{gml.xmlns}'] = gml.uri 

115 tag.append(['xsd:import', {'namespace': gml.uri, 'schemaLocation': gml.schemaLocation}]) 

116 

117 for lc in lcs: 

118 elements = [] 

119 for f in lc.model.fields: 

120 if user.can_read(f): 

121 if f.attributeType == gws.AttributeType.geometry: 

122 typ = _GEOM_TO_XSD.get(gws.u.get(f, 'geometryType')) 

123 else: 

124 typ = _ATTR_TO_XSD.get(f.attributeType) 

125 

126 if typ: 

127 elements.append(['xsd:element', { 

128 'maxOccurs': '1', 

129 'minOccurs': '0', 

130 'nillable': 'false' if f.isRequired else 'true', 

131 'name': f.name, 

132 'type': typ, 

133 }]) 

134 

135 type_name = f'{lc.featureName}Type' 

136 

137 type_def = ['xsd:complexContent'] 

138 if ns.extendsGml: 

139 type_def.append(['xsd:extension', {'base': 'gml:AbstractFeatureType'}, ['xsd:sequence', elements]]) 

140 else: 

141 type_def.append(['xsd:sequence', elements]) 

142 

143 atts = { 

144 'name': lc.featureName, 

145 'type': type_name, 

146 } 

147 if ns.extendsGml: 

148 atts['substitutionGroup'] = 'gml:AbstractFeature' 

149 

150 tag.append(['xsd:complexType', {'name': type_name}, type_def]) 

151 tag.append(['xsd:element', atts]) 

152 

153 return xmlx.tag(*tag) 

154 

155 

156# map attributes types to XSD 

157# https://www.w3.org/TR/xmlschema11-2/#built-in-primitive-datatypes 

158 

159_ATTR_TO_XSD = { 

160 gws.AttributeType.bool: 'xsd:boolean', 

161 gws.AttributeType.bytes: 'xsd:hexBinary', 

162 gws.AttributeType.date: 'xsd:date', 

163 gws.AttributeType.datetime: 'xsd:dateTime', 

164 gws.AttributeType.feature: '', 

165 gws.AttributeType.featurelist: '', 

166 gws.AttributeType.file: '', 

167 gws.AttributeType.float: 'xsd:float', 

168 gws.AttributeType.floatlist: '', 

169 gws.AttributeType.geometry: 'gml:GeometryPropertyType', 

170 gws.AttributeType.int: 'xsd:decimal', 

171 gws.AttributeType.intlist: '', 

172 gws.AttributeType.str: 'xsd:string', 

173 gws.AttributeType.strlist: '', 

174 gws.AttributeType.time: 'xsd:time', 

175} 

176 

177_GEOM_TO_XSD = { 

178 gws.GeometryType.point: 'gml:PointPropertyType', 

179 gws.GeometryType.linestring: 'gml.LineStringPropertyType', 

180 gws.GeometryType.polygon: 'gml:PolygonPropertyType', 

181 gws.GeometryType.multipoint: 'gml:MultiPointPropertyType', 

182 gws.GeometryType.multilinestring: 'gml:MultiLineStringPropertyType', 

183 gws.GeometryType.multipolygon: 'gml:MultiPolygonPropertyType', 

184 gws.GeometryType.multicurve: 'gml:MultiCurvePropertyType', 

185 gws.GeometryType.multisurface: 'gml:MultiSurfacePropertyType', 

186 gws.GeometryType.linearring: 'gml:LinearRingPropertyType', 

187 gws.GeometryType.tin: 'gml:TinPropertyType', 

188 gws.GeometryType.surface: 'gml:SurfacePropertyType', 

189}