Repository: geopandas/geopandas

Issue:
Increase flexibility of `to_crs` function
Right now, the CRS tools won't accept a proj4 string as a string -- it requires they be broken out into a dictionary. But behind the scenes is the pyproj library which parses proj4 strings quite well. 

I propose a small change to the `to_crs` implementation (PR #288) that would preserve current functionality (can still pass CRS as dictionaries) but also allow users to just pass proj4 strings. 



Discussion:

Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -350,6 +350,12 @@ def to_crs(self, crs=None, epsg=None, inplace=False):
         joining points are assumed to be lines in the current
         projection, not geodesics.  Objects crossing the dateline (or
         other projection boundary) will have undesirable behavior.
+
+        `to_crs` passes the `crs` argument to the `Proj` function from the
+        `pyproj` library (with the option `preserve_units=True`). It can
+        therefore accept proj4 projections in any format
+        supported by `Proj`, including dictionaries, or proj4 strings.
+
         """
         if inplace:
             df = self
diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py
--- a/geopandas/geoseries.py
+++ b/geopandas/geoseries.py
@@ -254,6 +254,12 @@ def to_crs(self, crs=None, epsg=None):
         joining points are assumed to be lines in the current
         projection, not geodesics.  Objects crossing the dateline (or
         other projection boundary) will have undesirable behavior.
+
+        `to_crs` passes the `crs` argument to the `Proj` function from the
+        `pyproj` library (with the option `preserve_units=True`). It can
+        therefore accept proj4 projections in any format
+        supported by `Proj`, including dictionaries, or proj4 strings.
+
         """
         from fiona.crs import from_epsg
         if self.crs is None:
@@ -264,8 +270,8 @@ def to_crs(self, crs=None, epsg=None):
                 crs = from_epsg(epsg)
             except TypeError:
                 raise TypeError('Must set either crs or epsg for output.')
-        proj_in = pyproj.Proj(preserve_units=True, **self.crs)
-        proj_out = pyproj.Proj(preserve_units=True, **crs)
+        proj_in = pyproj.Proj(self.crs, preserve_units=True)
+        proj_out = pyproj.Proj(crs, preserve_units=True)
         project = partial(pyproj.transform, proj_in, proj_out)
         result = self.apply(lambda geom: transform(project, geom))
         result.__class__ = GeoSeries


Test patch:
diff --git a/tests/test_geoseries.py b/tests/test_geoseries.py
--- a/tests/test_geoseries.py
+++ b/tests/test_geoseries.py
@@ -151,5 +151,28 @@ def test_geoseries_geointerface(self):
         self.assertEqual(len(self.g1.__geo_interface__['features']),
                          self.g1.shape[0])
 
+    def test_proj4strings(self):
+        # As string
+        reprojected = self.g3.to_crs('+proj=utm +zone=30N')
+        reprojected_back = reprojected.to_crs(epsg=4326)
+        self.assertTrue(np.alltrue(self.g3.geom_almost_equals(reprojected_back)))
+
+        # As dict
+        reprojected = self.g3.to_crs({'proj': 'utm', 'zone': '30N'})
+        reprojected_back = reprojected.to_crs(epsg=4326)
+        self.assertTrue(np.alltrue(self.g3.geom_almost_equals(reprojected_back)))
+
+        # Set to equivalent string, convert, compare to original
+        copy = self.g3.copy()
+        copy.crs = '+init=epsg:4326'
+        reprojected = copy.to_crs({'proj': 'utm', 'zone': '30N'})
+        reprojected_back = reprojected.to_crs(epsg=4326)
+        self.assertTrue(np.alltrue(self.g3.geom_almost_equals(reprojected_back)))
+
+        # Conversions by different format
+        reprojected_string = self.g3.to_crs('+proj=utm +zone=30N')
+        reprojected_dict = self.g3.to_crs({'proj': 'utm', 'zone': '30N'})
+        self.assertTrue(np.alltrue(reprojected_string.geom_almost_equals(reprojected_dict)))
+
 if __name__ == '__main__':
     unittest.main()