Skip to content

Commit 4ec51d2

Browse files
authored
Merge pull request #31 from GeoscienceAustralia/upgrade/rema_return_over_ocean
Upgrade/rema return over ocean
2 parents 19dd2d3 + 114d373 commit 4ec51d2

File tree

3 files changed

+119
-39
lines changed

3 files changed

+119
-39
lines changed

dem_handler/dem/cop_glo30.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,6 @@ def make_empty_cop_glo30_profile_for_bounds(
631631
----------
632632
bounds : BoundingBox | tuple[float | int, float | int, float | int, float | int]
633633
The set of bounds (left, bottom, right, top)
634-
pixel_buffer | int
635-
The number of pixels to add as a buffer to the profile
636634
637635
Returns
638636
-------

dem_handler/dem/rema.py

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import geopandas as gpd
66
import rasterio
77
from rasterio.profiles import Profile
8+
from rasterio.crs import CRS
89
import numpy as np
910
import math
1011
import logging
12+
from affine import Affine
1113

1214
from 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

tests/rema/test_dem_rema.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ class TestDem:
6161
True,
6262
)
6363

64+
# over ocean where no tile intersections exists
65+
no_tile_intersection_bbox = BoundingBox(143.0, -63.0, 143.5, -62.5)
66+
test_no_intersection_ellipsoid_h = TestDem(
67+
no_tile_intersection_bbox,
68+
os.path.join(TEST_DATA_PATH, "rema_32m_no_tile_intersection_ellipsoid_h.tif"),
69+
32,
70+
None,
71+
os.path.join(GEOID_DATA_PATH, "egm_08_geoid_rema_32m_no_tile_intersection.tif"),
72+
True,
73+
)
74+
6475
# over land and ocean where tile data partially exists
6576
ocean_no_data_bbox = BoundingBox(166.8, -77.0, 167.0, -76.7)
6677
test_one_tile_and_no_tile_overlap_ellipsoid_h = TestDem(
@@ -80,6 +91,7 @@ class TestDem:
8091
test_single_tile_ellipsoid_h,
8192
test_four_tiles_ellipsoid_h,
8293
test_one_tile_ocean_ellipsoid_h,
94+
test_no_intersection_ellipsoid_h,
8395
test_one_tile_and_no_tile_overlap_ellipsoid_h,
8496
]
8597

@@ -100,6 +112,11 @@ class TestDem:
100112
dem_file=test_one_tile_ocean_ellipsoid_h.dem_file.replace("ellipsoid", "geoid"),
101113
ellipsoid_heights=False,
102114
)
115+
test_no_intersection_geoid_h = replace(
116+
test_no_intersection_ellipsoid_h,
117+
dem_file=test_no_intersection_ellipsoid_h.dem_file.replace("ellipsoid", "geoid"),
118+
ellipsoid_heights=False,
119+
)
103120
test_one_tile_and_no_tile_overlap_geoid_h = replace(
104121
test_one_tile_and_no_tile_overlap_ellipsoid_h,
105122
dem_file=test_one_tile_and_no_tile_overlap_ellipsoid_h.dem_file.replace(
@@ -112,6 +129,7 @@ class TestDem:
112129
test_single_tile_geoid_h,
113130
test_four_tiles_geoid_h,
114131
test_one_tile_ocean_geoid_h,
132+
test_no_intersection_geoid_h,
115133
test_one_tile_and_no_tile_overlap_geoid_h,
116134
]
117135

0 commit comments

Comments
 (0)