def _mean(data, excludes): agg = xr.DataArray(data) mapper = ArrayTypeFunctionMapping( numpy_func=_mean_numpy, cupy_func=_mean_cupy, dask_func=_mean_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages='mean() does not support dask with cupy backed DataArray.'), # noqa ) out = mapper(agg)(agg.data, excludes) return out
def convolve_2d(data, kernel): mapper = ArrayTypeFunctionMapping( numpy_func=_convolve_2d_numpy, cupy_func=_convolve_2d_cupy, dask_func=_convolve_2d_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages='convolution_2d() does not support dask with cupy backed xr.DataArray' # noqa ) ) out = mapper(xr.DataArray(data))(data, kernel) return out
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 aspect(agg: xr.DataArray, name: Optional[str] = 'aspect') -> xr.DataArray: """ Calculates the aspect value of an elevation aggregate. Calculates, for all cells in the array, the downward slope direction of each cell based on the elevation of its neighbors in a 3x3 grid. The value is measured clockwise in degrees with 0 (due north), and 360 (again due north). Values along the edges are not calculated. Direction of the aspect can be determined by its value: From 0 to 22.5: North From 22.5 to 67.5: Northeast From 67.5 to 112.5: East From 112.5 to 157.5: Southeast From 157.5 to 202.5: South From 202.5 to 247.5: West From 247.5 to 292.5: Northwest From 337.5 to 360: North Note that values of -1 denote flat areas. Parameters ---------- agg : xarray.DataArray 2D NumPy, CuPy, or Dask with NumPy-backed xarray DataArray of elevation values. name : str, default='aspect' Name of ouput DataArray. Returns ------- aspect_agg : xarray.DataArray of the same type as `agg` 2D aggregate array of calculated aspect values. All other input attributes are preserved. References ---------- - arcgis: http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-aspect-works.htm#ESRI_SECTION1_4198691F8852475A9F4BC71246579FAA # noqa Examples -------- Aspect works with NumPy backed xarray DataArray .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial import aspect >>> data = np.array([ [1, 1, 1, 1, 1], [1, 1, 1, 2, 0], [1, 1, 1, 0, 0], [4, 4, 9, 2, 4], [1, 5, 0, 1, 4], [1, 5, 0, 5, 5] ], dtype=np.float32) >>> raster = xr.DataArray(data, dims=['y', 'x'], name='raster') >>> print(raster) <xarray.DataArray 'raster' (y: 6, x: 5)> array([[1., 1., 1., 1., 1.], [1., 1., 1., 2., 0.], [1., 1., 1., 0., 0.], [4., 4., 9., 2., 4.], [1., 5., 0., 1., 4.], [1., 5., 0., 5., 5.]]) Dimensions without coordinates: y, x >>> aspect_agg = aspect(raster) >>> print(aspect_agg) <xarray.DataArray 'aspect' (y: 6, x: 5)> array([[ nan, nan , nan , nan , nan], [ nan, -1. , 225. , 135. , nan], [ nan, 343.61045967, 8.97262661, 33.69006753, nan], [ nan, 307.87498365, 71.56505118, 54.46232221, nan], [ nan, 191.30993247, 144.46232221, 255.96375653, nan], [ nan, nan , nan , nan , nan]]) Dimensions without coordinates: y, x Aspect works with Dask with NumPy backed xarray DataArray .. sourcecode:: python >>> import dask.array as da >>> data_da = da.from_array(data, chunks=(3, 3)) >>> raster_da = xr.DataArray(data_da, dims=['y', 'x'], name='raster_da') >>> print(raster_da) <xarray.DataArray 'raster' (y: 6, x: 5)> dask.array<array, shape=(6, 5), dtype=int64, chunksize=(3, 3), chunktype=numpy.ndarray> Dimensions without coordinates: y, x >>> aspect_da = aspect(raster_da) >>> print(aspect_da) <xarray.DataArray 'aspect' (y: 6, x: 5)> dask.array<_trim, shape=(6, 5), dtype=float32, chunksize=(3, 3), chunktype=numpy.ndarray> Dimensions without coordinates: y, x >>> print(aspect_da.compute()) # compute the results <xarray.DataArray 'aspect' (y: 6, x: 5)> array([[ nan, nan , nan , nan , nan], [ nan, -1. , 225. , 135. , nan], [ nan, 343.61045967, 8.97262661, 33.69006753, nan], [ nan, 307.87498365, 71.56505118, 54.46232221, nan], [ nan, 191.30993247, 144.46232221, 255.96375653, nan], [ nan, nan , nan , nan , nan]]) Dimensions without coordinates: y, x Aspect works with CuPy backed xarray DataArray. Make sure you have a GPU and CuPy installed to run this example. .. sourcecode:: python >>> import cupy >>> data_cupy = cupy.asarray(data) >>> raster_cupy = xr.DataArray(data_cupy, dims=['y', 'x']) >>> aspect_cupy = aspect(raster_cupy) >>> print(type(aspect_cupy.data)) <class 'cupy.core.core.ndarray'> >>> print(aspect_cupy) <xarray.DataArray 'aspect' (y: 6, x: 5)> array([[ nan, nan, nan, nan, nan], [ nan, -1., 225., 135., nan], [ nan, 343.61047, 8.972626, 33.690067, nan], [ nan, 307.87497, 71.56505 , 54.462322, nan], [ nan, 191.30994, 144.46233 , 255.96376, nan], [ nan, nan, nan, nan, nan]], dtype=float32) Dimensions without coordinates: y, x """ mapper = ArrayTypeFunctionMapping( numpy_func=_run_numpy, dask_func=_run_dask_numpy, cupy_func=_run_cupy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'aspect() does not support dask with cupy backed DataArray' ) # noqa ) out = mapper(agg)(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 = 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 equal_interval(agg: xr.DataArray, k: int = 5, name: Optional[str] = 'equal_interval') -> xr.DataArray: """ Reclassifies data for array `agg` into new values based on intervals of equal width. Parameters ---------- agg : xarray.DataArray 2D NumPy, CuPy, NumPy-backed Dask, or Cupy-backed Dask array of values to be reclassified. k : int, default=5 Number of classes to be produced. name : str, default='equal_interval' Name of output aggregate. Returns ------- equal_interval_agg : xarray.DataArray of the same type as `agg` 2D aggregate array of equal interval allocations. All other input attributes are preserved. References ---------- - PySAL: https://pysal.org/mapclassify/_modules/mapclassify/classifiers.html#EqualInterval # noqa - scikit-learn: https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html#sphx-glr-auto-examples-classification-plot-classifier-comparison-py # noqa Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.classify import equal_interval >>> elevation = np.array([ [np.nan, 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.], [20., 21., 22., 23., np.inf] ]) >>> agg_numpy = xr.DataArray(elevation, attrs={'res': (10.0, 10.0)}) >>> numpy_equal_interval = equal_interval(agg_numpy, k=5) >>> print(numpy_equal_interval) <xarray.DataArray 'equal_interval' (dim_0: 5, dim_1: 5)> array([[nan, 0., 0., 0., 0.], [ 0., 0., 0., 0., 1.], [ 1., 1., 1., 1., 1.], [ 1., 2., 2., 2., 2.], [ 2., 2., 2., 2., nan]], dtype=float32) Dimensions without coordinates: dim_0, dim_1 Attributes: res: (10.0, 10.0) """ mapper = ArrayTypeFunctionMapping( numpy_func=lambda *args: _run_equal_interval(*args, module=np), dask_func=lambda *args: _run_equal_interval(*args, module=da), cupy_func=lambda *args: _run_equal_interval(*args, module=cupy), dask_cupy_func=lambda *args: not_implemented_func( *args, messages='equal_interval() does support dask with cupy backed DataArray.'), # noqa ) out = mapper(agg)(agg, k) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def natural_breaks(agg: xr.DataArray, num_sample: Optional[int] = 20000, name: Optional[str] = 'natural_breaks', k: int = 5) -> xr.DataArray: """ Reclassifies data for array `agg` into new values based on Natural Breaks or K-Means clustering method. Values are grouped so that similar values are placed in the same group and space between groups is maximized. Parameters ---------- agg : xarray.DataArray 2D NumPy DataArray of values to be reclassified. num_sample : int, default=20000 Number of sample data points used to fit the model. Natural Breaks (Jenks) classification is indeed O(n²) complexity, where n is the total number of data points, i.e: `agg.size` When n is large, we should fit the model on a small sub-sample of the data instead of using the whole dataset. k : int, default=5 Number of classes to be produced. name : str, default='natural_breaks' Name of output aggregate. Returns ------- natural_breaks_agg : xarray.DataArray of the same type as `agg` 2D aggregate array of natural break allocations. All other input attributes are preserved. References ---------- - PySAL: https://pysal.org/mapclassify/_modules/mapclassify/classifiers.html#NaturalBreaks # noqa - jenks: https://github.com/perrygeo/jenks/blob/master/jenks.pyx Examples ------- natural_breaks() works with numpy backed xarray DataArray. .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.classify import natural_breaks >>> elevation = np.array([ [np.nan, 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.], [20., 21., 22., 23., np.inf] ]) >>> agg_numpy = xr.DataArray(elevation, attrs={'res': (10.0, 10.0)}) >>> numpy_natural_breaks = natural_breaks(agg_numpy, k=5) >>> print(numpy_natural_breaks) <xarray.DataArray 'natural_breaks' (dim_0: 5, dim_1: 5)> array([[nan, 0., 0., 0., 0.], [ 1., 1., 1., 1., 2.], [ 2., 2., 2., 2., 3.], [ 3., 3., 3., 3., 4.], [ 4., 4., 4., 4., nan]], dtype=float32) Dimensions without coordinates: dim_0, dim_1 Attributes: res: (10.0, 10.0) natural_breaks() works with cupy backed xarray DataArray. .. sourcecode:: python >>> import cupy >>> agg_cupy = xr.DataArray(cupy.asarray(elevation)) >>> cupy_natural_breaks = natural_breaks(agg_cupy) >>> print(type(cupy_natural_breaks)) <class 'xarray.core.dataarray.DataArray'> >>> print(cupy_natural_breaks) <xarray.DataArray 'natural_breaks' (dim_0: 5, dim_1: 5)> array([[nan, 0., 0., 0., 0.], [ 1., 1., 1., 1., 2.], [ 2., 2., 2., 2., 3.], [ 3., 3., 3., 3., 4.], [ 4., 4., 4., 4., nan]], dtype=float32) Dimensions without coordinates: dim_0, dim_1 """ mapper = ArrayTypeFunctionMapping( numpy_func=lambda *args: _run_natural_break(*args), dask_func=lambda *args: not_implemented_func( *args, messages='natural_breaks() does not support dask with numpy backed DataArray.'), # noqa cupy_func=lambda *args: not_implemented_func( *args, messages='natural_breaks() does not support cupy backed DataArray.'), # noqa dask_cupy_func=lambda *args: not_implemented_func( *args, messages='natural_breaks() does not support dask with cupy backed DataArray.'), # noqa ) out = mapper(agg)(agg, num_sample, k) return xr.DataArray(out, name=name, coords=agg.coords, dims=agg.dims, attrs=agg.attrs)
def hotspots(raster, kernel): """ Identify statistically significant hot spots and cold spots in an input raster. To be a statistically significant hot spot, a feature will have a high value and be surrounded by other features with high values as well. Neighborhood of a feature defined by the input kernel, which currently support a shape of circle, annulus, or custom kernel. The result should be a raster with the following 7 values: - 90 for 90% confidence high value cluster - 95 for 95% confidence high value cluster - 99 for 99% confidence high value cluster - 90 for 90% confidence low value cluster - 95 for 95% confidence low value cluster - 99 for 99% confidence low value cluster - 0 for no significance Parameters ---------- raster : xarray.DataArray 2D Input raster image with `raster.shape` = (height, width). Can be a NumPy backed, CuPy backed, or Dask with NumPy backed DataArray kernel : Numpy Array 2D array where values of 1 indicate the kernel. Returns ------- hotspots_agg : xarray.DataArray of same type as `raster` 2D array of hotspots with values indicating confidence level. Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.convolution import custom_kernel >>> kernel = custom_kernel(np.array([[1, 1, 0]])) >>> data = np.array([ ... [0, 1000, 1000, 0, 0, 0], ... [0, 0, 0, -1000, -1000, 0], ... [0, -900, -900, 0, 0, 0], ... [0, 100, 1000, 0, 0, 0]]) >>> from xrspatial.focal import hotspots >>> hotspots(xr.DataArray(data), kernel) array([[ 0, 0, 95, 0, 0, 0], [ 0, 0, 0, 0, -90, 0], [ 0, 0, -90, 0, 0, 0], [ 0, 0, 0, 0, 0, 0]], dtype=int8) Dimensions without coordinates: dim_0, dim_1 """ # validate raster if not isinstance(raster, DataArray): raise TypeError("`raster` must be instance of DataArray") if raster.ndim != 2: raise ValueError("`raster` must be 2D") mapper = ArrayTypeFunctionMapping( numpy_func=_hotspots_numpy, cupy_func=_hotspots_cupy, dask_func=_hotspots_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'hotspots() does not support dask with cupy backed DataArray.' ), # noqa ) out = mapper(raster)(raster, kernel) attrs = copy.deepcopy(raster.attrs) attrs['unit'] = '%' return DataArray(out, coords=raster.coords, dims=raster.dims, attrs=attrs)
def apply(raster, kernel, func=_calc_mean, name='focal_apply'): """ Returns custom function applied array using a user-created window. Parameters ---------- raster : xarray.DataArray 2D array of input values to be filtered. Can be a NumPy backed, or Dask with NumPy backed DataArray. kernel : numpy.ndarray 2D array where values of 1 indicate the kernel. func : callable, default=xrspatial.focal._calc_mean Function which takes an input array and returns an array. Returns ------- agg : xarray.DataArray of same type as `raster` 2D aggregate array of filtered values. Examples -------- Focal apply works with NumPy backed xarray DataArray .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.convolution import circle_kernel >>> from xrspatial.focal import apply >>> data = np.arange(20, dtype=np.float64).reshape(4, 5) >>> raster = xr.DataArray(data, dims=['y', 'x'], name='raster') >>> print(raster) <xarray.DataArray 'raster' (y: 4, x: 5)> array([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]]) Dimensions without coordinates: y, x >>> kernel = circle_kernel(2, 2, 3) >>> kernel array([[0., 1., 0.], [1., 1., 1.], [0., 1., 0.]]) >>> # apply kernel mean by default >>> apply_mean_agg = apply(raster, kernel) >>> apply_mean_agg <xarray.DataArray 'focal_apply' (y: 4, x: 5)> array([[ 2. , 2.25 , 3.25 , 4.25 , 5.33333333], [ 5.25 , 6. , 7. , 8. , 8.75 ], [10.25 , 11. , 12. , 13. , 13.75 ], [13.66666667, 14.75 , 15.75 , 16.75 , 17. ]]) Dimensions without coordinates: y, x Focal apply works with Dask with NumPy backed xarray DataArray. Note that if input raster is a numpy or dask with numpy backed data array, the applied function must be decorated with ``numba.jit`` xrspatial already provides ``ngjit`` decorator, where: ``ngjit = numba.jit(nopython=True, nogil=True)`` .. sourcecode:: python >>> from xrspatial.utils import ngjit >>> from xrspatial.convolution import custom_kernel >>> kernel = custom_kernel(np.array([ [0, 1, 0], [0, 1, 1], [0, 1, 0], ])) >>> weight = np.array([ [0, 0.5, 0], [0, 1, 0.5], [0, 0.5, 0], ]) >>> @ngjit >>> def func(kernel_data): ... weight = np.array([ ... [0, 0.5, 0], ... [0, 1, 0.5], ... [0, 0.5, 0], ... ]) ... return np.nansum(kernel_data * weight) >>> import dask.array as da >>> data_da = da.from_array(np.ones((6, 4), dtype=np.float64), chunks=(3, 2)) >>> raster_da = xr.DataArray(data_da, dims=['y', 'x'], name='raster_da') >>> print(raster_da) <xarray.DataArray 'raster_da' (y: 6, x: 4)> dask.array<array, shape=(6, 4), dtype=float64, chunksize=(3, 2), chunktype=numpy.ndarray> # noqa Dimensions without coordinates: y, x >>> apply_func_agg = apply(raster_da, kernel, func) >>> print(apply_func_agg) <xarray.DataArray 'focal_apply' (y: 6, x: 4)> dask.array<_trim, shape=(6, 4), dtype=float64, chunksize=(3, 2), chunktype=numpy.ndarray> # noqa Dimensions without coordinates: y, x >>> print(apply_func_agg.compute()) <xarray.DataArray 'focal_apply' (y: 6, x: 4)> array([[2. , 2. , 2. , 1.5], [2.5, 2.5, 2.5, 2. ], [2.5, 2.5, 2.5, 2. ], [2.5, 2.5, 2.5, 2. ], [2.5, 2.5, 2.5, 2. ], [2. , 2. , 2. , 1.5]]) Dimensions without coordinates: y, x """ # validate raster if not isinstance(raster, DataArray): raise TypeError("`raster` must be instance of DataArray") if raster.ndim != 2: raise ValueError("`raster` must be 2D") # Validate the kernel kernel = custom_kernel(kernel) # apply kernel to raster values # if raster is a numpy or dask with numpy backed data array, # the function func must be a @ngjit mapper = ArrayTypeFunctionMapping( numpy_func=_apply_numpy, cupy_func=lambda *args: not_implemented_func( *args, messages='apply() does not support cupy backed DataArray.'), dask_func=_apply_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'apply() does not support dask with cupy backed DataArray.'), ) out = mapper(raster)(raster.data, kernel, func) result = DataArray(out, name=name, coords=raster.coords, dims=raster.dims, attrs=raster.attrs) return result
def focal_stats(agg, kernel, stats_funcs=[ 'mean', 'max', 'min', 'range', 'std', 'var', 'sum' ]): """ Calculates statistics of the values within a specified focal neighborhood for each pixel in an input raster. The statistics types are Mean, Maximum, Minimum, Range, Standard deviation, Variation and Sum. Parameters ---------- agg : xarray.DataArray 2D array of input values to be analysed. Can be a NumPy backed, Cupy backed, or Dask with NumPy backed DataArray. kernel : numpy.array 2D array where values of 1 indicate the kernel. stats_funcs: list of string List of statistics types to be calculated. Default set to ['mean', 'max', 'min', 'range', 'std', 'var', 'sum']. Returns ------- stats_agg : xarray.DataArray of same type as `agg` 3D array with dimensions of `(stat, y, x)` and with values indicating the focal stats. Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.convolution import circle_kernel >>> kernel = circle_kernel(1, 1, 1) >>> kernel array([[0., 1., 0.], [1., 1., 1.], [0., 1., 0.]]) >>> data = np.array([ [0, 0, 0, 0, 0, 0], [1, 1, 2, 2, 1, 1], [2, 2, 1, 1, 2, 2], [3, 3, 0, 0, 3, 3], ]) >>> from xrspatial.focal import focal_stats >>> focal_stats(xr.DataArray(data), kernel, stats_funcs=['min', 'sum']) <xarray.DataArray 'focal_apply' (stats: 2, dim_0: 4, dim_1: 6)> array([[[0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.], [1., 1., 0., 0., 1., 1.], [2., 0., 0., 0., 0., 2.]], [[1., 1., 2., 2., 1., 1.], [4., 6., 6., 6., 6., 4.], [8., 9., 6., 6., 9., 8.], [8., 8., 4., 4., 8., 8.]]]) Coordinates: * stats (stats) object 'min' 'sum' Dimensions without coordinates: dim_0, dim_1 """ # validate raster if not isinstance(agg, DataArray): raise TypeError("`agg` must be instance of DataArray") if agg.ndim != 2: raise ValueError("`agg` must be 2D") # Validate the kernel kernel = custom_kernel(kernel) mapper = ArrayTypeFunctionMapping( numpy_func=_focal_stats_cpu, cupy_func=_focal_stats_cupy, dask_func=_focal_stats_cpu, dask_cupy_func=lambda *args: not_implemented_func( *args, messages='focal_stats() does not support dask with cupy backed DataArray.'), ) result = mapper(agg)(agg, kernel, stats_funcs) return result
def true_color(r, g, b, nodata=1, c=10.0, th=0.125, name='true_color'): """ Create true color composite from a combination of red, green and blue bands satellite images. A sigmoid function will be used to improve the contrast of output image. The function is defined as ``normalized_pixel = 1 / (1 + np.exp(c * (th - normalized_pixel)))`` where ``c`` and ``th`` are contrast and brightness controlling parameters. Parameters ---------- r : xarray.DataArray 2D array of red band data. g : xarray.DataArray 2D array of green band data. b : xarray.DataArray 2D array of blue band data. nodata : int, float numeric value Nodata value of input DataArrays. c : float, default=10 Contrast and brighness controlling parameter for output image. th : float, default=0.125 Contrast and brighness controlling parameter for output image. name : str, default='true_color' Name of output DataArray. Returns ------- true_color_agg : xarray.DataArray of the same type as inputs 3D array true color image with dims of [y, x, band]. All output attributes are copied from red band image. Examples -------- .. plot:: :include-source: >>> from xrspatial.datasets import get_data >>> data = get_data('sentinel-2') # Open Example Data >>> red = data['Red'] >>> green = data['Green'] >>> blue = data['Blue'] >>> from xrspatial.multispectral import true_color >>> # Generate true color image >>> true_color_img = true_color(r=red, g=green, b=blue) >>> true_color_img.plot.imshow() """ mapper = ArrayTypeFunctionMapping( numpy_func=_true_color_numpy, dask_func=_true_color_dask, cupy_func=lambda *args: not_implemented_func( *args, messages= 'true_color() does not support cupy backed DataArray', # noqa ), dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'true_color() does not support dask with cupy backed DataArray', # noqa ), ) with warnings.catch_warnings(): warnings.simplefilter('ignore') out = mapper(r)(r, g, b, nodata, c, th) # TODO: output metadata: coords, dims, attrs _dims = ['y', 'x', 'band'] _attrs = r.attrs _coords = {'y': r['y'], 'x': r['x'], 'band': [0, 1, 2, 3]} return DataArray( out, name=name, dims=_dims, coords=_coords, attrs=_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 perlin(agg: xr.DataArray, freq: tuple = (1, 1), seed: int = 5, name: str = 'perlin') -> xr.DataArray: """ Generate perlin noise aggregate. Parameters ---------- agg : xr.DataArray 2D array of size width x height, will be used to determine height/ width and which platform to use for calculation. freq : tuple, default=(1,1) (x, y) frequency multipliers. seed : int, default=5 Seed for random number generator. Returns ------- perlin_agg : xarray.DataArray 2D array of perlin noise values. References ---------- - Paul Panzer: https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy # noqa - ICA: http://www.mountaincartography.org/mt_hood/pdfs/nighbert_bump1.pdf # noqa Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial import perlin >>> W = 4 >>> H = 3 >>> data = np.zeros((H, W), dtype=np.float32) >>> raster = xr.DataArray(data, dims=['y', 'x']) >>> perlin_noise = perlin(raster) >>> print(perlin_noise) <xarray.DataArray 'perlin' (y: 3, x: 4)> array([[0.39268944, 0.27577767, 0.01621884, 0.05518942], [1. , 0.8229485 , 0.2935367 , 0. ], [1. , 0.8715414 , 0.41902685, 0.02916668]], dtype=float32) # noqa Dimensions without coordinates: y, x """ mapper = ArrayTypeFunctionMapping( numpy_func=_perlin_numpy, cupy_func=_perlin_cupy, dask_func=_perlin_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'perlin() does not support dask with cupy backed DataArray', # noqa )) out = mapper(agg)(agg.data, freq, seed) result = xr.DataArray(out, dims=agg.dims, attrs=agg.attrs, name=name) return result
def hillshade(agg: xr.DataArray, azimuth: int = 225, angle_altitude: int = 25, name: Optional[str] = 'hillshade') -> xr.DataArray: """ Calculates, for all cells in the array, an illumination value of each cell based on illumination from a specific azimuth and altitude. Parameters ---------- agg : xarray.DataArray 2D NumPy, CuPy, NumPy-backed Dask, or Cupy-backed Dask array of elevation values. angle_altitude : int, default=25 Altitude angle of the sun specified in degrees. azimuth : int, default=225 The angle between the north vector and the perpendicular projection of the light source down onto the horizon specified in degrees. name : str, default='hillshade' Name of output DataArray. Returns ------- hillshade_agg : xarray.DataArray, of same type as `agg` 2D aggregate array of illumination values. References ---------- - GeoExamples: http://geoexamples.blogspot.com/2014/03/shaded-relief-images-using-gdal-python.html # noqa Examples -------- .. plot:: :include-source: import numpy as np import xarray as xr from xrspatial import hillshade data = np.array([ [0., 0., 0., 0., 0.], [0., 1., 0., 2., 0.], [0., 0., 3., 0., 0.], [0., 0., 0., 0., 0.], [0., 0., 0., 0., 0.] ]) n, m = data.shape raster = xr.DataArray(data, dims=['y', 'x'], name='raster') raster['y'] = np.arange(n)[::-1] raster['x'] = np.arange(m) hillshade_agg = hillshade(raster) .. sourcecode:: python >>> print(hillshade_agg) <xarray.DataArray 'hillshade' (y: 5, x: 5)> array([[ nan, nan, nan, nan, nan], [ nan, 0.71130913, 0.44167341, 0.71130913, nan], [ nan, 0.95550163, 0.71130913, 0.52478473, nan], [ nan, 0.71130913, 0.88382559, 0.71130913, nan], [ nan, nan, nan, nan, nan]]) Coordinates: * y (y) int32 4 3 2 1 0 * x (x) int32 0 1 2 3 4 """ 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= 'hillshade() does not support dask with cupy backed DataArray' # noqa ), ) out = mapper(agg)(agg.data, azimuth, angle_altitude) 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 -------- .. 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 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