Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

es.hillshade() #953

Open
kstueb opened this issue Sep 17, 2024 · 1 comment
Open

es.hillshade() #953

kstueb opened this issue Sep 17, 2024 · 1 comment

Comments

@kstueb
Copy link

kstueb commented Sep 17, 2024

The function hillshade = es.hillshade(arr)...
...from earthpy.spatial yields a correct hillshade raster only if the arr represents a DTM with a spatial resolution of 1m.

This is easily fixed if the line x, y = np.gradient(arr) in the function hillshade() is replaced with x, y = np.gradient(arr, resolution), where resolution is the spatial resolution of the DTM passed to the function as additional parameter.

https://earthpy.readthedocs.io/en/latest/_modules/earthpy/spatial.html#hillshade

Oversteepened hillshade generated with es.hillshade() from a 25m resolution DTM:
image

Correct hillshade from the same DTM calculated with the proposed correction to es.hillshade():
image

Both hillshades plotted with ep.plot_bands()

@kstueb
Copy link
Author

kstueb commented Sep 17, 2024

Proposed corrected function:

def hillshade(arr, azimuth=30, altitude=30, resolution=1):
    """Create hillshade from a numpy array containing elevation data.

    Parameters
    ----------
    arr : numpy array of shape (rows, columns)
        Numpy array with elevation values to be used to created hillshade.
    azimuth : float (default=30)
        The desired azimuth for the hillshade.
    altitude : float (default=30)
        The desired sun angle altitude for the hillshade.
    resolution : float (default=1)
        The spatial resolution of arr in the same units as the elevation values. 

    Returns
    -------
    numpy array
        A numpy array containing hillshade values.

    Example
    -------
    .. plot::

        >>> import matplotlib.pyplot as plt
        >>> import rasterio as rio
        >>> import earthpy.spatial as es
        >>> from earthpy.io import path_to_example
        >>> with rio.open(path_to_example('rmnp-dem.tif')) as src:
        ...     dem = src.read()
        >>> print(dem.shape)
        (1, 187, 152)
        >>> squeezed_dem = dem.squeeze() # remove first dimension
        >>> print(squeezed_dem.shape)
        (187, 152)
        >>> shade = es.hillshade(squeezed_dem)
        >>> plt.imshow(shade, cmap="Greys")
        <matplotlib.image.AxesImage object at 0x...>
    """
    try:
        # Possibly allowing different resolutions in x and y:
        # Note that resolution would have to be passed as a tuple (-y, x)! 
        #if isinstance(resolution, (list, tuple)):
        #    x, y = np.gradient(arr, *resolution) 
        #else:
        x, y = np.gradient(arr, resolution)
    except ValueError:
        raise ValueError("Input array should be two-dimensional")

    if azimuth <= 360.0:
        azimuth = 360.0 - azimuth
        azimuthrad = azimuth * np.pi / 180.0
    else:
        raise ValueError(
            "Azimuth value should be less than or equal to 360 degrees"
        )

    if altitude <= 90.0:
        altituderad = altitude * np.pi / 180.0
    else:
        raise ValueError(
            "Altitude value should be less than or equal to 90 degrees"
        )

    slope = np.pi / 2.0 - np.arctan(np.sqrt(x * x + y * y))
    aspect = np.arctan2(-x, y)

    shaded = np.sin(altituderad) * np.sin(slope) + np.cos(
        altituderad
    ) * np.cos(slope) * np.cos((azimuthrad - np.pi / 2.0) - aspect)

    return 255 * (shaded + 1) / 2

@kstueb kstueb changed the title es.hillshade es.hillshade() Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant