We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rasterio/rio/helpers.py`
Content:
```
1 """
2 Helper objects used by multiple CLI commands.
3 """
4 
5 import json
6 import os
7 
8 from rasterio.errors import FileOverwriteError
9 
10 
11 def coords(obj):
12     """Yield all coordinate coordinate tuples from a geometry or feature.
13     From python-geojson package."""
14     if isinstance(obj, (tuple, list)):
15         coordinates = obj
16     elif 'geometry' in obj:
17         coordinates = obj['geometry']['coordinates']
18     else:
19         coordinates = obj.get('coordinates', obj)
20     for e in coordinates:
21         if isinstance(e, (float, int)):
22             yield tuple(coordinates)
23             break
24         else:
25             for f in coords(e):
26                 yield f
27 
28 
29 def write_features(
30         fobj, collection, sequence=False, geojson_type='feature', use_rs=False,
31         **dump_kwds):
32     """Read an iterator of (feat, bbox) pairs and write to file using
33     the selected modes."""
34     # Sequence of features expressed as bbox, feature, or collection.
35     if sequence:
36         for feat in collection():
37             xs, ys = zip(*coords(feat))
38             bbox = (min(xs), min(ys), max(xs), max(ys))
39             if use_rs:
40                 fobj.write(u'\u001e')
41             if geojson_type == 'feature':
42                 fobj.write(json.dumps(feat, **dump_kwds))
43             elif geojson_type == 'bbox':
44                 fobj.write(json.dumps(bbox, **dump_kwds))
45             else:
46                 fobj.write(
47                     json.dumps({
48                         'type': 'FeatureCollection',
49                         'bbox': bbox,
50                         'features': [feat]}, **dump_kwds))
51             fobj.write('\n')
52     # Aggregate all features into a single object expressed as
53     # bbox or collection.
54     else:
55         features = list(collection())
56         if geojson_type == 'bbox':
57             fobj.write(json.dumps(collection.bbox, **dump_kwds))
58         elif geojson_type == 'feature':
59             fobj.write(json.dumps(features[0], **dump_kwds))
60         else:
61             fobj.write(json.dumps({
62                 'bbox': collection.bbox,
63                 'type': 'FeatureCollection',
64                 'features': features},
65                 **dump_kwds))
66         fobj.write('\n')
67 
68 
69 def resolve_inout(input=None, output=None, files=None, overwrite=False):
70     """Resolves inputs and outputs from standard args and options.
71 
72     :param input: a single input filename, optional.
73     :param output: a single output filename, optional.
74     :param files: a sequence of filenames in which the last is the
75         output filename.
76     :param overwrite: whether to force overwriting the output
77         file, bool.
78     :return: the resolved output filename and input filenames as a
79         tuple of length 2.
80 
81     If provided, the :param:`output` file may be overwritten. An output
82     file extracted from :param:`files` will not be overwritten unless
83     :param:`overwrite` is `True`.
84     """
85     resolved_output = output or (files[-1] if files else None)
86     if not overwrite and resolved_output and os.path.exists(
87             resolved_output):
88         raise FileOverwriteError(
89             "file exists and won't be overwritten without use of the "
90             "`--overwrite` option.")
91     resolved_inputs = (
92         [input] if input else [] +
93         list(files[:-1 if not output else None]) if files else [])
94     return resolved_output, resolved_inputs
95 
96 
97 def to_lower(ctx, param, value):
98     """Click callback, converts values to lowercase."""
99     return value.lower()
100
```

Path: `rasterio/rio/bounds.py`
Content:
```
1 import logging
2 import os
3 
4 import click
5 from cligj import (
6     precision_opt, indent_opt, compact_opt, projection_geographic_opt,
7     projection_mercator_opt, projection_projected_opt,
8     use_rs_opt, geojson_type_feature_opt, geojson_type_bbox_opt,
9     geojson_type_collection_opt)
10 
11 from .helpers import write_features, to_lower
12 import rasterio
13 from rasterio.rio import options
14 from rasterio.warp import transform_bounds
15 
16 
17 logger = logging.getLogger(__name__)
18 
19 
20 # Bounds command.
21 @click.command(short_help="Write bounding boxes to stdout as GeoJSON.")
22 # One or more files, the bounds of each are a feature in the collection
23 # object or feature sequence.
24 @click.argument('INPUT', nargs=-1, type=click.Path(), required=True)
25 @precision_opt
26 @indent_opt
27 @compact_opt
28 @projection_geographic_opt
29 @projection_projected_opt
30 @projection_mercator_opt
31 @click.option(
32     '--dst-crs', default='', metavar="EPSG:NNNN", callback=to_lower,
33     help="Output in specified coordinates.")
34 @options.sequence_opt
35 @use_rs_opt
36 @geojson_type_collection_opt(True)
37 @geojson_type_feature_opt(False)
38 @geojson_type_bbox_opt(False)
39 @click.pass_context
40 def bounds(ctx, input, precision, indent, compact, projection, dst_crs,
41            sequence, use_rs, geojson_type):
42     """Write bounding boxes to stdout as GeoJSON for use with, e.g.,
43     geojsonio
44 
45       $ rio bounds *.tif | geojsonio
46 
47     If a destination crs is passed via dst_crs, it takes precedence over
48     the projection parameter.
49     """
50     import rasterio.warp
51     dump_kwds = {'sort_keys': True}
52     if indent:
53         dump_kwds['indent'] = indent
54     if compact:
55         dump_kwds['separators'] = (',', ':')
56     stdout = click.get_text_stream('stdout')
57 
58     # This is the generator for (feature, bbox) pairs.
59     class Collection(object):
60 
61         def __init__(self, env):
62             self._xs = []
63             self._ys = []
64             self.env = env
65 
66         @property
67         def bbox(self):
68             return min(self._xs), min(self._ys), max(self._xs), max(self._ys)
69 
70         def __call__(self):
71             for i, path in enumerate(input):
72                 with rasterio.open(path) as src:
73                     bounds = src.bounds
74                     if dst_crs:
75                         bbox = transform_bounds(src.crs,
76                                                 dst_crs, *bounds)
77                     elif projection == 'mercator':
78                         bbox = transform_bounds(src.crs,
79                                                 {'init': 'epsg:3857'}, *bounds)
80                     elif projection == 'geographic':
81                         bbox = transform_bounds(src.crs,
82                                                 {'init': 'epsg:4326'}, *bounds)
83                     else:
84                         bbox = bounds
85 
86                 if precision >= 0:
87                     bbox = [round(b, precision) for b in bbox]
88 
89                 yield {
90                     'type': 'Feature',
91                     'bbox': bbox,
92                     'geometry': {
93                         'type': 'Polygon',
94                         'coordinates': [[
95                             [bbox[0], bbox[1]],
96                             [bbox[2], bbox[1]],
97                             [bbox[2], bbox[3]],
98                             [bbox[0], bbox[3]],
99                             [bbox[0], bbox[1]]]]},
100                     'properties': {
101                         'id': str(i),
102                         'title': path,
103                         'filename': os.path.basename(path)}}
104 
105                 self._xs.extend(bbox[::2])
106                 self._ys.extend(bbox[1::2])
107 
108     try:
109         with ctx.obj['env'] as env:
110             write_features(
111                 stdout, Collection(env), sequence=sequence,
112                 geojson_type=geojson_type, use_rs=use_rs,
113                 **dump_kwds)
114 
115     except Exception:
116         logger.exception("Exception caught during processing")
117         raise click.Abort()
118
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
             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,

{"golden_diff": "diff --git a/rasterio/rio/bounds.py b/rasterio/rio/bounds.py\n--- a/rasterio/rio/bounds.py\n+++ b/rasterio/rio/bounds.py\n@@ -33,8 +33,7 @@\n     help=\"Output in specified coordinates.\")\n @options.sequence_opt\n @use_rs_opt\n-@geojson_type_collection_opt(True)\n-@geojson_type_feature_opt(False)\n+@geojson_type_feature_opt(True)\n @geojson_type_bbox_opt(False)\n @click.pass_context\n def bounds(ctx, input, precision, indent, compact, projection, dst_crs,\ndiff --git a/rasterio/rio/helpers.py b/rasterio/rio/helpers.py\n--- a/rasterio/rio/helpers.py\n+++ b/rasterio/rio/helpers.py\n@@ -38,25 +38,18 @@\n             bbox = (min(xs), min(ys), max(xs), max(ys))\n             if use_rs:\n                 fobj.write(u'\\u001e')\n-            if geojson_type == 'feature':\n-                fobj.write(json.dumps(feat, **dump_kwds))\n-            elif geojson_type == 'bbox':\n+            if geojson_type == 'bbox':\n                 fobj.write(json.dumps(bbox, **dump_kwds))\n             else:\n-                fobj.write(\n-                    json.dumps({\n-                        'type': 'FeatureCollection',\n-                        'bbox': bbox,\n-                        'features': [feat]}, **dump_kwds))\n+                fobj.write(json.dumps(feat, **dump_kwds))\n             fobj.write('\\n')\n+\n     # Aggregate all features into a single object expressed as\n     # bbox or collection.\n     else:\n         features = list(collection())\n         if geojson_type == 'bbox':\n             fobj.write(json.dumps(collection.bbox, **dump_kwds))\n-        elif geojson_type == 'feature':\n-            fobj.write(json.dumps(features[0], **dump_kwds))\n         else:\n             fobj.write(json.dumps({\n                 'bbox': collection.bbox,\n", "issue": "Conflicting rio bounds optional command line arguments for GeoJSON output\n<!--\r\n\r\nWELCOME ABOARD!\r\n\r\nHi and welcome to the Rasterio project. We appreciate bug reports, questions\r\nabout documentation, and suggestions for new features. This issue template\r\nisn't intended to ward you off; only to intercept and redirect some particular\r\ncategories of reports, and to collect a few important facts that issue reporters\r\noften omit.\r\n\r\nThe primary forum for questions about installation and usage of Rasterio is \r\nhttps://rasterio.groups.io/g/main. The authors and other users will answer \r\nquestions when they have expertise to share and time to explain. Please take the\r\ntime to craft a clear question and be patient about responses. Please do not\r\nbring these questions to Rasterio's issue tracker, which we want to reserve for\r\nbug reports and other actionable issues.\r\n\r\nQuestions about development of Rasterio, brainstorming, requests for comment,\r\nand not-yet-actionable proposals are welcome in the project's developers \r\ndiscussion group https://rasterio.groups.io/g/dev. Issues opened in Rasterio's\r\nGitHub repo which haven't been socialized there may be perfunctorily closed.\r\n\r\nPlease note: Rasterio contains extension modules and is thus susceptible to\r\nC library compatibility issues. If you are reporting an installation or module\r\nimport issue, please note that this project only accepts reports about problems\r\nwith packages downloaded from the Python Package Index. Conda users should take\r\nissues to one of the following trackers:\r\n\r\n- https://github.com/ContinuumIO/anaconda-issues/issues\r\n- https://github.com/conda-forge/rasterio-feedstock\r\n\r\nAlso please note: we are currently working on 1.0 and pre-releases. Bugs found\r\nin version 0.36 will not be fixed except in a 1.0 alpha or beta release. In \r\nsome cases, 0.36 bugs have already been fixed in recent pre-releases.\r\n\r\nYou think you've found a bug? We believe you!\r\n-->\r\n\r\n## Expected behavior and actual behavior.\r\n\r\nInitially discussed: https://rasterio.groups.io/g/main/topic/35000585#344\r\n\r\nWhen using the command `rio bounds` there is conflicting behaviour of optional command line arguments `--sequence/--collection` and `--collection`. \r\n\r\n## Steps to reproduce the problem.\r\n\r\n```\r\n$ rio bounds --collection /path/to/a.tif /path/to/b.tif\r\n{\"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\"}\r\n{\"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\"}\r\n```\r\n\r\n## Operating system\r\n\r\nTested on Windows 10 1709 and CentOS Linux release 7.7.1908\r\n\r\n## Rasterio version and provenance\r\nrasterio==1.1.0\r\ntested with both gdal==3.0.1 and gdal==2.2.4\r\nrasterio and gdal both installed from conda\r\n\r\n\n", "before_files": [{"content": "\"\"\"\nHelper objects used by multiple CLI commands.\n\"\"\"\n\nimport json\nimport os\n\nfrom rasterio.errors import FileOverwriteError\n\n\ndef coords(obj):\n    \"\"\"Yield all coordinate coordinate tuples from a geometry or feature.\n    From python-geojson package.\"\"\"\n    if isinstance(obj, (tuple, list)):\n        coordinates = obj\n    elif 'geometry' in obj:\n        coordinates = obj['geometry']['coordinates']\n    else:\n        coordinates = obj.get('coordinates', obj)\n    for e in coordinates:\n        if isinstance(e, (float, int)):\n            yield tuple(coordinates)\n            break\n        else:\n            for f in coords(e):\n                yield f\n\n\ndef write_features(\n        fobj, collection, sequence=False, geojson_type='feature', use_rs=False,\n        **dump_kwds):\n    \"\"\"Read an iterator of (feat, bbox) pairs and write to file using\n    the selected modes.\"\"\"\n    # Sequence of features expressed as bbox, feature, or collection.\n    if sequence:\n        for feat in collection():\n            xs, ys = zip(*coords(feat))\n            bbox = (min(xs), min(ys), max(xs), max(ys))\n            if use_rs:\n                fobj.write(u'\\u001e')\n            if geojson_type == 'feature':\n                fobj.write(json.dumps(feat, **dump_kwds))\n            elif geojson_type == 'bbox':\n                fobj.write(json.dumps(bbox, **dump_kwds))\n            else:\n                fobj.write(\n                    json.dumps({\n                        'type': 'FeatureCollection',\n                        'bbox': bbox,\n                        'features': [feat]}, **dump_kwds))\n            fobj.write('\\n')\n    # Aggregate all features into a single object expressed as\n    # bbox or collection.\n    else:\n        features = list(collection())\n        if geojson_type == 'bbox':\n            fobj.write(json.dumps(collection.bbox, **dump_kwds))\n        elif geojson_type == 'feature':\n            fobj.write(json.dumps(features[0], **dump_kwds))\n        else:\n            fobj.write(json.dumps({\n                'bbox': collection.bbox,\n                'type': 'FeatureCollection',\n                'features': features},\n                **dump_kwds))\n        fobj.write('\\n')\n\n\ndef resolve_inout(input=None, output=None, files=None, overwrite=False):\n    \"\"\"Resolves inputs and outputs from standard args and options.\n\n    :param input: a single input filename, optional.\n    :param output: a single output filename, optional.\n    :param files: a sequence of filenames in which the last is the\n        output filename.\n    :param overwrite: whether to force overwriting the output\n        file, bool.\n    :return: the resolved output filename and input filenames as a\n        tuple of length 2.\n\n    If provided, the :param:`output` file may be overwritten. An output\n    file extracted from :param:`files` will not be overwritten unless\n    :param:`overwrite` is `True`.\n    \"\"\"\n    resolved_output = output or (files[-1] if files else None)\n    if not overwrite and resolved_output and os.path.exists(\n            resolved_output):\n        raise FileOverwriteError(\n            \"file exists and won't be overwritten without use of the \"\n            \"`--overwrite` option.\")\n    resolved_inputs = (\n        [input] if input else [] +\n        list(files[:-1 if not output else None]) if files else [])\n    return resolved_output, resolved_inputs\n\n\ndef to_lower(ctx, param, value):\n    \"\"\"Click callback, converts values to lowercase.\"\"\"\n    return value.lower()\n", "path": "rasterio/rio/helpers.py"}, {"content": "import logging\nimport os\n\nimport click\nfrom cligj import (\n    precision_opt, indent_opt, compact_opt, projection_geographic_opt,\n    projection_mercator_opt, projection_projected_opt,\n    use_rs_opt, geojson_type_feature_opt, geojson_type_bbox_opt,\n    geojson_type_collection_opt)\n\nfrom .helpers import write_features, to_lower\nimport rasterio\nfrom rasterio.rio import options\nfrom rasterio.warp import transform_bounds\n\n\nlogger = logging.getLogger(__name__)\n\n\n# Bounds command.\n@click.command(short_help=\"Write bounding boxes to stdout as GeoJSON.\")\n# One or more files, the bounds of each are a feature in the collection\n# object or feature sequence.\n@click.argument('INPUT', nargs=-1, type=click.Path(), required=True)\n@precision_opt\n@indent_opt\n@compact_opt\n@projection_geographic_opt\n@projection_projected_opt\n@projection_mercator_opt\n@click.option(\n    '--dst-crs', default='', metavar=\"EPSG:NNNN\", callback=to_lower,\n    help=\"Output in specified coordinates.\")\n@options.sequence_opt\n@use_rs_opt\n@geojson_type_collection_opt(True)\n@geojson_type_feature_opt(False)\n@geojson_type_bbox_opt(False)\n@click.pass_context\ndef bounds(ctx, input, precision, indent, compact, projection, dst_crs,\n           sequence, use_rs, geojson_type):\n    \"\"\"Write bounding boxes to stdout as GeoJSON for use with, e.g.,\n    geojsonio\n\n      $ rio bounds *.tif | geojsonio\n\n    If a destination crs is passed via dst_crs, it takes precedence over\n    the projection parameter.\n    \"\"\"\n    import rasterio.warp\n    dump_kwds = {'sort_keys': True}\n    if indent:\n        dump_kwds['indent'] = indent\n    if compact:\n        dump_kwds['separators'] = (',', ':')\n    stdout = click.get_text_stream('stdout')\n\n    # This is the generator for (feature, bbox) pairs.\n    class Collection(object):\n\n        def __init__(self, env):\n            self._xs = []\n            self._ys = []\n            self.env = env\n\n        @property\n        def bbox(self):\n            return min(self._xs), min(self._ys), max(self._xs), max(self._ys)\n\n        def __call__(self):\n            for i, path in enumerate(input):\n                with rasterio.open(path) as src:\n                    bounds = src.bounds\n                    if dst_crs:\n                        bbox = transform_bounds(src.crs,\n                                                dst_crs, *bounds)\n                    elif projection == 'mercator':\n                        bbox = transform_bounds(src.crs,\n                                                {'init': 'epsg:3857'}, *bounds)\n                    elif projection == 'geographic':\n                        bbox = transform_bounds(src.crs,\n                                                {'init': 'epsg:4326'}, *bounds)\n                    else:\n                        bbox = bounds\n\n                if precision >= 0:\n                    bbox = [round(b, precision) for b in bbox]\n\n                yield {\n                    'type': 'Feature',\n                    'bbox': bbox,\n                    'geometry': {\n                        'type': 'Polygon',\n                        'coordinates': [[\n                            [bbox[0], bbox[1]],\n                            [bbox[2], bbox[1]],\n                            [bbox[2], bbox[3]],\n                            [bbox[0], bbox[3]],\n                            [bbox[0], bbox[1]]]]},\n                    'properties': {\n                        'id': str(i),\n                        'title': path,\n                        'filename': os.path.basename(path)}}\n\n                self._xs.extend(bbox[::2])\n                self._ys.extend(bbox[1::2])\n\n    try:\n        with ctx.obj['env'] as env:\n            write_features(\n                stdout, Collection(env), sequence=sequence,\n                geojson_type=geojson_type, use_rs=use_rs,\n                **dump_kwds)\n\n    except Exception:\n        logger.exception(\"Exception caught during processing\")\n        raise click.Abort()\n", "path": "rasterio/rio/bounds.py"}], "after_files": [{"content": "\"\"\"\nHelper objects used by multiple CLI commands.\n\"\"\"\n\nimport json\nimport os\n\nfrom rasterio.errors import FileOverwriteError\n\n\ndef coords(obj):\n    \"\"\"Yield all coordinate coordinate tuples from a geometry or feature.\n    From python-geojson package.\"\"\"\n    if isinstance(obj, (tuple, list)):\n        coordinates = obj\n    elif 'geometry' in obj:\n        coordinates = obj['geometry']['coordinates']\n    else:\n        coordinates = obj.get('coordinates', obj)\n    for e in coordinates:\n        if isinstance(e, (float, int)):\n            yield tuple(coordinates)\n            break\n        else:\n            for f in coords(e):\n                yield f\n\n\ndef write_features(\n        fobj, collection, sequence=False, geojson_type='feature', use_rs=False,\n        **dump_kwds):\n    \"\"\"Read an iterator of (feat, bbox) pairs and write to file using\n    the selected modes.\"\"\"\n    # Sequence of features expressed as bbox, feature, or collection.\n    if sequence:\n        for feat in collection():\n            xs, ys = zip(*coords(feat))\n            bbox = (min(xs), min(ys), max(xs), max(ys))\n            if use_rs:\n                fobj.write(u'\\u001e')\n            if geojson_type == 'bbox':\n                fobj.write(json.dumps(bbox, **dump_kwds))\n            else:\n                fobj.write(json.dumps(feat, **dump_kwds))\n            fobj.write('\\n')\n\n    # Aggregate all features into a single object expressed as\n    # bbox or collection.\n    else:\n        features = list(collection())\n        if geojson_type == 'bbox':\n            fobj.write(json.dumps(collection.bbox, **dump_kwds))\n        else:\n            fobj.write(json.dumps({\n                'bbox': collection.bbox,\n                'type': 'FeatureCollection',\n                'features': features},\n                **dump_kwds))\n        fobj.write('\\n')\n\n\ndef resolve_inout(input=None, output=None, files=None, overwrite=False):\n    \"\"\"Resolves inputs and outputs from standard args and options.\n\n    :param input: a single input filename, optional.\n    :param output: a single output filename, optional.\n    :param files: a sequence of filenames in which the last is the\n        output filename.\n    :param overwrite: whether to force overwriting the output\n        file, bool.\n    :return: the resolved output filename and input filenames as a\n        tuple of length 2.\n\n    If provided, the :param:`output` file may be overwritten. An output\n    file extracted from :param:`files` will not be overwritten unless\n    :param:`overwrite` is `True`.\n    \"\"\"\n    resolved_output = output or (files[-1] if files else None)\n    if not overwrite and resolved_output and os.path.exists(\n            resolved_output):\n        raise FileOverwriteError(\n            \"file exists and won't be overwritten without use of the \"\n            \"`--overwrite` option.\")\n    resolved_inputs = (\n        [input] if input else [] +\n        list(files[:-1 if not output else None]) if files else [])\n    return resolved_output, resolved_inputs\n\n\ndef to_lower(ctx, param, value):\n    \"\"\"Click callback, converts values to lowercase.\"\"\"\n    return value.lower()\n", "path": "rasterio/rio/helpers.py"}, {"content": "import logging\nimport os\n\nimport click\nfrom cligj import (\n    precision_opt, indent_opt, compact_opt, projection_geographic_opt,\n    projection_mercator_opt, projection_projected_opt,\n    use_rs_opt, geojson_type_feature_opt, geojson_type_bbox_opt,\n    geojson_type_collection_opt)\n\nfrom .helpers import write_features, to_lower\nimport rasterio\nfrom rasterio.rio import options\nfrom rasterio.warp import transform_bounds\n\n\nlogger = logging.getLogger(__name__)\n\n\n# Bounds command.\n@click.command(short_help=\"Write bounding boxes to stdout as GeoJSON.\")\n# One or more files, the bounds of each are a feature in the collection\n# object or feature sequence.\n@click.argument('INPUT', nargs=-1, type=click.Path(), required=True)\n@precision_opt\n@indent_opt\n@compact_opt\n@projection_geographic_opt\n@projection_projected_opt\n@projection_mercator_opt\n@click.option(\n    '--dst-crs', default='', metavar=\"EPSG:NNNN\", callback=to_lower,\n    help=\"Output in specified coordinates.\")\n@options.sequence_opt\n@use_rs_opt\n@geojson_type_feature_opt(True)\n@geojson_type_bbox_opt(False)\n@click.pass_context\ndef bounds(ctx, input, precision, indent, compact, projection, dst_crs,\n           sequence, use_rs, geojson_type):\n    \"\"\"Write bounding boxes to stdout as GeoJSON for use with, e.g.,\n    geojsonio\n\n      $ rio bounds *.tif | geojsonio\n\n    If a destination crs is passed via dst_crs, it takes precedence over\n    the projection parameter.\n    \"\"\"\n    import rasterio.warp\n    dump_kwds = {'sort_keys': True}\n    if indent:\n        dump_kwds['indent'] = indent\n    if compact:\n        dump_kwds['separators'] = (',', ':')\n    stdout = click.get_text_stream('stdout')\n\n    # This is the generator for (feature, bbox) pairs.\n    class Collection(object):\n\n        def __init__(self, env):\n            self._xs = []\n            self._ys = []\n            self.env = env\n\n        @property\n        def bbox(self):\n            return min(self._xs), min(self._ys), max(self._xs), max(self._ys)\n\n        def __call__(self):\n            for i, path in enumerate(input):\n                with rasterio.open(path) as src:\n                    bounds = src.bounds\n                    if dst_crs:\n                        bbox = transform_bounds(src.crs,\n                                                dst_crs, *bounds)\n                    elif projection == 'mercator':\n                        bbox = transform_bounds(src.crs,\n                                                {'init': 'epsg:3857'}, *bounds)\n                    elif projection == 'geographic':\n                        bbox = transform_bounds(src.crs,\n                                                {'init': 'epsg:4326'}, *bounds)\n                    else:\n                        bbox = bounds\n\n                if precision >= 0:\n                    bbox = [round(b, precision) for b in bbox]\n\n                yield {\n                    'type': 'Feature',\n                    'bbox': bbox,\n                    'geometry': {\n                        'type': 'Polygon',\n                        'coordinates': [[\n                            [bbox[0], bbox[1]],\n                            [bbox[2], bbox[1]],\n                            [bbox[2], bbox[3]],\n                            [bbox[0], bbox[3]],\n                            [bbox[0], bbox[1]]]]},\n                    'properties': {\n                        'id': str(i),\n                        'title': path,\n                        'filename': os.path.basename(path)}}\n\n                self._xs.extend(bbox[::2])\n                self._ys.extend(bbox[1::2])\n\n    try:\n        with ctx.obj['env'] as env:\n            write_features(\n                stdout, Collection(env), sequence=sequence,\n                geojson_type=geojson_type, use_rs=use_rs,\n                **dump_kwds)\n\n    except Exception:\n        logger.exception(\"Exception caught during processing\")\n        raise click.Abort()\n", "path": "rasterio/rio/bounds.py"}]}