"""
Module for manipulation of geographical information
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Sequence, Tuple, cast
from typing_extensions import deprecated
from .constants import CRS
from .exceptions import SHDeprecationWarning
if TYPE_CHECKING:
from .geometry import BBox
ERR = 0.1
[docs]def bbox_to_dimensions(bbox: BBox, resolution: float | tuple[float, float]) -> tuple[int, int]:
"""Calculates width and height in pixels for a given bbox of a given pixel resolution (in meters). The result is
rounded to the nearest integers.
:param bbox: bounding box
:param resolution: Resolution of desired image in meters. It can be a single number or a tuple of two numbers -
resolution in horizontal and resolution in vertical direction.
:return: width and height in pixels for given bounding box and pixel resolution
"""
utm_bbox = to_utm_bbox(bbox)
east1, north1 = utm_bbox.lower_left
east2, north2 = utm_bbox.upper_right
resx, resy = resolution if isinstance(resolution, tuple) else (resolution, resolution)
return round(abs(east2 - east1) / resx), round(abs(north2 - north1) / resy)
[docs]def bbox_to_resolution(bbox: BBox, width: int, height: int, meters: bool = True) -> tuple[float, float]:
"""Calculates pixel resolution for a given bbox of a given width and height. By default, it returns result in
meters.
:param bbox: bounding box
:param width: width of bounding box in pixels
:param height: height of bounding box in pixels
:param meters: If `True` result will be given in meters, otherwise it will be given in units of current CRS
:return: resolution east-west at north and south, and resolution north-south for given CRS
:raises: ValueError if CRS is not supported
"""
if meters:
bbox = to_utm_bbox(bbox)
east1, north1 = bbox.lower_left
east2, north2 = bbox.upper_right
return abs(east2 - east1) / width, abs(north2 - north1) / height
[docs]def get_image_dimension(bbox: BBox, width: int | None = None, height: int | None = None) -> int:
"""Given bounding box and one of the parameters width or height it will return the other parameter that will best
fit the bounding box dimensions
:param bbox: bounding box
:param width: image width or `None` if height is unknown
:param height: image height or `None` if height is unknown
:return: width or height rounded to integer
"""
utm_bbox = to_utm_bbox(bbox)
east1, north1 = utm_bbox.lower_left
east2, north2 = utm_bbox.upper_right
if isinstance(width, int):
return round(width * abs(north2 - north1) / abs(east2 - east1))
if isinstance(height, int):
return round(height * abs(east2 - east1) / abs(north2 - north1))
raise ValueError("At least one of the parameters `width` and `height` must be given.")
[docs]def to_utm_bbox(bbox: BBox) -> BBox:
"""Transform bbox into UTM CRS
:param bbox: bounding box
:return: bounding box in UTM CRS
"""
if CRS.is_utm(bbox.crs):
return bbox
lng, lat = bbox.middle
utm_crs = get_utm_crs(lng, lat, source_crs=bbox.crs)
return bbox.transform(utm_crs)
[docs]@deprecated("The function `get_utm_bbox` has been deprecated.", category=SHDeprecationWarning)
def get_utm_bbox(img_bbox: Sequence[float], transform: Sequence[float]) -> list[float]:
"""Get UTM coordinates given a bounding box in pixels and a transform
:param img_bbox: boundaries of bounding box in pixels as `[row1, col1, row2, col2]`
:param transform: georeferencing transform of the image, e.g. `(x_upper_left, res_x, 0, y_upper_left, 0, -res_y)`
:return: UTM coordinates as [east1, north1, east2, north2]
"""
east1, north1 = pixel_to_utm(img_bbox[0], img_bbox[1], transform)
east2, north2 = pixel_to_utm(img_bbox[2], img_bbox[3], transform)
return [east1, north1, east2, north2]
[docs]@deprecated(
"The function `wgs84_to_utm` has been deprecated. Use `transform_point` and `get_utm_crs` instead.",
category=SHDeprecationWarning,
)
def wgs84_to_utm(lng: float, lat: float, utm_crs: CRS | None = None) -> tuple[float, float]:
"""Convert WGS84 coordinates to UTM. If UTM CRS is not set it will be calculated automatically.
:param lng: longitude in WGS84 system
:param lat: latitude in WGS84 system
:param utm_crs: UTM coordinate reference system enum constants
:return: east, north coordinates in UTM system
"""
if utm_crs is None:
utm_crs = get_utm_crs(lng, lat)
return transform_point((lng, lat), CRS.WGS84, utm_crs)
[docs]@deprecated(
"The function `to_wgs84` has been deprecated. Use `transform_point` instead.", category=SHDeprecationWarning
)
def to_wgs84(east: float, north: float, crs: CRS) -> tuple[float, float]:
"""Convert any CRS with (east, north) coordinates to WGS84
:param east: east coordinate
:param north: north coordinate
:param crs: CRS enum constants
:return: latitude and longitude coordinates in WGS84 system
"""
return transform_point((east, north), crs, CRS.WGS84)
[docs]def utm_to_pixel(
east: float, north: float, transform: Sequence[float], truncate: bool = True
) -> tuple[float, float] | tuple[int, int]:
"""Convert a UTM coordinate to image coordinate given a transform
:param east: east coordinate of point
:param north: north coordinate of point
:param transform: georeferencing transform of the image, e.g. `(x_upper_left, res_x, 0, y_upper_left, 0, -res_y)`
:param truncate: Truncate pixel coordinates. Default is `True`
:return: row and column pixel image coordinates
"""
column = (east - transform[0]) / transform[1]
row = (north - transform[3]) / transform[5]
if truncate:
return int(row + ERR), int(column + ERR)
return row, column
[docs]def pixel_to_utm(row: float, column: float, transform: Sequence[float]) -> tuple[float, float]:
"""Convert pixel coordinate to UTM coordinate given a transform
:param row: row pixel coordinate
:param column: column pixel coordinate
:param transform: georeferencing transform of the image, e.g. `(x_upper_left, res_x, 0, y_upper_left, 0, -res_y)`
:return: east, north UTM coordinates
"""
east = transform[0] + column * transform[1]
north = transform[3] + row * transform[5]
return east, north
[docs]@deprecated("The function `wgs84_to_pixel` has been deprecated.", category=SHDeprecationWarning)
def wgs84_to_pixel(
lng: float, lat: float, transform: Sequence[float], utm_epsg: CRS | None = None, truncate: bool = True
) -> tuple[float, float] | tuple[int, int]:
"""Convert WGS84 coordinates to pixel image coordinates given transform and UTM CRS. If no CRS is given it will be
calculated it automatically.
:param lng: longitude of point
:param lat: latitude of point
:param transform: georeferencing transform of the image, e.g. `(x_upper_left, res_x, 0, y_upper_left, 0, -res_y)`
:param utm_epsg: UTM coordinate reference system enum constants
:param truncate: Truncate pixel coordinates. Default is `True`
:return: row and column pixel image coordinates
"""
east, north = wgs84_to_utm(lng, lat, utm_epsg)
row, column = utm_to_pixel(east, north, transform, truncate=truncate)
return row, column
[docs]def get_utm_crs(lng: float, lat: float, source_crs: CRS = CRS.WGS84) -> CRS:
"""Get CRS for UTM zone in which (lat, lng) is contained.
:param lng: longitude
:param lat: latitude
:param source_crs: source CRS
:return: CRS of the zone containing the lat,lon point
"""
if source_crs is not CRS.WGS84:
lng, lat = transform_point((lng, lat), source_crs, CRS.WGS84)
return CRS.get_utm_from_wgs84(lng, lat)