samgis-lisa-on-zero / samgis /io /tms2geotiff.py
alessandro trinca tornidor
[feat] prepare entire docker build with nvidia GPU on hf space cloning https://huggingface.co/spaces/aletrn/samgis
0914710
raw
history blame
9.34 kB
import os
from numpy import ndarray
from samgis_core.utilities.type_hints import tuple_float
from xyzservices import TileProvider
from samgis import app_logger
from samgis.utilities.constants import (OUTPUT_CRS_STRING, DRIVER_RASTERIO_GTIFF, N_MAX_RETRIES, N_CONNECTION, N_WAIT,
ZOOM_AUTO, BOOL_USE_CACHE)
from samgis.utilities.type_hints import tuple_ndarray_transform
bool_use_cache = int(os.getenv("BOOL_USE_CACHE", BOOL_USE_CACHE))
n_connection = int(os.getenv("N_CONNECTION", N_CONNECTION))
n_max_retries = int(os.getenv("N_MAX_RETRIES", N_MAX_RETRIES))
n_wait = int(os.getenv("N_WAIT", N_WAIT))
zoom_auto_string = os.getenv("ZOOM_AUTO", ZOOM_AUTO)
def download_extent(w: float, s: float, e: float, n: float, zoom: int or str = zoom_auto_string,
source: TileProvider or str = None,
wait: int = n_wait, max_retries: int = n_max_retries, n_connections: int = n_connection,
use_cache: bool = bool_use_cache) -> tuple_ndarray_transform:
"""
Download, merge and crop a list of tiles into a single geo-referenced image or a raster geodata
Args:
w: West edge
s: South edge
e: East edge
n: North edge
zoom: Level of detail
source: The tile source: web tile provider or path to local file. The web tile provider can be in the form of
a :class:`xyzservices.TileProvider` object or a URL. The placeholders for the XYZ in the URL need to be
`{x}`, `{y}`, `{z}`, respectively. For local file paths, the file is read with `rasterio` and all bands are
loaded into the basemap. IMPORTANT: tiles are assumed to be in the Spherical Mercator projection
(EPSG:3857), unless the `crs` keyword is specified.
wait: if the tile API is rate-limited, the number of seconds to wait
between a failed request and the next try
max_retries: total number of rejected requests allowed before contextily will stop trying to fetch more tiles
from a rate-limited API.
n_connections: Number of connections for downloading tiles in parallel. Be careful not to overload the tile
server and to check the tile provider's terms of use before increasing this value. E.g., OpenStreetMap has
a max. value of 2 (https://operations.osmfoundation.org/policies/tiles/). If allowed to download in
parallel, a recommended value for n_connections is 16, and should never be larger than 64.
use_cache: If False, caching of the downloaded tiles will be disabled. This can be useful in resource
constrained environments, especially when using n_connections > 1, or when a tile provider's terms of use
don't allow caching.
Returns:
parsed request input
"""
try:
from samgis import contextily_tile
from samgis.io.coordinates_pixel_conversion import _from4326_to3857
app_logger.info(f"connection number:{n_connections}, type:{type(n_connections)}.")
app_logger.info(f"zoom:{zoom}, type:{type(zoom)}.")
app_logger.debug(f"download raster from source:{source} with bounding box w:{w}, s:{s}, e:{e}, n:{n}.")
app_logger.debug(f"types w:{type(w)}, s:{type(s)}, e:{type(e)}, n:{type(n)}.")
downloaded_raster, bbox_raster = contextily_tile.bounds2img(
w, s, e, n, zoom=zoom, source=source, ll=True, wait=wait, max_retries=max_retries,
n_connections=n_connections, use_cache=use_cache)
xp0, yp0 = _from4326_to3857(n, e)
xp1, yp1 = _from4326_to3857(s, w)
cropped_image_ndarray, cropped_transform = crop_raster(yp1, xp1, yp0, xp0, downloaded_raster, bbox_raster)
return cropped_image_ndarray, cropped_transform
except Exception as e_download_extent:
app_logger.exception(f"e_download_extent:{e_download_extent}.", exc_info=True)
raise e_download_extent
def crop_raster(w: float, s: float, e: float, n: float, raster: ndarray, raster_bbox: tuple_float,
crs: str = OUTPUT_CRS_STRING, driver: str = DRIVER_RASTERIO_GTIFF) -> tuple_ndarray_transform:
"""
Crop a raster using given bounding box (w, s, e, n) values
Args:
w: cropping west edge
s: cropping south edge
e: cropping east edge
n: cropping north edge
raster: raster image to crop
raster_bbox: bounding box of raster to crop
crs: The coordinate reference system. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes.
driver: A short format driver name (e.g. "GTiff" or "JPEG") or a list of such names (see GDAL docs at
https://gdal.org/drivers/raster/index.html ). In 'w' or 'w+' modes a single name is required. In 'r' or 'r+'
modes the driver can usually be omitted. Registered drivers will be tried sequentially until a match is
found. When multiple drivers are available for a format such as JPEG2000, one of them can be selected by
using this keyword argument.
Returns:
cropped raster with its Affine transform
"""
try:
from rasterio.io import MemoryFile
from rasterio.mask import mask as rio_mask
from shapely.geometry import Polygon
from geopandas import GeoSeries
app_logger.debug(f"raster: type {type(raster)}, raster_ext:{type(raster_bbox)}, {raster_bbox}.")
img_to_save, transform = get_transform_raster(raster, raster_bbox)
img_height, img_width, number_bands = img_to_save.shape
# https://rasterio.readthedocs.io/en/latest/topics/memory-files.html
with MemoryFile() as rio_mem_file:
app_logger.debug("writing raster in-memory to crop it with rasterio.mask.mask()")
with rio_mem_file.open(
driver=driver,
height=img_height,
width=img_width,
count=number_bands,
dtype=str(img_to_save.dtype.name),
crs=crs,
transform=transform,
) as src_raster_rw:
for band in range(number_bands):
src_raster_rw.write(img_to_save[:, :, band], band + 1)
app_logger.debug("cropping raster in-memory with rasterio.mask.mask()")
with rio_mem_file.open() as src_raster_ro:
shapes_crop_polygon = Polygon([(n, e), (s, e), (s, w), (n, w), (n, e)])
shapes_crop = GeoSeries([shapes_crop_polygon])
app_logger.debug(f"cropping with polygon::{shapes_crop_polygon}.")
cropped_image, cropped_transform = rio_mask(src_raster_ro, shapes=shapes_crop, crop=True)
cropped_image_ndarray = reshape_as_image(cropped_image)
app_logger.info(f"cropped image::{cropped_image_ndarray.shape}.")
return cropped_image_ndarray, cropped_transform
except Exception as e_crop_raster:
try:
app_logger.error(f"raster type:{type(raster)}.")
app_logger.error(f"raster shape:{raster.shape}, dtype:{raster.dtype}.")
except Exception as e_shape_dtype:
app_logger.exception(f"raster shape or dtype not found:{e_shape_dtype}.", exc_info=True)
app_logger.exception(f"e_crop_raster:{e_crop_raster}.", exc_info=True)
raise e_crop_raster
def get_transform_raster(raster: ndarray, raster_bbox: tuple_float) -> tuple_ndarray_transform:
"""
Convert the input raster image to RGB and extract the Affine
Args:
raster: raster image to geo-reference
raster_bbox: bounding box of raster to crop
Returns:
rgb raster image and its Affine transform
"""
try:
from rasterio.transform import from_origin
from numpy import array as np_array, linspace as np_linspace, uint8 as np_uint8
from PIL.Image import fromarray
app_logger.debug(f"raster: type {type(raster)}, raster_ext:{type(raster_bbox)}, {raster_bbox}.")
rgb = fromarray(np_uint8(raster)).convert('RGB')
np_rgb = np_array(rgb)
img_height, img_width, _ = np_rgb.shape
min_x, max_x, min_y, max_y = raster_bbox
app_logger.debug(f"raster rgb shape:{np_rgb.shape}, raster rgb bbox {raster_bbox}.")
x = np_linspace(min_x, max_x, img_width)
y = np_linspace(min_y, max_y, img_height)
res_x = (x[-1] - x[0]) / img_width
res_y = (y[-1] - y[0]) / img_height
transform = from_origin(x[0] - res_x / 2, y[-1] + res_y / 2, res_x, res_y)
return np_rgb, transform
except Exception as e_get_transform_raster:
app_logger.error(f"arguments raster: {type(raster)}, {raster}.")
app_logger.error(f"arguments raster_bbox: {type(raster_bbox)}, {raster_bbox}.")
app_logger.exception(f"e_get_transform_raster:{e_get_transform_raster}.", exc_info=True)
raise e_get_transform_raster
def reshape_as_image(arr):
try:
from numpy import swapaxes
return swapaxes(swapaxes(arr, 0, 2), 0, 1)
except Exception as e_reshape_as_image:
app_logger.error(f"arguments: {type(arr)}, {arr}.")
app_logger.exception(f"e_reshape_as_image:{e_reshape_as_image}.", exc_info=True)
raise e_reshape_as_image