Add a rio create command. (#3023)

* Add a rio create command.

Resolves #3021.

* Eliminate deprecated pytest.warns usage

* Add CLI doc for rio create

* Fix punctuation.

* Add bounds option for geotransform, remove duplicate nodata option.

* Warn about duplicate georeferencing
This commit is contained in:
Sean Gillies 2024-02-08 16:13:30 -07:00 committed by GitHub
parent 34b0d1b34d
commit 35a5906f57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 756 additions and 74 deletions

View File

@ -14,6 +14,7 @@ Deprecations:
New Features:
- The new "rio create" command allows creation of new, empty datasets (#3023).
- An optional range keyword argument (like that of numpy.histogram()) has been
added to show_hist() (#2873, #3001).
- Datasets stored in proprietary systems or addressable only through protocols

View File

@ -15,7 +15,7 @@ namespace and handling of context variables.
.. code-block:: console
$ rio --help
$ rio --help
Usage: rio [OPTIONS] COMMAND [ARGS]...
Rasterio command line interface.
@ -28,6 +28,7 @@ namespace and handling of context variables.
--aws-requester-pays Requester pays data transfer costs
--version Show the version and exit.
--gdal-version
--show-versions Show dependency versions
--help Show this message and exit.
Commands:
@ -36,6 +37,7 @@ namespace and handling of context variables.
calc Raster data calculator.
clip Clip a raster to given bounds.
convert Copy and convert raster dataset.
create Create an empty or filled dataset.
edit-info Edit dataset metadata.
env Print information about the Rasterio environment.
gcps Print ground control points as GeoJSON.
@ -52,11 +54,9 @@ namespace and handling of context variables.
transform Transform coordinates.
warp Warp a raster dataset.
Commands are shown below. See ``--help`` of individual commands for more
details.
creation options
----------------
@ -161,7 +161,6 @@ use with, e.g., `geojsonio-cli <https://github.com/mapbox/geojsonio-cli>`__.
Shoot the GeoJSON into a Leaflet map using geojsonio-cli by typing
``rio bounds tests/data/RGB.byte.tif | geojsonio``.
calc
----
@ -245,6 +244,28 @@ as uint8:
You can use `--rgb` as shorthand for `--co photometric=rgb`.
create
------
The ``create`` command creates an empty dataset.
The fundamental, required parameters are: format driver name, data type, count
of bands, height and width in pixels. Long and short options are provided for
each of these. Coordinate reference system and affine transformation matrix are
not strictly required and have long options only. All other format specific
creation outputs must be specified using the --co option.
The pixel values of an empty dataset are format specific. "Smart" formats like
GTiff use 0 or the nodata value if provided.
For example:
.. code-block:: console
$ rio create new.tif -f GTiff -t uint8 -n 3 -h 512 -w 512 \
> --co tiled=true --co blockxsize=256 --co blockysize=256
The command above produces a 3-band GeoTIFF with 256 x 256 internal tiling.
edit-info
---------

View File

@ -62,6 +62,7 @@ class NotGeoreferencedWarning(UserWarning):
class TransformWarning(UserWarning):
"""Warn that coordinate transformations may behave unexpectedly"""
class RPCError(ValueError):
"""Raised when RPC transformation is invalid"""

166
rasterio/rio/create.py Normal file
View File

@ -0,0 +1,166 @@
"""The rio create command."""
import click
import json
import os
import rasterio
from rasterio.crs import CRS
from rasterio.errors import (
CRSError,
FileOverwriteError,
RasterioIOError,
)
from rasterio.rio import options
from rasterio.transform import Affine, guard_transform
def crs_handler(ctx, param, value):
"""Get crs value from the command line."""
retval = None
if value is not None:
try:
retval = CRS.from_string(value)
except CRSError:
raise click.BadParameter(
f"{value} is not a recognized CRS.", param=param, param_hint="crs"
)
return retval
def transform_handler(ctx, param, value):
"""Get transform value from the command line."""
retval = None
if value is not None:
try:
value = json.loads(value)
retval = guard_transform(value)
except Exception:
raise click.BadParameter(
f"{value} is not recognized as a transformarray.",
param=param,
param_hint="transform",
)
return retval
@click.command(short_help="Create an empty or filled dataset.")
@options.file_out_arg
@options.format_opt
@options.dtype_opt
@click.option("--count", "-n", type=int, help="Number of raster bands.")
@click.option("--height", "-h", type=int, help="Raster height, or number of rows.")
@click.option("--width", "-w", type=int, help="Raster width, or number of columns.")
@options.nodata_opt
@click.option(
"--crs", callback=crs_handler, default=None, help="Coordinate reference system."
)
@click.option(
"--transform",
callback=transform_handler,
help="Affine transform matrix. Overrides any given bounds option.",
)
@options.bounds_opt
@options.overwrite_opt
@options.creation_options
@click.pass_context
def create(
ctx,
output,
driver,
dtype,
count,
height,
width,
nodata,
crs,
transform,
bounds,
overwrite,
creation_options,
):
"""Create an empty dataset.
The fundamental, required parameters are: format driver name, data
type, count of bands, height and width in pixels. Long and short
options are provided for each of these. Coordinate reference system
and affine transformation matrix are not strictly required and have
long options only. All other format specific creation outputs must
be specified using the --co option.
Simple north-up, non-rotated georeferencing can be set by using the
--bounds option. The --transform option will assign an arbitrarily
rotated affine transformation matrix to the dataset. Ground control
points, rational polynomial coefficients, and geolocation matrices
are not supported.
The pixel values of an empty dataset are format specific. "Smart"
formats like GTiff use 0 or the nodata value if provided.
Example:
\b
$ rio create new.tif -f GTiff -t uint8 -n 3 -h 512 -w 512 \\
> --co tiled=true --co blockxsize=256 --co blockysize=256
The command above produces a 3-band GeoTIFF with 256 x 256 internal
tiling.
"""
# Preventing rio create from overwriting local and remote files,
# objects, and datasets is complicated.
if os.path.exists(output):
if not overwrite:
raise FileOverwriteError(
"File exists and won't be overwritten without use of the '--overwrite' option."
)
else: # Check remote or other non-file output.
try:
with rasterio.open(output) as dataset:
# Dataset exists. May or may not be overwritten.
if not overwrite:
raise FileOverwriteError(
"Dataset exists and won't be overwritten without use of the '--overwrite' option."
)
except RasterioIOError as exc:
# TODO: raise a different exception from rasterio.open() in
# this case?
if "No such file or directory" in str(exc):
pass # Good, output does not exist. Continue with no error.
else:
# Remote output exists, but is not a rasterio dataset.
if not overwrite:
raise FileOverwriteError(
"Object exists and won't be overwritten without use of the '--overwrite' option."
)
# Prepare the dataset's georeferencing.
geo_transform = None
if bounds:
left, bottom, right, top = bounds
sx = (right - left) / width
sy = (bottom - top) / height
geo_transform = Affine.translation(left, top) * Affine.scale(sx, sy)
if transform:
if geo_transform is not None:
click.echo(
"--transform value is overriding --bounds value. "
"Use only one of these options to avoid this warning.",
err=True,
)
geo_transform = transform
profile = dict(
driver=driver,
dtype=dtype,
count=count,
height=height,
width=width,
nodata=nodata,
crs=crs,
transform=geo_transform,
**creation_options,
)
with ctx.obj["env"], rasterio.open(output, "w", **profile) as dataset:
pass

View File

@ -264,10 +264,13 @@ bidx_magic_opt = click.option(
multiple=True,
help="Indexes of input file bands.")
# TODO: may be better suited to cligj
# TODO: may be better suited to cligj?
bounds_opt = click.option(
'--bounds', default=None, callback=bounds_handler,
help='Bounds: "left bottom right top" or "[left, bottom, right, top]".')
"--bounds",
default=None,
callback=bounds_handler,
help="Bounds: 'left bottom right top' or '[left, bottom, right, top]'.",
)
dimensions_opt = click.option(
'--dimensions',
@ -370,5 +373,5 @@ sequence_opt = click.option(
"feature collection object.")
format_opt = click.option(
'-f', '--format', '--driver', 'driver',
help="Output format driver")
"-f", "--format", "--driver", "driver", help="Output format driver."
)

View File

@ -33,6 +33,7 @@ rasterio.rio_commands =
calc = rasterio.rio.calc:calc
clip = rasterio.rio.clip:clip
convert = rasterio.rio.convert:convert
create = rasterio.rio.create:create
edit-info = rasterio.rio.edit_info:edit
env = rasterio.rio.env:env
gcps = rasterio.rio.gcps:gcps

490
tests/test_rio_create.py Normal file
View File

@ -0,0 +1,490 @@
"""Tests of rio create."""
import rasterio
from rasterio.crs import CRS
from rasterio.io import MemoryFile
from rasterio.rio.main import main_group
def test_create_bad_transform(runner):
"""Raise BadParameter when transform is invalid."""
result = runner.invoke(
main_group,
[
"create",
"--transform",
"lol",
"test.tif",
],
)
assert result.exit_code == 2
def test_create_bad_crs(runner):
"""Raise BadParameter when CRS is invalid."""
result = runner.invoke(
main_group,
[
"create",
"--crs",
"lol",
"test.tif",
],
)
assert result.exit_code == 2
def test_create_bad_nodata(runner):
"""Raise BadParameter when nodata is invalid."""
result = runner.invoke(
main_group,
[
"create",
"--nodata",
"lol",
"test.tif",
],
)
assert result.exit_code == 2
def test_create_empty(tmp_path, runner):
"""Create a new empty tif."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"--format",
"GTiff",
"--dtype",
"uint8",
"--count",
"3",
"--height",
"512",
"--width",
"256",
"--crs",
"EPSG:32618",
"--transform",
"[300.0, 0.0, 101985.0, 0.0, -300.0, 2826915.0]",
outfile,
],
)
assert result.exit_code == 0
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.driver == "GTiff"
assert dataset.crs == CRS.from_epsg(32618)
assert dataset.res == (300.0, 300.0)
assert dataset.transform.xoff == 101985.0
assert dataset.transform.yoff == 2826915.0
assert (dataset.read() == 0).all()
def test_create_bounds(tmp_path, runner):
"""Create a new empty tif with a bounding box."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"--format",
"GTiff",
"--dtype",
"uint8",
"--count",
"3",
"--height",
"512",
"--width",
"256",
"--crs",
"EPSG:32618",
"--bounds",
",".join(
(
str(x)
for x in [
101985.0,
2826915.0 - 512 * 300.0,
101985.0 + 256 * 300.0,
2826915.0,
]
)
),
outfile,
],
)
assert result.exit_code == 0
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.driver == "GTiff"
assert dataset.crs == CRS.from_epsg(32618)
assert dataset.res == (300.0, 300.0)
assert dataset.transform.xoff == 101985.0
assert dataset.transform.yoff == 2826915.0
assert (dataset.read() == 0).all()
def test_create_override_warning(tmp_path, runner):
"""Warn if both --bounds and --transform are used."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"--format",
"GTiff",
"--dtype",
"uint8",
"--count",
"3",
"--height",
"512",
"--width",
"256",
"--crs",
"EPSG:32618",
"--transform",
"[300.0, 0.0, 101985.0, 0.0, -300.0, 2826915.0]",
"--bounds",
"0,0,1,1",
outfile,
],
)
assert result.exit_code == 0
assert "Use only one" in result.output
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.driver == "GTiff"
assert dataset.crs == CRS.from_epsg(32618)
assert dataset.res == (300.0, 300.0)
assert dataset.transform.xoff == 101985.0
assert dataset.transform.yoff == 2826915.0
assert (dataset.read() == 0).all()
def test_create_bounds(tmp_path, runner):
"""Create a new empty tif with a bounding box."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"--format",
"GTiff",
"--dtype",
"uint8",
"--count",
"3",
"--height",
"512",
"--width",
"256",
"--crs",
"EPSG:32618",
"--bounds",
",".join(
(
str(x)
for x in [
101985.0,
2826915.0 - 512 * 300.0,
101985.0 + 256 * 300.0,
2826915.0,
]
)
),
outfile,
],
)
assert result.exit_code == 0
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.driver == "GTiff"
assert dataset.crs == CRS.from_epsg(32618)
assert dataset.res == (300.0, 300.0)
assert dataset.transform.xoff == 101985.0
assert dataset.transform.yoff == 2826915.0
assert (dataset.read() == 0).all()
def test_create_short_opts(tmp_path, runner):
"""Create a new empty tif using short options."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
outfile,
],
)
assert result.exit_code == 0
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.driver == "GTiff"
assert (dataset.read() == 0).all()
def test_create_nodata(tmp_path, runner):
"""Create a new tif with no valid data."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
"--nodata",
"255",
outfile,
],
)
assert result.exit_code == 0
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.nodatavals == (255, 255, 255)
assert dataset.driver == "GTiff"
raster = dataset.read(masked=True)
assert (raster.data == 255).all()
assert raster.mask.all()
def test_create_creation_opts(tmp_path, runner):
"""Create a new tif with creation/opening options."""
outfile = str(tmp_path.joinpath("out.tif"))
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
"--co",
"tiled=true",
"--co",
"blockxsize=128",
"--co",
"blockysize=256",
outfile,
],
)
assert result.exit_code == 0
with rasterio.open(outfile) as dataset:
assert dataset.shape == (512, 256)
assert dataset.count == 3
assert dataset.dtypes == ("uint8", "uint8", "uint8")
assert dataset.driver == "GTiff"
assert all(((256, 128) == hw for hw in dataset.block_shapes))
def test_create_no_overwrite(tmp_path, runner):
"""Don't allow overwrite of existing file without option."""
outpath = tmp_path.joinpath("out.tif")
outpath.touch()
outfile = str(outpath)
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
outfile,
],
)
assert result.exit_code == 1
assert "File exists and won't be overwritten" in result.output
def test_create_overwrite(tmp_path, runner):
"""Allow overwrite of existing file with option."""
outpath = tmp_path.joinpath("out.tif")
outpath.touch()
outfile = str(outpath)
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
"--overwrite",
outfile,
],
)
assert result.exit_code == 0
def test_create_no_overwrite_nonfile(runner):
"""Don't allow overwrite of existing non-file without option."""
with MemoryFile(bytes(bytearray(100000))) as memfile:
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
memfile.name,
],
)
assert result.exit_code == 1
assert "Object exists and won't be overwritten" in result.output
def test_create_overwrite_nonfile(runner):
"""Allow overwrite of existing non-file with option."""
with MemoryFile(bytes(bytearray(100000))) as memfile:
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"1",
"-h",
"16",
"-w",
"16",
"--overwrite",
memfile.name,
],
)
assert result.exit_code == 0
with rasterio.open(memfile.name) as dataset:
assert dataset.count == 1
assert dataset.height == 16
assert dataset.width == 16
def test_create_no_overwrite_nonfile_2(path_rgb_byte_tif, runner):
"""Don't allow overwrite of existing non-file dataset without option."""
with open(path_rgb_byte_tif, "rb") as dataset:
data = dataset.read()
with MemoryFile(data) as memfile:
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"3",
"-h",
"512",
"-w",
"256",
memfile.name,
],
)
assert result.exit_code == 1
assert "Dataset exists and won't be overwritten" in result.output
assert memfile.read(1024) == data[:1024]
def test_create_overwrite_nonfile_2(path_rgb_byte_tif, runner):
"""Allow overwrite of existing non-file dataset with option."""
with open(path_rgb_byte_tif, "rb") as dataset:
data = dataset.read()
with MemoryFile(data) as memfile:
result = runner.invoke(
main_group,
[
"create",
"-f",
"GTiff",
"-t",
"uint8",
"-n",
"1",
"-h",
"512",
"-w",
"256",
"--overwrite",
memfile.name,
],
)
assert result.exit_code == 0
with rasterio.open(memfile.name) as dataset:
assert dataset.count == 1
assert dataset.height == 512
assert dataset.width == 256

View File

@ -19,16 +19,14 @@ def bbox(*args):
def test_shapes(runner, pixelated_image_file):
with pytest.warns(None):
result = runner.invoke(main_group, ['shapes', '--collection', pixelated_image_file])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert np.allclose(
json.loads(result.output)['features'][0]['geometry']['coordinates'],
[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]])
result = runner.invoke(main_group, ["shapes", "--collection", pixelated_image_file])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert np.allclose(
json.loads(result.output)["features"][0]["geometry"]["coordinates"],
[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
)
def test_shapes_invalid_bidx(runner, pixelated_image_file):
@ -44,15 +42,14 @@ def test_shapes_sequence(runner, pixelated_image_file):
--sequence option should produce 4 features in series rather than
inside a feature collection.
"""
with pytest.warns(None):
result = runner.invoke(
main_group, ["shapes", "--collection", pixelated_image_file, "--sequence"]
)
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--sequence'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 0
assert result.output.count('"Feature"') == 4
assert result.output.count('\n') == 4
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 0
assert result.output.count('"Feature"') == 4
assert result.output.count("\n") == 4
def test_shapes_sequence_rs(runner, pixelated_image_file):
@ -88,29 +85,27 @@ def test_shapes_indent(runner, pixelated_image_file):
"""
--indent option should produce lots of newlines and contiguous spaces
"""
with pytest.warns(None):
result = runner.invoke(
main_group, ["shapes", "--collection", pixelated_image_file, "--indent", 2]
)
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--indent', 2])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert result.output.count("\n") > 100
assert result.output.count(" ") > 100
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert result.output.count("\n") > 100
assert result.output.count(" ") > 100
def test_shapes_compact(runner, pixelated_image_file):
with pytest.warns(None):
result = runner.invoke(
main_group, ["shapes", "--collection", pixelated_image_file, "--compact"]
)
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--compact'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert result.output.count(', ') == 0
assert result.output.count(': ') == 0
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert result.output.count(", ") == 0
assert result.output.count(": ") == 0
def test_shapes_sampling(runner, pixelated_image_file):
@ -147,13 +142,13 @@ def test_shapes_mask(runner, pixelated_image, pixelated_image_file):
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
with pytest.warns(None):
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--mask'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 1
assert shape(json.loads(result.output)["features"][0]["geometry"]).area == 31.0
result = runner.invoke(
main_group, ["shapes", "--collection", pixelated_image_file, "--mask"]
)
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 1
assert shape(json.loads(result.output)["features"][0]["geometry"]).area == 31.0
def test_shapes_mask_sampling(runner, pixelated_image, pixelated_image_file):
@ -170,16 +165,15 @@ def test_shapes_mask_sampling(runner, pixelated_image, pixelated_image_file):
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
with pytest.warns(None):
result = runner.invoke(
main_group,
["shapes", "--collection", pixelated_image_file, "--mask", "--sampling", 5],
)
result = runner.invoke(
main_group,
['shapes', '--collection', pixelated_image_file, '--mask', '--sampling', 5])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 1
assert shape(json.loads(result.output)["features"][0]["geometry"]).area == 25.0
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 1
assert shape(json.loads(result.output)["features"][0]["geometry"]).area == 25.0
def test_shapes_band1_as_mask(runner, pixelated_image, pixelated_image_file):
@ -195,12 +189,20 @@ def test_shapes_band1_as_mask(runner, pixelated_image, pixelated_image_file):
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
with pytest.warns(None):
result = runner.invoke(
main_group,
['shapes', '--collection', pixelated_image_file, '--band', '--bidx', '1', '--as-mask'])
result = runner.invoke(
main_group,
[
"shapes",
"--collection",
pixelated_image_file,
"--band",
"--bidx",
"1",
"--as-mask",
],
)
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 3
assert shape(json.loads(result.output)["features"][0]["geometry"]).area == 1.0
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 3
assert shape(json.loads(result.output)["features"][0]["geometry"]).area == 1.0

View File

@ -36,8 +36,5 @@ def test_no_notgeoref_warning(transform, gcps, rpcs):
if rpcs:
src.rpcs = rpcs
with pytest.warns(None) as record:
with mem.open() as dst:
pass
assert len(record) == 0
with mem.open() as dst:
pass