Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 31 additions & 22 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions benchmarks/benchmarks/watershed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from xrspatial import flow_direction, watershed, basins
from xrspatial import flow_direction, watershed, basin

from .common import get_xr_dataarray

Expand Down Expand Up @@ -59,6 +59,6 @@ def setup(self, nx, type):
)

def time_basins(self, nx, type):
result = basins(self.flow_dir)
result = basin(self.flow_dir)
if hasattr(result.data, 'compute'):
result.data.compute()
7 changes: 7 additions & 0 deletions docs/source/reference/surface.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ Perlin Noise

xrspatial.perlin.perlin

Worley Noise
============
.. autosummary::
:toctree: _autosummary

xrspatial.worley.worley

Bump Mapping
============
.. autosummary::
Expand Down
1 change: 1 addition & 0 deletions docs/source/reference/zonal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Zonal Statistics
:toctree: _autosummary

xrspatial.zonal.get_full_extent
xrspatial.zonal.hypsometric_integral
xrspatial.zonal.stats
xrspatial.zonal.suggest_zonal_canvas

Expand Down
167 changes: 165 additions & 2 deletions xrspatial/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from xrspatial._numpy_floor import _check_numpy_version # noqa: F401
from xrspatial import _numpy_floor # noqa: F401 — raises on NumPy 1.x before any submodule loads
from xrspatial.aspect import aspect # noqa
from xrspatial.aspect import eastness # noqa
from xrspatial.aspect import northness # noqa
Expand Down Expand Up @@ -55,6 +55,9 @@
from xrspatial.hydro import flow_direction # noqa: unified wrapper
from xrspatial.hydro import flow_length # noqa: unified wrapper
from xrspatial.hydro import flow_path # noqa: unified wrapper
from xrspatial.focal import apply as focal_apply # noqa
from xrspatial.focal import focal_stats # noqa
from xrspatial.focal import hotspots # noqa
from xrspatial.focal import mean # noqa
from xrspatial.glcm import glcm_texture # noqa
from xrspatial.morphology import morph_black_tophat # noqa
Expand All @@ -69,22 +72,28 @@
from xrspatial.mahalanobis import mahalanobis # noqa
from xrspatial.multispectral import arvi # noqa
from xrspatial.multispectral import bai # noqa
from xrspatial.multispectral import ebbi # noqa
from xrspatial.multispectral import evi # noqa
from xrspatial.multispectral import gci # noqa
from xrspatial.multispectral import mndwi # noqa
from xrspatial.multispectral import msavi2 # noqa
from xrspatial.multispectral import nbr # noqa
from xrspatial.multispectral import nbr2 # noqa
from xrspatial.multispectral import ndbi # noqa
from xrspatial.multispectral import ndmi # noqa
from xrspatial.multispectral import ndsi # noqa
from xrspatial.multispectral import ndvi # noqa
from xrspatial.multispectral import ndwi # noqa
from xrspatial.multispectral import osavi # noqa
from xrspatial.multispectral import savi # noqa
from xrspatial.multispectral import sipi # noqa
from xrspatial.multispectral import true_color # noqa
from xrspatial.pathfinding import a_star_search # noqa
from xrspatial.pathfinding import multi_stop_search # noqa
from xrspatial.normalize import rescale # noqa
from xrspatial.normalize import standardize # noqa
from xrspatial.perlin import perlin # noqa
from xrspatial.worley import worley # noqa
from xrspatial.preview import preview # noqa
from xrspatial.proximity import allocation # noqa
from xrspatial.rasterize import rasterize # noqa
Expand Down Expand Up @@ -121,7 +130,6 @@
from xrspatial.visibility import line_of_sight # noqa
from xrspatial.visibility import visibility_frequency # noqa
from xrspatial.hydro import basin # noqa: unified wrapper
from xrspatial.hydro import basins # noqa: backward-compat alias
from xrspatial.hydro import watershed # noqa: unified wrapper
from xrspatial.zonal import apply as zonal_apply # noqa
from xrspatial.zonal import crop # noqa
Expand All @@ -142,6 +150,161 @@
import xrspatial.mcda # noqa: F401 — exposes xrspatial.mcda subpackage
import xrspatial.accessor # noqa: F401 — registers .xrs accessors

# The names below, plus the ``xrspatial.mcda`` subpackage and the ``.xrs``
# accessor, are the 1.x LTS surface (docs/source/user_guide/stability_policy.rst).
# xrspatial/tests/test_public_surface.py keeps this list and the README
# feature matrix in step.
__all__ = [
'a_star_search',
'allocation',
'arvi',
'aspect',
'bai',
'balanced_allocation',
'basin',
'bilateral',
'binary',
'box_plot',
'bump',
'burn_severity_class',
'clip_polygon',
'contours',
'cost_distance',
'crop',
'cumulative_viewshed',
'curvature',
'curve_number_runoff',
'diagnose',
'diffuse',
'direction',
'disaggregate',
'dnbr',
'eastness',
'ebbi',
'emerging_hotspots',
'equal_interval',
'erode',
'euclidean_distance',
'evi',
'fill',
'fireline_intensity',
'flame_length',
'flood_depth',
'flood_depth_vegetation',
'flow_accumulation',
'flow_direction',
'flow_length',
'flow_path',
'focal_apply',
'focal_stats',
'from_template',
'fused_overlap',
'gci',
'generate_terrain',
'glcm_texture',
'great_circle_distance',
'hand',
'head_tail_breaks',
'hillshade',
'hotspots',
'hypsometric_integral',
'idw',
'inundation',
'kbdi',
'kde',
'kriging',
'LANDFORM_CLASSES',
'landforms',
'laplacian',
'least_cost_corridor',
'line_density',
'line_of_sight',
'list_templates',
'mahalanobis',
'manhattan_distance',
'maximum_breaks',
'mcda',
'mean',
'merge',
'mndwi',
'morph_black_tophat',
'morph_closing',
'morph_dilate',
'morph_erode',
'morph_gradient',
'morph_opening',
'morph_white_tophat',
'msavi2',
'multi_overlap',
'multi_stop_search',
'natural_breaks',
'nbr',
'nbr2',
'ndbi',
'ndmi',
'ndsi',
'ndvi',
'ndwi',
'northness',
'open_geotiff',
'osavi',
'percentiles',
'perlin',
'polygonize',
'preview',
'prewitt_x',
'prewitt_y',
'proximity',
'pycnophylactic',
'quantile',
'rasterize',
'rate_of_spread',
'rdnbr',
'rechunk_no_shuffle',
'reclassify',
'regions',
'reproject',
'resample',
'rescale',
'roughness',
'savi',
'sieve',
'sink',
'sipi',
'sky_view_factor',
'slope',
'snap_pour_point',
'sobel_x',
'sobel_y',
'spline',
'standardize',
'std_mean',
'stream_link',
'stream_order',
'suggest_zonal_canvas',
'surface_allocation',
'surface_direction',
'surface_distance',
'to_geotiff',
'tpi',
'travel_time',
'tri',
'trim',
'true_color',
'twi',
'validate',
'validate_disaggregation',
'vegetation_curve_number',
'vegetation_roughness',
'viewshed',
'visibility_frequency',
'watershed',
'worley',
'zonal_apply',
'zonal_crosstab',
'zonal_stats',
]


try:
from ._version import version as __version__
Expand Down
4 changes: 3 additions & 1 deletion xrspatial/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2379,7 +2379,9 @@ def _delegated_doc(method_name):
"""Return the docstring to surface for an accessor method, or None."""
if method_name == 'min_observable_height':
from .experimental.min_observable_height import min_observable_height
return min_observable_height.__doc__
return ('Experimental: not part of the 1.x LTS surface, and its '
'signature or output may change in a minor release.\n\n'
+ (min_observable_height.__doc__ or ''))
import xrspatial
source_name = _DOC_SOURCE_OVERRIDES.get(method_name, method_name)
func = getattr(xrspatial, source_name, None)
Expand Down
2 changes: 1 addition & 1 deletion xrspatial/hydro/tests/test_routing_public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
WRAPPERS = [
'flow_direction', 'flow_accumulation', 'flow_length', 'flow_path',
'watershed', 'hand', 'stream_link', 'stream_order',
'basin', 'basins', 'sink', 'snap_pour_point', 'fill', 'twi',
'basin', 'sink', 'snap_pour_point', 'fill', 'twi',
]

# Representative suffixed implementations across every family.
Expand Down
2 changes: 1 addition & 1 deletion xrspatial/tests/test_numpy_floor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""
import pytest

from xrspatial import _check_numpy_version
from xrspatial._numpy_floor import _check_numpy_version


@pytest.mark.parametrize("version", ["1.26.0", "1.26.4", "1.26.0.dev0+abc", "1.99.9"])
Expand Down
75 changes: 75 additions & 0 deletions xrspatial/tests/test_public_surface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""The README feature matrix and ``xrspatial.__all__`` describe the same
surface (issue #2930). A row without an export, or an export without a
row, fails here so the two cannot drift apart.
"""
import re
from pathlib import Path

import pytest

import xrspatial
import xrspatial.mcda

README = Path(xrspatial.__file__).resolve().parents[1] / "README.md"

# Exported names that are not spatial operations and so have no matrix row.
EXPORTS_WITHOUT_ROW = {
"LANDFORM_CLASSES", # constant backing the Landforms row
"mcda", # subpackage; its functions are checked against xrspatial.mcda
}

_ROW = re.compile(r"^\|\s*\[([^\]]+)\]\(([^)]+)\)\s*(?:`([^`]+)`)?")


def _row_name(label, path, override):
if override:
return override
m = re.search(r"\(([^()]+)\)\s*$", label)
if m:
return m.group(1).lower()
return re.sub(r"[\s/-]+", "_", label.strip()).lower()


def _matrix_rows():
"""Yield (section, function name, link path) for each function row."""
text = README.read_text(encoding="utf-8")
start = text.index("#### Supported Spatial Functions")
section = None
for line in text[start:].splitlines():
if line.startswith("### "):
section = line.strip("# *")
continue
m = _ROW.match(line)
if m and m.group(2).startswith("xrspatial/"):
yield section, _row_name(*m.groups()), m.group(2)


def test_readme_matrix_matches_public_surface():
if not README.exists():
pytest.skip("README.md is only present in a repository checkout")
top_rows, mcda_rows = {}, {}
for section, name, path in _matrix_rows():
if path.startswith("xrspatial/experimental/"):
continue # experimental: documented, deliberately not exported
(mcda_rows if "MCDA" in section else top_rows)[name] = path

exports = set(xrspatial.__all__) - EXPORTS_WITHOUT_ROW
mcda_exports = set(xrspatial.mcda.__all__)

missing_export = sorted(n for n in top_rows if n not in exports)
missing_row = sorted(exports - set(top_rows))
mcda_missing_export = sorted(n for n in mcda_rows if n not in mcda_exports)

assert not missing_export, (
"README rows with no matching name in xrspatial.__all__ "
f"(add the export or a `name` override on the row): {missing_export}")
assert not missing_row, (
"xrspatial.__all__ names with no README matrix row "
f"(add a row or list it in EXPORTS_WITHOUT_ROW): {missing_row}")
assert not mcda_missing_export, (
f"MCDA README rows not in xrspatial.mcda.__all__: {mcda_missing_export}")


def test_all_names_resolve():
for name in xrspatial.__all__:
assert hasattr(xrspatial, name), name
Loading