[refactor] get constants from env variables, handle possible exceptions on tiles download/merge/crop
Browse files- src/__init__.py +2 -0
- src/io/tms2geotiff.py +101 -65
- src/utilities/constants.py +5 -5
src/__init__.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
"""Get machine learning predictions from geodata raster images"""
|
2 |
from aws_lambda_powertools import Logger
|
|
|
|
|
3 |
from pathlib import Path
|
4 |
|
5 |
from src.utilities.constants import SERVICE_NAME
|
|
|
1 |
"""Get machine learning predictions from geodata raster images"""
|
2 |
from aws_lambda_powertools import Logger
|
3 |
+
# not used here but contextily_tile is imported in src.io.tms2geotiff
|
4 |
+
from contextily import tile as contextily_tile
|
5 |
from pathlib import Path
|
6 |
|
7 |
from src.utilities.constants import SERVICE_NAME
|
src/io/tms2geotiff.py
CHANGED
@@ -1,14 +1,22 @@
|
|
|
|
1 |
from numpy import ndarray
|
2 |
|
3 |
from src import app_logger
|
4 |
-
from src.utilities.constants import OUTPUT_CRS_STRING, DRIVER_RASTERIO_GTIFF,
|
5 |
-
|
6 |
from src.utilities.type_hints import tuple_ndarray_transform, tuple_float
|
7 |
|
8 |
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
"""
|
13 |
Download, merge and crop a list of tiles into a single geo-referenced image or a raster geodata
|
14 |
|
@@ -42,17 +50,24 @@ def download_extent(w: float, s: float, e: float, n: float, zoom: int or str = O
|
|
42 |
Returns:
|
43 |
parsed request input
|
44 |
"""
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
|
58 |
def crop_raster(w: float, s: float, e: float, n: float, raster: ndarray, raster_bbox: tuple_float,
|
@@ -77,38 +92,42 @@ def crop_raster(w: float, s: float, e: float, n: float, raster: ndarray, raster_
|
|
77 |
Returns:
|
78 |
cropped raster with its Affine transform
|
79 |
"""
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
|
|
|
|
|
|
|
|
112 |
|
113 |
|
114 |
def get_transform_raster(raster: ndarray, raster_bbox: tuple_float) -> tuple_ndarray_transform:
|
@@ -122,20 +141,37 @@ def get_transform_raster(raster: ndarray, raster_bbox: tuple_float) -> tuple_nda
|
|
122 |
Returns:
|
123 |
rgb raster image and its Affine transform
|
124 |
"""
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
from numpy import ndarray
|
3 |
|
4 |
from src import app_logger
|
5 |
+
from src.utilities.constants import (OUTPUT_CRS_STRING, DRIVER_RASTERIO_GTIFF, N_MAX_RETRIES, N_CONNECTION, N_WAIT,
|
6 |
+
ZOOM_AUTO, BOOL_USE_CACHE)
|
7 |
from src.utilities.type_hints import tuple_ndarray_transform, tuple_float
|
8 |
|
9 |
|
10 |
+
bool_use_cache = int(os.getenv("BOOL_USE_CACHE", BOOL_USE_CACHE))
|
11 |
+
n_connection = int(os.getenv("N_CONNECTION", N_CONNECTION))
|
12 |
+
n_max_retries = int(os.getenv("N_MAX_RETRIES", N_MAX_RETRIES))
|
13 |
+
n_wait = int(os.getenv("N_WAIT", N_WAIT))
|
14 |
+
zoom_auto_string = os.getenv("ZOOM_AUTO", ZOOM_AUTO)
|
15 |
+
|
16 |
+
|
17 |
+
def download_extent(w: float, s: float, e: float, n: float, zoom: int or str = zoom_auto_string, source: str = None,
|
18 |
+
wait: int = n_wait, max_retries: int = n_max_retries, n_connections: int = n_connection,
|
19 |
+
use_cache: bool = bool_use_cache) -> tuple_ndarray_transform:
|
20 |
"""
|
21 |
Download, merge and crop a list of tiles into a single geo-referenced image or a raster geodata
|
22 |
|
|
|
50 |
Returns:
|
51 |
parsed request input
|
52 |
"""
|
53 |
+
try:
|
54 |
+
from src import contextily_tile
|
55 |
+
from src.io.coordinates_pixel_conversion import _from4326_to3857
|
56 |
+
|
57 |
+
app_logger.info(f"connection number:{n_connections}, type:{type(n_connections)}.")
|
58 |
+
app_logger.info(f"zoom:{zoom}, type:{type(zoom)}.")
|
59 |
+
app_logger.debug(f"download raster from source:{source} with bounding box w:{w}, s:{s}, e:{e}, n:{n}.")
|
60 |
+
app_logger.debug(f"types w:{type(w)}, s:{type(s)}, e:{type(e)}, n:{type(n)}.")
|
61 |
+
downloaded_raster, bbox_raster = contextily_tile.bounds2img(
|
62 |
+
w, s, e, n, zoom=zoom, source=source, ll=True, wait=wait, max_retries=max_retries, n_connections=n_connections,
|
63 |
+
use_cache=use_cache)
|
64 |
+
xp0, yp0 = _from4326_to3857(n, e)
|
65 |
+
xp1, yp1 = _from4326_to3857(s, w)
|
66 |
+
cropped_image_ndarray, cropped_transform = crop_raster(yp1, xp1, yp0, xp0, downloaded_raster, bbox_raster)
|
67 |
+
return cropped_image_ndarray, cropped_transform
|
68 |
+
except Exception as e_download_extent:
|
69 |
+
app_logger.exception(f"e_download_extent:{e_download_extent}.", exc_info=True)
|
70 |
+
raise e_download_extent
|
71 |
|
72 |
|
73 |
def crop_raster(w: float, s: float, e: float, n: float, raster: ndarray, raster_bbox: tuple_float,
|
|
|
92 |
Returns:
|
93 |
cropped raster with its Affine transform
|
94 |
"""
|
95 |
+
try:
|
96 |
+
from rasterio.io import MemoryFile
|
97 |
+
from rasterio.mask import mask as rio_mask
|
98 |
+
from shapely.geometry import Polygon
|
99 |
+
from geopandas import GeoSeries
|
100 |
+
|
101 |
+
app_logger.debug(f"raster: type {type(raster)}, raster_ext:{type(raster_bbox)}, {raster_bbox}.")
|
102 |
+
img_to_save, transform = get_transform_raster(raster, raster_bbox)
|
103 |
+
img_height, img_width, number_bands = img_to_save.shape
|
104 |
+
# https://rasterio.readthedocs.io/en/latest/topics/memory-files.html
|
105 |
+
with MemoryFile() as rio_mem_file:
|
106 |
+
app_logger.debug("writing raster in-memory to crop it with rasterio.mask.mask()")
|
107 |
+
with rio_mem_file.open(
|
108 |
+
driver=driver,
|
109 |
+
height=img_height,
|
110 |
+
width=img_width,
|
111 |
+
count=number_bands,
|
112 |
+
dtype=str(img_to_save.dtype.name),
|
113 |
+
crs=crs,
|
114 |
+
transform=transform,
|
115 |
+
) as src_raster_rw:
|
116 |
+
for band in range(number_bands):
|
117 |
+
src_raster_rw.write(img_to_save[:, :, band], band + 1)
|
118 |
+
app_logger.debug("cropping raster in-memory with rasterio.mask.mask()")
|
119 |
+
with rio_mem_file.open() as src_raster_ro:
|
120 |
+
shapes_crop_polygon = Polygon([(n, e), (s, e), (s, w), (n, w), (n, e)])
|
121 |
+
shapes_crop = GeoSeries([shapes_crop_polygon])
|
122 |
+
app_logger.debug(f"cropping with polygon::{shapes_crop_polygon}.")
|
123 |
+
cropped_image, cropped_transform = rio_mask(src_raster_ro, shapes=shapes_crop, crop=True)
|
124 |
+
cropped_image_ndarray = reshape_as_image(cropped_image)
|
125 |
+
app_logger.info(f"cropped image::{cropped_image_ndarray.shape}.")
|
126 |
+
return cropped_image_ndarray, cropped_transform
|
127 |
+
except Exception as e_crop_raster:
|
128 |
+
app_logger.exception(f"arguments raster: {type(raster)}, {raster}.")
|
129 |
+
app_logger.exception(f"e_crop_raster:{e_crop_raster}.", exc_info=True)
|
130 |
+
raise e_crop_raster
|
131 |
|
132 |
|
133 |
def get_transform_raster(raster: ndarray, raster_bbox: tuple_float) -> tuple_ndarray_transform:
|
|
|
141 |
Returns:
|
142 |
rgb raster image and its Affine transform
|
143 |
"""
|
144 |
+
try:
|
145 |
+
from rasterio.transform import from_origin
|
146 |
+
from numpy import array as np_array, linspace as np_linspace, uint8 as np_uint8
|
147 |
+
from PIL.Image import fromarray
|
148 |
+
|
149 |
+
app_logger.debug(f"raster: type {type(raster)}, raster_ext:{type(raster_bbox)}, {raster_bbox}.")
|
150 |
+
rgb = fromarray(np_uint8(raster)).convert('RGB')
|
151 |
+
np_rgb = np_array(rgb)
|
152 |
+
img_height, img_width, _ = np_rgb.shape
|
153 |
+
|
154 |
+
min_x, max_x, min_y, max_y = raster_bbox
|
155 |
+
app_logger.debug(f"raster rgb shape:{np_rgb.shape}, raster rgb bbox {raster_bbox}.")
|
156 |
+
x = np_linspace(min_x, max_x, img_width)
|
157 |
+
y = np_linspace(min_y, max_y, img_height)
|
158 |
+
res_x = (x[-1] - x[0]) / img_width
|
159 |
+
res_y = (y[-1] - y[0]) / img_height
|
160 |
+
transform = from_origin(x[0] - res_x / 2, y[-1] + res_y / 2, res_x, res_y)
|
161 |
+
return np_rgb, transform
|
162 |
+
except Exception as e_get_transform_raster:
|
163 |
+
app_logger.exception(f"arguments raster: {type(raster)}, {raster}.")
|
164 |
+
app_logger.exception(f"arguments raster_bbox: {type(raster_bbox)}, {raster_bbox}.")
|
165 |
+
app_logger.exception(f"e_get_transform_raster:{e_get_transform_raster}.")
|
166 |
+
raise e_get_transform_raster
|
167 |
+
|
168 |
+
|
169 |
+
def reshape_as_image(arr):
|
170 |
+
try:
|
171 |
+
from numpy import swapaxes
|
172 |
+
|
173 |
+
return swapaxes(swapaxes(arr, 0, 2), 0, 1)
|
174 |
+
except Exception as e_reshape_as_image:
|
175 |
+
app_logger.exception(f"arguments: {type(arr)}, {arr}.")
|
176 |
+
app_logger.exception(f"e_reshape_as_image:{e_reshape_as_image}.", exc_info=True)
|
177 |
+
raise e_reshape_as_image
|
src/utilities/constants.py
CHANGED
@@ -26,8 +26,8 @@ DEFAULT_LOG_LEVEL = 'INFO'
|
|
26 |
RETRY_DOWNLOAD = 3
|
27 |
TIMEOUT_DOWNLOAD = 60
|
28 |
CALLBACK_INTERVAL_DOWNLOAD = 0.05
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
26 |
RETRY_DOWNLOAD = 3
|
27 |
TIMEOUT_DOWNLOAD = 60
|
28 |
CALLBACK_INTERVAL_DOWNLOAD = 0.05
|
29 |
+
BOOL_USE_CACHE = True
|
30 |
+
N_WAIT = 0
|
31 |
+
N_MAX_RETRIES = 2
|
32 |
+
N_CONNECTION = 2
|
33 |
+
ZOOM_AUTO = "auto"
|