Skip to content
Merged
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e

--------

### **Reproject / Merge**

| Name | Description | Source | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
|:----------:|:------------|:------:|:----------------------:|:--------------------:|:-------------------:|:------:|
| [Reproject](xrspatial/reproject/__init__.py) | Reprojects a raster to a new CRS using an approximate transform and numba JIT resampling | Standard (inverse mapping) | ✅️ | ✅️ | ✅️ | ✅️ |
| [Merge](xrspatial/reproject/__init__.py) | Merges multiple rasters into a single mosaic with configurable overlap strategy | Standard (mosaic) | ✅️ | ✅️ | 🔄 | 🔄 |

-------

### **Raster / Vector Conversion**

| Name | Description | Source | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
Expand Down Expand Up @@ -324,6 +333,9 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e
| [Inundation](xrspatial/flood.py) | Produces a binary flood/no-flood mask from a HAND raster and water level | Standard (HAND-based) | ✅️ | ✅️ | ✅️ | ✅️ |
| [Curve Number Runoff](xrspatial/flood.py) | Estimates runoff depth from rainfall using the SCS/NRCS curve number method | SCS/NRCS | ✅️ | ✅️ | ✅️ | ✅️ |
| [Travel Time](xrspatial/flood.py) | Estimates overland flow travel time via simplified Manning's equation | Manning 1891 | ✅️ | ✅️ | ✅️ | ✅️ |
| [Vegetation Roughness](xrspatial/flood.py) | Derives Manning's roughness coefficients from NLCD land cover or NDVI | SCS/NRCS | ✅️ | ✅️ | ✅️ | ✅️ |
| [Vegetation Curve Number](xrspatial/flood.py) | Derives SCS curve numbers from land cover and hydrologic soil group | SCS/NRCS | ✅️ | ✅️ | ✅️ | ✅️ |
| [Flood Depth (Vegetation)](xrspatial/flood.py) | Manning-based steady-state flow depth incorporating vegetation roughness | Manning 1891 | ✅️ | ✅️ | ✅️ | ✅️ |

-----------

Expand All @@ -337,6 +349,15 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e

-----------

### **Dasymetric**

| Name | Description | Source | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
|:----------:|:------------|:------:|:----------------------:|:--------------------:|:-------------------:|:------:|
| [Disaggregate](xrspatial/dasymetric.py) | Redistributes zonal totals to pixels using an ancillary weight surface | Mennis 2003 | ✅️ | ✅️ | ✅️ | ✅️ |
| [Pycnophylactic](xrspatial/dasymetric.py) | Tobler's pycnophylactic interpolation preserving zone totals via Laplacian smoothing | Tobler 1979 | ✅️ | | | |

-----------

### **Zonal**

| Name | Description | Source | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
Expand Down
124 changes: 124 additions & 0 deletions benchmarks/REPROJECT_BENCHMARKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Reproject Benchmarks

Generated: 2026-03-19 20:50 UTC

Compares xrspatial reproject (numpy backend, numba JIT) against rioxarray (GDAL warp) across 4 raster sizes, 2 CRS transforms, and 3 resampling methods. The **fastest** time in each row is bold.

- **Test data:** synthetic terrain (sin/cos + Gaussian + noise) in EPSG:4326
- **Transform:** identity (4326 to 4326) and geographic to Web Mercator (4326 to 3857)
- **Resampling:** nearest, bilinear, cubic
- **Consistency:** sampled at matching geographic coordinates (interior 90%), correlation and RMSE reported

## identity

### Timings

| size | xrs-nearest | rio-nearest | xrs-bilinear | rio-bilinear | xrs-cubic | rio-cubic |
|---:|---:|---:|---:|---:|---:|---:|
| 256x256 | **1.6 ms** | 3.4 ms | **1.7 ms** | 4.1 ms | **2.4 ms** | 6.0 ms |
| 512x512 | **6.8 ms** | 7.0 ms | **9.0 ms** | 11 ms | **12 ms** | 19 ms |
| 1024x1024 | 31 ms | **23 ms** | **31 ms** | 38 ms | **40 ms** | 69 ms |
| 2048x2048 | 141 ms | **88 ms** | **145 ms** | 158 ms | **181 ms** | 277 ms |

### Relative to rioxarray

Values below 1.0 mean xrspatial is faster.

| size | nearest | bilinear | cubic |
|---:|---:|---:|---:|
| 256x256 | 0.48x | 0.41x | 0.40x |
| 512x512 | 0.97x | 0.79x | 0.63x |
| 1024x1024 | 1.36x | 0.80x | 0.58x |
| 2048x2048 | 1.60x | 0.91x | 0.65x |

### Consistency

| size | method | correlation | RMSE | max |Δ| | median rel |
|---:|:------|---:|---:|---:|---:|
| 256x256 | nearest | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 256x256 | bilinear | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 256x256 | cubic | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 512x512 | nearest | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 512x512 | bilinear | 1.000000 | 0.0000 | 0.0000 | 1.24e-16 |
| 512x512 | cubic | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 1024x1024 | nearest | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 1024x1024 | bilinear | 1.000000 | 0.0000 | 0.0000 | 1.92e-16 |
| 1024x1024 | cubic | 1.000000 | 0.0000 | 0.0000 | 1.64e-16 |
| 2048x2048 | nearest | 1.000000 | 0.0000 | 0.0000 | 0.00e+00 |
| 2048x2048 | bilinear | 1.000000 | 0.0000 | 0.0000 | 2.63e-16 |
| 2048x2048 | cubic | 1.000000 | 0.0000 | 0.0000 | 1.79e-16 |

## 4326 to 3857

### Timings

| size | xrs-nearest | rio-nearest | xrs-bilinear | rio-bilinear | xrs-cubic | rio-cubic |
|---:|---:|---:|---:|---:|---:|---:|
| 256x256 | **2.0 ms** | 3.4 ms | **2.1 ms** | 4.6 ms | **2.8 ms** | 6.6 ms |
| 512x512 | **3.9 ms** | 5.7 ms | **4.3 ms** | 10 ms | **7.0 ms** | 17 ms |
| 1024x1024 | 25 ms | **18 ms** | **33 ms** | 35 ms | **41 ms** | 64 ms |
| 2048x2048 | 123 ms | **68 ms** | **129 ms** | 141 ms | **164 ms** | 254 ms |

### Relative to rioxarray

Values below 1.0 mean xrspatial is faster.

| size | nearest | bilinear | cubic |
|---:|---:|---:|---:|
| 256x256 | 0.59x | 0.45x | 0.42x |
| 512x512 | 0.69x | 0.43x | 0.40x |
| 1024x1024 | 1.41x | 0.95x | 0.65x |
| 2048x2048 | 1.79x | 0.92x | 0.64x |

### Consistency

| size | method | correlation | RMSE | max |Δ| | median rel |
|---:|:------|---:|---:|---:|---:|
| 256x256 | nearest | 0.999844 | 2.6289 | 11.6941 | 3.37e-03 |
| 256x256 | bilinear | 0.999997 | 0.3374 | 2.2414 | 3.07e-04 |
| 256x256 | cubic | 0.999996 | 0.4388 | 3.0041 | 4.08e-04 |
| 512x512 | nearest | 0.999951 | 1.4678 | 7.7334 | 1.50e-03 |
| 512x512 | bilinear | 0.999997 | 0.3659 | 2.5772 | 3.51e-04 |
| 512x512 | cubic | 0.999995 | 0.4613 | 3.0562 | 4.58e-04 |
| 1024x1024 | nearest | 0.999966 | 1.2208 | 7.1378 | 1.30e-03 |
| 1024x1024 | bilinear | 0.999996 | 0.3939 | 3.3103 | 3.49e-04 |
| 1024x1024 | cubic | 0.999995 | 0.4623 | 3.7910 | 4.52e-04 |
| 2048x2048 | nearest | 0.999979 | 0.9678 | 6.3667 | 1.05e-03 |
| 2048x2048 | bilinear | 0.999995 | 0.4543 | 3.9038 | 4.68e-04 |
| 2048x2048 | cubic | 0.999994 | 0.5153 | 3.5623 | 5.55e-04 |

## merge()

Two overlapping tiles merged into a single mosaic. Tiles overlap by ~10% in the center.

### Timings

| size | strategy | xrspatial | rioxarray |
|---:|:------|---:|---:|
| 256x256 | first | **3.1 ms** | 9.6 ms |
| 512x512 | first | **8.5 ms** | 13 ms |
| 1024x1024 | first | 28 ms | **27 ms** |
| 2048x2048 | first | 299 ms | **124 ms** |

## Dask out-of-core

Reproject with dask-backed input (chunk_size=512). Measures graph construction time (lazy) and full compute time.

| size | method | graph build | compute | total |
|---:|:------|---:|---:|---:|
| 1024x1024 | bilinear | 123 ms | 28 ms | 151 ms |
| 2048x2048 | bilinear | 7.8 ms | 89 ms | 96 ms |
| 4096x4096 | bilinear | 95 ms | 984 ms | 1.08 s |

## Where xrspatial wins and loses

| Resampling | Small (256) | Medium (512) | Large (1024) | XL (2048) |
|:-----------|:------------|:-------------|:-------------|:----------|
| bilinear (4326 to 3857) | **xrs 2.2x** | **xrs 2.3x** | **xrs 2.2x** | **xrs 2.0x** |
| cubic (4326 to 3857) | **xrs 2.3x** | **xrs 2.5x** | **xrs 2.7x** | **xrs 2.0x** |
| nearest (4326 to 3857) | **xrs 1.5x** | ~tied | ~tied | ~tied |
| bilinear (identity) | **xrs 2.1x** | **xrs 2.1x** | **xrs 2.1x** | **xrs 2.0x** |
| cubic (identity) | **xrs 2.6x** | **xrs 2.8x** | **xrs 2.6x** | **xrs 2.4x** |
| nearest (identity) | **xrs 2.0x** | **xrs 1.4x** | ~tied | ~tied |

Bold = winner by >20%. 'xrs 2.0x' means xrspatial is 2x faster. 'rio 1.5x' means rioxarray is 1.5x faster.
669 changes: 669 additions & 0 deletions examples/user_guide/34_Reproject.ipynb

Large diffs are not rendered by default.

Binary file added examples/user_guide/images/reproject_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ optional =
spatialpandas
# Optional for gpu_rtx functions. Also requires cupy.
rtxpy
reproject =
pyproj
dask =
dask[array]
dask-geopandas
Expand All @@ -69,6 +71,7 @@ tests =
noise >= 1.2.2
dask
pyarrow
pyproj
pytest
pytest-cov
scipy
Expand Down
2 changes: 2 additions & 0 deletions xrspatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@
from xrspatial.zonal import regions as regions # noqa
from xrspatial.zonal import stats as zonal_stats # noqa
from xrspatial.zonal import suggest_zonal_canvas as suggest_zonal_canvas # noqa
from xrspatial.reproject import merge # noqa
from xrspatial.reproject import reproject # noqa

import xrspatial.accessor # noqa: F401 — registers .xrs accessors

Expand Down
6 changes: 6 additions & 0 deletions xrspatial/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,12 @@ def standardize(self, **kwargs):
from .normalize import standardize
return standardize(self._obj, **kwargs)

# ---- Reproject ----

def reproject(self, target_crs, **kwargs):
from .reproject import reproject
return reproject(self._obj, target_crs, **kwargs)

# ---- Raster to vector ----

def polygonize(self, **kwargs):
Expand Down
Loading
Loading