def write_crs(self, input_crs=None, grid_mapping_name=DEFAULT_GRID_MAP, inplace=False): """ Write the CRS to the dataset in a CF compliant manner. Parameters ---------- input_crs: object Anything accepted by `rasterio.crs.CRS.from_user_input`. grid_mapping_name: str, optional Name of the coordinate to store the CRS information in. inplace: bool, optional If True, it will write to the existing dataset. Default is False. Returns ------- xarray.Dataset or xarray.DataArray: Modified dataset with CF compliant CRS information. """ if input_crs is not None: data_obj = self.set_crs(input_crs, inplace=inplace) else: data_obj = self._get_obj(inplace=inplace) # remove old grid maping coordinate if exists try: del data_obj.coords[grid_mapping_name] except KeyError: pass if data_obj.rio.crs is None: raise MissingCRS( "CRS not found. Please set the CRS with 'set_crs()'.") # add grid mapping coordinate data_obj.coords[grid_mapping_name] = xarray.Variable((), 0) crs_wkt = crs_to_wkt(data_obj.rio.crs) grid_map_attrs = dict() grid_map_attrs["spatial_ref"] = crs_wkt # http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/cf-conventions.html#appendix-grid-mappings # http://desktop.arcgis.com/en/arcmap/10.3/manage-data/netcdf/spatial-reference-for-netcdf-data.htm grid_map_attrs["crs_wkt"] = crs_wkt data_obj.coords[grid_mapping_name].rio.set_attrs(grid_map_attrs, inplace=True) # add grid mapping attribute to variables if hasattr(data_obj, "data_vars"): for var in data_obj.data_vars: if (self.x_dim in data_obj[var].dims and self.y_dim in data_obj[var].dims): data_obj[var].rio.update_attrs( dict(grid_mapping=grid_mapping_name), inplace=True) data_obj.rio.update_attrs(dict(grid_mapping=grid_mapping_name), inplace=True) return data_obj
def write_crs(self, input_crs=None, grid_mapping_name=None, inplace=False): """ Write the CRS to the dataset in a CF compliant manner. Parameters ---------- input_crs: object Anything accepted by `rasterio.crs.CRS.from_user_input`. grid_mapping_name: str, optional Name of the grid_mapping coordinate to store the CRS information in. Default is the grid_mapping name of the dataset. inplace: bool, optional If True, it will write to the existing dataset. Default is False. Returns ------- :obj:`xarray.Dataset` | :obj:`xarray.DataArray`: Modified dataset with CF compliant CRS information. """ if input_crs is not None: data_obj = self.set_crs(input_crs, inplace=inplace) else: data_obj = self._get_obj(inplace=inplace) # get original transform transform = self._cached_transform() # remove old grid maping coordinate if exists grid_mapping_name = (self.grid_mapping if grid_mapping_name is None else grid_mapping_name) try: del data_obj.coords[grid_mapping_name] except KeyError: pass if data_obj.rio.crs is None: raise MissingCRS( "CRS not found. Please set the CRS with 'rio.write_crs()'.") # add grid mapping coordinate 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) grid_map_attrs["spatial_ref"] = crs_wkt grid_map_attrs["crs_wkt"] = crs_wkt if transform is not None: grid_map_attrs["GeoTransform"] = " ".join( [str(item) for item in transform.to_gdal()]) data_obj.coords[grid_mapping_name].rio.set_attrs(grid_map_attrs, inplace=True) return data_obj.rio.write_grid_mapping( grid_mapping_name=grid_mapping_name, inplace=True)
def clip( self, geometries, crs=None, all_touched=False, drop=True, invert=False, from_disk=False, ): """ Crops a :obj:`xarray.DataArray` by geojson like geometry dicts. Powered by `rasterio.features.geometry_mask`. Examples: >>> geometry = ''' {"type": "Polygon", ... "coordinates": [ ... [[-94.07955380199459, 41.69085871273774], ... [-94.06082436942204, 41.69103313774798], ... [-94.06063203899649, 41.67932439500822], ... [-94.07935807746362, 41.679150041277325], ... [-94.07955380199459, 41.69085871273774]]]}''' >>> cropping_geometries = [geojson.loads(geometry)] >>> xds = xarray.open_rasterio('cool_raster.tif') >>> cropped = xds.rio.clip(geometries=cropping_geometries, crs=4326) .. versionadded:: 0.2 from_disk Parameters ---------- geometries: list A list of geojson geometry dicts or objects with __geom_interface__ with if you have rasterio 1.2+. crs: :obj:`rasterio.crs.CRS`, optional The CRS of the input geometries. Default is to assume it is the same as the dataset. all_touched : bool, optional If True, all pixels touched by geometries will be burned in. If false, only pixels whose center is within the polygon or that are selected by Bresenham's line algorithm will be burned in. drop: bool, optional If True, drop the data outside of the extent of the mask geoemtries Otherwise, it will return the same raster with the data masked. Default is True. invert: boolean, optional If False, pixels that do not overlap shapes will be set as nodata. Otherwise, pixels that overlap the shapes will be set as nodata. False by default. from_disk: boolean, optional If True, it will clip from disk using rasterio.mask.mask if possible. This is beneficial when the size of the data is larger than memory. Default is False. Returns ------- :obj:`xarray.DataArray`: The clipped object. """ if self.crs is None: raise MissingCRS( "CRS not found. Please set the CRS with 'rio.write_crs()'." f"{_get_data_var_message(self._obj)}") 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) else: geometries = [ rasterio.warp.transform_geom(crs, self.crs, geometry) for geometry in geometries ] cropped_ds = None if from_disk: cropped_ds = _clip_from_disk( self._obj, geometries=geometries, all_touched=all_touched, drop=drop, invert=invert, ) if cropped_ds is None: cropped_ds = _clip_xarray( self._obj, geometries=geometries, all_touched=all_touched, drop=drop, invert=invert, ) if (cropped_ds.coords[self.x_dim].size < 1 or cropped_ds.coords[self.y_dim].size < 1): raise NoDataInBounds( f"No data found in bounds.{_get_data_var_message(self._obj)}") # make sure correct attributes preserved & projection added _add_attrs_proj(cropped_ds, self._obj) return cropped_ds
def reproject( self, dst_crs, resolution=None, shape=None, transform=None, resampling=Resampling.nearest, ): """ Reproject :obj:`xarray.DataArray` objects Powered by `rasterio.warp.reproject` .. note:: Only 2D/3D arrays with dimensions 'x'/'y' are currently supported. Requires either a grid mapping variable with 'spatial_ref' or a 'crs' attribute to be set containing a valid CRS. If using a WKT (e.g. from spatiareference.org), make sure it is an OGC WKT. .. versionadded:: 0.0.27 shape .. versionadded:: 0.0.28 transform Parameters ---------- dst_crs: str OGC WKT string or Proj.4 string. resolution: float or tuple(float, float), optional Size of a destination pixel in destination projection units (e.g. degrees or metres). shape: tuple(int, int), optional Shape of the destination in pixels (dst_height, dst_width). Cannot be used together with resolution. transform: optional The destination transform. resampling: Resampling method, optional See rasterio.warp.reproject for more details. Returns ------- :obj:`xarray.DataArray`: The reprojected DataArray. """ if resolution is not None and (shape is not None or transform is not None): raise RioXarrayError( "resolution cannot be used with shape or transform.") if self.crs is None: raise MissingCRS( "CRS not found. Please set the CRS with 'rio.write_crs()'." f"{_get_data_var_message(self._obj)}") src_affine = self.transform(recalc=True) if transform is None: dst_affine, dst_width, dst_height = _make_dst_affine( self._obj, self.crs, dst_crs, resolution, shape) else: dst_affine = transform if shape is not None: dst_height, dst_width = shape else: dst_height, dst_width = self.shape extra_dim = self._check_dimensions() if extra_dim: dst_data = np.zeros( (self._obj[extra_dim].size, dst_height, dst_width), dtype=self._obj.dtype.type, ) else: dst_data = np.zeros((dst_height, dst_width), dtype=self._obj.dtype.type) dst_nodata = self._obj.dtype.type( self.nodata if self.nodata is not None else -9999) src_nodata = self._obj.dtype.type( self.nodata if self.nodata is not None else dst_nodata) rasterio.warp.reproject( source=self._obj.values, destination=dst_data, src_transform=src_affine, src_crs=self.crs, src_nodata=src_nodata, dst_transform=dst_affine, dst_crs=dst_crs, dst_nodata=dst_nodata, resampling=resampling, ) # add necessary attributes new_attrs = _generate_attrs(self._obj, dst_nodata) # make sure dimensions with coordinates renamed to x,y dst_dims = [] for dim in self._obj.dims: if dim == self.x_dim: dst_dims.append("x") elif dim == self.y_dim: dst_dims.append("y") else: dst_dims.append(dim) xda = xarray.DataArray( name=self._obj.name, data=dst_data, coords=_make_coords(self._obj, dst_affine, dst_width, dst_height), dims=tuple(dst_dims), attrs=new_attrs, ) xda.encoding = self._obj.encoding xda.rio.write_transform(dst_affine, inplace=True) xda.rio.write_crs(dst_crs, inplace=True) xda.rio.write_coordinate_system(inplace=True) return xda
def reproject( self, dst_crs, resolution=None, shape=None, transform=None, resampling=Resampling.nearest, nodata=None, **kwargs, ): """ Reproject :obj:`xarray.DataArray` objects Powered by :func:`rasterio.warp.reproject` .. note:: Only 2D/3D arrays with dimensions 'x'/'y' are currently supported. Requires either a grid mapping variable with 'spatial_ref' or a 'crs' attribute to be set containing a valid CRS. If using a WKT (e.g. from spatiareference.org), make sure it is an OGC WKT. .. versionadded:: 0.0.27 shape .. versionadded:: 0.0.28 transform .. versionadded:: 0.5.0 nodata, kwargs Parameters ---------- dst_crs: str OGC WKT string or Proj.4 string. resolution: float or tuple(float, float), optional Size of a destination pixel in destination projection units (e.g. degrees or metres). shape: tuple(int, int), optional Shape of the destination in pixels (dst_height, dst_width). Cannot be used together with resolution. transform: Affine, optional The destination transform. resampling: rasterio.enums.Resampling, optional See :func:`rasterio.warp.reproject` for more details. nodata: float, optional The nodata value used to initialize the destination; it will remain in all areas not covered by the reprojected source. Defaults to the nodata value of the source image if none provided and exists or attempts to find an appropriate value by dtype. **kwargs: dict Additional keyword arguments to pass into :func:`rasterio.warp.reproject`. To override: - src_transform: `rio.write_transform` - src_crs: `rio.write_crs` - src_nodata: `rio.write_nodata` Returns ------- :obj:`xarray.DataArray`: The reprojected DataArray. """ if resolution is not None and (shape is not None or transform is not None): raise RioXarrayError( "resolution cannot be used with shape or transform.") if self.crs is None: raise MissingCRS( "CRS not found. Please set the CRS with 'rio.write_crs()'." f"{_get_data_var_message(self._obj)}") gcps = self.get_gcps() if gcps: kwargs.setdefault("gcps", gcps) src_affine = None if "gcps" in kwargs else self.transform(recalc=True) if transform is None: dst_affine, dst_width, dst_height = _make_dst_affine( self._obj, self.crs, dst_crs, resolution, shape, **kwargs) else: dst_affine = transform if shape is not None: dst_height, dst_width = shape else: dst_height, dst_width = self.shape dst_data = self._create_dst_data(dst_height, dst_width) dst_nodata = self._get_dst_nodata(nodata) rasterio.warp.reproject( source=self._obj.values, destination=dst_data, src_transform=src_affine, src_crs=self.crs, src_nodata=self.nodata, dst_transform=dst_affine, dst_crs=dst_crs, dst_nodata=dst_nodata, resampling=resampling, **kwargs, ) # add necessary attributes new_attrs = _generate_attrs(self._obj, dst_nodata) # make sure dimensions with coordinates renamed to x,y dst_dims = [] for dim in self._obj.dims: if dim == self.x_dim: dst_dims.append("x") elif dim == self.y_dim: dst_dims.append("y") else: dst_dims.append(dim) xda = xarray.DataArray( name=self._obj.name, data=dst_data, coords=_make_coords(self._obj, dst_affine, dst_width, dst_height), dims=tuple(dst_dims), attrs=new_attrs, ) xda.encoding = self._obj.encoding xda.rio.write_transform(dst_affine, inplace=True) xda.rio.write_crs(dst_crs, inplace=True) xda.rio.write_coordinate_system(inplace=True) return xda