Coverage for gws-app/gws/plugin/ows_client/wms/provider.py: 0%

65 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 23:09 +0200

1"""WMS provider. 

2 

3References. 

4 

5 - OGC 01-068r3: WMS 1.1.1 

6 - OGC 06-042: WMS 1.3.0 

7 

8see also https://docs.geoserver.org/latest/en/user/services/wms/reference.html 

9 

10A note on layer order: 

11 

12Internally we always list source layers topmost layer first, 

13which corresponds to the layer tree display. 

14 

15WMS capabilities are assumed to be top-first by default, 

16for servers with bottom-first caps, set ``bottomFirst=True``, 

17in which case the capabilities parser will revert all layer lists. 

18 

19The order of GetMap is always bottom first: 

20 

21> A WMS shall render the requested layers by drawing the leftmost in the list bottommost, 

22> the next one over that, and so on. (OGC 06-042, 7.3.3.3) 

23 

24therefore when invoking GetMap, our layer lists should be reversed. 

25 

26""" 

27 

28from typing import Optional, cast 

29 

30import gws 

31import gws.base.ows.client 

32import gws.config.util 

33import gws.lib.crs 

34import gws.lib.extent 

35import gws.gis.source 

36 

37 

38from . import caps 

39 

40 

41class Config(gws.base.ows.client.provider.Config): 

42 """WMS provider configuration.""" 

43 

44 bottomFirst: bool = False 

45 """True if layers are listed from bottom to top.""" 

46 

47 

48class Object(gws.base.ows.client.provider.Object): 

49 protocol = gws.OwsProtocol.WMS 

50 

51 def configure(self): 

52 cc = caps.parse(self.get_capabilities(), self.cfg('bottomFirst', default=False)) 

53 

54 self.metadata = cc.metadata 

55 self.sourceLayers = cc.sourceLayers 

56 self.version = cc.version 

57 

58 self.configure_operations(cc.operations) 

59 

60 DEFAULT_GET_FEATURE_LIMIT = 100 

61 

62 def get_features(self, search, source_layers): 

63 v3 = self.version >= '1.3' 

64 

65 shape = search.shape 

66 if not shape or shape.type != gws.GeometryType.point: 

67 return [] 

68 

69 request_crs = self.forceCrs 

70 if not request_crs: 

71 request_crs = gws.lib.crs.best_match( 

72 shape.crs, 

73 gws.gis.source.combined_crs_list(source_layers)) 

74 

75 box_size_m = 500 

76 box_size_deg = 1 

77 box_size_px = 500 

78 

79 size = None 

80 

81 if shape.crs.uom == gws.Uom.m: 

82 size = box_size_px * search.resolution 

83 if shape.crs.uom == gws.Uom.deg: 

84 # @TODO use search.resolution here as well 

85 size = box_size_deg 

86 if not size: 

87 gws.log.debug('cannot request crs {crs!r}, unsupported unit') 

88 return [] 

89 

90 bbox = ( 

91 shape.x - (size / 2), 

92 shape.y - (size / 2), 

93 shape.x + (size / 2), 

94 shape.y + (size / 2), 

95 ) 

96 

97 bbox = gws.lib.extent.transform(bbox, shape.crs, request_crs) 

98 

99 always_xy = self.alwaysXY or not v3 

100 if request_crs.isYX and not always_xy: 

101 bbox = gws.lib.extent.swap_xy(bbox) 

102 

103 layer_names = [sl.name for sl in source_layers] 

104 

105 params = { 

106 'BBOX': bbox, 

107 'CRS' if v3 else 'SRS': request_crs.to_string(gws.CrsFormat.epsg), 

108 'WIDTH': box_size_px, 

109 'HEIGHT': box_size_px, 

110 'I' if v3 else 'X': box_size_px >> 1, 

111 'J' if v3 else 'Y': box_size_px >> 1, 

112 'LAYERS': layer_names, 

113 'QUERY_LAYERS': layer_names, 

114 'STYLES': [''] * len(layer_names), 

115 'VERSION': self.version, 

116 'FEATURE_COUNT': search.limit or self.DEFAULT_GET_FEATURE_LIMIT, 

117 } 

118 

119 if search.extraParams: 

120 params = gws.u.merge(params, gws.u.to_upper_dict(search.extraParams)) 

121 

122 op = self.get_operation(gws.OwsVerb.GetFeatureInfo) 

123 if not op: 

124 return [] 

125 

126 if op.preferredFormat: 

127 params.setdefault('INFO_FORMAT', op.preferredFormat) 

128 

129 args = self.prepare_operation(op, params=params) 

130 text = gws.base.ows.client.request.get_text(args) 

131 

132 try: 

133 records = gws.base.ows.client.featureinfo.parse(text, default_crs=request_crs, always_xy=self.alwaysXY) 

134 except gws.Error as exc: 

135 gws.log.error(f'get_features: parse error: {exc!r}') 

136 return [] 

137 

138 gws.log.debug(f'get_features: FOUND={len(records)} params={params!r}') 

139 

140 for rec in records: 

141 if rec.shape: 

142 rec.shape = rec.shape.transformed_to(shape.crs) 

143 

144 return records