55import geopandas as gpd
66import rasterio
77from rasterio .profiles import Profile
8+ from rasterio .crs import CRS
89import numpy as np
910import math
1011import logging
12+ from affine import Affine
1113
1214from dem_handler .utils .spatial import (
1315 BoundingBox ,
@@ -38,6 +40,7 @@ def get_rema_dem_for_bounds(
3840 buffer_metres : int = 0 ,
3941 buffer_pixels : int = 0 ,
4042 ellipsoid_heights : bool = True ,
43+ return_over_ocean : bool = True ,
4144 geoid_tif_path : Path | str = "egm_08_geoid.tif" ,
4245 download_geoid : bool = False ,
4346 num_cpus : int = 1 ,
@@ -67,6 +70,9 @@ def get_rema_dem_for_bounds(
6770 buffer to add to the dem in pixels.
6871 ellipsoid_heights : bool, optional
6972 Subtracts the geoid height from the tiles to get the ellipsoid height, by default True
73+ return_over_ocean: bool, optional
74+ If no tile intersections are found with the dem, return elevation heights. i.e.
75+ ellipsoid height if ellipsoid_heights = True, else DEM of zero values.
7076 geoid_tif_path : Path | str, optional
7177 Path to the existing ellipsoid file, by default "egm_08_geoid.tif"
7278 download_geoid : bool, optional
@@ -126,9 +132,10 @@ def get_rema_dem_for_bounds(
126132 bounds = BoundingBox (
127133 * transform_polygon (box (* bounds .bounds ), bounds_src_crs , REMA_CRS ).bounds
128134 )
129- bounds_src_crs = REMA_CRS
130135 logging .info (f"Adjusted bounds in 3031 : { bounds } " )
131136
137+ # bounds crs is now 3031
138+ bounds_crs = REMA_CRS
132139 bounds_poly = box (* bounds .bounds )
133140
134141 # buffer in 3031 based on user input
@@ -152,50 +159,65 @@ def get_rema_dem_for_bounds(
152159 rema_layer = f"REMA_Mosaic_Index_v2_{ resolution } m"
153160 rema_index_df = gpd .read_file (rema_index_path , layer = rema_layer )
154161
162+ # adjust the bounds to include whole dem pixels and stop offsets being introduced
163+ logging .info ("Adjusting bounds to include whole dem pixels" )
164+ sample_rema_tile = extract_s3_path (rema_index_df .iloc [0 ].s3url )
165+ bounds = adjust_bounds_for_rema_profile (bounds , [sample_rema_tile ])
166+ logging .info (f"Adjusted bounds : { bounds } " )
167+
155168 intersecting_rema_files = rema_index_df [
156169 rema_index_df .geometry .intersects (bounds_poly )
157170 ]
171+ s3_url_list = [Path (url ) for url in intersecting_rema_files ["s3url" ].to_list ()]
172+ raster_paths = [] # list to store paths to rasters
173+
158174 if len (intersecting_rema_files .s3url ) == 0 :
159175 logging .info ("No REMA tiles found for this bounding box" )
160- return None , None , None
161- logging .info (f"{ len (intersecting_rema_files .s3url )} intersecting tiles found" )
162-
163- s3_url_list = [Path (url ) for url in intersecting_rema_files ["s3url" ].to_list ()]
164- raster_paths = []
165- if local_dem_dir :
166- raster_paths = list (local_dem_dir .rglob ("*.tif" ))
167- raster_names = [r .stem .replace ("_dem" , "" ) for r in raster_paths ]
168- s3_url_list = [url for url in s3_url_list if url .stem not in raster_names ]
169-
170- if return_paths :
171- if num_tasks :
172- raster_paths .extend (
173- [
174- download_dir / u .name .replace (".json" , "_dem.tif" )
175- for u in s3_url_list
176- ]
176+ if not return_over_ocean :
177+ logging .info (
178+ "Exiting process. To return zero valued DEM or ellipsoid heights, set return_over_ocean=True."
177179 )
180+ return None , None , None
178181 else :
179- dem_urls = [extract_s3_path (url .as_posix ()) for url in s3_url_list ]
180- raster_paths .extend (
181- [
182- download_dir / dem_url .split ("amazonaws.com" )[1 ][1 :]
183- for dem_url in dem_urls
184- ]
185- )
186- return raster_paths
182+ logging .info ("Returning sea-level DEM" )
183+ # Generate a zero's out DEM. If ellipsoid heights
184+ # Are required, zero values will be replaced with logic below
185+ dem_profile = make_empty_rema_profile_for_bounds (bounds , resolution )
186+ dem_array = 0 * np .ones ((dem_profile ["height" ], dem_profile ["width" ]))
187187
188- raster_paths .extend (
189- download_rema_tiles (s3_url_list , download_dir , num_cpus , num_tasks )
190- )
188+ else :
189+ logging .info (f"{ len (intersecting_rema_files .s3url )} intersecting tiles found" )
190+ if local_dem_dir :
191+ raster_paths = list (local_dem_dir .rglob ("*.tif" ))
192+ raster_names = [r .stem .replace ("_dem" , "" ) for r in raster_paths ]
193+ s3_url_list = [url for url in s3_url_list if url .stem not in raster_names ]
194+
195+ if return_paths :
196+ if num_tasks :
197+ raster_paths .extend (
198+ [
199+ download_dir / u .name .replace (".json" , "_dem.tif" )
200+ for u in s3_url_list
201+ ]
202+ )
203+ else :
204+ dem_urls = [extract_s3_path (url .as_posix ()) for url in s3_url_list ]
205+ raster_paths .extend (
206+ [
207+ download_dir / dem_url .split ("amazonaws.com" )[1 ][1 :]
208+ for dem_url in dem_urls
209+ ]
210+ )
211+ return raster_paths
191212
192- # adjust the bounds to include whole dem pixels and stop offsets being introduced
193- logging .info ("Adjusting bounds to include whole dem pixels" )
194- bounds = adjust_bounds_for_rema_profile (bounds , raster_paths )
195- logging .info (f"Adjusted bounds : { bounds } " )
213+ raster_paths .extend (
214+ download_rema_tiles (s3_url_list , download_dir , num_cpus , num_tasks )
215+ )
196216
197- logging .info ("combining found DEMs" )
198- dem_array , dem_profile = crop_datasets_to_bounds (raster_paths , bounds , save_path )
217+ logging .info ("combining found DEMs" )
218+ dem_array , dem_profile = crop_datasets_to_bounds (
219+ raster_paths , bounds , save_path
220+ )
199221
200222 # return the dem in either ellipsoid or geoid referenced heights. The REMA dem is already
201223 # referenced to the ellipsoid. Values are set to zero where no tile data exists
@@ -212,9 +234,9 @@ def get_rema_dem_for_bounds(
212234 return dem_array , dem_profile , raster_paths
213235 else :
214236 geoid_bounds = bounds
215- if bounds_src_crs != GEOID_CRS :
237+ if bounds_crs != GEOID_CRS :
216238 geoid_bounds = transform_polygon (
217- box (* bounds .bounds ), bounds_src_crs , GEOID_CRS
239+ box (* bounds .bounds ), bounds_crs , GEOID_CRS
218240 ).bounds
219241 if not download_geoid and not geoid_tif_path .exists ():
220242 raise FileExistsError (
@@ -320,3 +342,45 @@ def _round_up_to_step(coord, origin, res):
320342 adjusted_bounds = (xmin , ymin , xmax , ymax )
321343
322344 return BoundingBox (* adjusted_bounds )
345+
346+
347+ def make_empty_rema_profile_for_bounds (
348+ bounds : BoundingBox | tuple [float , float , float , float ],
349+ resolution : int ,
350+ ) -> dict :
351+ """Make an empty REMA DEM rasterio profile for given bounds.
352+
353+ Parameters
354+ ----------
355+ bounds : BoundingBox | tuple
356+ (left, bottom, right, top) in EPSG:3031
357+ resolution : int
358+ Pixel size in metres
359+
360+ Returns
361+ -------
362+ dict
363+ Rasterio profile
364+ """
365+ if isinstance (bounds , tuple ):
366+ bounds = BoundingBox (* bounds )
367+
368+ width = math .ceil ((bounds .right - bounds .left ) / resolution )
369+ height = math .ceil ((bounds .top - bounds .bottom ) / resolution )
370+
371+ transform = Affine .translation (bounds .left , bounds .top ) * Affine .scale (
372+ resolution , - resolution
373+ )
374+
375+ dem_profile = {
376+ "driver" : "GTiff" ,
377+ "dtype" : "float32" ,
378+ "nodata" : np .nan ,
379+ "width" : width ,
380+ "height" : height ,
381+ "count" : 1 ,
382+ "crs" : CRS .from_epsg (3031 ),
383+ "transform" : transform ,
384+ }
385+
386+ return dem_profile
0 commit comments