Remove redundant file size guards from rio-warp, add --dry-run (#2889)

* Remove redundant file size guards from rio-warp, add --dry-run

Resolves #2887

* Update change log
This commit is contained in:
Sean Gillies 2023-07-26 14:36:03 -06:00 committed by GitHub
parent 377ec3a940
commit f424fce16e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 47 deletions

View File

@ -1,6 +1,14 @@
Changes
=======
Next
----
- The output file size limits of rio-warp were made redundant by changes to the
GTiff driver in GDAL 2.1 and have been removed (#2889). A --dry-run option
has been added to the command. If used, the profile of the output dataset
will be printed and no warping will occur.
1.3.8 (2023-06-26)
------------------

View File

@ -1,5 +1,6 @@
"""$ rio warp"""
"""rio warp: CLI for reprojecting rasters."""
import json
import logging
from math import ceil, floor
import sys
@ -20,12 +21,6 @@ from rasterio.warp import (
logger = logging.getLogger(__name__)
# Improper usage of rio-warp can lead to accidental creation of
# extremely large datasets. We'll put a hard limit on the size of
# datasets and raise a usage error if the limits are exceeded.
MAX_OUTPUT_WIDTH = 100000
MAX_OUTPUT_HEIGHT = 100000
@click.command(short_help='Warp a raster dataset.')
@options.files_inout_arg
@ -93,6 +88,11 @@ MAX_OUTPUT_HEIGHT = 100000
callback=_cb_key_val,
help="GDAL warper and coordinate transformer options.",
)
@click.option(
"--dry-run",
is_flag=True,
help="Do not create an output file, but report on its expected size and other characteristics.",
)
@click.pass_context
def warp(
ctx,
@ -114,6 +114,7 @@ def warp(
creation_options,
target_aligned_pixels,
warper_options,
dry_run,
):
"""
Warp a raster dataset.
@ -127,23 +128,19 @@ def warp(
\b
$ rio warp input.tif output.tif --like template.tif
The output coordinate reference system may be either a PROJ.4 or
EPSG:nnnn string,
The destination's coordinate reference system may be an authority
name, PROJ4 string, JSON-encoded PROJ4, or WKT.
\b
--dst-crs EPSG:4326
--dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84'
or a JSON text-encoded PROJ.4 object.
\b
--dst-crs '{"proj": "utm", "zone": 18, ...}'
If --dimensions are provided, --res and --bounds are not applicable and an
exception will be raised.
Resolution is calculated based on the relationship between the
raster bounds in the target coordinate system and the dimensions,
and may produce rectangular rather than square pixels.
If --dimensions are provided, --res and --bounds are not applicable
and an exception will be raised. Resolution is calculated based on
the relationship between the raster bounds in the target coordinate
system and the dimensions, and may produce rectangular rather than
square pixels.
\b
$ rio warp input.tif output.tif --dimensions 100 200 \\
@ -354,15 +351,6 @@ def warp(
# Update the dst nodata value
out_kwargs.update(nodata=dst_nodata)
# When the bounds option is misused, extreme values of
# destination width and height may result.
if (dst_width < 0 or dst_height < 0 or
dst_width > MAX_OUTPUT_WIDTH or
dst_height > MAX_OUTPUT_HEIGHT):
raise click.BadParameter(
"Invalid output dimensions: {0}.".format(
(dst_width, dst_height)))
out_kwargs.update(
crs=dst_crs,
transform=dst_transform,
@ -386,18 +374,29 @@ def warp(
out_kwargs.update(**creation_options)
with rasterio.open(output, 'w', **out_kwargs) as dst:
reproject(
source=rasterio.band(src, list(range(1, src.count + 1))),
destination=rasterio.band(
dst, list(range(1, src.count + 1))),
src_transform=src.transform,
src_crs=src.crs,
src_nodata=src_nodata,
dst_transform=out_kwargs['transform'],
dst_crs=out_kwargs['crs'],
dst_nodata=dst_nodata,
resampling=resampling,
num_threads=threads,
**warper_options
)
if dry_run:
crs = out_kwargs.get("crs", None)
if crs:
epsg = src.crs.to_epsg()
if epsg:
out_kwargs['crs'] = 'EPSG:{}'.format(epsg)
else:
out_kwargs['crs'] = src.crs.to_string()
click.echo("Output dataset profile:")
click.echo(json.dumps(dict(**out_kwargs), indent=2))
else:
with rasterio.open(output, "w", **out_kwargs) as dst:
reproject(
source=rasterio.band(src, list(range(1, src.count + 1))),
destination=rasterio.band(dst, list(range(1, src.count + 1))),
src_transform=src.transform,
src_crs=src.crs,
src_nodata=src_nodata,
dst_transform=out_kwargs["transform"],
dst_crs=out_kwargs["crs"],
dst_nodata=dst_nodata,
resampling=resampling,
num_threads=threads,
**warper_options
)

View File

@ -332,14 +332,25 @@ def test_warp_reproject_multi_bounds_fail(runner, tmpdir):
def test_warp_reproject_bounds_crossup_fail(runner, tmpdir):
"""Crossed-up bounds raises click.BadParameter."""
"""Crossed-up bounds raises RasterioIOError."""
srcname = 'tests/data/shade.tif'
outputname = str(tmpdir.join('test.tif'))
out_bounds = [-11850000, 4810000, -11849000, 4812000]
result = runner.invoke(main_group, [
'warp', srcname, outputname, '--dst-crs', 'EPSG:4326', '--res', 0.001,
'--bounds'] + out_bounds)
assert result.exit_code == 2
result = runner.invoke(
main_group,
[
"warp",
srcname,
outputname,
"--dst-crs",
"EPSG:4326",
"--res",
0.001,
"--bounds",
]
+ out_bounds,
)
assert result.exit_code == 1
def test_warp_reproject_src_bounds_res(runner, tmpdir):
@ -604,3 +615,29 @@ def test_coordinate_operation(runner, tmp_path, wotopt):
with rasterio.open(outputname) as src:
assert src.checksum(1) == 4705
def test_dry_run(runner, tmpdir):
# See also warp_reproject_bounds_crossup_fail.
srcname = 'tests/data/shade.tif'
outputname = str(tmpdir.join('test.tif'))
out_bounds = [-11850000, 4810000, -11849000, 4812000]
result = runner.invoke(
main_group,
[
"warp",
"--dry-run",
srcname,
outputname,
"--dst-crs",
"EPSG:4326",
"--res",
0.001,
"--bounds",
]
+ out_bounds,
)
assert result.exit_code == 0
assert '"width": 1000000' in result.output
assert '"height": 2000000' in result.output