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

64 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +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.gis.crs 

34import gws.gis.extent 

35import gws.gis.source 

36 

37 

38from . import caps 

39 

40 

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

42 bottomFirst: bool = False 

43 """true if layers are listed from bottom to top""" 

44 

45 

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

47 protocol = gws.OwsProtocol.WMS 

48 

49 def configure(self): 

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

51 

52 self.metadata = cc.metadata 

53 self.sourceLayers = cc.sourceLayers 

54 self.version = cc.version 

55 

56 self.configure_operations(cc.operations) 

57 

58 DEFAULT_GET_FEATURE_LIMIT = 100 

59 

60 def get_features(self, search, source_layers): 

61 v3 = self.version >= '1.3' 

62 

63 shape = search.shape 

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

65 return [] 

66 

67 request_crs = self.forceCrs 

68 if not request_crs: 

69 request_crs = gws.gis.crs.best_match( 

70 shape.crs, 

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

72 

73 box_size_m = 500 

74 box_size_deg = 1 

75 box_size_px = 500 

76 

77 size = None 

78 

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

80 size = box_size_px * search.resolution 

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

82 # @TODO use search.resolution here as well 

83 size = box_size_deg 

84 if not size: 

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

86 return [] 

87 

88 bbox = ( 

89 shape.x - (size / 2), 

90 shape.y - (size / 2), 

91 shape.x + (size / 2), 

92 shape.y + (size / 2), 

93 ) 

94 

95 bbox = gws.gis.extent.transform(bbox, shape.crs, request_crs) 

96 

97 always_xy = self.alwaysXY or not v3 

98 if request_crs.isYX and not always_xy: 

99 bbox = gws.gis.extent.swap_xy(bbox) 

100 

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

102 

103 params = { 

104 'BBOX': bbox, 

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

106 'WIDTH': box_size_px, 

107 'HEIGHT': box_size_px, 

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

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

110 'LAYERS': layer_names, 

111 'QUERY_LAYERS': layer_names, 

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

113 'VERSION': self.version, 

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

115 } 

116 

117 if search.extraParams: 

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

119 

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

121 if not op: 

122 return [] 

123 

124 if op.preferredFormat: 

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

126 

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

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

129 

130 fdata = gws.base.ows.client.featureinfo.parse(text, default_crs=request_crs, always_xy=always_xy) 

131 

132 if fdata is None: 

133 gws.log.debug(f'get_features: NOT_PARSED params={params!r}') 

134 return [] 

135 

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

137 

138 for fd in fdata: 

139 if fd.shape: 

140 fd.shape = fd.shape.transformed_to(shape.crs) 

141 

142 return fdata