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
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1from typing import Optional, Iterable
3import re
5import gws
6import gws.gis.bounds
7import gws.gis.extent
8import gws.gis.crs
11##
13class LayerFilter(gws.Data):
14 """Source layer filter"""
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"""
34def layer_matches(sl: gws.SourceLayer, f: LayerFilter) -> bool:
35 """Check if a source layer matches the filter"""
37 if not f:
38 return True
40 if f.level and sl.aLevel != f.level:
41 return False
43 if f.names and sl.name not in f.names:
44 return False
46 if f.titles and sl.title not in f.titles:
47 return False
49 if f.pattern and not re.search(f.pattern, sl.aPath):
50 return False
52 if f.isGroup is not None and sl.isGroup != f.isGroup:
53 return False
55 if f.isImage is not None and sl.isImage != f.isImage:
56 return False
58 if f.isQueryable is not None and sl.isQueryable != f.isQueryable:
59 return False
61 if f.isVisible is not None and sl.isVisible != f.isVisible:
62 return False
64 return True
67def check_layers(layers: Iterable[gws.SourceLayer], revert: bool = False) -> list[gws.SourceLayer]:
68 """Insert our properties in the source layer tree.
70 Also remove empty layers.
72 Args:
73 layers: List of source layers
74 revert: Revert the order of layers and sub-layers.
75 """
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))
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)
92 return sl
94 ls = gws.u.compact(walk(sl, '', 1) for sl in layers)
95 if revert:
96 ls = list(reversed(ls))
97 return ls
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."""
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
120 if slf:
121 if extra:
122 slf = LayerFilter(slf, extra)
123 elif extra:
124 slf = LayerFilter(extra)
125 else:
126 return layers
128 found = []
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)
139 for sl in layers:
140 walk(sl)
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
145 if slf.names:
146 found.sort(key=lambda sl: slf.names.index(sl.name) if sl.name in slf.names else -1)
148 return found
151def combined_crs_list(layers: list[gws.SourceLayer]) -> list[gws.Crs]:
152 """Return an intersection of crs supported by each source layer."""
154 cs: set = set()
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)
164 return list(cs)
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)