From 29fb395062462f16ef626016422640b286c70ac0 Mon Sep 17 00:00:00 2001 From: "Alan D. Snow" Date: Mon, 15 Feb 2021 15:28:46 -0600 Subject: [PATCH] REF: Reduce pyproj.CRS internal usage for speed (#242) --- docs/history.rst | 1 + rioxarray/_io.py | 2 +- rioxarray/crs.py | 20 +++++++++++++++----- rioxarray/raster_array.py | 8 +++----- rioxarray/rioxarray.py | 18 +++++++++++++++--- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/docs/history.rst b/docs/history.rst index bb8abfe8..d1216269 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -3,6 +3,7 @@ History Latest ------ +- REF: Reduce pyproj.CRS internal usage for speed (issue #241) 0.2.0 ------ diff --git a/rioxarray/_io.py b/rioxarray/_io.py index 56bcedea..4d4644e1 100644 --- a/rioxarray/_io.py +++ b/rioxarray/_io.py @@ -291,7 +291,7 @@ def _parse_tags(tags): 2: np.char, # NC_CHAR 3: np.short, # NC_SHORT 4: np.int_, # NC_INT, NC_LONG - 5: np.float, # NC_FLOAT + 5: float, # NC_FLOAT 6: np.double, # NC_DOUBLE 7: np.ubyte, # NC_UBYTE 8: np.ushort, # NC_USHORT diff --git a/rioxarray/crs.py b/rioxarray/crs.py index 14249f51..88e44bb5 100644 --- a/rioxarray/crs.py +++ b/rioxarray/crs.py @@ -5,11 +5,15 @@ from distutils.version import LooseVersion import rasterio +import rasterio.crs from pyproj import CRS +from rasterio.errors import CRSError -def crs_to_wkt(crs_input): +def crs_from_user_input(crs_input): """ + Return a rasterio.crs.CRS from user input. + This is to deal with change in rasterio.crs.CRS as well as to assist in the transition between GDAL 2/3. @@ -20,16 +24,22 @@ def crs_to_wkt(crs_input): Returns ------- - str: WKT string. + rasterio.crs.CRS """ + if isinstance(crs_input, rasterio.crs.CRS): + return crs_input try: - # rasterio.crs.CRS <1.0.14 and # old versions of opendatacube CRS crs_input = crs_input.wkt except AttributeError: pass + try: + return rasterio.crs.CRS.from_user_input(crs_input) + except CRSError: + pass + # use pyproj for edge cases crs = CRS.from_user_input(crs_input) if LooseVersion(rasterio.__gdal_version__) > LooseVersion("3.0.0"): - return crs.to_wkt() - return crs.to_wkt("WKT1_GDAL") + return rasterio.crs.CRS.from_wkt(crs.to_wkt()) + return rasterio.crs.CRS.from_wkt(crs.to_wkt("WKT1_GDAL")) diff --git a/rioxarray/raster_array.py b/rioxarray/raster_array.py index e5bf9574..f5704dd1 100644 --- a/rioxarray/raster_array.py +++ b/rioxarray/raster_array.py @@ -19,12 +19,11 @@ import rasterio.mask import rasterio.warp import xarray -from rasterio.crs import CRS from rasterio.enums import Resampling from rasterio.features import geometry_mask from scipy.interpolate import griddata -from rioxarray.crs import crs_to_wkt +from rioxarray.crs import crs_from_user_input from rioxarray.exceptions import ( MissingCRS, NoDataInBounds, @@ -430,9 +429,8 @@ def reproject_match(self, match_data_array, resampling=Resampling.nearest): Contains the data from the src_data_array, reprojected to match match_data_array. """ - dst_crs = crs_to_wkt(match_data_array.rio.crs) return self.reproject( - dst_crs, + match_data_array.rio.crs, transform=match_data_array.rio.transform(recalc=True), shape=match_data_array.rio.shape, resampling=resampling, @@ -697,7 +695,7 @@ def clip( "CRS not found. Please set the CRS with 'rio.write_crs()'." f"{_get_data_var_message(self._obj)}" ) - crs = CRS.from_wkt(crs_to_wkt(crs)) if crs is not None else self.crs + crs = crs_from_user_input(crs) if crs is not None else self.crs if self.crs != crs: if LooseVersion(rasterio.__version__) >= LooseVersion("1.2"): geometries = rasterio.warp.transform_geom(crs, self.crs, geometries) diff --git a/rioxarray/rioxarray.py b/rioxarray/rioxarray.py index b2741fcf..f0c0f739 100644 --- a/rioxarray/rioxarray.py +++ b/rioxarray/rioxarray.py @@ -12,7 +12,7 @@ from affine import Affine from rasterio.crs import CRS -from rioxarray.crs import crs_to_wkt +from rioxarray.crs import crs_from_user_input from rioxarray.exceptions import ( DimensionError, DimensionMissingCoordinateError, @@ -151,6 +151,18 @@ def crs(self): if self._crs is not None: return None if self._crs is False else self._crs + # look in wkt attributes to avoid using + # pyproj CRS if possible for performance + for crs_attr in ("spatial_ref", "crs_wkt"): + try: + self.set_crs( + self._obj.coords[self.grid_mapping].attrs[crs_attr], + inplace=True, + ) + return self._crs + except KeyError: + pass + # look in grid_mapping try: self.set_crs( @@ -207,7 +219,7 @@ def set_crs(self, input_crs, inplace=True): :obj:`xarray.Dataset` | :obj:`xarray.DataArray`: Dataset with crs attribute. """ - crs = CRS.from_wkt(crs_to_wkt(input_crs)) + crs = crs_from_user_input(input_crs) obj = self._get_obj(inplace=inplace) obj.rio._crs = crs return obj @@ -316,7 +328,7 @@ def write_crs(self, input_crs=None, grid_mapping_name=None, inplace=False): data_obj.coords[grid_mapping_name] = xarray.Variable((), 0) grid_map_attrs = pyproj.CRS.from_user_input(data_obj.rio.crs).to_cf() # spatial_ref is for compatibility with GDAL - crs_wkt = crs_to_wkt(data_obj.rio.crs) + crs_wkt = data_obj.rio.crs.to_wkt() grid_map_attrs["spatial_ref"] = crs_wkt grid_map_attrs["crs_wkt"] = crs_wkt if transform is not None: