Esempio n. 1
0
    def from_vector(self, vector_data):
        """Get the geobox to use for the grid.

        Parameters
        ----------
        vector_data: str or :obj:`geopandas.GeoDataFrame`
            A file path to an OGR supported source or GeoDataFrame
            containing the vector data.

        Returns
        -------
        :obj:`datacube.utils.geometry.GeoBox`
            The geobox for the grid to be generated from the vector data.

        """
        vector_data = load_vector_data(vector_data)

        if self.like is not None:
            assert (self.output_crs is
                    None), "'like' and 'output_crs' are not supported together"
            assert (self.resolution is
                    None), "'like' and 'resolution' are not supported together"
            assert self.align is None, "'like' and 'align' are not supported together"
            try:
                geobox = self.like.geobox
            except AttributeError:
                geobox = geobox_from_rio(self.like)
            return geobox

        if self.resolution is None:
            raise RuntimeError(
                "Must specify 'resolution' if 'like' not specified.")

        if self.output_crs:
            crs = geometry.CRS(crs_to_wkt(self.output_crs))
        else:
            crs = geometry.CRS(crs_to_wkt(vector_data.crs))

        if self.geom is None and self.output_crs:
            geopoly = geometry.Geometry(
                mapping(
                    box(*vector_data.to_crs(_datacube_to_geopandas_crs(
                        crs)).total_bounds)),
                crs=crs,
            )
        elif self.geom is None:
            geopoly = geometry.Geometry(mapping(
                box(*vector_data.total_bounds)),
                                        crs=crs)

        else:
            geom_json = json.loads(self.geom)
            geom_crs = geometry.CRS(geom_json["crs"]["properties"]["name"]
                                    if "crs" in geom_json else "epsg:4326")

            geopoly = geometry.Geometry(geom_json, crs=geom_crs)

        return geometry.GeoBox.from_geopolygon(geopoly, self.resolution, crs,
                                               self.align)
Esempio n. 2
0
    def reproject_match(self, match_data_array, resampling=Resampling.nearest):
        """
        Reproject a DataArray object to match the resolution, projection,
        and region of another DataArray.

        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.

        Parameters
        ----------
        match_data_array:  :obj:`xarray.DataArray` | :obj:`xarray.Dataset`
            DataArray of the target resolution and projection.
        resampling: Resampling method, optional
            See rasterio.warp.reproject for more details.


        Returns
        --------
        :obj:`xarray.DataArray`:
            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,
            transform=match_data_array.rio.transform(recalc=True),
            shape=match_data_array.rio.shape,
            resampling=resampling,
        )
Esempio n. 3
0
    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
Esempio n. 4
0
    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)
Esempio n. 5
0
def add_spatial_ref(in_ds, dst_crs, grid_map_name):
    # remove old grid map if exists
    try:
        del in_ds.coords[grid_map_name]
    except KeyError:
        pass

    # add grid mapping variable
    in_ds.coords[grid_map_name] = xarray.Variable((), 0)
    match_proj = crs_to_wkt(CRS.from_user_input(dst_crs))

    grid_map_attrs = dict()
    # add grid mapping variable
    grid_map_attrs["spatial_ref"] = match_proj
    # 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"] = match_proj
    in_ds.coords[grid_map_name].attrs = grid_map_attrs
    return in_ds
Esempio n. 6
0
    def set_crs(self, input_crs, inplace=True):
        """
        Set the CRS value for the Dataset/DataArray without modifying
        the dataset/data array.

        Parameters
        ----------
        input_crs: object
            Anything accepted by `rasterio.crs.CRS.from_user_input`.
        inplace: bool, optional
            If True, it will write to the existing dataset. Default is False.

        Returns
        -------
        :obj:`xarray.Dataset` | :obj:`xarray.DataArray`:
            Dataset with crs attribute.
        """
        crs = CRS.from_wkt(crs_to_wkt(input_crs))
        obj = self._get_obj(inplace=inplace)
        obj.rio._crs = crs
        return obj
Esempio n. 7
0
def geobox_from_rio(xds):
    """This function retrieves the geobox using rioxarray extension.

    Parameters
    ----------
    xds: :obj:`xarray.DataArray` or :obj:`xarray.Dataset`
        The xarray dataset to get the geobox from.

    Returns
    -------
    :obj:`datacube.utils.geometry.GeoBox`

    """
    width, height = xds.rio.shape
    try:
        transform = xds.rio.transform()
    except AttributeError:
        transform = xds[xds.rio.vars[0]].rio.transform()
    return geometry.GeoBox(
        width=width,
        height=height,
        affine=transform,
        crs=geometry.CRS(crs_to_wkt(xds.rio.crs)),
    )
Esempio n. 8
0
    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_wkt(crs_to_wkt(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