Coverage for gws-app/gws/lib/bounds/__init__.py: 91%
43 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Utilities to work with Bounds objects."""
3from typing import Optional
5import gws
6import gws.lib.crs
7import gws.lib.extent
8import gws.lib.gml
11def from_request_bbox(bbox: str, default_crs: gws.Crs = None, always_xy=False) -> Optional[gws.Bounds]:
12 """Create Bounds from a KVP BBOX param.
14 See OGC 06-121r9, 10.2.3 Bounding box KVP encoding.
16 Args:
17 bbox: A string with four coordinates, optionally followed by a CRS spec.
18 default_crs: Default Crs.
19 always_xy: If ``True``, coordinates are assumed to be in the XY (lon/lat) order
21 Returns:
22 A Bounds object.
23 """
25 if not bbox:
26 return None
28 crs = default_crs
30 # x,y,x,y,crs
31 ls = bbox.split(',')
32 if len(ls) == 5:
33 crs = gws.lib.crs.get(ls.pop())
35 if not crs:
36 return None
38 extent = gws.lib.extent.from_list(ls)
39 if not extent:
40 return None
42 return from_extent(extent, crs, always_xy)
45def from_extent(extent: gws.Extent, crs: gws.Crs, always_xy=False) -> gws.Bounds:
46 """Create Bounds from an Extent.
48 Args:
49 extent: An Extent.
50 crs: A Crs object.
51 always_xy: If ``True``, coordinates are assumed to be in the XY (lon/lat) order
53 Returns:
54 A Bounds object.
55 """
57 if crs.isYX and not always_xy:
58 extent = gws.lib.extent.swap_xy(extent)
60 return gws.Bounds(crs=crs, extent=extent)
63def copy(b: gws.Bounds) -> gws.Bounds:
64 """Copies and creates a new bounds object."""
65 return gws.Bounds(crs=b.crs, extent=b.extent)
68def union(bs: list[gws.Bounds]) -> gws.Bounds:
69 """Creates the smallest bound that contains all the given bounds.
71 Args:
72 bs: Bounds.
74 Returns:
75 A Bounds object. Its crs is the same as the crs of the first object in bs.
76 """
78 crs = bs[0].crs
79 exts = [gws.lib.extent.transform(b.extent, b.crs, crs) for b in bs]
80 return gws.Bounds(
81 crs=crs,
82 extent=gws.lib.extent.union(exts),
83 )
86def intersect(b1: gws.Bounds, b2: gws.Bounds) -> bool:
87 """Returns ``True`` if the bounds are intersecting, otherwise ``False``."""
88 e1 = b1.extent
89 e2 = gws.lib.extent.transform(b2.extent, crs_from=b2.crs, crs_to=b1.crs)
90 return gws.lib.extent.intersect(e1, e2)
93def transform(b: gws.Bounds, crs_to: gws.Crs) -> gws.Bounds:
94 """Transforms the bounds object to a different crs.
96 Args:
97 b: Bounds object.
98 crs_to: Output crs.
100 Returns:
101 A bounds object.
102 """
103 if b.crs == crs_to:
104 return b
105 return gws.Bounds(
106 crs=crs_to,
107 extent=b.crs.transform_extent(b.extent, crs_to),
108 )
111def wgs_extent(b: gws.Bounds) -> Optional[gws.Extent]:
112 ext = gws.lib.extent.transform(b.extent, b.crs, gws.lib.crs.WGS84)
113 return ext if gws.lib.extent.is_valid(ext) else None
116def buffer(b: gws.Bounds, buf_size: int) -> gws.Bounds:
117 """Creates a bounds object with buffer to another bounds object.
119 Args:
120 b: A Bounds object.
121 buf_size: Buffer between b and the output. If buf is positive the returned bounds object will be bigger.
123 Returns:
124 A bounds object.
125 """
126 if buf_size == 0:
127 return b
128 return gws.Bounds(crs=b.crs, extent=gws.lib.extent.buffer(b.extent, buf_size))