"""Tests for ``$ rio shapes``."""
import json
import re
import numpy as np
import pytest
import rasterio
from rasterio.rio.main import main_group
DEFAULT_SHAPE = (10, 10)
def bbox(*args):
return ' '.join([str(x) for x in args])
def test_shapes(runner, pixelated_image_file):
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):
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--bidx', 4])
assert result.exit_code == 1
def test_shapes_sequence(runner, pixelated_image_file):
"""
--sequence option should produce 4 features in series rather than
inside a feature collection.
"""
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
def test_shapes_sequence_rs(runner, pixelated_image_file):
""" --rs option should use the feature separator character. """
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--sequence', '--rs'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 0
assert result.output.count('"Feature"') == 4
assert result.output.count("\u001e") == 4
def test_shapes_with_nodata(runner, pixelated_image, pixelated_image_file):
"""
An area of nodata should also be represented with a shape when using
--with-nodata option
"""
pixelated_image[0:2, 8:10] = 255
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--with-nodata'])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 5
def test_shapes_indent(runner, pixelated_image_file):
"""
--indent option should produce lots of newlines and contiguous spaces
"""
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
def test_shapes_compact(runner, pixelated_image_file):
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
def test_shapes_sampling(runner, pixelated_image_file):
""" --sampling option should remove the single pixel features """
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--sampling', 2])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 2
def test_shapes_precision(runner, pixelated_image_file):
""" Output numbers should have no more than 1 decimal place """
result = runner.invoke(
main_group, ['shapes', '--collection', pixelated_image_file, '--precision', 1])
assert result.exit_code == 0
assert result.output.count('"FeatureCollection"') == 1
assert result.output.count('"Feature"') == 4
assert re.search(r'\s\d*\.\d{2,}', result.output) is None
def test_shapes_mask(runner, pixelated_image, pixelated_image_file):
""" --mask should extract the nodata area of the image """
shapely = pytest.importorskip("shapely", reason="Test requires shapely.")
from shapely.geometry import shape
pixelated_image[0:5, 0:10] = 255
pixelated_image[0:10, 0:3] = 255
pixelated_image[8:10, 8:10] = 255
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
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):
"""using --sampling with the mask should snap coordinates to the nearest
factor of 5
"""
shapely = pytest.importorskip("shapely", reason="Test requires shapely.")
from shapely.geometry import shape
pixelated_image[0:5, 0:10] = 255
pixelated_image[0:10, 0:3] = 255
pixelated_image[8:10, 8:10] = 255
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
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
def test_shapes_band1_as_mask(runner, pixelated_image, pixelated_image_file):
"""
When using --as-mask option, pixel value should not matter, only depends
on pixels being contiguous.
"""
shapely = pytest.importorskip("shapely", reason="Test requires shapely.")
from shapely.geometry import shape
pixelated_image[2:3, 2:3] = 4
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
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