Coverage for gws-app/gws/gis/extent/__init__.py: 28%
92 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
3import math
4import re
6import gws
7import gws.gis.crs
10def from_string(s: str) -> Optional[gws.Extent]:
11 """Create an extent from a comma-separated string "1000,2000,20000 40000".
13 Args:
14 s: ``"x-min,y-min,x-max,y-max"``
16 Returns:
17 An extent.
18 """
20 return from_list(s.split(','))
23def from_list(ls: list) -> Optional[gws.Extent]:
24 """Create an extent from a list of values.
26 Args:
27 ls: ``[x-min,y-min,x-max,y-max]``
29 Returns:
30 An extent."""
32 return _check(ls)
35def from_points(a: gws.Point, b: gws.Point) -> gws.Extent:
36 """Create an extent from two points.
38 Args:
39 a:``(x-min,y-min)``
40 b:``(x-max,y-max)``
42 Returns:
43 An extent."""
45 return _check([a[0], a[1], b[0], b[1]])
48def from_center(xy: gws.Point, size: gws.Size) -> gws.Extent:
49 """Create an extent with certain size from a center-point.
51 Args:
52 xy: Center-point ``(x,y)``
53 size: Extent's size.
55 Returns:
56 An Extent."""
58 return (
59 xy[0] - size[0] / 2,
60 xy[1] - size[1] / 2,
61 xy[0] + size[0] / 2,
62 xy[1] + size[1] / 2,
63 )
66def from_box(box: str) -> Optional[gws.Extent]:
67 """Create an extent from a Postgis BOX(1000 2000,20000 40000).
69 Args:
70 box: Postgis BOX.
72 Returns:
73 An extent."""
75 if not box:
76 return None
78 m = re.match(r'^BOX\((.+?)\)$', str(box).upper())
79 if not m:
80 return None
82 a, b = m.group(1).split(',')
83 c, d = a.split(), b.split()
85 return _check([c[0], c[1], d[0], d[1]])
88#
90def intersection(exts: list[gws.Extent]) -> Optional[gws.Extent]:
91 """Creates an extent that is the intersection of all given extents.
93 Args:
94 exts: Extents.
96 Returns:
97 An extent.
98 """
100 if not exts:
101 return
103 res = (-math.inf, -math.inf, math.inf, math.inf)
105 for ext in exts:
106 _sort(ext)
107 if not intersect(res, ext):
108 return
109 res = (
110 max(res[0], ext[0]),
111 max(res[1], ext[1]),
112 min(res[2], ext[2]),
113 min(res[3], ext[3]),
114 )
115 return res
118def center(e: gws.Extent) -> gws.Point:
119 """The center-point of the extent"""
121 return (
122 e[0] + (e[2] - e[0]) / 2,
123 e[1] + (e[3] - e[1]) / 2,
124 )
127def size(e: gws.Extent) -> gws.Size:
128 """The size of the extent ``(width,height)"""
130 return (
131 e[2] - e[0],
132 e[3] - e[1],
133 )
136def diagonal(e: gws.Extent) -> float:
137 """The length of the diagonal"""
139 return math.sqrt((e[2] - e[0]) ** 2 + (e[3] - e[1]) ** 2)
142def circumsquare(e: gws.Extent) -> gws.Extent:
143 """A circumscribed square of the extent."""
145 d = diagonal(e)
146 return from_center(center(e), (d, d))
149def buffer(e: gws.Extent, buf: int) -> gws.Extent:
150 """Creates an extent with buffer to another extent.
152 Args:
153 e: An extent.
154 buf: Buffer between e and the output. If buf is positive the returned extent will be bigger.
156 Returns:
157 An extent.
158 """
160 if buf == 0:
161 return e
162 e = _sort(e)
163 return (
164 e[0] - buf,
165 e[1] - buf,
166 e[2] + buf,
167 e[3] + buf,
168 )
171def union(exts: list[gws.Extent]) -> gws.Extent:
172 """Creates the smallest extent that contains all the given extents.
174 Args:
175 exts: Extents.
177 Returns:
178 An Extent.
179 """
181 ext = exts[0]
182 for e in exts:
183 e = _sort(e)
184 ext = (
185 min(ext[0], e[0]),
186 min(ext[1], e[1]),
187 max(ext[2], e[2]),
188 max(ext[3], e[3])
189 )
190 return ext
193def intersect(a: gws.Extent, b: gws.Extent) -> bool:
194 """Returns ``True`` if the extents are intersecting, otherwise ``False``."""
196 a = _sort(a)
197 b = _sort(b)
198 return a[0] <= b[2] and a[2] >= b[0] and a[1] <= b[3] and a[3] >= b[1]
201def transform(e: gws.Extent, crs_from: gws.Crs, crs_to: gws.Crs) -> gws.Extent:
202 """Transforms the extent to a different coordinate reference system.
204 Args:
205 e: An extent.
206 crs_from: Input crs.
207 crs_to: Output crs.
209 Returns:
210 The transformed extent.
211 """
213 return crs_from.transform_extent(e, crs_to)
216def transform_from_wgs(e: gws.Extent, crs_to: gws.Crs) -> gws.Extent:
217 """Transforms the extent in WGS84 to a different coordinate reference system.
219 Args:
220 e: An extent.
221 crs_to: Output crs.
223 Returns:
224 The transformed extent.
225 """
227 return gws.gis.crs.WGS84.transform_extent(e, crs_to)
230def transform_to_wgs(e: gws.Extent, crs_from: gws.Crs) -> gws.Extent:
231 """Transforms the extent to WGS84.
233 Args:
234 e: An extent.
235 crs_from: Input crs.
237 Returns:
238 The WGS84 extent.
239 """
241 return crs_from.transform_extent(e, gws.gis.crs.WGS84)
244def swap_xy(e: gws.Extent) -> gws.Extent:
245 """Swaps the x and y values of the extent"""
246 return e[1], e[0], e[3], e[2]
249def is_valid(e: gws.Extent) -> bool:
250 if not e or len(e) != 4:
251 return False
252 if not all(math.isfinite(p) for p in e):
253 return False
254 if e[0] >= e[2] or e[1] >= e[3]:
255 return False
256 return True
259def is_valid_wgs(e: gws.Extent) -> bool:
260 if not is_valid(e):
261 return False
262 w = gws.gis.crs.WGS84.extent
263 return e[0] >= w[0] and e[1] >= w[1] and e[2] <= w[2] and e[3] <= w[3]
266def _check(ls: list) -> Optional[gws.Extent]:
267 if len(ls) != 4:
268 return None
269 try:
270 e = [float(p) for p in ls]
271 except ValueError:
272 return None
273 if not all(math.isfinite(p) for p in e):
274 return None
275 e = _sort(e)
276 if e[0] >= e[2] or e[1] >= e[3]:
277 return None
278 return e
281def _sort(e):
282 # our extents are always [minx, miny, maxx, maxy]
283 return (
284 min(e[0], e[2]),
285 min(e[1], e[3]),
286 max(e[0], e[2]),
287 max(e[1], e[3]),
288 )