Skip to content
Snippets Groups Projects

Urban morphology

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    The snippet can be accessed without any authentication.
    Authored by Sasu Karttunen
    Edited
    urban_morphology.py 3.13 KiB
    @numba.stencil(cval=0.0)
    def grid_cell_wall_area(
        building_height=np.array([[]]), dx: float = 1.0, dy: float = 1.0
    ) -> numba.float64[:, :]:
        # Using negative difference helps with out-of-bounds (which are cval=0.0).
        dz_bottom = building_height[0, 0] - building_height[-1, 0]
        dz_top = building_height[0, 0] - building_height[1, 0]
        dz_left = building_height[0, 0] - building_height[0, -1]
        dz_right = building_height[0, 0] - building_height[0, 1]
    
        grid_wall_area = 0.0
    
        # Checking for negative difference only ensures the walls aren't double-counted.
        if dz_top < 0.0:
            grid_wall_area -= dz_top * dx  # Top wall
        if dz_bottom < 0.0:
            grid_wall_area -= dz_bottom * dx  # Bottom wall
        if dz_left < 0.0:
            grid_wall_area -= dz_left * dy  # Left wall
        if dz_right < 0.0:
            grid_wall_area -= dz_right * dy  # Right wall
    
        return grid_wall_area
    
    
    @numba.njit(parallel=True, looplift=True)
    def compute_wall_area(
        building_height=np.array([[]]),
        dx: float = 1.0,
        dy: float = 1.0,
        nx: int = 0,
        ny: int = 0,
    ) -> float:
        wall_area = np.sum(grid_cell_wall_area(building_height, dx, dy))
    
        return wall_area
    
    
    def urban_parameters_from_morphology(
        lcz_map: xr.DataArray,
        usm_driver: xr.Dataset,
    ) -> xr.Dataset:
    
        zones = np.unique(lcz_map)
        params = {}
        dx_usm = float(usm_driver.x[1] - usm_driver.x[0])
        dy_usm = float(usm_driver.y[1] - usm_driver.y[0])
        nx_usm = usm_driver.x.size
        ny_usm = usm_driver.y.size
        extent_x_usm = dx_usm * nx_usm
        extent_y_usm = dy_usm * ny_usm
    
        for zone in zones:
            params[zone] = {}
    
            lcz_mask = lcz_map == zone
            building_mask = lcz_mask & (usm_driver["building_type"] > 0)
            street_mask = lcz_mask & (usm_driver["street_type"] > 0)
    
            total_gridpoints = lcz_mask.sum()
            total_area = total_gridpoints * dx_usm * dy_usm
    
            # Mean building height
            params[zone]["building_height"] = float(
                usm_driver["buildings_2d"].where(building_mask).mean()
            )
    
            # Urban fraction
            params[zone]["urban_fraction"] = float(
                (building_mask | street_mask).sum() / total_gridpoints
            )
    
            urban_area = total_area * params[zone]["urban_fraction"]
    
            # Plan area fraction
            params[zone]["building_plan_area_fraction"] = float(
                building_mask.sum() / total_gridpoints
            )
    
            # Compute total wall area
            building_height = (
                usm_driver["buildings_2d"].where(building_mask).fillna(0.0)
            )
            wall_area = compute_wall_area(
                building_height=building_height.data,
                dx=dx_usm,
                dy=dy_usm,
                nx=nx_usm,
                ny=ny_usm,
            )
    
            wall_area_density = wall_area / total_area
    
            # Street canyon aspect ratio
            params[zone]["street_canyon_aspect_ratio"] = float(
                0.5
                * wall_area_density
                / (1.0 - params[zone]["building_plan_area_fraction"])
            )
    
            # Frontal area fraction
            params[zone]["building_frontal_area_fraction"] = float(
                wall_area_density / 4
            )
    
        return params
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment