"""
A Python class for terrain data I/O, creation, and manipulation
"""
import numpy as np
import sys
from osgeo import gdal
from osgeo import osr
from pyHMT2D.Hydraulic_Models_Data import HydraulicData
[docs]class Terrain(HydraulicData):
"""A Python class for terrain data I/O, creation, and manipulation
Typical work flow is as follows:
1. create the Terrain object
2. create the terrain (elevation, pixel_width/height): user can either call
some pre-defined terrains such as constant slope, or create the terrain by themsleves
and then call set_terrain(...), and set_pixel_size(...)
3. set the georeferencing by calling set_georeference(...)
4. save the terrain to file by calling save_terrain_to_file(...)
Attributes
----------
name : str
name of the terrain
elevation : numpy.ndarray
elevation of the terrain (2D numpy array)
geoTopLeft_x : float
raster's top-left corner georeferenced x-coordinate
geoTopLeft_y : float
raster's top-left corner georeferenced y-coordinate
pixel_width : float
pixel width (in x), i.e, each pixel is how many meters/feet wide?
pixel_height : float
pixel height (in y), i.e, each pixel is how many meters/feet high?
EPSGCode : int
EPSG (European Petroleum Survey Group) code that defines the coordinate reference system
geoTransform : list
affine transform for the raster image from image pixel to real coordinates
supportedGDALDrivers : list
list of supported GDAL drivers (short names only)
"""
def __init__(self, name):
"""Terrain class constructor
Parameters
----------
name : str
name of the terrain
"""
HydraulicData.__init__(self, "Terrain")
self.name = name # name of the terrain
self.elevation = np.array([]) # elevation of the terrain, should be 2D numpy array
self.geoTopLeft_x = 0.0 # raster's top-left corner georeferenced location
self.geoTopleft_y = 0.0
self.pixel_width = -1.0 # pixel width (in x), i.e, each pixel is how many meters/feet wide?
self.pixel_height = -1.0 # pixel height (in y)
self.EPSGCode = -1 # EPSG (European Petroleum Survey Group) code that defines the coordinate reference system
self.geoTransform = [] # affine transform for the raster image from image pixel to real coordinates
self.supportedGDALDrivers = [] #list of supported GDAL drivers (short names only)
self.build_supported_GDAL_drivers_list()
[docs] def get_terrain_name(self):
"""Get the terrain name
Returns
-------
name : str
name of the terrain
"""
return self.name
[docs] def get_elevation(self):
"""Get the elevation array
Returns
-------
elevation : numpy.array
elevation 2D array
"""
return self.elevation
[docs] def set_elevation(self, elevation):
"""Set the elevation array
Parameters
_______
elevation : numpy.ndarray
elevation 2D array
Returns
-------
"""
self.elevation = elevation
[docs] def set_pixel_size(self, pixel_width, pixel_height):
"""Set the pixel size in x and y directions
Parameters
----------
pixel_width : float
the width of each pixel in real world
pixel_height : float
the height of each pixel in real world
Returns
-------
"""
self.pixel_width = pixel_width
self.pixel_height = pixel_height
[docs] def build_supported_GDAL_drivers_list(self):
"""Build the list of supported GDAL drivers list
Returns
-------
"""
for i in range(gdal.GetDriverCount()):
drv = gdal.GetDriver(i)
self.supportedGDALDrivers.append(drv.ShortName)
print("Supported GDAL drivers are: ", self.supportedGDALDrivers)
[docs] def create_constant_slope_channel_elevation(self, slope, channel_lenx, channel_leny, pixel_width,
pixel_height, elevation_origin=0, extra_len=0):
"""Create a constant slope channel elevation
The slope is in the x direction only. The slope in the y direction is zero.
Parameters
-----------
slope : float
slope in x
channel_lenx : float
channel length in x
channel_leny : float
channel length in y
elevation_origin : float
the elevation at the origin (top left; does not account for the extra fringe)
extra_len : float
optional extra fringe length added to the channel domain (to
have some free room in developing 2D models in e.g., SMS or HEC-RAS.
Returns
-------
"""
# raster image width and height in pixels
self.pixel_width = pixel_width
self.pixel_height = pixel_height
#raster size (without extra length)
nx = int(channel_lenx/pixel_width)
ny = int(channel_leny/pixel_height)
#extra length (fringe)
extra_len_nx = int(extra_len/pixel_width)
extra_len_ny = int(extra_len/pixel_height)
self.elevation = np.zeros((ny+extra_len_ny*2,nx+extra_len_nx*2))
#set the elevation
for iy in range(0, ny + extra_len_ny * 2):
for ix in range(0, nx + extra_len_nx * 2):
self.elevation[iy, ix] = elevation_origin -slope * (ix-extra_len_nx) * pixel_width
[docs] def set_georeference(self, geoTopLeft_x, geoTopLeft_y, EPSGCode):
"""Set the georeferencing information
Parameters
----------
geoTopLeft_x : float
raster's top-left corner georeferenced x location
geoTopLeft_y : float
raster's top-left corner georeferenced y location
EPSGCode : int
EPSG (European Petroleum Survey Group) code that defines the coordinate reference system
Returns
-------
"""
if self.pixel_width < 0 or self.pixel_height < 0:
print("Either pixel_width or pixel_height is negative. Need to create the terrain first.")
print("Exiting...")
sys.exit()
self.geoTopLeft_x = geoTopLeft_x
self.geoTopleft_y = geoTopLeft_y
self.geoTransform = [geoTopLeft_x, self.pixel_width, 0, self.geoTopleft_y, 0, -self.pixel_height]
self.EPSGCode = EPSGCode
[docs] def save_terrain_to_file(self, terrainFileName, geoDriverName = 'GTiff'):
"""save terrain to file, such as GeoTiff
Parameters
-----------
terrainFileName : str
file name for the saved terrain
geoDriverName : str
GDAL raster driver names, such as 'GTiff'
"""
print("Save the terrain to file:", terrainFileName)
if geoDriverName not in self.supportedGDALDrivers:
print("The provided geoDriverName", geoDriverName, "is not supported.")
print("Supported GDAL driver names are:", self.supportedGDALDrivers)
print("Exiting...")
sys.exit()
if (not self.elevation.size) or (self.pixel_width < 0) or (self.pixel_height < 0) \
or (self.EPSGCode < 0) or (len(self.geoTransform) == 0):
print("Terrain data is not complete. Missing elevation, pixel size, EPSG code, "
"or geoTransform.")
print("Exiting...")
sys.exit()
driver = gdal.GetDriverByName(geoDriverName)
dst_filename = terrainFileName
dst_ds = driver.Create(dst_filename, self.elevation.shape[1],
self.elevation.shape[0], 1, gdal.GDT_Float32)
dst_ds.SetGeoTransform(self.geoTransform)
srs = osr.SpatialReference()
srs.ImportFromEPSG(self.EPSGCode)
dst_ds.SetProjection(srs.ExportToWkt())
dst_ds.GetRasterBand(1).WriteArray(self.elevation)
# Once we're done, close properly the dataset
dst_ds = None