Skip to content

Commit 26ed84c

Browse files
committed
Add merge benchmark: xrspatial vs rioxarray (#1045)
Merges 4 overlapping WGS84 tiles and compares timing and pixel-level consistency against rioxarray.merge_arrays. Baseline results (xrspatial is currently slower on merge): 512x512 tiles: 59ms vs 56ms (1.1x) 1024x1024: 293ms vs 114ms (2.6x) 2048x2048: 2.52s vs 656ms (3.8x) Consistency: RMSE < 0.012, NaN agreement > 99.8%.
1 parent d83f010 commit 26ed84c

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

xrspatial/tests/bench_reproject_vs_rioxarray.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,102 @@ def run_real_world(files=None, resamplings=None):
452452
f'| {stats["exact"][1]:>12.6f} |')
453453

454454

455+
def run_merge(sizes=None):
456+
"""Benchmark xrspatial.merge vs rioxarray.merge_arrays.
457+
458+
Creates 4 overlapping rasters in a 2x2 grid arrangement and merges
459+
them into a single mosaic with each library.
460+
"""
461+
from rioxarray.merge import merge_arrays as rio_merge_arrays
462+
463+
from xrspatial.reproject import merge as xrs_merge
464+
465+
sizes = sizes or [(512, 512), (1024, 1024), (2048, 2048)]
466+
467+
print()
468+
print('=' * 100)
469+
print('MERGE BENCHMARK: xrspatial.merge vs rioxarray.merge_arrays (4 overlapping tiles)')
470+
print('=' * 100)
471+
print()
472+
print(f'| {"Tile size":>12} '
473+
f'| {"xrs merge":>11} | {"rio merge":>11} '
474+
f'| {"xrs/rio":>7} '
475+
f'| {"RMSE":>10} | {"MaxErr":>10} '
476+
f'| {"Valid px":>10} | {"NaN agree":>9} |')
477+
print(f'|{"-" * 14}'
478+
f'|{"-" * 13}|{"-" * 13}'
479+
f'|{"-" * 9}'
480+
f'|{"-" * 12}|{"-" * 12}'
481+
f'|{"-" * 12}|{"-" * 11}|')
482+
483+
for h, w in sizes:
484+
# Build 4 overlapping tiles in a 2x2 grid.
485+
# Each tile spans 10 degrees; overlap is 2 degrees on each shared edge.
486+
# Total coverage: 18 x 18 degrees (from -9 to 9 lon, 41 to 59 lat).
487+
tile_specs = [
488+
# (x_range, y_range) -- 2-degree overlap between neighbours
489+
((-9, 1), (49, 59)), # top-left
490+
((-1, 9), (49, 59)), # top-right
491+
((-9, 1), (41, 51)), # bottom-left
492+
((-1, 9), (41, 51)), # bottom-right
493+
]
494+
495+
tiles_xrs = []
496+
tiles_rio = []
497+
for x_range, y_range in tile_specs:
498+
da = _make_raster(h, w, crs='EPSG:4326',
499+
x_range=x_range, y_range=y_range)
500+
tiles_xrs.append(da)
501+
tiles_rio.append(_make_rio_raster(da, 'EPSG:4326'))
502+
503+
# Benchmark xrspatial merge
504+
xrs_time, xrs_result = _timer(
505+
lambda: xrs_merge(tiles_xrs),
506+
warmup=1, runs=3,
507+
)
508+
509+
# Benchmark rioxarray merge
510+
rio_time, rio_result = _timer(
511+
lambda: rio_merge_arrays(tiles_rio),
512+
warmup=1, runs=3,
513+
)
514+
515+
xrs_vals = xrs_result.values
516+
rio_vals = rio_result.values
517+
518+
# Crop to common shape if they differ
519+
common_h = min(xrs_vals.shape[0], rio_vals.shape[0])
520+
common_w = min(xrs_vals.shape[1], rio_vals.shape[1])
521+
xrs_vals = xrs_vals[:common_h, :common_w]
522+
rio_vals = rio_vals[:common_h, :common_w]
523+
524+
# Compare where both have valid data
525+
xrs_nan = np.isnan(xrs_vals)
526+
rio_nan = np.isnan(rio_vals)
527+
both_valid = ~xrs_nan & ~rio_nan
528+
n_valid = int(both_valid.sum())
529+
nan_agree = np.mean(xrs_nan == rio_nan) * 100
530+
531+
if n_valid > 0:
532+
diff = xrs_vals[both_valid] - rio_vals[both_valid]
533+
rmse = np.sqrt(np.mean(diff ** 2))
534+
max_err = np.max(np.abs(diff))
535+
rmse_str = f'{rmse:.6f}'
536+
max_str = f'{max_err:.6f}'
537+
else:
538+
rmse_str = 'N/A'
539+
max_str = 'N/A'
540+
541+
ratio = xrs_time / rio_time if rio_time > 0 else float('inf')
542+
543+
print(f'| {_fmt_shape((h, w)):>12} '
544+
f'| {_fmt_time(xrs_time):>11} '
545+
f'| {_fmt_time(rio_time):>11} '
546+
f'| {ratio:>6.2f}x '
547+
f'| {rmse_str:>10} | {max_str:>10} '
548+
f'| {n_valid:>10} | {nan_agree:>8.1f}% |')
549+
550+
455551
def main():
456552
if not HAS_PYPROJ:
457553
print('ERROR: pyproj is required for reprojection benchmarks')
@@ -476,6 +572,7 @@ def main():
476572
run_consistency()
477573
run_performance()
478574
run_real_world()
575+
run_merge()
479576

480577

481578
if __name__ == '__main__':

0 commit comments

Comments
 (0)