diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py index 1fc67bd..5782406 100644 --- a/geopandas/geoseries.py +++ b/geopandas/geoseries.py @@ -18,7 +18,8 @@ from geopandas.plotting import plot_series from .array import GeometryDtype, from_shapely from .base import is_geometry_type -_PYPROJ2 = LooseVersion(pyproj.__version__) >= LooseVersion("2.1.0") + +_PYPROJ_VERSION = LooseVersion(pyproj.__version__) _SERIES_WARNING_MSG = """\ @@ -389,22 +390,38 @@ class GeoSeries(GeoPandasBase, Series): """ from fiona.crs import from_epsg + if crs is None and epsg is None: + raise TypeError("Must set either crs or epsg for output.") + if self.crs is None: raise ValueError( "Cannot transform naive geometries. " "Please set a crs on the object first." ) + if crs is None: try: crs = from_epsg(epsg) - except TypeError: - raise TypeError("Must set either crs or epsg for output.") - proj_in = pyproj.Proj(self.crs, preserve_units=True) - proj_out = pyproj.Proj(crs, preserve_units=True) - if _PYPROJ2: - transformer = pyproj.Transformer.from_proj(proj_in, proj_out) + except (TypeError, ValueError): + raise ValueError("Invalid epsg: {}".format(epsg)) + + # skip transformation if the input CRS and output CRS are the exact same + if _PYPROJ_VERSION >= LooseVersion("2.1.2") and pyproj.CRS.from_user_input( + self.crs + ).is_exact_same(pyproj.CRS.from_user_input(crs)): + return self + + if _PYPROJ_VERSION >= LooseVersion("2.2.0"): + # if availale, use always_xy=True to preserve GIS axis order + transformer = pyproj.Transformer.from_crs(self.crs, crs, always_xy=True) + project = transformer.transform + elif _PYPROJ_VERSION >= LooseVersion("2.1.0"): + # use transformer for repeated transformations + transformer = pyproj.Transformer.from_crs(self.crs, crs) project = transformer.transform else: + 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 diff --git a/geopandas/tests/test_crs.py b/geopandas/tests/test_crs.py index f32cb23..e08ca88 100644 --- a/geopandas/tests/test_crs.py +++ b/geopandas/tests/test_crs.py @@ -1,6 +1,11 @@ +from distutils.version import LooseVersion + import numpy as np +from shapely.geometry import Point + from geopandas import GeoDataFrame, points_from_xy +from geopandas.geoseries import _PYPROJ_VERSION from geopandas.testing import assert_geodataframe_equal import pytest @@ -94,3 +99,22 @@ def test_transform2(epsg4326, epsg26918): # can't check for CRS equality, as the formats differ although representing # the same CRS assert_geodataframe_equal(df, utm, check_less_precise=True, check_crs=False) + + +@pytest.mark.skipif( + _PYPROJ_VERSION < LooseVersion("2.2.0"), + reason="EPSG strings without +init= won't work on previous versions of pyproj.", +) +def test_crs_axis_order__always_xy(): + df = GeoDataFrame(geometry=[Point(-1683723, 6689139)], crs="epsg:26918") + lonlat = df.to_crs("epsg:4326") + test_lonlat = GeoDataFrame( + geometry=[Point(-110.1399901, 55.1350011)], crs="epsg:4326" + ) + assert_geodataframe_equal(lonlat, test_lonlat, check_less_precise=True) + + +def test_skip_exact_same(): + df = df_epsg26918() + utm = df.to_crs(df.crs) + assert_geodataframe_equal(df, utm, check_less_precise=True)