From 691f577be0e18d30176183adad13f0968c307267 Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Wed, 20 May 2026 08:05:17 -0700 Subject: [PATCH 1/3] reproject: add bounds_policy parameter to control bounds heuristics (#2187) _compute_output_grid silently clamped geographic bounds and fell back to 2/98 percentile bounds when the projected extent blew up. Both branches cropped real data without telling the caller. Add an explicit bounds_policy parameter with four options: auto (default, current behaviour), raw (no heuristic), clamp (geographic clamp only), and percentile (force 2/98 fallback). When auto / clamp / percentile actually alters the bounds, emit a UserWarning naming the policy and reporting the per-side delta vs raw. Plumbed through reproject() and merge(). Tests cover the four policies, the warning behaviour, an explicit-bounds bypass, and the dask backend. --- xrspatial/reproject/__init__.py | 41 +++++ xrspatial/reproject/_grid.py | 104 +++++++++++- xrspatial/tests/test_reproject.py | 256 ++++++++++++++++++++++++++++++ 3 files changed, 394 insertions(+), 7 deletions(-) diff --git a/xrspatial/reproject/__init__.py b/xrspatial/reproject/__init__.py index cfab24881..ef0fcb17b 100644 --- a/xrspatial/reproject/__init__.py +++ b/xrspatial/reproject/__init__.py @@ -535,6 +535,7 @@ def reproject( max_memory=None, src_vertical_crs=None, tgt_vertical_crs=None, + bounds_policy="auto", ): """Reproject a raster DataArray to a new coordinate reference system. @@ -586,6 +587,32 @@ def reproject( tgt_vertical_crs : str or None Target vertical datum. Same options as *src_vertical_crs*. Both must be set to trigger a vertical transformation. + bounds_policy : {"auto", "raw", "clamp", "percentile"}, default "auto" + How to derive the output extent from the source extent when + ``bounds`` is not supplied. Only relevant when projecting near a + singularity (antimeridian, pole, projection edge): + + - ``"raw"``: use the true projected extent of the source corners + and edges. No clamp, no percentile, no heuristic. The output + may be very large if the input straddles a projection + singularity. Use this when you want a true projection of the + source extent. + - ``"clamp"``: trim geographic source bounds inward by 0.01 deg + from +/-180 longitude and +/-90 latitude before projecting. + Avoids infinities at singularities. No percentile fallback. + - ``"percentile"``: project a dense interior grid of the source + extent and use the 2nd/98th percentiles of the result as the + output bounds. Rejects projection outliers at the cost of + trimming valid pixels. + - ``"auto"`` (default): apply ``"clamp"`` for geographic source + CRSes and fall back to ``"percentile"`` when the projected + extent is more than 50x the source extent. Matches the + historical behaviour. + + When ``"auto"``, ``"clamp"``, or ``"percentile"`` actually alters + the bounds, a ``UserWarning`` is emitted naming the policy and + reporting the per-side delta versus the raw projected bounds. + Filter with ``warnings.filterwarnings`` if the crop is intentional. Returns ------- @@ -642,6 +669,9 @@ def reproject( _validate_resampling(resampling) + from ._grid import _validate_bounds_policy + _validate_bounds_policy(bounds_policy, func_name='reproject') + # Reject unknown vertical-datum tokens at the API boundary so we never # write None into attrs['vertical_crs'] for typos / unsupported values. for _name, _val in (('src_vertical_crs', src_vertical_crs), @@ -676,6 +706,7 @@ def reproject( src_bounds, src_shape, src_crs, tgt_crs, resolution=resolution, bounds=bounds, width=width, height=height, + bounds_policy=bounds_policy, ) out_bounds = grid['bounds'] out_shape = grid['shape'] @@ -1662,6 +1693,7 @@ def merge( chunk_size=None, transform_precision=16, name=None, + bounds_policy="auto", ): """Merge multiple rasters into a single mosaic. @@ -1695,6 +1727,10 @@ def merge( Set to 0 for exact per-pixel transforms matching GDAL/rasterio. name : str or None Name for the output DataArray. + bounds_policy : {"auto", "raw", "clamp", "percentile"}, default "auto" + How to derive the unified output extent from the input rasters + when ``bounds`` is not supplied. See :func:`reproject` for the + full description of each option. Ignored when ``bounds`` is given. Returns ------- @@ -1760,6 +1796,9 @@ def merge( _validate_resampling(resampling) _validate_strategy(strategy) + from ._grid import _validate_bounds_policy + _validate_bounds_policy(bounds_policy, func_name='merge') + # Resolve target CRS tgt_crs = _resolve_crs(target_crs) if tgt_crs is None: @@ -1808,6 +1847,7 @@ def merge( info['src_bounds'], info['src_shape'], info['src_crs'], tgt_crs, resolution=resolution, + bounds_policy=bounds_policy, ) all_bounds.append(grid['bounds']) left = min(b[0] for b in all_bounds) @@ -1824,6 +1864,7 @@ def merge( info0['src_bounds'], info0['src_shape'], info0['src_crs'], tgt_crs, resolution=resolution, bounds=merged_bounds, + bounds_policy=bounds_policy, ) out_bounds = grid['bounds'] out_shape = grid['shape'] diff --git a/xrspatial/reproject/_grid.py b/xrspatial/reproject/_grid.py index 5eeea1171..87e8e20a7 100644 --- a/xrspatial/reproject/_grid.py +++ b/xrspatial/reproject/_grid.py @@ -1,9 +1,33 @@ """Output grid computation and chunk layout for reprojection.""" from __future__ import annotations +import warnings + import numpy as np +_VALID_BOUNDS_POLICIES = ("auto", "raw", "clamp", "percentile") + + +def _validate_bounds_policy(bounds_policy, func_name): + """Reject unknown bounds_policy tokens at the API boundary.""" + if bounds_policy not in _VALID_BOUNDS_POLICIES: + raise ValueError( + f"{func_name}(): bounds_policy must be one of " + f"{list(_VALID_BOUNDS_POLICIES)}, got {bounds_policy!r}" + ) + + +def _bounds_delta(raw, adjusted): + """Return per-side deltas (adjusted - raw) for a bounds 4-tuple.""" + return ( + adjusted[0] - raw[0], + adjusted[1] - raw[1], + adjusted[2] - raw[2], + adjusted[3] - raw[3], + ) + + def _validate_grid_params(*, resolution, bounds, width, height, transform_precision, func_name): """Range-check user-supplied grid parameters.""" @@ -121,7 +145,8 @@ def _transform_boundary(source_crs, target_crs, xs, ys): def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, - resolution=None, bounds=None, width=None, height=None): + resolution=None, bounds=None, width=None, height=None, + bounds_policy="auto"): """Compute the output raster grid parameters. Parameters @@ -138,28 +163,45 @@ def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, Explicit (left, bottom, right, top) in target CRS. width, height : int or None Explicit output dimensions. + bounds_policy : {"auto", "raw", "clamp", "percentile"} + How to handle output bounds near projection singularities. + See :func:`reproject` for the full description. Ignored when + ``bounds`` is supplied. Returns ------- dict with keys: bounds, shape, res_x, res_y """ + _validate_bounds_policy(bounds_policy, func_name='_compute_output_grid') + if bounds is None: # Transform source corners and edges to target CRS src_left, src_bottom, src_right, src_top = source_bounds + src_left_raw, src_bottom_raw = src_left, src_bottom + src_right_raw, src_top_raw = src_right, src_top # Clamp geographic coordinates away from singularities. # Exactly +/-180 longitude produces inf in many projections; # latitudes beyond +/-90 are invalid. - if source_crs.is_geographic: + clamp_applied = False + if source_crs.is_geographic and bounds_policy in ("auto", "clamp"): clamp = 0.01 # degrees - src_left = max(src_left, -180 + clamp) - src_right = min(src_right, 180 - clamp) - src_bottom = max(src_bottom, -90 + clamp) - src_top = min(src_top, 90 - clamp) + new_left = max(src_left, -180 + clamp) + new_right = min(src_right, 180 - clamp) + new_bottom = max(src_bottom, -90 + clamp) + new_top = min(src_top, 90 - clamp) + clamp_applied = ( + new_left != src_left or new_right != src_right + or new_bottom != src_bottom or new_top != src_top + ) + src_left, src_right = new_left, new_right + src_bottom, src_top = new_bottom, new_top if src_left >= src_right: src_left, src_right = source_bounds[0], source_bounds[2] + clamp_applied = False if src_bottom >= src_top: src_bottom, src_top = source_bounds[1], source_bounds[3] + clamp_applied = False # Sample edges densely plus an interior grid so that # projections with curvature (e.g. UTM near zone edges) @@ -185,6 +227,15 @@ def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, ixx, iyy = np.meshgrid(ix, iy) xs = np.concatenate([edge_xs, ixx.ravel()]) ys = np.concatenate([edge_ys, iyy.ravel()]) + # For policy='raw', include the unclamped corners explicitly so + # the raw bounds reflect the user's original extent verbatim. + if bounds_policy == "raw": + xs = np.concatenate([xs, np.array([ + src_left_raw, src_right_raw, src_left_raw, src_right_raw + ])]) + ys = np.concatenate([ys, np.array([ + src_bottom_raw, src_bottom_raw, src_top_raw, src_top_raw + ])]) tx, ty = _transform_boundary(source_crs, target_crs, xs, ys) tx = np.asarray(tx) ty = np.asarray(ty) @@ -213,7 +264,13 @@ def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, # to a dense interior grid to get a tighter bounding box. ratio_x = out_w_crs / (abs(src_w_crs) + 1e-30) ratio_y = out_h_crs / (abs(src_h_crs) + 1e-30) - if ratio_x > 50 or ratio_y > 50: + auto_blowup = (ratio_x > 50 or ratio_y > 50) + use_percentile = ( + bounds_policy == "percentile" + or (bounds_policy == "auto" and auto_blowup) + ) + percentile_applied = False + if use_percentile: # Sample a dense interior grid instead n_dense = 51 ix = np.linspace(src_left, src_right, n_dense) @@ -236,11 +293,44 @@ def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, float(np.percentile(itx, q_hi)), float(np.percentile(ity, q_hi)), ) + percentile_applied = True else: bounds = raw_bounds else: bounds = raw_bounds + # Emit a warning when the policy actually altered the bounds vs. + # the raw projection. Silent cropping is the bug this knob fixes. + if percentile_applied and bounds != raw_bounds: + delta = _bounds_delta(raw_bounds, bounds) + trigger = "auto blow-up heuristic" if ( + bounds_policy == "auto" and auto_blowup + ) else "percentile policy" + warnings.warn( + f"reproject bounds_policy={bounds_policy!r}: " + f"{trigger} replaced raw projected bounds " + f"{raw_bounds} with 2/98 percentile bounds {bounds}; " + f"per-side delta (adjusted - raw) = {delta}. Pass " + f"bounds_policy='raw' to disable this crop.", + UserWarning, + stacklevel=2, + ) + elif clamp_applied and not percentile_applied: + # The clamp affects which input points get sampled, not the + # output bounds directly. Still surface it so callers know + # we trimmed source coordinates before projecting. + warnings.warn( + f"reproject bounds_policy={bounds_policy!r}: " + f"geographic clamp trimmed source extent from " + f"({src_left_raw}, {src_bottom_raw}, " + f"{src_right_raw}, {src_top_raw}) to " + f"({src_left}, {src_bottom}, {src_right}, {src_top}) " + f"before projecting. Pass bounds_policy='raw' to " + f"disable this clamp.", + UserWarning, + stacklevel=2, + ) + left, bottom, right, top = bounds # Determine resolution diff --git a/xrspatial/tests/test_reproject.py b/xrspatial/tests/test_reproject.py index 6edd7012f..e06f0ec9f 100644 --- a/xrspatial/tests/test_reproject.py +++ b/xrspatial/tests/test_reproject.py @@ -3888,3 +3888,259 @@ def test_merge_rejects_3d_dataarray(self): ) with pytest.raises(ValueError, match=r"must be 2D"): merge([a, b], resolution=1.0) + + +# --------------------------------------------------------------------------- +# Issue #2187: bounds_policy parameter +# --------------------------------------------------------------------------- + +class TestBoundsPolicy: + """reproject(): bounds_policy controls the bounds-derivation heuristics. + + Without the policy knob, _compute_output_grid silently clamps + geographic bounds and falls back to 2/98 percentile bounds when the + projected extent blows up. These tests pin the four policy options: + auto (default, current behaviour with warnings), raw (no heuristic), + clamp (geographic clamp only), and percentile (force 2/98 fallback). + """ + + @staticmethod + def _global_geographic(): + """Global lat/lon raster that triggers the polar / antimeridian + blow-up when projected to Web Mercator.""" + data = np.random.RandomState(0).rand(50, 100).astype(np.float32) + return xr.DataArray( + data, dims=['y', 'x'], + coords={'y': np.linspace(90, -90, 50), + 'x': np.linspace(-180, 180, 100)}, + attrs={'crs': 'EPSG:4326'}, + ) + + @staticmethod + def _benign_geographic(): + """Mid-latitude raster well away from any singularity.""" + data = np.random.RandomState(0).rand(32, 32).astype(np.float32) + return xr.DataArray( + data, dims=['y', 'x'], + coords={'y': np.linspace(55, 45, 32), + 'x': np.linspace(-5, 5, 32)}, + attrs={'crs': 'EPSG:4326'}, + ) + + def test_raw_skips_clamp_and_percentile(self): + """bounds_policy='raw' returns un-cropped bounds for a blow-up case. + + A global geographic raster projected to Web Mercator hits the + polar singularity. Under 'auto' the percentile fallback fires + and crops the output extent. Under 'raw' the caller gets the + true projected extent of the corners/edges. + """ + from xrspatial.reproject import reproject + + r = self._global_geographic() + import warnings + with warnings.catch_warnings(): + warnings.simplefilter('ignore', UserWarning) + auto_result = reproject(r, 'EPSG:3857', bounds_policy='auto') + raw_result = reproject(r, 'EPSG:3857', bounds_policy='raw') + + auto_x = auto_result.coords['x'].values + raw_x = raw_result.coords['x'].values + auto_y = auto_result.coords['y'].values + raw_y = raw_result.coords['y'].values + + auto_x_span = auto_x.max() - auto_x.min() + raw_x_span = raw_x.max() - raw_x.min() + auto_y_span = auto_y.max() - auto_y.min() + raw_y_span = raw_y.max() - raw_y.min() + + # Raw should be at least as wide as auto on x, and meaningfully + # taller on y (the polar blow-up is the y axis under EPSG:3857). + assert raw_x_span >= auto_x_span - 1.0 + assert raw_y_span > auto_y_span * 1.1, ( + f"raw y span {raw_y_span} should exceed auto {auto_y_span}" + ) + + def test_percentile_reproduces_98_2_behaviour(self): + """bounds_policy='percentile' matches the previous 2/98 fallback + even on inputs that wouldn't trigger the blow-up heuristic.""" + from xrspatial.reproject import reproject + from xrspatial.reproject._grid import _compute_output_grid + from xrspatial.reproject._crs_utils import _resolve_crs + + r = self._benign_geographic() + src_crs = _resolve_crs('EPSG:4326') + tgt_crs = _resolve_crs('EPSG:3857') + + import warnings + with warnings.catch_warnings(): + warnings.simplefilter('ignore', UserWarning) + grid_percentile = _compute_output_grid( + (-5.0, 45.0, 5.0, 55.0), (32, 32), + src_crs, tgt_crs, bounds_policy='percentile', + ) + grid_raw = _compute_output_grid( + (-5.0, 45.0, 5.0, 55.0), (32, 32), + src_crs, tgt_crs, bounds_policy='raw', + ) + + # Percentile bounds should be strictly inside raw bounds (or + # equal to floating-point precision) for a benign input. + pl, pb, pr, pt = grid_percentile['bounds'] + rl, rb, rr, rt = grid_raw['bounds'] + assert pl >= rl - 1.0 + assert pr <= rr + 1.0 + assert pb >= rb - 1.0 + assert pt <= rt + 1.0 + + def test_warns_when_percentile_fires_under_auto(self): + """auto policy emits UserWarning when the 2/98 fallback triggers.""" + from xrspatial.reproject import reproject + + r = self._global_geographic() + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + reproject(r, 'EPSG:3857', bounds_policy='auto') + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + assert matched, "expected a bounds_policy warning under auto" + assert any('blow-up' in str(wi.message) or 'percentile' in str(wi.message) + for wi in matched) + + def test_warns_when_clamp_actually_trims(self): + """clamp policy emits UserWarning when source bounds get trimmed.""" + from xrspatial.reproject._grid import _compute_output_grid + from xrspatial.reproject._crs_utils import _resolve_crs + + src_crs = _resolve_crs('EPSG:4326') + tgt_crs = _resolve_crs('EPSG:3857') + + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + _compute_output_grid( + (-180.0, -90.0, 180.0, 90.0), (50, 100), + src_crs, tgt_crs, bounds_policy='clamp', + ) + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'clamp' in str(wi.message) + ] + assert matched, "expected a clamp warning on full-globe input" + + def test_no_warning_on_benign_input(self): + """auto policy stays silent on inputs that don't trigger heuristics. + + Same-units projections (UTM->UTM) have comparable spans so the + blow-up ratio stays below the 50x threshold and the geographic + clamp doesn't apply. No warning should fire. + """ + from xrspatial.reproject import reproject + + data = np.random.RandomState(0).rand(32, 32).astype(np.float32) + r = xr.DataArray( + data, dims=['y', 'x'], + coords={'y': np.linspace(5000000, 4000000, 32), + 'x': np.linspace(400000, 600000, 32)}, + attrs={'crs': 'EPSG:32633'}, + ) + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + reproject(r, 'EPSG:32632', bounds_policy='auto') + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + assert not matched, ( + f"unexpected bounds_policy warning(s): {[str(m.message) for m in matched]}" + ) + + def test_invalid_policy_rejected(self): + """Unknown bounds_policy tokens raise ValueError at the API boundary.""" + from xrspatial.reproject import reproject + + r = self._benign_geographic() + with pytest.raises(ValueError, match=r"bounds_policy"): + reproject(r, 'EPSG:3857', bounds_policy='bogus') + + def test_invalid_policy_rejected_in_merge(self): + from xrspatial.reproject import merge + + r = self._benign_geographic() + with pytest.raises(ValueError, match=r"bounds_policy"): + merge([r], target_crs='EPSG:3857', bounds_policy='bogus') + + def test_explicit_bounds_skips_policy_logic(self): + """When the caller passes bounds, the policy heuristics don't run.""" + from xrspatial.reproject import reproject + + r = self._global_geographic() + # Mercator-y bounds chosen well inside the projection envelope. + explicit = (-2.0e7, -2.0e7, 2.0e7, 2.0e7) + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + out = reproject( + r, 'EPSG:3857', + bounds=explicit, + resolution=2e5, + bounds_policy='auto', + ) + + # No bounds_policy warning fires when bounds are explicit. + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + assert not matched + # Output extent reflects the explicit bounds (within one pixel). + out_x = out.coords['x'].values + out_y = out.coords['y'].values + assert abs(out_x.min() - explicit[0]) < 2.5e5 + assert abs(out_x.max() - explicit[2]) < 2.5e5 + assert abs(out_y.min() - explicit[1]) < 2.5e5 + assert abs(out_y.max() - explicit[3]) < 2.5e5 + + def test_merge_passes_policy_through(self): + """merge() plumbs bounds_policy to _compute_output_grid.""" + from xrspatial.reproject import merge + + r = self._global_geographic() + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + merge([r], target_crs='EPSG:3857', bounds_policy='auto') + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + # merge() on a single global geographic raster should also + # trigger the percentile fallback warning under auto. + assert matched + + @pytest.mark.skipif(not HAS_DASK, reason="dask required") + def test_raw_policy_dask_backend(self): + """bounds_policy='raw' works with a dask-backed input.""" + from xrspatial.reproject import reproject + + r = self._benign_geographic() + r = r.chunk({'y': 16, 'x': 16}) + out = reproject(r, 'EPSG:3857', bounds_policy='raw') + # Result is also dask-backed (lazy). + assert hasattr(out.data, 'dask') + # Compute and confirm we got finite output. + arr = out.compute() + assert np.isfinite(arr.data).any() From bcae1e6c3a829e31c3243bbd045c7771e82dde2e Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Wed, 20 May 2026 08:38:37 -0700 Subject: [PATCH 2/3] Document reproject bounds_policy and add user guide notebook (#2187) --- docs/source/reference/index.rst | 1 + docs/source/reference/reproject.rst | 38 +++ .../55_Reproject_Bounds_Policy.ipynb | 294 ++++++++++++++++++ .../55_reproject_bounds_policy_preview.png | Bin 0 -> 99567 bytes 4 files changed, 333 insertions(+) create mode 100644 docs/source/reference/reproject.rst create mode 100644 examples/user_guide/55_Reproject_Bounds_Policy.ipynb create mode 100644 examples/user_guide/images/55_reproject_bounds_policy_preview.png diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index d851b0523..fa9315866 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -23,6 +23,7 @@ Reference multispectral pathfinding proximity + reproject resample surface terrain_metrics diff --git a/docs/source/reference/reproject.rst b/docs/source/reference/reproject.rst new file mode 100644 index 000000000..1d2932431 --- /dev/null +++ b/docs/source/reference/reproject.rst @@ -0,0 +1,38 @@ +.. _reference.reproject: + +********* +Reproject +********* + +Reproject a raster to a new coordinate reference system, and merge +multiple rasters into a unified output grid. + +Reproject +========= +.. autosummary:: + :toctree: _autosummary + + xrspatial.reproject.reproject + xrspatial.reproject.merge + +Bounds policy +============= + +The ``bounds_policy`` parameter on :func:`xrspatial.reproject.reproject` +and :func:`xrspatial.reproject.merge` controls how the output extent is +derived from the input extent when no explicit ``bounds`` is supplied. +The available options are: + +- ``"auto"`` (default): clamp geographic source bounds inward by + 0.01 deg from +/-180 longitude and +/-90 latitude, and fall back to + the 2nd/98th percentile of a dense interior grid when the projected + extent is more than 50x the source extent. Matches the historical + behaviour. Emits a ``UserWarning`` when the clamp or percentile + fallback actually alters the bounds. +- ``"raw"``: use the true projected extent of the source corners and + edges. No clamp, no percentile. Pass this when downstream tooling + needs the exact projection of the input footprint and you are willing + to handle large outputs near projection singularities. +- ``"clamp"``: apply only the geographic-bounds clamp. +- ``"percentile"``: always use the 2/98 percentile bounds, even when + the projected extent looks well-behaved. diff --git a/examples/user_guide/55_Reproject_Bounds_Policy.ipynb b/examples/user_guide/55_Reproject_Bounds_Policy.ipynb new file mode 100644 index 000000000..cb4090a6e --- /dev/null +++ b/examples/user_guide/55_Reproject_Bounds_Policy.ipynb @@ -0,0 +1,294 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Xarray-Spatial Reproject: bounds policy at projection singularities\n", + "\n", + "Reprojecting a raster near the antimeridian or a pole can produce huge or undefined output extents. xrspatial.reproject ships heuristics that crop the output to a sane size, but those heuristics fire silently and can discard real data. The `bounds_policy` parameter lets you pick the trade-off explicitly." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What you'll build\n", + "\n", + "1. Project a global geographic raster to Web Mercator under each of the four policies.\n", + "2. Compare the resulting output bounds side by side.\n", + "3. Surface the `UserWarning` emitted under the default `auto` policy.\n", + "4. Walk through the geographic clamp on a near-antimeridian raster.\n", + "\n", + "![preview](images/55_reproject_bounds_policy_preview.png)\n", + "\n", + "- [Setup](#Setup)\n", + "- [The four policies](#The-four-policies)\n", + "- [Auto policy emits a warning](#Auto-policy-emits-a-warning)\n", + "- [Geographic clamp on a near-antimeridian raster](#Geographic-clamp-on-a-near-antimeridian-raster)\n", + "- [References](#References)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Standard imports. The reproject function is the only thing we need from xrspatial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "import numpy as np\n", + "import xarray as xr\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from xrspatial.reproject import reproject" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Build a near-global geographic raster (EPSG:4326). The longitude range stops just short of +/-180 so we still have a meaningful projected extent, and the latitudes climb to +/-85 to put us near the Web Mercator polar singularity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(42)\n", + "lats = np.linspace(85, -85, 80)\n", + "lons = np.linspace(-178, 178, 160)\n", + "yy, xx = np.meshgrid(lats, lons, indexing='ij')\n", + "data = (np.sin(np.deg2rad(xx) * 2) * np.cos(np.deg2rad(yy)) + 1.0) / 2.0\n", + "\n", + "raster = xr.DataArray(\n", + " data.astype(np.float32),\n", + " dims=['y', 'x'],\n", + " coords={'y': lats, 'x': lons},\n", + " attrs={'crs': 'EPSG:4326'},\n", + ")\n", + "raster" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The source values are smooth east-west bands modulated by latitude. The exact pattern is incidental. What matters is the spatial extent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "raster.plot.imshow(size=4, aspect=2, cmap='viridis', add_colorbar=False)\n", + "plt.title('Source raster (EPSG:4326)')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The four policies\n", + "\n", + "Run the same reprojection four times, once per policy. Suppress the warnings for now and just compare the resulting output bounds and shapes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "policies = ['auto', 'raw', 'clamp', 'percentile']\n", + "results = {}\n", + "for policy in policies:\n", + " with warnings.catch_warnings():\n", + " warnings.simplefilter('ignore', UserWarning)\n", + " out = reproject(raster, 'EPSG:3857', resolution=2e5, bounds_policy=policy)\n", + " results[policy] = out\n", + "\n", + "import pandas as pd\n", + "rows = []\n", + "for policy, out in results.items():\n", + " x = out.coords['x'].values\n", + " y = out.coords['y'].values\n", + " rows.append({\n", + " 'policy': policy,\n", + " 'shape': out.shape,\n", + " 'x_min': float(x.min()),\n", + " 'x_max': float(x.max()),\n", + " 'y_min': float(y.min()),\n", + " 'y_max': float(y.max()),\n", + " })\n", + "pd.DataFrame(rows)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`auto` and `percentile` produce the smallest extents because the 2/98 percentile fallback throws away the extremes near the poles. `raw` keeps every projected pixel and ends up the largest. `clamp` sits in between because it trims the source extent inward by 0.01 deg before projecting, but it still keeps all the projected output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 2, figsize=(12, 9))\n", + "for ax, policy in zip(axes.flat, policies):\n", + " out = results[policy]\n", + " out.plot.imshow(ax=ax, cmap='viridis', add_colorbar=False)\n", + " ax.set_title(f\"bounds_policy={policy!r} shape={out.shape}\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Picking a policy. Use raw when you need the true projected extent of your input footprint, for example when mosaicking many tiles or computing area statistics. Use auto (or percentile) when you'd rather lose a few pixels near a singularity than allocate a giant output grid.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Auto policy emits a warning\n", + "\n", + "Under `auto`, when the percentile fallback or the geographic clamp actually alters the bounds, reproject emits a `UserWarning`. The warning names the policy and reports the per-side delta vs the raw projected bounds." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with warnings.catch_warnings(record=True) as caught:\n", + " warnings.simplefilter('always')\n", + " reproject(raster, 'EPSG:3857', resolution=2e5, bounds_policy='auto')\n", + "\n", + "for w in caught:\n", + " if issubclass(w.category, UserWarning) and 'bounds_policy' in str(w.message):\n", + " print(w.message)\n", + " print('---')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you intentionally want the silent cropping back, suppress the warning:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\n", + " 'ignore', category=UserWarning, message='.*bounds_policy.*'\n", + " )\n", + " out = reproject(raster, 'EPSG:3857', resolution=2e5)\n", + " print(f'output shape: {out.shape}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Geographic clamp on a near-antimeridian raster\n", + "\n", + "When the source CRS is geographic and the source extent touches +/-180 longitude, the default policy trims it inward by 0.01 deg before projecting. This avoids infinities at the antimeridian, but it also trims a sliver of real data. Surface the trim with the `clamp` policy on a synthetic raster whose extent runs to exactly +/-180 longitude." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "edge_raster = xr.DataArray(\n", + " np.random.RandomState(0).rand(40, 80).astype(np.float32),\n", + " dims=['y', 'x'],\n", + " coords={'y': np.linspace(60, -60, 40),\n", + " 'x': np.linspace(-180, 180, 80)},\n", + " attrs={'crs': 'EPSG:4326'},\n", + ")\n", + "\n", + "with warnings.catch_warnings(record=True) as caught:\n", + " warnings.simplefilter('always')\n", + " out_clamp = reproject(\n", + " edge_raster, 'EPSG:3857', resolution=2e5, bounds_policy='clamp'\n", + " )\n", + "\n", + "for w in caught:\n", + " if issubclass(w.category, UserWarning) and 'clamp' in str(w.message):\n", + " print(w.message)\n", + " print('---')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The warning names the original source extent and the trimmed one. If you need the antimeridian-touching data preserved, switch to `bounds_policy='raw'` and handle any projection infinities downstream." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Antimeridian wrap. Geographic rasters that genuinely span the antimeridian are still tricky. Even with raw, single-pass reprojection cannot reconstruct a discontinuous footprint. Consider splitting the raster on the antimeridian and reprojecting each half separately, then merging.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- xarray-spatial reproject reference: https://xarray-spatial.org/reference/reproject.html\n", + "- Web Mercator (EPSG:3857) polar truncation: https://epsg.io/3857\n", + "- Issue #2187: bounds heuristics audit" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.x" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/user_guide/images/55_reproject_bounds_policy_preview.png b/examples/user_guide/images/55_reproject_bounds_policy_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec410110a5ab0af509e667215662c39ef39b98b GIT binary patch literal 99567 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

*Fl_3CZim)^?hN@?TPDk~L% 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 z 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�+@95`22VX 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 LCsHN!Bk=zL+iabn literal 0 HcmV?d00001 From f959b9fcef056780315da42a2598f2c5e0efe836 Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Wed, 20 May 2026 08:42:32 -0700 Subject: [PATCH 3/3] Address review: dedupe merge warnings, drop dead 'raw' corner block, docstring + tests (#2187) --- xrspatial/reproject/__init__.py | 45 ++++++++++++++--- xrspatial/reproject/_grid.py | 16 +++--- xrspatial/tests/test_reproject.py | 83 +++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 17 deletions(-) diff --git a/xrspatial/reproject/__init__.py b/xrspatial/reproject/__init__.py index ef0fcb17b..fdf9d0b81 100644 --- a/xrspatial/reproject/__init__.py +++ b/xrspatial/reproject/__init__.py @@ -600,6 +600,8 @@ def reproject( - ``"clamp"``: trim geographic source bounds inward by 0.01 deg from +/-180 longitude and +/-90 latitude before projecting. Avoids infinities at singularities. No percentile fallback. + No-op on projected source CRSes (UTM, Mercator, etc.) since + the clamp only applies in degrees. - ``"percentile"``: project a dense interior grid of the source extent and use the 2nd/98th percentiles of the result as the output bounds. Rejects projection outliers at the cost of @@ -1840,16 +1842,43 @@ def merge( # Compute unified output grid if bounds is None: - # Union of all raster bounds in target CRS + # Union of all raster bounds in target CRS. + # Capture bounds_policy warnings during the per-input gather and + # emit a single deduplicated message afterwards. Otherwise a + # mosaic of N near-antimeridian rasters yields N identical + # warnings, which is noise the caller cannot act on individually. + import warnings as _warnings all_bounds = [] - for info in raster_infos: - grid = _compute_output_grid( - info['src_bounds'], info['src_shape'], - info['src_crs'], tgt_crs, - resolution=resolution, - bounds_policy=bounds_policy, + with _warnings.catch_warnings(record=True) as _caught: + _warnings.simplefilter('always', UserWarning) + for info in raster_infos: + grid = _compute_output_grid( + info['src_bounds'], info['src_shape'], + info['src_crs'], tgt_crs, + resolution=resolution, + bounds_policy=bounds_policy, + ) + all_bounds.append(grid['bounds']) + _policy_msgs = [ + str(w.message) for w in _caught + if issubclass(w.category, UserWarning) + and 'bounds_policy' in str(w.message) + ] + if _policy_msgs: + _unique = list(dict.fromkeys(_policy_msgs)) + _summary = ( + f"merge: bounds_policy={bounds_policy!r} altered the " + f"projected extent for {len(_policy_msgs)} input " + f"raster(s); {len(_unique)} unique trigger(s). " + f"First trigger: {_unique[0]}" ) - all_bounds.append(grid['bounds']) + _warnings.warn(_summary, UserWarning, stacklevel=2) + # Re-emit non-bounds_policy warnings that we captured. + for _w in _caught: + if 'bounds_policy' not in str(_w.message): + _warnings.warn_explicit( + _w.message, _w.category, _w.filename, _w.lineno, + ) left = min(b[0] for b in all_bounds) bottom = min(b[1] for b in all_bounds) right = max(b[2] for b in all_bounds) diff --git a/xrspatial/reproject/_grid.py b/xrspatial/reproject/_grid.py index 87e8e20a7..312db7126 100644 --- a/xrspatial/reproject/_grid.py +++ b/xrspatial/reproject/_grid.py @@ -196,6 +196,10 @@ def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, ) src_left, src_right = new_left, new_right src_bottom, src_top = new_bottom, new_top + # Defensive: if the input was so narrow that the 0.02 deg + # clamp inverted the range, restore the originals. Upstream + # _validate_grid_params already rejects degenerate bounds, + # so this branch should be unreachable in practice. if src_left >= src_right: src_left, src_right = source_bounds[0], source_bounds[2] clamp_applied = False @@ -227,15 +231,9 @@ def _compute_output_grid(source_bounds, source_shape, source_crs, target_crs, ixx, iyy = np.meshgrid(ix, iy) xs = np.concatenate([edge_xs, ixx.ravel()]) ys = np.concatenate([edge_ys, iyy.ravel()]) - # For policy='raw', include the unclamped corners explicitly so - # the raw bounds reflect the user's original extent verbatim. - if bounds_policy == "raw": - xs = np.concatenate([xs, np.array([ - src_left_raw, src_right_raw, src_left_raw, src_right_raw - ])]) - ys = np.concatenate([ys, np.array([ - src_bottom_raw, src_bottom_raw, src_top_raw, src_top_raw - ])]) + # Under policy='raw' the clamp branch above is skipped, so the + # edge samples already start from the original src_*_raw values. + # No extra corner injection needed. tx, ty = _transform_boundary(source_crs, target_crs, xs, ys) tx = np.asarray(tx) ty = np.asarray(ty) diff --git a/xrspatial/tests/test_reproject.py b/xrspatial/tests/test_reproject.py index e06f0ec9f..dae552998 100644 --- a/xrspatial/tests/test_reproject.py +++ b/xrspatial/tests/test_reproject.py @@ -4144,3 +4144,86 @@ def test_raw_policy_dask_backend(self): # Compute and confirm we got finite output. arr = out.compute() assert np.isfinite(arr.data).any() + + def test_clamp_policy_noop_on_benign_geographic(self): + """bounds_policy='clamp' is silent on a mid-latitude geographic + input whose extent does not touch +/-180 or +/-90. + + The clamp branch runs but trims nothing, so no warning should + fire. This pins the behaviour so a future change that always + emits a clamp warning shows up here. + """ + from xrspatial.reproject import reproject + + r = self._benign_geographic() + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + reproject(r, 'EPSG:3857', bounds_policy='clamp') + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + assert not matched, ( + f"clamp on benign geographic input should not warn; got " + f"{[str(m.message) for m in matched]}" + ) + + def test_clamp_policy_noop_on_projected_source(self): + """bounds_policy='clamp' is a no-op when source CRS is projected. + + The clamp condition is gated on `source_crs.is_geographic`, so + a UTM input under 'clamp' should run without trimming or + warning regardless of how close to a singularity the extent is. + """ + from xrspatial.reproject import reproject + + data = np.random.RandomState(0).rand(32, 32).astype(np.float32) + # UTM-style coords, mid-latitudes. + r = xr.DataArray( + data, dims=['y', 'x'], + coords={'y': np.linspace(5000000, 4000000, 32), + 'x': np.linspace(400000, 600000, 32)}, + attrs={'crs': 'EPSG:32633'}, + ) + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + reproject(r, 'EPSG:4326', bounds_policy='clamp') + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + assert not matched + + def test_merge_dedupes_per_input_warnings(self): + """merge() collapses per-input bounds_policy warnings into one. + + When several inputs all trigger the percentile fallback, the + caller should see a single summary warning rather than N + near-identical messages. + """ + from xrspatial.reproject import merge + + r = self._global_geographic() + import warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + merge([r, r, r], target_crs='EPSG:3857', bounds_policy='auto') + + matched = [ + wi for wi in w + if issubclass(wi.category, UserWarning) + and 'bounds_policy' in str(wi.message) + ] + # Three identical inputs should yield exactly one summary + # warning from merge(), not three. + summary = [m for m in matched if 'merge:' in str(m.message)] + assert len(summary) == 1, ( + f"expected one merge summary warning, got " + f"{[str(m.message) for m in matched]}" + )