"""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): 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]]]) 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 # Underlying exception message trapped by shapes 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']) 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(u'\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 """ with pytest.warns(None): 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') == 231 assert result.output.count(' ') == 180 def test_shapes_compact(runner, pixelated_image_file): with pytest.warns(None): 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 """ 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) 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 np.allclose( json.loads(result.output)['features'][0]['geometry']['coordinates'], [[[3, 5], [3, 10], [8, 10], [8, 8], [9, 8], [10, 8], [10, 5], [3, 5]]]) 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 """ 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) with pytest.warns(None): 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 np.allclose( json.loads(result.output)['features'][0]['geometry']['coordinates'], [[[5, 5], [5, 10], [10, 10], [10, 5], [5, 5]]]) 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. """ pixelated_image[2:3, 2:3] = 4 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']) assert result.exit_code == 0 assert result.output.count('"FeatureCollection"') == 1 assert result.output.count('"Feature"') == 3 assert np.allclose( json.loads(result.output)['features'][1]['geometry']['coordinates'], [[[2, 2], [2, 5], [5, 5], [5, 2], [2, 2]]])