def _process_dask(raster, xs, ys): if max_distance >= max_possible_distance: # consider all targets in the whole raster # the data array is computed at once, # make sure your data fit your memory height, width = raster.shape raster.data = raster.data.rechunk({0: height, 1: width}) xs = xs.rechunk({0: height, 1: width}) ys = ys.rechunk({0: height, 1: width}) pad_y = pad_x = 0 else: cellsize_x, cellsize_y = get_dataarray_resolution(raster) # calculate padding for each chunk pad_y = int(max_distance / cellsize_y + 0.5) pad_x = int(max_distance / cellsize_x + 0.5) out = da.map_overlap( _process_numpy, raster.data, xs, ys, depth=(pad_y, pad_x), boundary=np.nan, meta=np.array(()), ) return out
def calc_cellsize(raster): """ Calculates cell size of an array based on its attributes. Supported units are: meter, kelometer, foot, and mile. Cellsize will be converted to meters. Parameters ---------- raster : xarray.DataArray 2D array of input values. Returns ------- cellsize : tuple Tuple of (cellsize_x, cellsize_y). Where cellsize_x is the size of cells in x-direction, and cellsize_y is the size of cells in y-direction. Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> h, w = 100, 200 >>> data = np.ones((h, w)) >>> from xrspatial.convolution import calc_cellsize >>> # cellsize that already specified as an attribute of input raster >>> raster_1 = xr.DataArray(data, attrs={'res': (0.5, 0.5)}) >>> calc_cellsize(raster_1) (0.5, 0.5) >>> # if no unit specified, default to meters >>> raster_2 = xr.DataArray(data, dims=['y', 'x']) >>> raster_2['y'] = np.linspace(1, h, h) >>> raster_2['x'] = np.linspace(1, w, w) >>> calc_cellsize(raster_2) (1.0, 1.0) # convert cellsize to meters >>> raster_3 = xr.DataArray( ... data, dims=['y', 'x'], attrs={'unit': 'km'}) >>> raster_3['y'] = np.linspace(1, h, h) >>> raster_3['x'] = np.linspace(1, w, w) >>> calc_cellsize(raster_3) >>> (1000.0, 1000.0) """ if 'unit' in raster.attrs: unit = raster.attrs['unit'] else: unit = DEFAULT_UNIT cellsize_x, cellsize_y = get_dataarray_resolution(raster) cellsize_x = _to_meters(cellsize_x, unit) cellsize_y = _to_meters(cellsize_y, unit) # avoid negative cellsize in y return cellsize_x, np.abs(cellsize_y)
def slope(agg:xr.DataArray, name: str='slope') -> xr.DataArray: """Returns slope of input aggregate in degrees. Parameters ---------- agg : xr.DataArray name : str - name property of output xr.DataArray Returns ------- data: xr.DataArray Notes: ------ Algorithm References: - http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-slope-works.htm - Burrough, P. A., and McDonell, R. A., 1998. Principles of Geographical Information Systems (Oxford University Press, New York), pp 406 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) # numpy case if isinstance(agg.data, np.ndarray): out = _run_numpy(agg.data, cellsize_x, cellsize_y) # cupy case elif has_cuda() and isinstance(agg.data, cupy.ndarray): out = _run_cupy(agg.data, cellsize_x, cellsize_y) # dask + cupy case elif has_cuda() and isinstance(agg.data, da.Array) and is_cupy_backed(agg): out = _run_dask_cupy(agg.data, cellsize_x, cellsize_y) # dask + numpy case elif isinstance(agg.data, da.Array): out = _run_dask_numpy(agg.data, cellsize_x, cellsize_y) else: raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def _get_pixel_id(point, raster, xdim=None, ydim=None): # get location in `raster` pixel space for `point` in y-x coordinate space # point: (y, x) - coordinates of the point # xdim: name of the x coordinate dimension in input `raster`. # ydim: name of the x coordinate dimension in input `raster` if ydim is None: ydim = raster.dims[-2] if xdim is None: xdim = raster.dims[-1] y_coords = raster.coords[ydim].data x_coords = raster.coords[xdim].data cellsize_x, cellsize_y = get_dataarray_resolution(raster, xdim, ydim) py = int(abs(point[0] - y_coords[0]) / cellsize_y) px = int(abs(point[1] - x_coords[0]) / cellsize_x) # return index of row and column where the `point` located. return py, px
def curvature(agg, name='curvature'): """Compute the curvature (second derivatives) of a agg surface. Parameters ---------- agg: xarray.xr.DataArray 2D input agg image with shape=(height, width) Returns ------- curvature: xarray.xr.DataArray Curvature image with shape=(height, width) """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) cellsize = (cellsize_x + cellsize_y) / 2 # numpy case if isinstance(agg.data, np.ndarray): out = _run_numpy(agg.data, cellsize) # cupy case elif has_cuda() and isinstance(agg.data, cupy.ndarray): out = _run_cupy(agg.data, cellsize) # dask + numpy case elif isinstance(agg.data, da.Array): out = _run_dask_numpy(agg.data, cellsize) # dask + cupy case elif has_cuda() and isinstance(agg.data, da.Array) and is_cupy_backed(agg): out = _run_dask_cupy(agg.data, cellsize) else: raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def curvature(agg: xr.DataArray, name: Optional[str] = 'curvature') -> xr.DataArray: """ Calculates, for all cells in the array, the curvature (second derivative) of each cell based on the elevation of its neighbors in a 3x3 grid. A positive curvature indicates the surface is upwardly convex. A negative value indicates it is upwardly concave. A value of 0 indicates a flat surface. Units of the curvature output raster are one hundredth (1/100) of a z-unit. Parameters ---------- agg : xarray.DataArray 2D NumPy, CuPy, NumPy-backed Dask xarray DataArray of elevation values. Must contain `res` attribute. name : str, default='curvature' Name of output DataArray. Returns ------- curvature_agg : xarray.DataArray, of the same type as `agg` 2D aggregate array of curvature values. All other input attributes are preserved. References ---------- - arcgis: https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/how-curvature-works.htm # noqa Examples -------- Curvature works with NumPy backed xarray DataArray .. sourcecode:: python >>> import numpy as np >>> import dask.array as da >>> import xarray as xr >>> from xrspatial import curvature >>> flat_data = np.zeros((5, 5), dtype=np.float32) >>> flat_raster = xr.DataArray(flat_data, attrs={'res': (1, 1)}) >>> flat_curv = curvature(flat_raster) >>> print(flat_curv) <xarray.DataArray 'curvature' (dim_0: 5, dim_1: 5)> array([[nan, nan, nan, nan, nan], [nan, -0., -0., -0., nan], [nan, -0., -0., -0., nan], [nan, -0., -0., -0., nan], [nan, nan, nan, nan, nan]]) Dimensions without coordinates: dim_0, dim_1 Attributes: res: (1, 1) Curvature works with Dask with NumPy backed xarray DataArray .. sourcecode:: python >>> convex_data = np.array([ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, -1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], dtype=np.float32) >>> convex_raster = xr.DataArray( da.from_array(convex_data, chunks=(3, 3)), attrs={'res': (10, 10)}, name='convex_dask_numpy_raster') >>> print(convex_raster) <xarray.DataArray 'convex_dask_numpy_raster' (dim_0: 5, dim_1: 5)> dask.array<array, shape=(5, 5), dtype=float32, chunksize=(3, 3), chunktype=numpy.ndarray> Dimensions without coordinates: dim_0, dim_1 Attributes: res: (10, 10) >>> convex_curv = curvature(convex_raster, name='convex_curvature') >>> print(convex_curv) # return a xarray DataArray with Dask-backed array <xarray.DataArray 'convex_curvature' (dim_0: 5, dim_1: 5)> dask.array<_trim, shape=(5, 5), dtype=float32, chunksize=(3, 3), chunktype=numpy.ndarray> Dimensions without coordinates: dim_0, dim_1 Attributes: res: (10, 10) >>> print(convex_curv.compute()) <xarray.DataArray 'convex_curvature' (dim_0: 5, dim_1: 5)> array([[nan, nan, nan, nan, nan], [nan, -0., 1., -0., nan], [nan, 1., -4., 1., nan], [nan, -0., 1., -0., nan], [nan, nan, nan, nan, nan]]) Dimensions without coordinates: dim_0, dim_1 Attributes: res: (10, 10) Curvature works with CuPy backed xarray DataArray. .. sourcecode:: python >>> import cupy >>> concave_data = np.array([ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], dtype=np.float32) >>> concave_raster = xr.DataArray( cupy.asarray(concave_data), attrs={'res': (10, 10)}, name='concave_cupy_raster') >>> concave_curv = curvature(concave_raster) >>> print(type(concave_curv.data)) <class 'cupy.core.core.ndarray'> >>> print(concave_curv) <xarray.DataArray 'curvature' (dim_0: 5, dim_1: 5)> array([[nan, nan, nan, nan, nan], [nan, -0., -1., -0., nan], [nan, -1., 4., -1., nan], [nan, -0., -1., -0., nan], [nan, nan, nan, nan, nan]], dtype=float32) Dimensions without coordinates: dim_0, dim_1 Attributes: res: (10, 10) """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) cellsize = (cellsize_x + cellsize_y) / 2 mapper = ArrayTypeFunctionMapping( numpy_func=_run_numpy, cupy_func=_run_cupy, dask_func=_run_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'curvature() does not support dask with cupy backed DataArray.' ), # noqa ) out = mapper(agg)(agg.data, cellsize) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def generate_terrain(agg: xr.DataArray, x_range: tuple = (0, 500), y_range: tuple = (0, 500), seed: int = 10, zfactor: int = 4000, full_extent: Optional[Union[Tuple, List]] = None, name: str = 'terrain') -> xr.DataArray: """ Generates a pseudo-random terrain which can be helpful for testing raster functions. Parameters ---------- x_range : tuple, default=(0, 500) Range of x values. x_range : tuple, default=(0, 500) Range of y values. seed : int, default=10 Seed for random number generator. zfactor : int, default=4000 Multipler for z values. full_extent : str, default=None bbox<xmin, ymin, xmax, ymax>. Full extent of coordinate system. Returns ------- terrain : xr.DataArray 2D array of generated terrain values. References ---------- - Michael McHugh: https://www.youtube.com/watch?v=O33YV4ooHSo - Red Blob Games: https://www.redblobgames.com/maps/terrain-from-noise/ Examples -------- .. plot:: :include-source: >>> import numpy as np >>> import xarray as xr >>> from xrspatial import generate_terrain >>> W = 400 >>> H = 300 >>> data = np.zeros((H, W), dtype=np.float32) >>> raster = xr.DataArray(data, dims=['y', 'x']) >>> xrange = (-20e6, 20e6) >>> yrange = (-20e6, 20e6) >>> seed = 2 >>> zfactor = 10 >>> terrain = generate_terrain(raster, xrange, yrange, seed, zfactor) >>> terrain.plot.imshow() """ height, width = agg.shape if full_extent is None: full_extent = (x_range[0], y_range[0], x_range[1], y_range[1]) elif not isinstance(full_extent, (list, tuple)) and len(full_extent) != 4: raise TypeError('full_extent must be tuple(4)') full_xrange = (full_extent[0], full_extent[2]) full_yrange = (full_extent[1], full_extent[3]) x_range_scaled = (_scale(x_range[0], full_xrange, (0.0, 1.0)), _scale(x_range[1], full_xrange, (0.0, 1.0))) y_range_scaled = (_scale(y_range[0], full_yrange, (0.0, 1.0)), _scale(y_range[1], full_yrange, (0.0, 1.0))) mapper = ArrayTypeFunctionMapping( numpy_func=_terrain_numpy, cupy_func=_terrain_cupy, dask_func=_terrain_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'generate_terrain() does not support dask with cupy backed DataArray' # noqa )) out = mapper(agg)(agg.data, seed, x_range_scaled, y_range_scaled, zfactor) canvas = ds.Canvas(plot_width=width, plot_height=height, x_range=x_range, y_range=y_range) # DataArray coords were coming back different from cvs.points... hack_agg = canvas.points(pd.DataFrame({'x': [], 'y': []}), 'x', 'y') res = get_dataarray_resolution(hack_agg) result = xr.DataArray(out, name=name, coords=hack_agg.coords, dims=hack_agg.dims, attrs={'res': res}) return result
def curvature(agg: xr.DataArray, name: Optional[str] = 'curvature') -> xr.DataArray: """ Calculates, for all cells in the array, the curvature (second derivative) of each cell based on the elevation of its neighbors in a 3x3 grid. A positive curvature indicates the surface is upwardly convex. A negative value indicates it is upwardly concave. A value of 0 indicates a flat surface. Units of the curvature output raster are one hundredth (1/100) of a z-unit. Parameters: ---------- agg: xarray.DataArray 2D array of elevation values NumPy, CuPy, NumPy-backed Dask, or Cupy-backed Dask array. Must contain "res" attribute. name: str (default = "curvature") Name of output DataArray. Returns: ---------- curvature: xarray.DataArray 2D array, of the same type as the input, of calculated curvature values All other input attributes are preserved. Notes: ---------- Algorithm References: - https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/how-curvature-works.htm Examples: ---------- Imports >>> import numpy as np >>> import xarray as xr >>> from xrspatial import curvature Create Initial DataArray >>> agg = xr.DataArray(np.array([[0, 1, 0, 0], >>> [1, 1, 0, 0], >>> [0, 1, 2, 2], >>> [1, 0, 2, 0], >>> [0, 2, 2, 2]]), >>> dims = ["lat", "lon"], >>> attrs = dict(res = 1)) >>> height, width = agg.shape >>> _lon = np.linspace(0, width - 1, width) >>> _lat = np.linspace(0, height - 1, height) >>> agg["lon"] = _lon >>> agg["lat"] = _lat >>> print(agg) <xarray.DataArray (lat: 5, lon: 4)> array([[0, 1, 0, 0], [1, 1, 0, 0], [0, 1, 2, 2], [1, 0, 2, 0], [0, 2, 2, 2]]) Coordinates: * lon (lon) float64 0.0 1.0 2.0 3.0 * lat (lat) float64 0.0 1.0 2.0 3.0 4.0 Attributes: res: 1 Create Curvature DataArray >>> print(curvature(agg)) <xarray.DataArray 'curvature' (lat: 5, lon: 4)> array([[ nan, nan, nan, nan], [ nan, 100., -300., nan], [ nan, 100., 300., nan], [ nan, -600., 400., nan], [ nan, nan, nan, nan]]) Coordinates: * lon (lon) float64 0.0 1.0 2.0 3.0 * lat (lat) float64 0.0 1.0 2.0 3.0 4.0 Attributes: res: 1 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) cellsize = (cellsize_x + cellsize_y) / 2 # numpy case if isinstance(agg.data, np.ndarray): out = _run_numpy(agg.data, cellsize) # cupy case elif has_cuda() and isinstance(agg.data, cupy.ndarray): out = _run_cupy(agg.data, cellsize) # dask + cupy case elif has_cuda() and isinstance(agg.data, da.Array) and is_cupy_backed(agg): out = _run_dask_cupy(agg.data, cellsize) # dask + numpy case elif isinstance(agg.data, da.Array): out = _run_dask_numpy(agg.data, cellsize) else: raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def slope(agg: xr.DataArray, name: str = 'slope') -> xr.DataArray: """ Returns slope of input aggregate in degrees. Parameters ---------- agg : xr.DataArray 2D array of elevation data. name : str, default='slope' Name of output DataArray. Returns ------- slope_agg : xr.DataArray of same type as `agg` 2D array of slope values. All other input attributes are preserved. References ---------- - arcgis: http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-slope-works.htm # noqa Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial import slope >>> data = np.array([ ... [0, 0, 0, 0, 0], ... [0, 0, 0, -1, 2], ... [0, 0, 0, 0, 1], ... [0, 0, 0, 5, 0]]) >>> agg = xr.DataArray(data) >>> slope_agg = slope(agg) >>> slope_agg <xarray.DataArray 'slope' (dim_0: 4, dim_1: 5)> array([[ nan, nan, nan, nan, nan], [ nan, 0. , 14.036243, 32.512516, nan], [ nan, 0. , 42.031113, 53.395725, nan], [ nan, nan, nan, nan, nan]], dtype=float32) Dimensions without coordinates: dim_0, dim_1 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) mapper = ArrayTypeFunctionMapping( numpy_func=_run_numpy, cupy_func=_run_cupy, dask_func=_run_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'slope() does not support dask with cupy backed DataArray' # noqa ), ) out = mapper(agg)(agg.data, cellsize_x, cellsize_y) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def calc_cellsize(raster): """ Calculates cell size of an array based on its attributes. Default = meters. If lat-lon, units are converted to meters. Parameters ---------- raster : xarray.DataArray 2D array of input values. Returns ------- cellsize : tuple Tuple of (cellsize_x, cellsize_y). cellsize_x : float Size of cells in x-direction. cellsize_y : float Size of cells in y-direction. Examples -------- .. plot:: :include-source: import matplotlib.pyplot as plt import numpy as np import xarray as xr from xrspatial import generate_terrain from xrspatial.convolution import calc_cellsize # Generate Example Terrain W = 500 H = 300 template_terrain = xr.DataArray(np.zeros((H, W))) x_range=(-20e6, 20e6) y_range=(-20e6, 20e6) terrain_agg = generate_terrain( template_terrain, x_range=x_range, y_range=y_range ) # Edit Attributes terrain_agg = terrain_agg.assign_attrs( { 'Description': 'Example Terrain', 'units': 'km', 'Max Elevation': '4000', } ) terrain_agg = terrain_agg.rename({'x': 'lon', 'y': 'lat'}) terrain_agg = terrain_agg.rename('Elevation') .. sourcecode:: python >>> print(terrain_agg[200:203, 200:202]) <xarray.DataArray 'Elevation' (lat: 3, lon: 2)> array([[1264.02296597, 1261.947921 ], [1285.37105519, 1282.48079719], [1306.02339636, 1303.4069579 ]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: (80000.0, 133333.3333333333) Description: Example Terrain units: km Max Elevation: 4000 .. sourcecode:: python >>> # Calculate Cellsize >>> cellsize = calc_cellsize(terrain_agg) >>> print(cellsize) (80000.0, 133333.3333333333) """ if 'unit' in raster.attrs: unit = raster.attrs['unit'] else: unit = DEFAULT_UNIT cellsize_x, cellsize_y = get_dataarray_resolution(raster) cellsize_x = _to_meters(cellsize_x, unit) cellsize_y = _to_meters(cellsize_y, unit) # When converting from lnglat_to_meters, could have negative cellsize in y return cellsize_x, np.abs(cellsize_y)
def slope(agg: xr.DataArray, name: str = 'slope') -> xr.DataArray: """ Returns slope of input aggregate in degrees. Parameters: --------- agg: xarray.DataArray 2D array of elevation band data. name: str, optional (default = 'slope') name property of output xarray.DataArray Returns: --------- xarray.DataArray 2D array, of the same type as the input, of calculated slope values. All other input attributes are preserved. Notes: --------- Algorithm References: - http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-slope-works.htm - Burrough, P. A., and McDonell, R. A., 1998. Principles of Geographical Information Systems (Oxford University Press, New York), pp 406 Examples: --------- Imports >>> import numpy as np >>> import xarray as xr >>> from xrspatial import slope Create Data Array >>> agg = xr.DataArray(np.array([[0, 0, 0, 0, 0, 0, 0], >>> [0, 0, 2, 4, 0, 8, 0], >>> [0, 2, 2, 4, 6, 8, 0], >>> [0, 4, 4, 4, 6, 8, 0], >>> [0, 6, 6, 6, 6, 8, 0], >>> [0, 8, 8, 8, 8, 8, 0], >>> [0, 0, 0, 0, 0, 0, 0]]), >>> dims = ["lat", "lon"], >>> attrs = dict(res = 1)) >>> height, width = agg.shape >>> _lon = np.linspace(0, width - 1, width) >>> _lat = np.linspace(0, height - 1, height) >>> agg["lon"] = _lon >>> agg["lat"] = _lat Create Slope Data Array >>> print(slope(agg)) <xarray.DataArray 'slope' (lat: 7, lon: 7)> array([[ 0, 0, 0, 0, 0, 0, 0], [ 0, 46, 60, 63, 73, 70, 0], [ 0, 60, 54, 54, 68, 67, 0], [ 0, 68, 60, 54, 60, 71, 0], [ 0, 73, 63, 60, 54, 72, 0], [ 0, 74, 71, 71, 72, 75, 0], [ 0, 0, 0, 0, 0, 0, 0]]) Coordinates: * lon (lon) float64 0.0 1.0 2.0 3.0 4.0 5.0 6.0 * lat (lat) float64 0.0 1.0 2.0 3.0 4.0 5.0 6.0 Attributes: res: 1 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) # numpy case if isinstance(agg.data, np.ndarray): out = _run_numpy(agg.data, cellsize_x, cellsize_y) # cupy case elif has_cuda() and isinstance(agg.data, cupy.ndarray): out = _run_cupy(agg.data, cellsize_x, cellsize_y) # dask + cupy case elif has_cuda() and is_dask_cupy(agg): out = _run_dask_cupy(agg.data, cellsize_x, cellsize_y) # dask + numpy case elif isinstance(agg.data, da.Array): out = _run_dask_numpy(agg.data, cellsize_x, cellsize_y) else: raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def calc_cellsize(raster): """ Calculates cell size of an array based on its attributes. Default = meters. If lat-lon, units are converted to meters. Parameters ---------- raster : xarray.DataArray 2D array of input values. Returns ------- cellsize : tuple Tuple of (cellsize_x, cellsize_y). cellsize_x : float Size of cells in x-direction. cellsize_y : float Size of cells in y-direction. Examples -------- .. plot:: :include-source: import datashader as ds import matplotlib.pyplot as plt from xrspatial import generate_terrain from xrspatial.convolution import calc_cellsize # Create Canvas W = 500 H = 300 cvs = ds.Canvas(plot_width = W, plot_height = H, x_range = (-20e6, 20e6), y_range = (-20e6, 20e6)) # Generate Example Terrain terrain_agg = generate_terrain(canvas = cvs) # Edit Attributes terrain_agg = terrain_agg.assign_attrs( { 'Description': 'Example Terrain', 'units': 'km', 'Max Elevation': '4000', } ) terrain_agg = terrain_agg.rename({'x': 'lon', 'y': 'lat'}) terrain_agg = terrain_agg.rename('Elevation') .. sourcecode:: python >>> print(terrain_agg[200:203, 200:202]) <xarray.DataArray 'Elevation' (lat: 3, lon: 2)> array([[1264.02249454, 1261.94748873], [1285.37061171, 1282.48046696], [1306.02305679, 1303.40657515]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Example Terrain Max Elevation: 3000 units: km .. sourcecode:: python >>> # Calculate Cellsize >>> cellsize = calc_cellsize(terrain_agg) >>> print(cellsize) (1, 1) """ if 'unit' in raster.attrs: unit = raster.attrs['unit'] else: unit = DEFAULT_UNIT with warnings.catch_warnings(): warnings.simplefilter('default') warnings.warn('Raster distance unit not provided. ' 'Use meter as default.', Warning) cellsize_x, cellsize_y = get_dataarray_resolution(raster) cellsize_x = _to_meters(cellsize_x, unit) cellsize_y = _to_meters(cellsize_y, unit) # When converting from lnglat_to_meters, could have negative cellsize in y return cellsize_x, np.abs(cellsize_y)
def slope(agg: xr.DataArray, name: str = 'slope') -> xr.DataArray: """ Returns slope of input aggregate in degrees. Parameters ---------- agg : xr.DataArray 2D array of elevation data. name : str, default='slope' Name of output DataArray. Returns ------- slope_agg : xr.DataArray of same type as `agg` 2D array of slope values. All other input attributes are preserved. References ---------- - arcgis: http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-slope-works.htm # noqa Examples -------- .. plot:: :include-source: import matplotlib.pyplot as plt import numpy as np import xarray as xr from xrspatial import generate_terrain, slope # Generate Example Terrain W = 500 H = 300 template_terrain = xr.DataArray(np.zeros((H, W))) x_range=(-20e6, 20e6) y_range=(-20e6, 20e6) terrain_agg = generate_terrain( template_terrain, x_range=x_range, y_range=y_range ) # Edit Attributes terrain_agg = terrain_agg.assign_attrs( { 'Description': 'Example Terrain', 'units': 'km', 'Max Elevation': '4000', } ) terrain_agg = terrain_agg.rename({'x': 'lon', 'y': 'lat'}) terrain_agg = terrain_agg.rename('Elevation') # Create Slope Aggregate Array slope_agg = slope(agg = terrain_agg, name = 'Slope') # Edit Attributes slope_agg = slope_agg.assign_attrs({'Description': 'Example Slope', 'units': 'deg'}) # Plot Terrain terrain_agg.plot(cmap = 'terrain', aspect = 2, size = 4) plt.title("Terrain") plt.ylabel("latitude") plt.xlabel("longitude") # Plot Slope slope_agg.plot(aspect = 2, size = 4) plt.title("Slope") plt.ylabel("latitude") plt.xlabel("longitude") .. sourcecode:: python >>> print(terrain_agg[200:203, 200:202]) <xarray.DataArray 'Elevation' (lat: 3, lon: 2)> array([[1264.02296597, 1261.947921 ], [1285.37105519, 1282.48079719], [1306.02339636, 1303.4069579 ]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: (80000.0, 133333.3333333333) Description: Example Terrain units: km Max Elevation: 4000 >>> print(slope_agg[200:203, 200:202]) <xarray.DataArray 'Slope' (lat: 3, lon: 2)> array([[0.00757718, 0.00726441], [0.00893266, 0.00916095], [0.00773291, 0.00699103]], dtype=float32) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: (80000.0, 133333.3333333333) Description: Example Slope units: deg Max Elevation: 4000 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) mapper = ArrayTypeFunctionMapping( numpy_func=_run_numpy, cupy_func=_run_cupy, dask_func=_run_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'slope() does not support dask with cupy backed DataArray' # noqa ), ) out = mapper(agg)(agg.data, cellsize_x, cellsize_y) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def curvature(agg: xr.DataArray, name: Optional[str] = 'curvature') -> xr.DataArray: """ Calculates, for all cells in the array, the curvature (second derivative) of each cell based on the elevation of its neighbors in a 3x3 grid. A positive curvature indicates the surface is upwardly convex. A negative value indicates it is upwardly concave. A value of 0 indicates a flat surface. Units of the curvature output raster are one hundredth (1/100) of a z-unit. Parameters ---------- agg : xarray.DataArray 2D NumPy, CuPy, NumPy-backed Dask, or Cupy-backed Dask array of elevation values. Must contain `res` attribute. name : str, default='curvature' Name of output DataArray. Returns ------- curvature_agg : xarray.DataArray, of the same type as `agg` 2D aggregate array of curvature values. All other input attributes are preserved. References ---------- - arcgis: https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/how-curvature-works.htm # noqa Examples -------- .. plot:: :include-source: import datashader as ds import numpy as np import matplotlib.pyplot as plt from xrspatial import generate_terrain, curvature # Create Canvas W = 500 H = 300 cvs = ds.Canvas(plot_width = W, plot_height = H, x_range = (-20e6, 20e6), y_range = (-20e6, 20e6)) # Generate Example Terrain terrain_agg = generate_terrain(canvas = cvs) # Edit Attributes terrain_agg = terrain_agg.assign_attrs( { 'Description': 'Example Terrain', 'units': 'km', 'Max Elevation': '4000', } ) terrain_agg = terrain_agg.rename({'x': 'lon', 'y': 'lat'}) terrain_agg = terrain_agg.rename('Elevation') # Create Curvature Aggregate Array curvature_agg = curvature(agg = terrain_agg, name = 'Curvature') # Edit Attributes curvature_agg = curvature_agg.assign_attrs( { 'Description': 'Curvature', 'units': 'rad', } ) # Where cells are extremely upwardly convex curvature_agg.data = np.where( np.logical_and( curvature_agg.data > 3000, curvature_agg.data < 4000, ), 1, np.nan, ) # Plot Terrain terrain_agg.plot(cmap = 'terrain', aspect = 2, size = 4) plt.title("Terrain") plt.ylabel("latitude") plt.xlabel("longitude") # Plot Curvature curvature_agg.plot(aspect = 2, size = 4) plt.title("Curvature") plt.ylabel("latitude") plt.xlabel("longitude") .. sourcecode:: python >>> print(terrain_agg[200:203, 200:202]) <xarray.DataArray 'Elevation' (lat: 3, lon: 2)> array([[1264.02249454, 1261.94748873], [1285.37061171, 1282.48046696], [1306.02305679, 1303.40657515]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Example Terrain units: km Max Elevation: 4000 .. sourcecode:: python >>> print(curvature_agg[200:203, 200:202]) <xarray.DataArray 'Curvature' (lat: 3, lon: 2)> array([[nan, nan], [nan, nan], [nan, nan]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Curvature units: rad Max Elevation: 4000 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) cellsize = (cellsize_x + cellsize_y) / 2 # numpy case if isinstance(agg.data, np.ndarray): out = _run_numpy(agg.data, cellsize) # cupy case elif has_cuda() and isinstance(agg.data, cupy.ndarray): out = _run_cupy(agg.data, cellsize) # dask + cupy case elif has_cuda() and isinstance(agg.data, da.Array) and is_cupy_backed(agg): out = _run_dask_cupy(agg.data, cellsize) # dask + numpy case elif isinstance(agg.data, da.Array): out = _run_dask_numpy(agg.data, cellsize) else: raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def generate_terrain(agg: xr.DataArray, x_range: tuple = (0, 500), y_range: tuple = (0, 500), seed: int = 10, zfactor: int = 4000, full_extent: Optional[Union[Tuple, List]] = None, name: str = 'terrain') -> xr.DataArray: """ Generates a pseudo-random terrain which can be helpful for testing raster functions. Parameters ---------- x_range : tuple, default=(0, 500) Range of x values. x_range : tuple, default=(0, 500) Range of y values. seed : int, default=10 Seed for random number generator. zfactor : int, default=4000 Multipler for z values. full_extent : str, default=None bbox<xmin, ymin, xmax, ymax>. Full extent of coordinate system. Returns ------- terrain : xr.DataArray 2D array of generated terrain values. References ---------- - Michael McHugh: https://www.youtube.com/watch?v=O33YV4ooHSo - Red Blob Games: https://www.redblobgames.com/maps/terrain-from-noise/ Examples -------- .. plot:: :include-source: import numpy as np import xarray as xr from xrspatial import generate_terrain W = 4 H = 3 data = np.zeros((H, W), dtype=np.float32) raster = xr.DataArray(data, dims=['y', 'x']) xrange = (-20e6, 20e6) yrange = (-20e6, 20e6) seed = 2 zfactor = 10 terrain = generate_terrain(raster, xrange, yrange, seed, zfactor) .. sourcecode:: python >>> print(terrain) <xarray.DataArray 'terrain' (y: 3, x: 4)> array([[ 6.8067746, 5.263137 , 4.664292 , 6.821344 ], [ 7.4834156, 6.9849734, 4.3545456, 0. ], [ 6.7674546, 10. , 7.0946655, 7.015267 ]], dtype=float32) # noqa Coordinates: * x (x) float64 -1.5e+07 -5e+06 5e+06 1.5e+07 * y (y) float64 -1.333e+07 0.0 1.333e+07 Attributes: res: (10000000.0, -13333333.333333334) """ height, width = agg.shape if full_extent is None: full_extent = (x_range[0], y_range[0], x_range[1], y_range[1]) elif not isinstance(full_extent, (list, tuple)) and len(full_extent) != 4: raise TypeError('full_extent must be tuple(4)') full_xrange = (full_extent[0], full_extent[2]) full_yrange = (full_extent[1], full_extent[3]) x_range_scaled = (_scale(x_range[0], full_xrange, (0.0, 1.0)), _scale(x_range[1], full_xrange, (0.0, 1.0))) y_range_scaled = (_scale(y_range[0], full_yrange, (0.0, 1.0)), _scale(y_range[1], full_yrange, (0.0, 1.0))) mapper = ArrayTypeFunctionMapping( numpy_func=_terrain_numpy, cupy_func=_terrain_cupy, dask_func=_terrain_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'generate_terrain() does not support dask with cupy backed DataArray' # noqa )) out = mapper(agg)(agg.data, seed, x_range_scaled, y_range_scaled, zfactor) canvas = ds.Canvas(plot_width=width, plot_height=height, x_range=x_range, y_range=y_range) # DataArray coords were coming back different from cvs.points... hack_agg = canvas.points(pd.DataFrame({'x': [], 'y': []}), 'x', 'y') res = get_dataarray_resolution(hack_agg) result = xr.DataArray(out, name=name, coords=hack_agg.coords, dims=hack_agg.dims, attrs={'res': res}) return result
def slope(agg: xr.DataArray, name: str = 'slope') -> xr.DataArray: """ Returns slope of input aggregate in degrees. Parameters ---------- agg : xr.DataArray 2D array of elevation data. name : str, default='slope' Name of output DataArray. Returns ------- slope_agg : xr.DataArray of same type as `agg` 2D array of slope values. All other input attributes are preserved. References ---------- - arcgis: http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-slope-works.htm # noqa Examples -------- .. plot:: :include-source: import numpy as np import xarray as xr import datashader as ds import matplotlib.pyplot as plt from xrspatial import generate_terrain, slope # Create Canvas W = 500 H = 300 cvs = ds.Canvas(plot_width = W, plot_height = H, x_range = (-20e6, 20e6), y_range = (-20e6, 20e6)) # Generate Example Terrain terrain_agg = generate_terrain(canvas = cvs) # Edit Attributes terrain_agg = terrain_agg.assign_attrs( { 'Description': 'Example Terrain', 'units': 'km', 'Max Elevation': '4000', } ) terrain_agg = terrain_agg.rename({'x': 'lon', 'y': 'lat'}) terrain_agg = terrain_agg.rename('Elevation') # Create Slope Aggregate Array slope_agg = slope(agg = terrain_agg, name = 'Slope') # Edit Attributes slope_agg = slope_agg.assign_attrs({'Description': 'Example Slope', 'units': 'deg'}) # Plot Terrain terrain_agg.plot(cmap = 'terrain', aspect = 2, size = 4) plt.title("Terrain") plt.ylabel("latitude") plt.xlabel("longitude") # Plot Slope slope_agg.plot(aspect = 2, size = 4) plt.title("Slope") plt.ylabel("latitude") plt.xlabel("longitude") .. sourcecode:: python >>> print(terrain_agg[200:203, 200:202]) <xarray.DataArray 'Elevation' (lat: 3, lon: 2)> array([[1264.02249454, 1261.94748873], [1285.37061171, 1282.48046696], [1306.02305679, 1303.40657515]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Example Terrain units: km Max Elevation: 4000 >>> print(slope_agg[200:203, 200:202]) <xarray.DataArray 'Slope' (lat: 3, lon: 2)> array([[86.69626115, 86.55635267], [87.2235249 , 87.24527062], [86.69883402, 86.22918773]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Example Slope units: deg Max Elevation: 4000 """ cellsize_x, cellsize_y = get_dataarray_resolution(agg) # numpy case if isinstance(agg.data, np.ndarray): out = _run_numpy(agg.data, cellsize_x, cellsize_y) # cupy case elif has_cuda() and isinstance(agg.data, cupy.ndarray): out = _run_cupy(agg.data, cellsize_x, cellsize_y) # dask + cupy case elif has_cuda() and is_dask_cupy(agg): out = _run_dask_cupy(agg.data, cellsize_x, cellsize_y) # dask + numpy case elif isinstance(agg.data, da.Array): out = _run_dask_numpy(agg.data, cellsize_x, cellsize_y) else: raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)