Repository: rasterio/rasterio

Issue:
Reprojection Example/Documentation
I was having some trouble following the [reprojection](https://github.com/mapbox/rasterio/blob/master/examples/reproject.py) example; the Affine parameters for `dst_transform` aren't referenced anywhere before they are applied:

https://github.com/mapbox/rasterio/blob/master/examples/reproject.py#L29



Discussion:

Resolving patch:
diff --git a/examples/reproject.py b/examples/reproject.py
--- a/examples/reproject.py
+++ b/examples/reproject.py
@@ -5,7 +5,7 @@
 
 import numpy
 import rasterio
-from rasterio import Affine as A
+from rasterio import transform
 from rasterio.warp import reproject, RESAMPLING
 
 tempdir = '/tmp'
@@ -17,17 +17,15 @@
     # with each pixel covering 15".
     rows, cols = src_shape = (512, 512)
     dpp = 1.0/240 # decimal degrees per pixel
-    # The following is equivalent to 
-    # A(dpp, 0, -cols*dpp/2, 0, -dpp, rows*dpp/2).
-    src_transform = A.translation(-cols*dpp/2, rows*dpp/2) * A.scale(dpp, -dpp)
+    west, south, east, north = -cols*dpp/2, -rows*dpp/2, cols*dpp/2, rows*dpp/2
+    src_transform = transform.from_bounds(west, south, east, north, cols, rows)
     src_crs = {'init': 'EPSG:4326'}
     source = numpy.ones(src_shape, numpy.uint8)*255
 
     # Prepare to reproject this rasters to a 1024 x 1024 dataset in
-    # Web Mercator (EPSG:3857) with origin at -8928592, 2999585.
+    # Web Mercator (EPSG:3857) with origin at -237481.5, 237536.4.
     dst_shape = (1024, 1024)
-    dst_transform = A.from_gdal(-237481.5, 425.0, 0.0, 237536.4, 0.0, -425.0)
-    dst_transform = dst_transform.to_gdal()
+    dst_transform = transform.from_origin(-237481.5, 237536.4, 425.0, 425.0)
     dst_crs = {'init': 'EPSG:3857'}
     destination = numpy.zeros(dst_shape, numpy.uint8)
 
@@ -59,4 +57,3 @@
         dst.write_band(1, destination)
 
 info = subprocess.call(['open', tiffname])
-
diff --git a/rasterio/transform.py b/rasterio/transform.py
--- a/rasterio/transform.py
+++ b/rasterio/transform.py
@@ -23,3 +23,18 @@ def guard_transform(transform):
         else:
             transform = Affine(*transform)
     return transform
+
+
+def from_origin(west, north, xsize, ysize):
+    """Return an Affine transformation for a georeferenced raster given
+    the coordinates of its upper left corner `west`, `north` and pixel
+    sizes `xsize`, `ysize`."""
+    return Affine.translation(west, north) * Affine.scale(xsize, -ysize)
+
+
+def from_bounds(west, south, east, north, width, height):
+    """Return an Affine transformation for a georeferenced raster given
+    its bounds `west`, `south`, `east`, `north` and its `width` and
+    `height` in number of pixels."""
+    return Affine.translation(west, north) * Affine.scale(
+            (east - west)/width, (south - north)/height)


Test patch:
diff --git a/tests/test_transform.py b/tests/test_transform.py
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -1,4 +1,6 @@
 import rasterio
+from rasterio import transform
+
 
 def test_window():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
@@ -21,3 +23,18 @@ def test_window_transform():
                 ((-1, None), (-1, None))).c == src.bounds.left - src.res[0]
         assert src.window_transform(
                 ((-1, None), (-1, None))).f == src.bounds.top + src.res[1]
+
+
+def test_from_origin():
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        w, n = src.ul(0, 0)
+        xs, ys = src.res
+        tr = transform.from_origin(w, n, xs, ys)
+        assert [round(v, 7) for v in tr] == [round(v, 7) for v in src.affine]
+
+
+def test_from_bounds():
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        w, s, e, n = src.bounds
+        tr = transform.from_bounds(w, s, e, n, src.width, src.height)
+        assert [round(v, 7) for v in tr] == [round(v, 7) for v in src.affine]