Source code for src.j_dataprep.user_input
import json
from rusterizer_3d import rasterize_from_python
import numpy as np
from osgeo import gdal
from src.util.misc import saveraster
from scipy.ndimage import label
[docs]
class Surface_input:
'''
Class to create a new land cover input from user input for the Landcover class.
Parameters:
ncols (int): Number of columns in the raster grid.
nrows (int): Number of rows in the raster grid.
resolution (float): Resolution of the raster grid cells.
Attributes:
dictionary (dict): Loaded landcover category dictionary from 'landcover_bgt.json'.
res (float): Resolution of the raster grid cells.
ncols (int): Number of columns in the raster grid.
nrows (int): Number of rows in the raster grid.
'''
def __init__(self, ncols, nrows, resolution):
self.dictionary = json.load(open("src/databases/landcover_bgt.json", "r", encoding="utf-8"))
self.res = resolution
self.ncols = ncols
self.nrows = nrows
[docs]
def get_value(self, category, key) -> int | None:
'''
Retrieves the integer value for a given category and key from the landcover dictionary.
Parameters:
category (str): The category name in the landcover dictionary.
key (str): The key within the category to look up.
Returns:
int or None: The integer value corresponding to the category and key, or None if not found.
'''
return self.dictionary.get(category, {}).get(key, None)
[docs]
def user_input_array(self, obj_path, type):
'''
Rasterizes a given input object path and retrieves a type number based on provided type info.
Parameters:
obj_path (str): Path to the input object to rasterize.
type (list): List containing two elements [category, key] to fetch a type number.
Returns:
tuple: Tuple containing:
- np.ndarray: Rasterized array from the input object.
- int or None: Type number corresponding to the category and key.
'''
array = rasterize_from_python(obj_path, self.ncols, self.nrows, self.res, [0, 0], -9999)
typeno = self.get_value(type[0], type[1])
return array[0], typeno
[docs]
class Building3d_input:
'''
Class to handle 3D building input rasterization and create DSM layers with gaps.
Attributes:
cols (int): Number of columns in the raster grid.
rows (int): Number of rows in the raster grid.
res (float): Spatial resolution of each cell in the raster grid.
'''
def __init__(self, ncols, nrows, resolution):
'''
Initialize the Building3d_input instance with grid dimensions and resolution.
Parameters:
ncols (int): Number of columns in the raster grid.
nrows (int): Number of rows in the raster grid.
resolution (float): Spatial resolution of each cell in the grid.
'''
self.res = resolution
self.cols = ncols
self.rows = nrows
[docs]
@staticmethod
def buildings_input(arrays, num_gaps):
'''
Generate 3D-layered DSM representing building heights with gaps.
Parameters:
arrays (list of np.ndarray): List of input 2D arrays representing building heights and gap layers, output from Rusterizer.
num_gaps (int): Number of gaps to include in the DSM layering.
Returns
-------
dsms (np.ndarray):
3D array with shape (layers, rows, cols) of DSM layers with gaps.
base_array (np.ndarray):
The base building height array from input for reference.
'''
if num_gaps == 0:
return arrays[0], arrays[0]
layers = num_gaps * 2 + 1
if layers > len(arrays) + 1:
print("Amount of gaps is too high for the given input. ")
return
dsms = np.full((layers, arrays[0].shape[0], arrays[0].shape[1]), np.nan)
arrays = [np.where(arr == -9999, np.nan, arr) for arr in arrays]
while len(arrays) < 4:
dummy = np.full_like(arrays[0], np.nan)
arrays.append(dummy)
grounded_mask = arrays[1] == 0
direct_gaps = ~grounded_mask
gaps = arrays[3] > 0
both_masks = grounded_mask & gaps
dsms[0][grounded_mask] = np.nanmin(np.stack([arrays[0][grounded_mask], arrays[2][grounded_mask]]), axis=0)
if num_gaps == 1:
dsms[1][direct_gaps] = arrays[1][direct_gaps]
dsms[1][both_masks] = arrays[3][both_masks]
dsms[2][direct_gaps] = arrays[0][direct_gaps]
dsms[2][both_masks] = arrays[0][both_masks]
return dsms, arrays[0]
if num_gaps > 1:
if 4 < len(arrays):
# layer 1
dsms[1][direct_gaps] = arrays[1][direct_gaps]
dsms[1][both_masks] = arrays[3][both_masks]
direct_gaps_valid = direct_gaps & ~np.isnan(arrays[2])
direct_gaps_fallback = direct_gaps & np.isnan(arrays[2])
both_masks_valid = both_masks & ~np.isnan(arrays[4])
for i in range(1, num_gaps):
j = 2 * i
new_direct_valid = direct_gaps_valid & ~np.isnan(arrays[j])
new_direct_fallback = direct_gaps_valid & np.isnan(arrays[j])
dsms[j][new_direct_valid] = arrays[j][new_direct_valid]
dsms[j][new_direct_fallback] = arrays[0][new_direct_fallback]
if j + 1 < len(arrays):
dsms[j + 1][new_direct_valid] = arrays[j + 1][new_direct_valid]
# For both_masks
if j + 2 < len(arrays):
new_both_valid = both_masks_valid & ~np.isnan(arrays[j + 2])
new_both_fallback = both_masks_valid & np.isnan(arrays[j + 2])
dsms[j][new_both_valid] = arrays[j + 2][new_both_valid]
dsms[j][new_both_fallback] = arrays[0][new_both_fallback]
if j + 3 < len(arrays):
dsms[j + 1][new_both_valid] = arrays[j + 3][new_both_valid]
# Update masks for next iteration
direct_gaps_valid = new_direct_valid
both_masks_valid = new_both_valid if j + 2 < len(arrays) else both_masks_valid
# final layer
dsms[layers - 1][direct_gaps_valid] = arrays[0][direct_gaps_valid]
dsms[layers - 1][both_masks_valid] = arrays[0][both_masks_valid]
# Post-process to ensure no empty layers before filled ones (starting from DSM 2), this is done bc a mismatch can happen when the amount of grounded gap layers is lower than the amount of direct
for i in range(3, dsms.shape[0]):
j = i
while j > 3:
empty_below = np.isnan(dsms[j - 1]) & ~np.isnan(dsms[j])
if not np.any(empty_below):
break
dsms[j - 1][empty_below] = dsms[j][empty_below]
dsms[j][empty_below] = np.nan
j -= 1
return dsms, arrays[0]
elif len(arrays) == 4:
dsms[1][direct_gaps] = arrays[1][direct_gaps]
dsms[1][both_masks] = arrays[3][both_masks]
direct_gaps_valid = direct_gaps & ~np.isnan(arrays[2])
direct_gaps_fallback = direct_gaps & np.isnan(arrays[2])
dsms[2][direct_gaps_valid] = arrays[2][direct_gaps_valid]
dsms[2][direct_gaps_fallback] = arrays[0][direct_gaps_fallback]
dsms[2][both_masks] = arrays[0][both_masks]
dsms[3][direct_gaps_valid] = arrays[3][direct_gaps_valid]
dsms[4][direct_gaps_valid] = arrays[0][direct_gaps_valid]
return dsms, arrays[0]
[docs]
def rasterize_3dbuilding(self, obj_path, num_gaps):
'''
Rasterize a 3D building OBJ file into DSM layers with gaps.
Parameters:
obj_path (str): File path to the 3D building OBJ file.
num_gaps (int): Number of gaps to consider when layering DSMs.
Returns
-------
dsms (np.ndarray):
3D array of DSM layers with gaps.
highest_array (np.ndarray):
The highest building height layer from input.
input_arrays (list of np.ndarray):
Raw input rasterized arrays from the OBJ.
'''
input_arrays = rasterize_from_python(obj_path, self.cols, self.rows, self.res, [0, 0], -9999)
dsms, highest_array = self.buildings_input(input_arrays, num_gaps)
return dsms, highest_array, input_arrays