cht_utils 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cht_utils/__init__.py +28 -0
- cht_utils/cog/__init__.py +6 -0
- cht_utils/cog/geotiff_to_cog.py +79 -0
- cht_utils/cog/netcdf_to_cog.py +85 -0
- cht_utils/cog/xyz_to_cog.py +86 -0
- cht_utils/colors/__init__.py +6 -0
- cht_utils/colors/colors.py +117 -0
- cht_utils/fileio/__init__.py +21 -0
- cht_utils/fileio/deltares_ini.py +326 -0
- cht_utils/fileio/json_js.py +72 -0
- cht_utils/fileio/pli_file.py +233 -0
- cht_utils/fileio/tekal.py +234 -0
- cht_utils/fileio/xml.py +184 -0
- cht_utils/fileio/yaml.py +39 -0
- cht_utils/fileops/__init__.py +25 -0
- cht_utils/fileops/fileops.py +344 -0
- cht_utils/interpolation/__init__.py +5 -0
- cht_utils/interpolation/interpolation.py +152 -0
- cht_utils/maps/__init__.py +2 -0
- cht_utils/maps/fileops.py +191 -0
- cht_utils/maps/flood_map.py +1231 -0
- cht_utils/maps/topobathy_map.py +463 -0
- cht_utils/maps/utils.py +700 -0
- cht_utils/physics/__init__.py +8 -0
- cht_utils/physics/deshoal.py +63 -0
- cht_utils/physics/disper.py +91 -0
- cht_utils/physics/runup_vo21.py +229 -0
- cht_utils/physics/waves.py +59 -0
- cht_utils/probabilistic/__init__.py +5 -0
- cht_utils/probabilistic/prob_maps.py +263 -0
- cht_utils/remote/__init__.py +4 -0
- cht_utils/remote/s3.py +380 -0
- cht_utils/remote/sftp.py +192 -0
- cht_utils-2.0.0.dist-info/METADATA +30 -0
- cht_utils-2.0.0.dist-info/RECORD +39 -0
- cht_utils-2.0.0.dist-info/WHEEL +5 -0
- cht_utils-2.0.0.dist-info/licenses/LICENSE +21 -0
- cht_utils-2.0.0.dist-info/top_level.txt +1 -0
- cht_utils-2.0.0.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""Topography/bathymetry map generation from Cloud Optimized GeoTIFF (COG) files.
|
|
2
|
+
|
|
3
|
+
Provides the TopoBathyMap class for reading elevation data, applying color
|
|
4
|
+
maps, writing output files (GeoTIFF/NetCDF), creating map overlays, and
|
|
5
|
+
plotting with matplotlib.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import contextily as ctx
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
import numpy as np
|
|
14
|
+
import rasterio
|
|
15
|
+
import rioxarray
|
|
16
|
+
import xarray as xr
|
|
17
|
+
from matplotlib.colors import BoundaryNorm, ListedColormap
|
|
18
|
+
from matplotlib.patches import Patch
|
|
19
|
+
from pyproj import Transformer
|
|
20
|
+
from rasterio.warp import Resampling
|
|
21
|
+
|
|
22
|
+
from cht_utils.maps.flood_map import (
|
|
23
|
+
get_appropriate_overview_level,
|
|
24
|
+
get_rgb_data_array,
|
|
25
|
+
reproject_bbox,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = ["TopoBathyMap"]
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TopoBathyMap:
|
|
34
|
+
"""Manages reading, processing, and visualising topography/bathymetry data.
|
|
35
|
+
|
|
36
|
+
Wraps a Cloud Optimized GeoTIFF elevation file and provides methods for
|
|
37
|
+
reading at various overview levels, applying elevation masks, writing
|
|
38
|
+
output as GeoTIFF or NetCDF, creating PNG map overlays, and producing
|
|
39
|
+
matplotlib plots.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
topobathy_file : str | Path | None
|
|
44
|
+
Path to the topobathy COG file.
|
|
45
|
+
zbmin : float
|
|
46
|
+
Minimum allowable elevation value; values below are masked to NaN.
|
|
47
|
+
zbmax : float
|
|
48
|
+
Maximum allowable elevation value; values above are masked to NaN.
|
|
49
|
+
max_pixel_size : float
|
|
50
|
+
Maximum pixel size for selecting the appropriate overview level.
|
|
51
|
+
data_array_name : str
|
|
52
|
+
Name of the elevation variable in the output dataset.
|
|
53
|
+
cmap : str | None
|
|
54
|
+
Matplotlib colormap name for coloring.
|
|
55
|
+
cmin : float | None
|
|
56
|
+
Minimum value for colormap normalization (scaled by ``scale_factor``).
|
|
57
|
+
cmax : float | None
|
|
58
|
+
Maximum value for colormap normalization (scaled by ``scale_factor``).
|
|
59
|
+
color_values : list[dict] | None
|
|
60
|
+
Discrete color definitions with ``lower_value``, ``upper_value``,
|
|
61
|
+
and ``color`` keys.
|
|
62
|
+
scale_factor : float
|
|
63
|
+
Multiplicative factor applied to elevation values on read.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
topobathy_file: str | Path | None = None,
|
|
69
|
+
zbmin: float = -999999.9,
|
|
70
|
+
zbmax: float = 99999.9,
|
|
71
|
+
max_pixel_size: float = 0.0,
|
|
72
|
+
data_array_name: str = "elevation",
|
|
73
|
+
cmap: str | None = None,
|
|
74
|
+
cmin: float | None = None,
|
|
75
|
+
cmax: float | None = None,
|
|
76
|
+
color_values: list[dict] | None = None,
|
|
77
|
+
scale_factor: float = 1.0,
|
|
78
|
+
) -> None:
|
|
79
|
+
self.topobathy_file = topobathy_file
|
|
80
|
+
self.zb = rasterio.open(topobathy_file) if topobathy_file else None
|
|
81
|
+
self.zbmin = zbmin
|
|
82
|
+
self.zbmax = zbmax
|
|
83
|
+
self.max_pixel_size = max_pixel_size
|
|
84
|
+
self.data_array_name = data_array_name
|
|
85
|
+
self.cmap = cmap
|
|
86
|
+
self.cmin = cmin * scale_factor
|
|
87
|
+
self.cmax = cmax * scale_factor
|
|
88
|
+
self.color_values = color_values
|
|
89
|
+
self.scale_factor = scale_factor
|
|
90
|
+
self.ds = xr.Dataset()
|
|
91
|
+
|
|
92
|
+
def set_topobathy_file(self, topobathy_file: str | Path) -> None:
|
|
93
|
+
"""Set the topobathy file and open it with rasterio.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
topobathy_file : str | Path
|
|
98
|
+
Path to the topobathy COG file.
|
|
99
|
+
"""
|
|
100
|
+
self.topobathy_file = topobathy_file
|
|
101
|
+
self.zb = rasterio.open(self.topobathy_file)
|
|
102
|
+
|
|
103
|
+
def close(self) -> None:
|
|
104
|
+
"""Close the rasterio dataset and the xarray dataset."""
|
|
105
|
+
if self.zb is not None:
|
|
106
|
+
self.zb.close()
|
|
107
|
+
self.ds.close()
|
|
108
|
+
|
|
109
|
+
def read(self, tiffile: str | Path) -> None:
|
|
110
|
+
"""Read a GeoTIFF file with elevation data into the dataset.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
tiffile : str | Path
|
|
115
|
+
Path to the GeoTIFF file.
|
|
116
|
+
"""
|
|
117
|
+
self.ds = xr.Dataset()
|
|
118
|
+
self.ds[self.data_array_name] = rioxarray.open_rasterio(
|
|
119
|
+
tiffile, masked=True
|
|
120
|
+
).squeeze()
|
|
121
|
+
|
|
122
|
+
def make(
|
|
123
|
+
self,
|
|
124
|
+
max_pixel_size: float = 0.0,
|
|
125
|
+
bbox: tuple[float, float, float, float] | None = None,
|
|
126
|
+
) -> xr.Dataset:
|
|
127
|
+
"""Generate a topobathy dataset from the elevation COG.
|
|
128
|
+
|
|
129
|
+
Reads the data at an appropriate overview level, applies the scale
|
|
130
|
+
factor, clips to the bounding box, and masks values outside
|
|
131
|
+
``[zbmin, zbmax]``.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
max_pixel_size : float
|
|
136
|
+
Maximum pixel size in metres for overview selection. If 0.0,
|
|
137
|
+
the native resolution is used.
|
|
138
|
+
bbox : tuple[float, float, float, float] | None
|
|
139
|
+
Bounding box ``(minx, miny, maxx, maxy)`` to clip the data.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
xr.Dataset
|
|
144
|
+
Dataset containing the masked elevation data array.
|
|
145
|
+
"""
|
|
146
|
+
overview_level = 0
|
|
147
|
+
|
|
148
|
+
if max_pixel_size > 0.0:
|
|
149
|
+
overview_level = get_appropriate_overview_level(self.zb, max_pixel_size)
|
|
150
|
+
|
|
151
|
+
if overview_level == 0:
|
|
152
|
+
zb = rioxarray.open_rasterio(self.zb)
|
|
153
|
+
else:
|
|
154
|
+
zb = rioxarray.open_rasterio(self.zb, overview_level=overview_level)
|
|
155
|
+
|
|
156
|
+
zb = zb * self.scale_factor
|
|
157
|
+
|
|
158
|
+
if "band" in zb.dims and zb.sizes["band"] == 1:
|
|
159
|
+
zb = zb.squeeze(dim="band", drop=True)
|
|
160
|
+
|
|
161
|
+
if bbox is not None:
|
|
162
|
+
zb = zb.rio.clip_box(minx=bbox[0], miny=bbox[1], maxx=bbox[2], maxy=bbox[3])
|
|
163
|
+
|
|
164
|
+
elevation = zb.to_numpy()[:]
|
|
165
|
+
elevation[elevation < self.zbmin] = np.nan
|
|
166
|
+
elevation[elevation > self.zbmax] = np.nan
|
|
167
|
+
|
|
168
|
+
self.ds = xr.Dataset()
|
|
169
|
+
self.ds[self.data_array_name] = xr.DataArray(
|
|
170
|
+
elevation, dims=["y", "x"], coords={"y": zb.y, "x": zb.x}
|
|
171
|
+
)
|
|
172
|
+
self.ds[self.data_array_name] = self.ds[self.data_array_name].rio.write_crs(
|
|
173
|
+
zb.rio.crs, inplace=True
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return self.ds
|
|
177
|
+
|
|
178
|
+
def write(self, output_file: str | Path = "") -> None:
|
|
179
|
+
"""Write the topobathy data to a GeoTIFF or NetCDF file.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
output_file : str | Path
|
|
184
|
+
Output file path. Extension determines the format:
|
|
185
|
+
``".tif"`` for COG GeoTIFF, ``".nc"`` for NetCDF.
|
|
186
|
+
"""
|
|
187
|
+
if output_file.endswith(".nc"):
|
|
188
|
+
self.ds.to_netcdf(output_file)
|
|
189
|
+
|
|
190
|
+
elif output_file.endswith(".tif"):
|
|
191
|
+
if self.cmap is not None:
|
|
192
|
+
rgb_da = get_rgb_data_array(
|
|
193
|
+
self.ds[self.data_array_name],
|
|
194
|
+
cmap=self.cmap,
|
|
195
|
+
cmin=self.cmin,
|
|
196
|
+
cmax=self.cmax,
|
|
197
|
+
color_values=self.color_values,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
rgb_da.rio.to_raster(
|
|
201
|
+
output_file,
|
|
202
|
+
driver="COG",
|
|
203
|
+
compress="deflate",
|
|
204
|
+
blocksize=512,
|
|
205
|
+
overview_resampling="nearest",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
self.ds[self.data_array_name].rio.to_raster(
|
|
210
|
+
output_file,
|
|
211
|
+
driver="COG",
|
|
212
|
+
compress="deflate",
|
|
213
|
+
blocksize=512,
|
|
214
|
+
overview_resampling="nearest",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def map_overlay(
|
|
218
|
+
self,
|
|
219
|
+
file_name: str,
|
|
220
|
+
xlim: list[float] | None = None,
|
|
221
|
+
ylim: list[float] | None = None,
|
|
222
|
+
width: int = 800,
|
|
223
|
+
) -> bool:
|
|
224
|
+
"""Create a PNG map overlay of the topobathy data in EPSG:3857.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
file_name : str
|
|
229
|
+
Output PNG file path.
|
|
230
|
+
xlim : list[float] | None
|
|
231
|
+
Longitude extent ``[lon_min, lon_max]``.
|
|
232
|
+
ylim : list[float] | None
|
|
233
|
+
Latitude extent ``[lat_min, lat_max]``.
|
|
234
|
+
width : int
|
|
235
|
+
Width in pixels for resolution calculation.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
bool
|
|
240
|
+
True on success, False on failure.
|
|
241
|
+
"""
|
|
242
|
+
if self.ds is None:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
lon_min = xlim[0]
|
|
247
|
+
lat_min = ylim[0]
|
|
248
|
+
lon_max = xlim[1]
|
|
249
|
+
lat_max = ylim[1]
|
|
250
|
+
|
|
251
|
+
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
|
|
252
|
+
x_min, y_min = transformer.transform(lon_min, lat_min)
|
|
253
|
+
x_max, y_max = transformer.transform(lon_max, lat_max)
|
|
254
|
+
|
|
255
|
+
dxy = (x_max - x_min) / width
|
|
256
|
+
|
|
257
|
+
bbox = reproject_bbox(
|
|
258
|
+
lon_min,
|
|
259
|
+
lat_min,
|
|
260
|
+
lon_max,
|
|
261
|
+
lat_max,
|
|
262
|
+
crs_src="EPSG:4326",
|
|
263
|
+
crs_dst=self.zb.crs,
|
|
264
|
+
buffer=0.05,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
self.make(max_pixel_size=dxy, bbox=bbox)
|
|
268
|
+
|
|
269
|
+
rgb_da = get_rgb_data_array(
|
|
270
|
+
self.ds[self.data_array_name],
|
|
271
|
+
cmap=self.cmap,
|
|
272
|
+
cmin=self.cmin,
|
|
273
|
+
cmax=self.cmax,
|
|
274
|
+
color_values=self.color_values,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
rgb_3857 = rgb_da.rio.reproject(
|
|
278
|
+
"EPSG:3857", resampling=Resampling.bilinear, nodata=0
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
rgb_3857 = rgb_3857.rio.pad_box(
|
|
282
|
+
minx=x_min, miny=y_min, maxx=x_max, maxy=y_max, constant_values=0
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
rgb_crop = rgb_3857.rio.clip_box(
|
|
286
|
+
minx=x_min, miny=y_min, maxx=x_max, maxy=y_max
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
rgba = rgb_crop.transpose("y", "x", "band").to_numpy().astype("uint8")
|
|
290
|
+
|
|
291
|
+
plt.imsave(file_name, rgba)
|
|
292
|
+
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.exception(f"Error in map_overlay: {e}")
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
def plot(
|
|
300
|
+
self,
|
|
301
|
+
pngfile: str,
|
|
302
|
+
zoom: int | None = None,
|
|
303
|
+
title: str = "Elevation (m)",
|
|
304
|
+
color_values: list[dict] | None = None,
|
|
305
|
+
cmap: str = "terrain",
|
|
306
|
+
vmin: float | None = None,
|
|
307
|
+
vmax: float | None = None,
|
|
308
|
+
lon_lim: list[float] | None = None,
|
|
309
|
+
lat_lim: list[float] | None = None,
|
|
310
|
+
width: float = 10.0,
|
|
311
|
+
background: str = "EsriWorldImagery",
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Plot the topobathy data with a basemap and save to PNG.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
pngfile : str
|
|
318
|
+
Output PNG file path.
|
|
319
|
+
zoom : int | None
|
|
320
|
+
Basemap zoom level. If None, auto-detected.
|
|
321
|
+
title : str
|
|
322
|
+
Plot title.
|
|
323
|
+
color_values : list[dict] | None
|
|
324
|
+
Discrete color definitions. If a string is passed, a default
|
|
325
|
+
elevation color scheme is used.
|
|
326
|
+
cmap : str
|
|
327
|
+
Matplotlib colormap for continuous coloring.
|
|
328
|
+
vmin : float | None
|
|
329
|
+
Minimum value for color mapping. Auto-detected if None.
|
|
330
|
+
vmax : float | None
|
|
331
|
+
Maximum value for color mapping. Auto-detected if None.
|
|
332
|
+
lon_lim : list[float] | None
|
|
333
|
+
Longitude limits ``[lon_min, lon_max]`` for the plot extent.
|
|
334
|
+
lat_lim : list[float] | None
|
|
335
|
+
Latitude limits ``[lat_min, lat_max]`` for the plot extent.
|
|
336
|
+
width : float
|
|
337
|
+
Figure width in inches.
|
|
338
|
+
background : str
|
|
339
|
+
Basemap provider: ``"osm"`` for OpenStreetMap or
|
|
340
|
+
``"EsriWorldImagery"`` (default).
|
|
341
|
+
"""
|
|
342
|
+
if lon_lim is None or lat_lim is None:
|
|
343
|
+
lon_min = self.ds.x.min().to_numpy()
|
|
344
|
+
lat_min = self.ds.y.min().to_numpy()
|
|
345
|
+
lon_max = self.ds.x.max().to_numpy()
|
|
346
|
+
lat_max = self.ds.y.max().to_numpy()
|
|
347
|
+
crs = self.ds[self.data_array_name].rio.crs
|
|
348
|
+
transformer = Transformer.from_crs(crs, "EPSG:3857", always_xy=True)
|
|
349
|
+
x_min, y_min = transformer.transform(lon_min, lat_min)
|
|
350
|
+
x_max, y_max = transformer.transform(lon_max, lat_max)
|
|
351
|
+
else:
|
|
352
|
+
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
|
|
353
|
+
x_min, y_min = transformer.transform(lon_lim[0], lat_lim[0])
|
|
354
|
+
x_max, y_max = transformer.transform(lon_lim[1], lat_lim[1])
|
|
355
|
+
|
|
356
|
+
da_3857 = self.ds[self.data_array_name].rio.reproject("EPSG:3857")
|
|
357
|
+
|
|
358
|
+
if vmin is None:
|
|
359
|
+
vmin = float(da_3857.min())
|
|
360
|
+
if vmax is None:
|
|
361
|
+
vmax = float(da_3857.max())
|
|
362
|
+
|
|
363
|
+
if color_values is None:
|
|
364
|
+
discrete_colors = False
|
|
365
|
+
else:
|
|
366
|
+
discrete_colors = True
|
|
367
|
+
if isinstance(color_values, str):
|
|
368
|
+
color_values = []
|
|
369
|
+
color_values.append(
|
|
370
|
+
{"color": "blue", "lower_value": -100, "upper_value": 0}
|
|
371
|
+
)
|
|
372
|
+
color_values.append(
|
|
373
|
+
{"color": "lightblue", "lower_value": 0, "upper_value": 10}
|
|
374
|
+
)
|
|
375
|
+
color_values.append(
|
|
376
|
+
{"color": "green", "lower_value": 10, "upper_value": 50}
|
|
377
|
+
)
|
|
378
|
+
color_values.append(
|
|
379
|
+
{"color": "brown", "lower_value": 50, "upper_value": 100}
|
|
380
|
+
)
|
|
381
|
+
color_values.append({"color": "white", "lower_value": 100})
|
|
382
|
+
|
|
383
|
+
aspect_ratio = (y_max - y_min) / (x_max - x_min)
|
|
384
|
+
fig, ax = plt.subplots(figsize=(width, aspect_ratio * width))
|
|
385
|
+
|
|
386
|
+
if discrete_colors:
|
|
387
|
+
masked = da_3857.where(da_3857 >= color_values[0]["lower_value"])
|
|
388
|
+
|
|
389
|
+
classified = xr.full_like(masked, np.nan)
|
|
390
|
+
colors = []
|
|
391
|
+
labels = []
|
|
392
|
+
for icolor, color_value in enumerate(color_values):
|
|
393
|
+
if "upper_value" in color_value:
|
|
394
|
+
lv = color_value["lower_value"]
|
|
395
|
+
uv = color_value["upper_value"]
|
|
396
|
+
classified = classified.where(
|
|
397
|
+
~((masked > lv) & (masked <= uv)), icolor + 1
|
|
398
|
+
)
|
|
399
|
+
labels.append(f"{lv}--{uv} m")
|
|
400
|
+
else:
|
|
401
|
+
lv = color_value["lower_value"]
|
|
402
|
+
classified = classified.where(~(masked > lv), icolor + 1)
|
|
403
|
+
labels.append(f">{lv} m")
|
|
404
|
+
colors.append(color_value["color"])
|
|
405
|
+
|
|
406
|
+
cmap_obj = ListedColormap(colors)
|
|
407
|
+
bounds = list(range(1, len(colors) + 2))
|
|
408
|
+
norm = BoundaryNorm(bounds, cmap_obj.N)
|
|
409
|
+
|
|
410
|
+
classified.plot(ax=ax, cmap=cmap_obj, norm=norm, add_colorbar=False)
|
|
411
|
+
|
|
412
|
+
legend_elements = []
|
|
413
|
+
for i, color_value in enumerate(color_values):
|
|
414
|
+
legend_elements.append(
|
|
415
|
+
Patch(facecolor=color_value["color"], label=labels[i])
|
|
416
|
+
)
|
|
417
|
+
plt.legend(handles=legend_elements, title="Elevation", loc="lower right")
|
|
418
|
+
|
|
419
|
+
else:
|
|
420
|
+
da_3857.plot(
|
|
421
|
+
ax=ax,
|
|
422
|
+
cmap=cmap,
|
|
423
|
+
vmin=vmin,
|
|
424
|
+
vmax=vmax,
|
|
425
|
+
add_colorbar=True,
|
|
426
|
+
cbar_kwargs={"label": "Elevation (m)"},
|
|
427
|
+
alpha=0.75,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if background.lower() == "osm":
|
|
431
|
+
if zoom is None:
|
|
432
|
+
ctx.add_basemap(
|
|
433
|
+
ax, crs=da_3857.rio.crs, source=ctx.providers.OpenStreetMap.Mapnik
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
ctx.add_basemap(
|
|
437
|
+
ax,
|
|
438
|
+
crs=da_3857.rio.crs,
|
|
439
|
+
source=ctx.providers.OpenStreetMap.Mapnik,
|
|
440
|
+
zoom=zoom,
|
|
441
|
+
)
|
|
442
|
+
else:
|
|
443
|
+
if zoom is None:
|
|
444
|
+
ctx.add_basemap(
|
|
445
|
+
ax, crs=da_3857.rio.crs, source=ctx.providers.Esri.WorldImagery
|
|
446
|
+
)
|
|
447
|
+
else:
|
|
448
|
+
ctx.add_basemap(
|
|
449
|
+
ax,
|
|
450
|
+
crs=da_3857.rio.crs,
|
|
451
|
+
source=ctx.providers.Esri.WorldImagery,
|
|
452
|
+
zoom=zoom,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
ax.set_xlim(x_min, x_max)
|
|
456
|
+
ax.set_ylim(y_min, y_max)
|
|
457
|
+
|
|
458
|
+
ax.set_axis_off()
|
|
459
|
+
plt.title(title)
|
|
460
|
+
|
|
461
|
+
plt.tight_layout()
|
|
462
|
+
plt.savefig(pngfile, dpi=300, bbox_inches="tight", pad_inches=0.1)
|
|
463
|
+
plt.close()
|