Source code for gws.gis.extent

import math
import re

import fiona.transform

import gws.gis.proj
import gws.gis.shape

import gws.types as t


[docs]def from_string(s: str) -> t.Optional[t.Extent]: """Create an extent from a comma-separated string "1000,2000,20000 40000" """ try: ext = [float(n) for n in s.split(',')] except: return None return valid(ext)
[docs]def from_center(xy: t.Point, size: t.Size) -> t.Extent: return ( xy[0] - size[0] / 2, xy[1] - size[1] / 2, xy[0] + size[0] / 2, xy[1] + size[1] / 2, )
[docs]def from_box(box: str) -> t.Optional[t.Extent]: """Create an extent from a Postgis BOX(1000 2000,20000 40000)""" if not box: return None m = re.match(r'^BOX\((.+?)\)$', str(box).upper()) if not m: return None try: a, b = m.group(1).split(',') a, b = a.split(), b.split() ext = ( float(a[0]), float(a[1]), float(b[0]), float(b[1]), ) except: return None return valid(ext)
[docs]def valid(e) -> t.Optional[t.Extent]: try: ok = len(e) == 4 and all(math.isfinite(p) for p in e) except: ok = False return _sort(e) if ok else None
[docs]def list_valid(exts: t.List[t.Extent]) -> t.List[t.Extent]: ls = [] for e in exts: e = valid(e) if e: ls.append(e) return ls
[docs]def merge(exts: t.List[t.Extent]) -> t.Extent: c = False res = (1e20, 1e20, -1e20, -1e20) for e in exts: e = _sort(e) res = ( min(res[0], e[0]), min(res[1], e[1]), max(res[2], e[2]), max(res[3], e[3]) ) c = True return res if c else None
[docs]def constrain(a: t.Extent, b: t.Extent) -> t.Extent: a = _sort(a) b = _sort(b) return ( max(a[0], b[0]), max(a[1], b[1]), min(a[2], b[2]), min(a[3], b[3]), )
[docs]def center(e: t.Extent) -> t.Point: return ( e[0] + (e[2] - e[0]) / 2, e[1] + (e[3] - e[1]) / 2, )
[docs]def size(e: t.Extent) -> t.Size: return ( e[2] - e[0], e[3] - e[1], )
[docs]def diagonal(e: t.Extent) -> float: return math.sqrt((e[2] - e[0]) ** 2 + (e[3] - e[1]) ** 2)
[docs]def circumsquare(e: t.Extent) -> t.Extent: """A circumscribed square of the extent.""" d = diagonal(e) return from_center(center(e), (d, d))
[docs]def buffer(e: t.Extent, buf: int) -> t.Extent: e = _sort(e) return ( e[0] - buf, e[1] - buf, e[2] + buf, e[3] + buf, )
[docs]def intersect(a: t.Extent, b: t.Extent) -> bool: a = _sort(a) b = _sort(b) return a[0] <= b[2] and a[2] >= b[0] and a[1] <= b[3] and a[3] >= b[1]
[docs]def transform(e: t.Extent, src: str, dst: str) -> t.Extent: if gws.gis.proj.equal(src, dst): return e src = gws.gis.proj.as_proj(src) dst = gws.gis.proj.as_proj(dst) ax, ay, bx, by = _sort(e) sg = { 'type': 'Polygon', 'coordinates': [ [(bx, ay), (bx, by), (ax, by), (ax, ay), (bx, ay)] ] } dg = fiona.transform.transform_geom(src.epsg, dst.epsg, sg) cc = dg['coordinates'][0] return ( min(cc[0][0], cc[1][0], cc[2][0], cc[3][0], cc[4][0]), min(cc[0][1], cc[1][1], cc[2][1], cc[3][1], cc[4][1]), max(cc[0][0], cc[1][0], cc[2][0], cc[3][0], cc[4][0]), max(cc[0][1], cc[1][1], cc[2][1], cc[3][1], cc[4][1]), )
[docs]def transform_to_4326(e: t.Extent, crs: str) -> t.Extent: e = transform(e, crs, gws.EPSG_4326) return ( round(e[0], 5), round(e[1], 5), round(e[2], 5), round(e[3], 5), )
[docs]def swap_xy(e: t.Extent) -> t.Extent: return e[1], e[0], e[3], e[2]
def _sort(e): # our extents are always [minx, miny, maxx, maxy] return ( min(e[0], e[2]), min(e[1], e[3]), max(e[0], e[2]), max(e[1], e[3]), )