Repository: rasterio/rasterio

Issue:
Add pixel alignment for rio clip
`gdal_translate` [produces](https://lists.osgeo.org/pipermail/gdal-dev/2016-July/044911.html) an output whose cell alignment and content matches the source dataset if `-projwin` option is passed. For now there is no way to achieve the same result using rasterio. `rio clip` doesn't perform any alignment. `rio warp` has `--target-aligned-pixels` but it cannot be combined with `--src-bounds`:
```
Error: Invalid value: --target-aligned-pixels cannot be used with --src-bounds or --dst-bounds
```


Discussion:

Resolving patch:
diff --git a/rasterio/rio/clip.py b/rasterio/rio/clip.py
--- a/rasterio/rio/clip.py
+++ b/rasterio/rio/clip.py
@@ -135,9 +135,9 @@ def clip(
                     Window(0, 0, src.width, src.height)
                 )
 
-            # Get the window with integer height
-            # and width that contains the bounds window.
+            # Align window, as in gdal_translate.
             out_window = bounds_window.round_lengths()
+            out_window = out_window.round_offsets()
 
             height = int(out_window.height)
             width = int(out_window.width)
diff --git a/rasterio/rio/warp.py b/rasterio/rio/warp.py
--- a/rasterio/rio/warp.py
+++ b/rasterio/rio/warp.py
@@ -48,20 +48,38 @@
     '--bounds', '--dst-bounds', 'dst_bounds', nargs=4, type=float, default=None,
     help="Determine output extent from destination bounds: left bottom right top")
 @options.resolution_opt
-@click.option('--resampling',
-              type=click.Choice([r.name for r in SUPPORTED_RESAMPLING]),
-              default='nearest', help="Resampling method.",
-              show_default=True)
-@click.option('--src-nodata', default=None, show_default=True,
-              type=float, help="Manually override source nodata")
-@click.option('--dst-nodata', default=None, show_default=True,
-              type=float, help="Manually override destination nodata")
-@click.option('--threads', type=int, default=1,
-              help='Number of processing threads.')
-@click.option('--check-invert-proj/--no-check-invert-proj', default=True,
-              help='Constrain output to valid coordinate region in dst-crs')
-@click.option('--target-aligned-pixels/--no-target-aligned-pixels', default=False,
-              help='align the output bounds based on the resolution')
+@click.option(
+    "--resampling",
+    type=click.Choice([r.name for r in SUPPORTED_RESAMPLING]),
+    default="nearest",
+    help="Resampling method.",
+    show_default=True,
+)
+@click.option(
+    "--src-nodata",
+    default=None,
+    show_default=True,
+    type=float,
+    help="Manually override source nodata",
+)
+@click.option(
+    "--dst-nodata",
+    default=None,
+    show_default=True,
+    type=float,
+    help="Manually override destination nodata",
+)
+@click.option("--threads", type=int, default=1, help="Number of processing threads.")
+@click.option(
+    "--check-invert-proj/--no-check-invert-proj",
+    default=True,
+    help="Constrain output to valid coordinate region in dst-crs",
+)
+@click.option(
+    "--target-aligned-pixels/--no-target-aligned-pixels",
+    default=False,
+    help="align the output bounds based on the resolution",
+)
 @options.overwrite_opt
 @options.creation_options
 @click.option(
@@ -155,10 +173,6 @@ def warp(
         if not res:
             raise click.BadParameter(
                 '--target-aligned-pixels requires a specified resolution')
-        if src_bounds or dst_bounds:
-            raise click.BadParameter(
-                '--target-aligned-pixels cannot be used with '
-                '--src-bounds or --dst-bounds')
 
     # Check invalid parameter combinations
     if like:


Test patch: