Coverage for gws-app/gws/gis/source/__init__.py: 0%

104 statements  

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

1from typing import Optional, Iterable 

2 

3import re 

4 

5import gws 

6import gws.gis.bounds 

7import gws.gis.extent 

8import gws.gis.crs 

9 

10 

11## 

12 

13class LayerFilter(gws.Data): 

14 """Source layer filter""" 

15 

16 level: int = 0 

17 """match only layers at this level""" 

18 names: Optional[list[str]] 

19 """match these layer names (top-to-bottom order)""" 

20 titles: Optional[list[str]] 

21 """match these layer titles""" 

22 pattern: gws.Regex = '' 

23 """match layers whose full path matches a pattern""" 

24 isGroup: Optional[bool] 

25 """if true, match only group layers""" 

26 isImage: Optional[bool] 

27 """if true, match only images layers""" 

28 isQueryable: Optional[bool] 

29 """if true, match only queryable layers""" 

30 isVisible: Optional[bool] 

31 """if true, match only visible layers""" 

32 

33 

34def layer_matches(sl: gws.SourceLayer, f: LayerFilter) -> bool: 

35 """Check if a source layer matches the filter""" 

36 

37 if not f: 

38 return True 

39 

40 if f.level and sl.aLevel != f.level: 

41 return False 

42 

43 if f.names and sl.name not in f.names: 

44 return False 

45 

46 if f.titles and sl.title not in f.titles: 

47 return False 

48 

49 if f.pattern and not re.search(f.pattern, sl.aPath): 

50 return False 

51 

52 if f.isGroup is not None and sl.isGroup != f.isGroup: 

53 return False 

54 

55 if f.isImage is not None and sl.isImage != f.isImage: 

56 return False 

57 

58 if f.isQueryable is not None and sl.isQueryable != f.isQueryable: 

59 return False 

60 

61 if f.isVisible is not None and sl.isVisible != f.isVisible: 

62 return False 

63 

64 return True 

65 

66 

67def check_layers(layers: Iterable[gws.SourceLayer], revert: bool = False) -> list[gws.SourceLayer]: 

68 """Insert our properties in the source layer tree. 

69 

70 Also remove empty layers. 

71 

72 Args: 

73 layers: List of source layers 

74 revert: Revert the order of layers and sub-layers. 

75 """ 

76 

77 def walk(sl, parent_path, level): 

78 if not sl: 

79 return 

80 sl.aUid = gws.u.to_uid(sl.name or sl.metadata.get('title')) 

81 sl.aPath = parent_path + '/' + sl.aUid 

82 sl.aLevel = level 

83 sl.layers = gws.u.compact(walk(c, sl.aPath, level + 1) for c in (sl.layers or [])) 

84 if revert: 

85 sl.layers = list(reversed(sl.layers)) 

86 

87 if not sl.wgsExtent and sl.layers: 

88 exts = gws.u.compact(c.wgsExtent for c in sl.layers) 

89 if exts: 

90 sl.wgsExtent = gws.gis.extent.union(exts) 

91 

92 return sl 

93 

94 ls = gws.u.compact(walk(sl, '', 1) for sl in layers) 

95 if revert: 

96 ls = list(reversed(ls)) 

97 return ls 

98 

99 

100def filter_layers( 

101 layers: list[gws.SourceLayer], 

102 slf: LayerFilter = None, 

103 is_group: bool = None, 

104 is_image: bool = None, 

105 is_queryable: bool = None, 

106 is_visible: bool = None, 

107) -> list[gws.SourceLayer]: 

108 """Filter source layers by the given layer filter.""" 

109 

110 extra = {} 

111 if is_group is not None: 

112 extra['isGroup'] = is_group 

113 if is_image is not None: 

114 extra['isImage'] = is_image 

115 if is_queryable is not None: 

116 extra['isQueryable'] = is_queryable 

117 if is_visible is not None: 

118 extra['isVisible'] = is_visible 

119 

120 if slf: 

121 if extra: 

122 slf = LayerFilter(slf, extra) 

123 elif extra: 

124 slf = LayerFilter(extra) 

125 else: 

126 return layers 

127 

128 found = [] 

129 

130 def walk(sl): 

131 # if a layer matches, add it and don't go any further 

132 # otherwise, inspect the sublayers (@TODO optimize if slf.level is given) 

133 if layer_matches(sl, slf): 

134 found.append(sl) 

135 return 

136 for sl2 in sl.layers: 

137 walk(sl2) 

138 

139 for sl in layers: 

140 walk(sl) 

141 

142 # NB: if 'names' is given, maintain the given order, which is expected to be top-to-bottom 

143 # see note in ext/layers/wms 

144 

145 if slf.names: 

146 found.sort(key=lambda sl: slf.names.index(sl.name) if sl.name in slf.names else -1) 

147 

148 return found 

149 

150 

151def combined_crs_list(layers: list[gws.SourceLayer]) -> list[gws.Crs]: 

152 """Return an intersection of crs supported by each source layer.""" 

153 

154 cs: set = set() 

155 

156 for sl in layers: 

157 if not sl.supportedCrs: 

158 continue 

159 if not cs: 

160 cs.update(sl.supportedCrs) 

161 else: 

162 cs = cs.intersection(sl.supportedCrs) 

163 

164 return list(cs) 

165 

166 

167def combined_bounds(layers: list[gws.SourceLayer], crs: gws.Crs) -> Optional[gws.Bounds]: 

168 bs = gws.u.compact(sl.wgsExtent for sl in layers) 

169 if bs: 

170 b = gws.Bounds(extent=gws.gis.extent.union(bs), crs=gws.gis.crs.WGS84) 

171 return gws.gis.bounds.transform(b, crs)