@@ -437,3 +437,155 @@ def aspect(agg: xr.DataArray,
437437 coords = agg .coords ,
438438 dims = agg .dims ,
439439 attrs = agg .attrs )
440+
441+
442+ @supports_dataset
443+ def northness (agg : xr .DataArray ,
444+ name : Optional [str ] = 'northness' ,
445+ method : str = 'planar' ,
446+ z_unit : str = 'meter' ,
447+ boundary : str = 'nan' ) -> xr .DataArray :
448+ """
449+ Computes the north-south component of aspect.
450+
451+ Returns ``cos(aspect)`` for each cell, ranging from +1 (due north)
452+ to -1 (due south). Flat cells (where ``aspect()`` returns -1) are
453+ set to NaN.
454+
455+ This is the standard way to encode aspect for use in regression,
456+ clustering, and other models that assume linear inputs. Raw aspect
457+ in degrees is circular (0 and 360 are the same direction), so
458+ feeding it directly into a linear model gives wrong results.
459+
460+ Parameters
461+ ----------
462+ agg : xarray.DataArray or xr.Dataset
463+ 2D elevation raster (NumPy, CuPy, Dask, or Dask+CuPy backed).
464+ If a Dataset is passed, the operation is applied to each
465+ data variable independently.
466+ name : str, default='northness'
467+ Name of output DataArray.
468+ method : str, default='planar'
469+ Passed to :func:`aspect`. ``'planar'`` or ``'geodesic'``.
470+ z_unit : str, default='meter'
471+ Passed to :func:`aspect`. Only used when ``method='geodesic'``.
472+ boundary : str, default='nan'
473+ Passed to :func:`aspect`. ``'nan'``, ``'nearest'``,
474+ ``'reflect'``, or ``'wrap'``.
475+
476+ Returns
477+ -------
478+ northness_agg : xarray.DataArray or xr.Dataset
479+ Values in [-1, +1]. NaN where the input has NaN or where
480+ the surface is flat.
481+
482+ References
483+ ----------
484+ Stage, A.R. (1976). "An Expression for the Effect of Aspect, Slope,
485+ and Habitat Type on Tree Growth." *Forest Science* 22(4): 457-460.
486+
487+ Examples
488+ --------
489+ .. sourcecode:: python
490+
491+ >>> import numpy as np
492+ >>> import xarray as xr
493+ >>> from xrspatial import northness
494+
495+ >>> data = np.array([
496+ [1, 1, 1, 1, 1],
497+ [1, 1, 1, 2, 0],
498+ [1, 1, 1, 0, 0],
499+ [4, 4, 9, 2, 4],
500+ [1, 5, 0, 1, 4],
501+ [1, 5, 0, 5, 5]
502+ ], dtype=np.float32)
503+ >>> raster = xr.DataArray(data, dims=['y', 'x'])
504+ >>> north = northness(raster)
505+ """
506+ asp = aspect (agg , name = '_aspect' , method = method , z_unit = z_unit ,
507+ boundary = boundary )
508+ asp_data = asp .data
509+ trig = np .cos (np .deg2rad (asp_data ))
510+ out = np .where (asp_data == - 1 , np .nan , trig )
511+ return xr .DataArray (out ,
512+ name = name ,
513+ coords = agg .coords ,
514+ dims = agg .dims ,
515+ attrs = agg .attrs )
516+
517+
518+ @supports_dataset
519+ def eastness (agg : xr .DataArray ,
520+ name : Optional [str ] = 'eastness' ,
521+ method : str = 'planar' ,
522+ z_unit : str = 'meter' ,
523+ boundary : str = 'nan' ) -> xr .DataArray :
524+ """
525+ Computes the east-west component of aspect.
526+
527+ Returns ``sin(aspect)`` for each cell, ranging from +1 (due east)
528+ to -1 (due west). Flat cells (where ``aspect()`` returns -1) are
529+ set to NaN.
530+
531+ This is the standard way to encode aspect for use in regression,
532+ clustering, and other models that assume linear inputs. Raw aspect
533+ in degrees is circular (0 and 360 are the same direction), so
534+ feeding it directly into a linear model gives wrong results.
535+
536+ Parameters
537+ ----------
538+ agg : xarray.DataArray or xr.Dataset
539+ 2D elevation raster (NumPy, CuPy, Dask, or Dask+CuPy backed).
540+ If a Dataset is passed, the operation is applied to each
541+ data variable independently.
542+ name : str, default='eastness'
543+ Name of output DataArray.
544+ method : str, default='planar'
545+ Passed to :func:`aspect`. ``'planar'`` or ``'geodesic'``.
546+ z_unit : str, default='meter'
547+ Passed to :func:`aspect`. Only used when ``method='geodesic'``.
548+ boundary : str, default='nan'
549+ Passed to :func:`aspect`. ``'nan'``, ``'nearest'``,
550+ ``'reflect'``, or ``'wrap'``.
551+
552+ Returns
553+ -------
554+ eastness_agg : xarray.DataArray or xr.Dataset
555+ Values in [-1, +1]. NaN where the input has NaN or where
556+ the surface is flat.
557+
558+ References
559+ ----------
560+ Stage, A.R. (1976). "An Expression for the Effect of Aspect, Slope,
561+ and Habitat Type on Tree Growth." *Forest Science* 22(4): 457-460.
562+
563+ Examples
564+ --------
565+ .. sourcecode:: python
566+
567+ >>> import numpy as np
568+ >>> import xarray as xr
569+ >>> from xrspatial import eastness
570+
571+ >>> data = np.array([
572+ [1, 1, 1, 1, 1],
573+ [1, 1, 1, 2, 0],
574+ [1, 1, 1, 0, 0],
575+ [4, 4, 9, 2, 4],
576+ [1, 5, 0, 1, 4],
577+ [1, 5, 0, 5, 5]
578+ ], dtype=np.float32)
579+ >>> raster = xr.DataArray(data, dims=['y', 'x'])
580+ >>> east = eastness(raster)
581+ """
582+ asp = aspect (agg , name = '_aspect' , method = method , z_unit = z_unit ,
583+ boundary = boundary )
584+ asp_data = asp .data
585+ trig = np .sin (np .deg2rad (asp_data ))
586+ out = np .where (asp_data == - 1 , np .nan , trig )
587+ return xr .DataArray (out ,
588+ name = name ,
589+ coords = agg .coords ,
590+ dims = agg .dims ,
591+ attrs = agg .attrs )
0 commit comments