def test_make_coords__attr_trans(request, modis_reproject): with xarray.open_dataarray(modis_reproject["input"]) as xdi, request.param( modis_reproject["input"]) as xri: # calculate coordinates from the attribute transform width, height = xdi.rio.shape attr_transform = xdi.rio.transform() calc_coords_attr_trans = _make_coords(xdi, attr_transform, width, height, xdi.attrs["crs"]) widthr, heightr = xri.rio.shape calculated_transformr = xri.rio.transform() calc_coords_calc_transr = _make_coords(xri, calculated_transformr, widthr, heightr, xdi.attrs["crs"]) # check to see if they all match assert_array_equal(xri.coords["x"].values, calc_coords_calc_transr["x"].values) assert_array_equal(xri.coords["y"].values, calc_coords_calc_transr["y"].values) assert_array_equal(xri.coords["x"].values, calc_coords_attr_trans["x"].values) assert_array_equal(xri.coords["y"].values, calc_coords_attr_trans["y"].values) assert_almost_equal(xdi.coords["x"].values, xri.coords["x"].values, decimal=9) assert_almost_equal(xdi.coords["y"].values, xri.coords["y"].values, decimal=9)
def _clip_from_disk(xds, geometries, all_touched, drop, invert): """ clip from disk if the file object is available """ try: out_image, out_transform = rasterio.mask.mask( xds.rio._manager.acquire(), geometries, all_touched=all_touched, invert=invert, crop=drop, ) if xds.rio.encoded_nodata is not None and not np.isnan( xds.rio.encoded_nodata): out_image = out_image.astype(np.float64) out_image[out_image == xds.rio.encoded_nodata] = np.nan height, width = out_image.shape[-2:] cropped_ds = xarray.DataArray( name=xds.name, data=out_image, coords=_make_coords(xds, out_transform, width, height), dims=xds.dims, attrs=xds.attrs, ) cropped_ds.encoding = xds.encoding return cropped_ds except AttributeError: return None
def test_make_coords__calc_trans(modis_reproject): with xarray.open_dataarray(modis_reproject["input"], autoclose=True) as xdi, xarray.open_rasterio( modis_reproject["input"]) as xri: # calculate coordinates from the calculated transform width, height = xdi.rio.shape calculated_transform = xdi.rio.transform(recalc=True) calc_coords_calc_trans = _make_coords(xdi, calculated_transform, width, height, xdi.attrs["crs"]) widthr, heightr = xri.rio.shape calculated_transformr = xri.rio.transform(recalc=True) calc_coords_calc_transr = _make_coords(xri, calculated_transformr, widthr, heightr, xdi.attrs["crs"]) # check to see if they all match assert_array_equal(xri.coords["x"].values, calc_coords_calc_trans["x"].values) assert_array_equal(xri.coords["y"].values, calc_coords_calc_trans["y"].values) assert_array_equal(xri.coords["x"].values, calc_coords_calc_transr["x"].values) assert_array_equal(xri.coords["y"].values, calc_coords_calc_transr["y"].values)
def merge_arrays( dataarrays: Iterable[DataArray], bounds: Optional[Tuple] = None, res: Optional[Tuple] = None, nodata: Optional[float] = None, precision: Optional[float] = None, method: Union[str, Callable, None] = None, crs: Optional[CRS] = None, parse_coordinates: bool = True, ) -> DataArray: """ Merge data arrays geospatially. Uses rasterio.merge.merge: https://rasterio.readthedocs.io/en/stable/api/rasterio.merge.html#rasterio.merge.merge .. versionadded:: 0.2 crs Parameters ---------- dataarrays: list List of xarray.DataArray's with all geo attributes. The first one is assumed to have the same CRS, dtype, and dimensions as the others in the array. bounds: tuple, optional Bounds of the output image (left, bottom, right, top). If not set, bounds are determined from bounds of input DataArrays. res: tuple, optional Output resolution in units of coordinate reference system. If not set, the resolution of the first DataArray is used. If a single value is passed, output pixels will be square. nodata: float, optional nodata value to use in output file. If not set, uses the nodata value in the first input DataArray. precision: float, optional Number of decimal points of precision when computing inverse transform. method: str or callable, optional See rasterio docs. crs: rasterio.crs.CRS, optional Output CRS. If not set, the CRS of the first DataArray is used. parse_coordinates: bool, optional If False, it will disable loading spatial coordinates. Returns ------- :obj:`xarray.DataArray`: The geospatially merged data. """ input_kwargs = dict(bounds=bounds, res=res, nodata=nodata, precision=precision, method=method) if crs is None: crs = dataarrays[0].rio.crs if res is None: res = tuple(abs(res_val) for res_val in dataarrays[0].rio.resolution()) # prepare the duck arrays rioduckarrays = [] for dataarray in dataarrays: da_res = tuple(abs(res_val) for res_val in dataarray.rio.resolution()) if da_res != res or dataarray.rio.crs != crs: rioduckarrays.append( RasterioDatasetDuck( dataarray.rio.reproject(dst_crs=crs, resolution=res))) else: rioduckarrays.append(RasterioDatasetDuck(dataarray)) # use rasterio to merge merged_data, merged_transform = _rio_merge( rioduckarrays, **{key: val for key, val in input_kwargs.items() if val is not None}, ) # generate merged data array representative_array = rioduckarrays[0]._xds if parse_coordinates: coords = _make_coords( representative_array, merged_transform, merged_data.shape[-1], merged_data.shape[-2], ) else: coords = _get_nonspatial_coords(representative_array) xda = DataArray( name=representative_array.name, data=merged_data, coords=coords, dims=tuple(representative_array.dims), attrs=representative_array.attrs, ) xda.rio.write_nodata( nodata if nodata is not None else representative_array.rio.nodata, inplace=True) xda.rio.write_crs(representative_array.rio.crs, inplace=True) xda.rio.write_transform(merged_transform, inplace=True) return xda
def merge_datasets( datasets: Iterable[Dataset], bounds: Optional[Tuple] = None, res: Optional[Tuple] = None, nodata: Optional[float] = None, precision: Optional[float] = None, method: Union[str, Callable, None] = None, crs: Optional[CRS] = None, ) -> DataArray: """ Merge datasets geospatially. Uses rasterio.merge.merge: https://rasterio.readthedocs.io/en/stable/api/rasterio.merge.html#rasterio.merge.merge .. versionadded:: 0.2 crs Parameters ---------- datasets: list List of xarray.Dataset's with all geo attributes. The first one is assumed to have the same CRS, dtype, dimensions, and data_vars as the others in the array. bounds: tuple, optional Bounds of the output image (left, bottom, right, top). If not set, bounds are determined from bounds of input Dataset. res: tuple, optional Output resolution in units of coordinate reference system. If not set, the resolution of the first Dataset is used. If a single value is passed, output pixels will be square. nodata: float, optional nodata value to use in output file. If not set, uses the nodata value in the first input Dataset. precision: float, optional Number of decimal points of precision when computing inverse transform. method: str or callable, optional See rasterio docs. crs: rasterio.crs.CRS, optional Output CRS. If not set, the CRS of the first DataArray is used. Returns ------- :obj:`xarray.Dataset`: The geospatially merged data. """ representative_ds = datasets[0] merged_data = {} for data_var in representative_ds.data_vars: merged_data[data_var] = merge_arrays( [dataset[data_var] for dataset in datasets], bounds=bounds, res=res, nodata=nodata, precision=precision, method=method, crs=crs, parse_coordinates=False, ) data_var = list(representative_ds.data_vars)[0] xds = Dataset( merged_data, coords=_make_coords( merged_data[data_var], merged_data[data_var].rio.transform(), merged_data[data_var].shape[-1], merged_data[data_var].shape[-2], ), attrs=representative_ds.attrs, ) xds.rio.write_crs(merged_data[data_var].rio.crs, inplace=True) return xds
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 merge_arrays( dataarrays: Iterable[DataArray], bounds: Union[Tuple, None] = None, res: Union[Tuple, None] = None, nodata: Union[float, None] = None, precision: Union[float, None] = None, method: Union[str, Callable, None] = None, parse_coordinates: bool = True, ) -> DataArray: """ Merge data arrays geospatially. Uses rasterio.merge.merge: https://rasterio.readthedocs.io/en/stable/api/rasterio.merge.html#rasterio.merge.merge Parameters ---------- dataarrays: list List of xarray.DataArray's with all geo attributes. The first one is assumed to have the same CRS, dtype, and dimensions as the others in the array. bounds: tuple, optional Bounds of the output image (left, bottom, right, top). If not set, bounds are determined from bounds of input DataArrays. res: tuple, optional Output resolution in units of coordinate reference system. If not set, the resolution of the first DataArray is used. If a single value is passed, output pixels will be square. nodata: float, optional nodata value to use in output file. If not set, uses the nodata value in the first input DataArray. precision: float, optional Number of decimal points of precision when computing inverse transform. method: str or callable, optional See rasterio docs. parse_coordinates: bool, optional If False, it will disable loading spatial coordinates. Returns ------- DataArray: The geospatially merged data. """ input_kwargs = dict(bounds=bounds, res=res, nodata=nodata, precision=precision, method=method) merged_data, merged_transform = _rio_merge( [RasterioDatasetDuck(dataarray) for dataarray in dataarrays], **{key: val for key, val in input_kwargs.items() if val is not None}, ) merged_shape = merged_data.shape representative_array = dataarrays[0] merged_crs = representative_array.rio.crs if parse_coordinates: coords = _make_coords( representative_array, merged_transform, merged_shape[-1], merged_shape[-2], merged_crs, ) else: coords = _get_nonspatial_coords(representative_array) out_attrs = representative_array.attrs out_attrs["transform"] = tuple(merged_transform)[:6] xda = DataArray( name=dataarrays[0].name, data=merged_data, coords=coords, dims=tuple(representative_array.dims), attrs=out_attrs, ) out_nodata = nodata if nodata is not None else representative_array.rio.nodata xda.rio.write_nodata(out_nodata, inplace=True) xda.rio.write_crs(representative_array.rio.crs, 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