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

1"""Utilities to work with Bounds objects.""" 

2 

3from typing import Optional 

4 

5import gws 

6import gws.gis.crs 

7import gws.gis.extent 

8import gws.gis.gml 

9 

10 

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. 

13 

14 See OGC 06-121r9, 10.2.3 Bounding box KVP encoding. 

15 

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 

20 

21 Returns: 

22 A Bounds object. 

23 """ 

24 

25 if not bbox: 

26 return None 

27 

28 crs = default_crs 

29 

30 # x,y,x,y,crs 

31 ls = bbox.split(',') 

32 if len(ls) == 5: 

33 crs = gws.gis.crs.get(ls.pop()) 

34 

35 if not crs: 

36 return None 

37 

38 extent = gws.gis.extent.from_list(ls) 

39 if not extent: 

40 return None 

41 

42 return from_extent(extent, crs, always_xy) 

43 

44 

45def from_extent(extent: gws.Extent, crs: gws.Crs, always_xy=False) -> gws.Bounds: 

46 """Create Bounds from an Extent. 

47 

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 

52 

53 Returns: 

54 A Bounds object. 

55 """ 

56 

57 if crs.isYX and not always_xy: 

58 extent = gws.gis.extent.swap_xy(extent) 

59 

60 return gws.Bounds(crs=crs, extent=extent) 

61 

62 

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) 

66 

67 

68def union(bs: list[gws.Bounds]) -> gws.Bounds: 

69 """Creates the smallest bound that contains all the given bounds. 

70 

71 Args: 

72 bs: Bounds. 

73 

74 Returns: 

75 A Bounds object. Its crs is the same as the crs of the first object in bs. 

76 """ 

77 

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)) 

83 

84 

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) 

90 

91 

92def transform(b: gws.Bounds, crs_to: gws.Crs) -> gws.Bounds: 

93 """Transforms the bounds object to a different crs. 

94 

95 Args: 

96 b: Bounds object. 

97 crs_to: Output crs. 

98 

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)) 

107 

108 

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 

112 

113 

114def buffer(b: gws.Bounds, buf_size: int) -> gws.Bounds: 

115 """Creates a bounds object with buffer to another bounds object. 

116 

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. 

120 

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))