1- """Digital Elevation Map (DEM) downloading/ stitching/ upsampling
1+ """Digital Elevation Map (DEM) downloading, stitching, upsampling
22
33Module contains utilities for downloading all necessary .hgt files
44for a lon/lat rectangle, stiches them into one DEM, and creates a
3838 PROJECTION LL
3939"""
4040from __future__ import division , print_function
41+
4142import collections
4243import logging
4344import os
45+
4446import numpy as np
4547
46- from sardem import utils , loading , upsample_cy , conversions
47- from sardem .download import Tile , Downloader
48+ from sardem import conversions , loading , upsample_cy , utils
49+ from sardem .constants import DEFAULT_RES , NUM_PIXELS_SRTM1
50+ from sardem .download import Downloader , Tile
4851
49- NUM_PIXELS = 3601 # For SRTM1
5052RSC_KEYS = [
5153 "WIDTH" ,
5254 "FILE_LENGTH" ,
@@ -79,7 +81,7 @@ class Stitcher:
7981 """
8082
8183 def __init__ (
82- self , tile_names , filenames = [], data_source = "NASA" , num_pixels = NUM_PIXELS
84+ self , tile_names , filenames = [], data_source = "NASA" , num_pixels = NUM_PIXELS_SRTM1
8385 ):
8486 """List should come from Tile.srtm1_tile_names()"""
8587 self .tile_file_list = list (tile_names )
@@ -191,7 +193,7 @@ def _load_tile(self, tile_name):
191193 else :
192194 return loading .load_elevation (filename )
193195 else :
194- return np .zeros ((NUM_PIXELS , NUM_PIXELS ), dtype = self .dtype )
196+ return np .zeros ((NUM_PIXELS_SRTM1 , NUM_PIXELS_SRTM1 ), dtype = self .dtype )
195197
196198 def load_and_stitch (self ):
197199 """Function to load combine .hgt tiles
@@ -279,11 +281,11 @@ def create_dem_rsc(self):
279281 return rsc_dict
280282
281283
282- def crop_stitched_dem (bounds , stitched_dem , rsc_data ):
283- """Takes the output of Stitcher.load_and_stitch, crops to bounds
284+ def crop_stitched_dem (bbox , stitched_dem , rsc_data ):
285+ """Takes the output of Stitcher.load_and_stitch, crops to bbox
284286
285287 Args:
286- bounds (tuple[float]): (left, bot, right, top) lats and lons of
288+ bbox (tuple[float]): (left, bot, right, top) lats and lons of
287289 desired bounding box for the DEM
288290 stitched_dem (numpy.array, 2D): result from files
289291 through Stitcher.load_and_stitch()
@@ -293,7 +295,7 @@ def crop_stitched_dem(bounds, stitched_dem, rsc_data):
293295 numpy.array: a cropped version of the bigger stitched_dem
294296 """
295297 indexes , new_starts = utils .find_bounding_idxs (
296- bounds ,
298+ bbox ,
297299 rsc_data ["X_STEP" ],
298300 rsc_data ["Y_STEP" ],
299301 rsc_data ["X_FIRST" ],
@@ -305,11 +307,13 @@ def crop_stitched_dem(bounds, stitched_dem, rsc_data):
305307 return cropped_dem , new_starts , new_sizes
306308
307309
310+ def _float_is_on_bounds (x ):
311+ return int (x ) == x
312+
313+
308314def main (
309- left_lon = None ,
310- top_lat = None ,
311- dlon = None ,
312- dlat = None ,
315+ output_name = None ,
316+ bbox = None ,
313317 geojson = None ,
314318 wkt_file = None ,
315319 data_source = None ,
@@ -318,16 +322,16 @@ def main(
318322 make_isce_xml = False ,
319323 keep_egm = False ,
320324 shift_rsc = False ,
321- output_name = None ,
325+ cache_dir = None ,
322326):
323327 """Function for entry point to create a DEM with `sardem`
324328
325329 Args:
326- left_lon (float ): Left most longitude of DEM box
327- top_lat ( float): Top most longitude of DEM box
328- dlon (float): Width of box in longitude degrees
329- dlat (float ): Height of box in latitude degrees
330- geojson (dict ): geojson object outlining DEM (alternative to lat/lon )
330+ output_name (str ): name of file to save final DEM (default = elevation.dem)
331+ bbox (tuple[ float] ): (left, bot, right, top)
332+ Longitude/latitude desired bounding box for the DEM
333+ geojson (dict ): geojson object outlining DEM (alternative to bbox)
334+ wkt_file (str ): path to .wkt file outlining DEM (alternative to bbox )
331335 data_source (str): 'NASA' or 'AWS', where to download .hgt tiles from
332336 xrate (int): x-rate (columns) to upsample DEM (positive int)
333337 yrate (int): y-rate (rows) to upsample DEM (positive int)
@@ -337,47 +341,53 @@ def main(
337341 shift_rsc (bool): Shift the .dem.rsc file down/right so that the
338342 X_FIRST and Y_FIRST values represent the pixel *center* (instead of
339343 GDAL's convention of pixel edge). Default = False.
340- output_name (str): name of file to save final DEM (default = elevation.dem)
344+ cache_dir (str): directory to cache downloaded tiles
341345 """
342- if geojson :
343- bounds = utils .bounding_box (geojson = geojson )
344- elif wkt_file :
345- bounds = utils .get_wkt_bbox (wkt_file )
346- else :
347- bounds = utils .bounding_box (left_lon , top_lat , dlon , dlat )
348- logger .info ("Bounds: %s" , " " .join (str (b ) for b in bounds ))
349- outrows , outcols = utils .get_output_size (bounds , xrate , yrate )
346+ if bbox is None :
347+ if geojson :
348+ bbox = utils .bounding_box (geojson = geojson )
349+ elif wkt_file :
350+ bbox = utils .get_wkt_bbox (wkt_file )
351+
352+ if bbox is None :
353+ raise ValueError ("Must provide either bbox or geojson or wkt_file" )
354+ logger .info ("Bounds: %s" , " " .join (str (b ) for b in bbox ))
355+
356+ if all (_float_is_on_bounds (b ) for b in bbox ):
357+ logger .info ("Shifting bbox to nearest tile bounds" )
358+ bbox = utils .shift_integer_bbox (bbox )
359+ logger .info ("New edge bounds: %s" , " " .join (str (b ) for b in bbox ))
360+ # Now we're assuming that `bbox` refers to the edges of the desired bounding box
361+
362+ # Print a warning if they're possibly requesting too-large a box by mistake
363+ outrows , outcols = utils .get_output_size (bbox , xrate , yrate )
350364 if outrows * outcols > WARN_LIMIT :
351365 logger .warning (
352366 "Caution: Output size is {} x {} pixels." .format (outrows , outcols )
353367 )
354- logger .warning ("Are the bounds correct?" )
355-
356- # Are we using GDAL's convention (pixel edge) or the center?
357- # i.e. if `shift_rsc` is False, then we are `using_gdal_bounds`
358- using_gdal_bounds = not shift_rsc
368+ logger .warning ("Are the bounds correct (left, bottom, right, top)?" )
359369
370+ # For copernicus, use GDAL to warp from the VRT
360371 if data_source == "COP" :
361372 utils ._gdal_installed_correctly ()
362373 from sardem import cop_dem
363374
364375 cop_dem .download_and_stitch (
365376 output_name ,
366- bounds ,
377+ bbox ,
367378 keep_egm = keep_egm ,
368379 xrate = xrate ,
369380 yrate = yrate ,
370381 )
371382 if make_isce_xml :
372383 logger .info ("Creating ISCE2 XML file" )
373- utils .gdal2isce_xml (
374- output_name , keep_egm = keep_egm , using_gdal_bounds = using_gdal_bounds
375- )
384+ utils .gdal2isce_xml (output_name , keep_egm = keep_egm )
376385 return
377386
378- tile_names = list (Tile (* bounds ).srtm1_tile_names ())
387+ # If using SRTM, download tiles manually and stitch
388+ tile_names = list (Tile (* bbox ).srtm1_tile_names ())
379389
380- d = Downloader (tile_names , data_source = data_source )
390+ d = Downloader (tile_names , data_source = data_source , cache_dir = cache_dir )
381391 local_filenames = d .download_all ()
382392
383393 s = Stitcher (tile_names , filenames = local_filenames , data_source = data_source )
@@ -386,10 +396,10 @@ def main(
386396 # Now create corresponding rsc file
387397 rsc_dict = s .create_dem_rsc ()
388398
389- # Cropping: get very close to the bounds asked for:
399+ # Cropping: get very close to the bbox asked for:
390400 logger .info ("Cropping stitched DEM to boundaries" )
391401 stitched_dem , new_starts , new_sizes = crop_stitched_dem (
392- bounds , stitched_dem , rsc_dict
402+ bbox , stitched_dem , rsc_dict
393403 )
394404 new_x_first , new_y_first = new_starts
395405 new_rows , new_cols = new_sizes
@@ -398,8 +408,8 @@ def main(
398408 rsc_dict ["Y_FIRST" ] = new_y_first
399409 rsc_dict ["FILE_LENGTH" ] = new_rows
400410 rsc_dict ["WIDTH" ] = new_cols
401- if shift_rsc :
402- rsc_dict = utils .shift_rsc_dict (rsc_dict , to_gdal = True )
411+
412+ rsc_dict = utils .shift_rsc_dict (rsc_dict , to_gdal = True )
403413
404414 rsc_filename = output_name + ".rsc"
405415
@@ -452,17 +462,17 @@ def main(
452462
453463 if make_isce_xml :
454464 logger .info ("Creating ISCE2 XML file" )
455- utils .gdal2isce_xml (
456- output_name , keep_egm = keep_egm , using_gdal_bounds = using_gdal_bounds
457- )
465+ utils .gdal2isce_xml (output_name , keep_egm = keep_egm )
458466
459467 if keep_egm or data_source == "NASA_WATER" :
460468 logger .info ("Keeping DEM as EGM96 geoid heights" )
461469 else :
462470 logger .info ("Correcting DEM to heights above WGS84 ellipsoid" )
463- conversions .convert_dem_to_wgs84 (
464- output_name , using_gdal_bounds = using_gdal_bounds
465- )
471+ conversions .convert_dem_to_wgs84 (output_name , geoid = "egm96" )
472+
473+ # If the user wants the .rsc file to point to pixel center:
474+ if shift_rsc :
475+ utils .shift_rsc_file (rsc_filename , to_gdal = False )
466476
467477 # Overwrite with smaller dtype for water mask
468478 if data_source == "NASA_WATER" :
0 commit comments