Repository: rasterio/rasterio

Issue:
Conflicting rio bounds optional command line arguments for GeoJSON output
<!--

WELCOME ABOARD!

Hi and welcome to the Rasterio project. We appreciate bug reports, questions
about documentation, and suggestions for new features. This issue template
isn't intended to ward you off; only to intercept and redirect some particular
categories of reports, and to collect a few important facts that issue reporters
often omit.

The primary forum for questions about installation and usage of Rasterio is 
https://rasterio.groups.io/g/main. The authors and other users will answer 
questions when they have expertise to share and time to explain. Please take the
time to craft a clear question and be patient about responses. Please do not
bring these questions to Rasterio's issue tracker, which we want to reserve for
bug reports and other actionable issues.

Questions about development of Rasterio, brainstorming, requests for comment,
and not-yet-actionable proposals are welcome in the project's developers 
discussion group https://rasterio.groups.io/g/dev. Issues opened in Rasterio's
GitHub repo which haven't been socialized there may be perfunctorily closed.

Please note: Rasterio contains extension modules and is thus susceptible to
C library compatibility issues. If you are reporting an installation or module
import issue, please note that this project only accepts reports about problems
with packages downloaded from the Python Package Index. Conda users should take
issues to one of the following trackers:

- https://github.com/ContinuumIO/anaconda-issues/issues
- https://github.com/conda-forge/rasterio-feedstock

Also please note: we are currently working on 1.0 and pre-releases. Bugs found
in version 0.36 will not be fixed except in a 1.0 alpha or beta release. In 
some cases, 0.36 bugs have already been fixed in recent pre-releases.

You think you've found a bug? We believe you!
-->

## Expected behavior and actual behavior.

Initially discussed: https://rasterio.groups.io/g/main/topic/35000585#344

When using the command `rio bounds` there is conflicting behaviour of optional command line arguments `--sequence/--collection` and `--collection`. 

## Steps to reproduce the problem.

```
$ rio bounds --collection /path/to/a.tif /path/to/b.tif
{"bbox": [-117.43992919012538, 32.44224727533216, -115.91213159002874, 33.755623708770855], "features": [{"bbox": [-117.43992919012538, 32.44224727533216, -115.91213159002874, 33.755623708770855], "geometry": {"coordinates": [[[-117.43992919012538, 32.44224727533216], [-115.91213159002874, 32.44224727533216], [-115.91213159002874, 33.755623708770855], [-117.43992919012538, 33.755623708770855], [-117.43992919012538, 32.44224727533216]]], "type": "Polygon"}, "properties": {"filename": "imagery_HH.tif", "id": "0", "title": "imagery_HH.tif"}, "type": "Feature"}], "type": "FeatureCollection"}
{"bbox": [-117.43992919012538, 32.44224727533216, -115.91213159002874, 33.755623708770855], "features": [{"bbox": [-117.43992919012538, 32.44224727533216, -115.91213159002874, 33.755623708770855], "geometry": {"coordinates": [[[-117.43992919012538, 32.44224727533216], [-115.91213159002874, 32.44224727533216], [-115.91213159002874, 33.755623708770855], [-117.43992919012538, 33.755623708770855], [-117.43992919012538, 32.44224727533216]]], "type": "Polygon"}, "properties": {"filename": "imagery_HH.tif", "id": "1", "title": "imagery_HH.tif"}, "type": "Feature"}], "type": "FeatureCollection"}
```

## Operating system

Tested on Windows 10 1709 and CentOS Linux release 7.7.1908

## Rasterio version and provenance
rasterio==1.1.0
tested with both gdal==3.0.1 and gdal==2.2.4
rasterio and gdal both installed from conda




Discussion:
Thank you for the report!
from playing around with things initially, it seems that changing the `sequence_opt` to `--sequence/--no-sequence` to mirror the `sequence_opt` in https://github.com/mapbox/cligj/blob/1815692d99abfb4bc4b2d0411f67fa568f112c05/cligj/__init__.py#L97 resolves the namespace collision. I was able to then do 

```
rio bounds --no-sequence --collection /path/to/a.tif /path/to.b.tif
```

and output is indeed a single geojson feature collection

Resolving patch:
diff --git a/rasterio/rio/bounds.py b/rasterio/rio/bounds.py
--- a/rasterio/rio/bounds.py
+++ b/rasterio/rio/bounds.py
@@ -33,8 +33,7 @@
     help="Output in specified coordinates.")
 @options.sequence_opt
 @use_rs_opt
-@geojson_type_collection_opt(True)
-@geojson_type_feature_opt(False)
+@geojson_type_feature_opt(True)
 @geojson_type_bbox_opt(False)
 @click.pass_context
 def bounds(ctx, input, precision, indent, compact, projection, dst_crs,
diff --git a/rasterio/rio/helpers.py b/rasterio/rio/helpers.py
--- a/rasterio/rio/helpers.py
+++ b/rasterio/rio/helpers.py
@@ -38,25 +38,18 @@ def write_features(
             bbox = (min(xs), min(ys), max(xs), max(ys))
             if use_rs:
                 fobj.write(u'\u001e')
-            if geojson_type == 'feature':
-                fobj.write(json.dumps(feat, **dump_kwds))
-            elif geojson_type == 'bbox':
+            if geojson_type == 'bbox':
                 fobj.write(json.dumps(bbox, **dump_kwds))
             else:
-                fobj.write(
-                    json.dumps({
-                        'type': 'FeatureCollection',
-                        'bbox': bbox,
-                        'features': [feat]}, **dump_kwds))
+                fobj.write(json.dumps(feat, **dump_kwds))
             fobj.write('\n')
+
     # Aggregate all features into a single object expressed as
     # bbox or collection.
     else:
         features = list(collection())
         if geojson_type == 'bbox':
             fobj.write(json.dumps(collection.bbox, **dump_kwds))
-        elif geojson_type == 'feature':
-            fobj.write(json.dumps(features[0], **dump_kwds))
         else:
             fobj.write(json.dumps({
                 'bbox': collection.bbox,


Test patch:
diff --git a/tests/test_rio_bounds.py b/tests/test_rio_bounds.py
new file mode 100644
--- /dev/null
+++ b/tests/test_rio_bounds.py
@@ -0,0 +1,38 @@
+import pytest
+
+import rasterio
+from rasterio.rio.main import main_group
+
+
+def test_bounds_sequence_single(runner, basic_image_file):
+    """
+    --sequence option should produce a feature collection for a single image.
+    """
+    result = runner.invoke(main_group, ["bounds", "--sequence", basic_image_file])
+
+    assert result.output.count('"FeatureCollection"') == 0
+    assert result.output.count('"Feature"') == 1
+
+
+def tests_bounds_sequence_multiple(runner, basic_image_file):
+    """
+    --sequence option should produce a feature collection for each image passed as argument.
+    """
+    result = runner.invoke(
+        main_group, ["bounds", "--sequence", basic_image_file, basic_image_file]
+    )
+
+    assert result.output.count('"FeatureCollection"') == 0
+    assert result.output.count('"Feature"') == 2
+
+
+def test_bounds_no_sequence_multiple(runner, basic_image_file):
+    """
+    --no-sequence option should produce a single feature collection
+    """
+    result = runner.invoke(
+        main_group, ["bounds", "--collection", basic_image_file, basic_image_file]
+    )
+
+    assert result.output.count('"FeatureCollection"') == 1
+    assert result.output.count('"Feature"') == 2
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
--- a/tests/test_rio_info.py
+++ b/tests/test_rio_info.py
@@ -266,7 +266,7 @@ def test_bounds_defaults():
         'tests/data/RGB.byte.tif'
     ])
     assert result.exit_code == 0
-    assert 'FeatureCollection' in result.output
+    assert 'Feature' in result.output
 
 
 def test_bounds_err():