Coverage for gws-app/gws/gis/bounds/__init__.py: 0%
43 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
1"""Utilities to work with Bounds objects."""
3from typing import Optional
5import gws
6import gws.gis.crs
7import gws.gis.extent
8import gws.gis.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.gis.crs.get(ls.pop())
35 if not crs:
36 return None
38 extent = gws.gis.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.gis.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.gis.extent.transform(b.extent, b.crs, crs) for b in bs]
80 return gws.Bounds(
81 crs=crs,
82 extent=gws.gis.extent.union(exts))
85def intersect(b1: gws.Bounds, b2: gws.Bounds) -> bool:
86 """Returns ``True`` if the bounds are intersecting, otherwise ``False``."""
87 e1 = b1.extent
88 e2 = gws.gis.extent.transform(b2.extent, crs_from=b2.crs, crs_to=b1.crs)
89 return gws.gis.extent.intersect(e1, e2)
92def transform(b: gws.Bounds, crs_to: gws.Crs) -> gws.Bounds:
93 """Transforms the bounds object to a different crs.
95 Args:
96 b: Bounds object.
97 crs_to: Output crs.
99 Returns:
100 A bounds object.
101 """
102 if b.crs == crs_to:
103 return b
104 return gws.Bounds(
105 crs=crs_to,
106 extent=b.crs.transform_extent(b.extent, crs_to))
109def wgs_extent(b: gws.Bounds) -> Optional[gws.Extent]:
110 ext = gws.gis.extent.transform(b.extent, b.crs, gws.gis.crs.WGS84)
111 return ext if gws.gis.extent.is_valid(ext) else None
114def buffer(b: gws.Bounds, buf_size: int) -> gws.Bounds:
115 """Creates a bounds object with buffer to another bounds object.
117 Args:
118 b: A Bounds object.
119 buf_size: Buffer between b and the output. If buf is positive the returned bounds object will be bigger.
121 Returns:
122 A bounds object.
123 """
124 if buf_size == 0:
125 return b
126 return gws.Bounds(crs=b.crs, extent=gws.gis.extent.buffer(b.extent, buf_size))