Skip to content

Commit

Permalink
Add new .odc.explore() method
Browse files Browse the repository at this point in the history
  • Loading branch information
robbibt committed Jan 21, 2024
1 parent 215ec3e commit a26c406
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 45 deletions.
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Interfacing with :py:class:`xarray.DataArray` and :py:class:`xarray.Dataset` cla
ODCExtension.map_bounds
ODCExtension.crop
ODCExtension.mask
ODCExtension.explore

ODCExtensionDa
ODCExtensionDa.assign_crs
Expand Down
95 changes: 60 additions & 35 deletions odc/geo/_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,14 @@ def add_to(
return _add_to(url, bounds, map, name=name, **kw)


# pylint: disable=too-many-arguments, protected-access
# pylint: disable=too-many-arguments, protected-access, anomalous-backslash-in-string
def explore(
xx: Any,
map: Optional[Any] = None,
cmap: Optional[Any] = None,
bands: Optional[Tuple[str, str, str]] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
cmap: Optional[Any] = None,
robust: bool = False,
tiles: Any = "OpenStreetMap",
attr: Optional[str] = None,
Expand All @@ -172,31 +173,38 @@ def explore(
**kwargs: Any,
) -> Any:
"""
Plot an xr.DataArray on an interactive :py:mod:`folium` leaflet map.
Plot xarray data on an interactive :py:mod:`folium` leaflet map for
rapid data exploration.
Supports plotting both individual single-band arrays, or RGBA
multi-band arrays. Data is first reprojected to a map-appropriate
CRS, then added to a new or existing interactive map object.
:py:class:`xarray.Dataset` inputs are automatically converted to
multi-band RGB plots, while single-band :py:class:`xarray.DataArray`
inputs can be plotted using matplotlib colormaps (needs matplotlib
installed).
:param xx:
The :py:class:`~xarray.DataArray` to plot on the map.
The :py:class:`~xarray.Dataset` or :py:class:`~xarray.DataArray`
to plot on the map.
:param map:
An optional existing :py:mod:`folium` map object to use. By
default, data will be plotted into a new map object.
:param cmap:
The colormap used to colorise single-band arrays. If not
provided, this will default to 'viridis'. Ignored for RGBA arrays.
An optional existing :py:mod:`folium` map object to plot into.
By default, a new map object will be created.
:param bands:
Bands used for RGB colours when converting from a
:py:class:`~xarray.Dataset` (order should be red, green, blue).
By default, the function will attempt to guess bands
automatically. Ignored for :py:class:`~xarray.DataArray` inputs.
:param vmin:
Lower value to anchor the colormap. Used for single-band arrays
only; ignored for RGBA arrays.
Lower value used for the color stretch.
:param vmax:
Upper value to anchor the colormap. Used for single-band arrays
only; ignored for RGBA arrays.
Upper value used for the color stretch.
:param cmap:
The colormap used to colorise single-band arrays. If not
provided, this will default to 'viridis'. Ignored for multi-band
inputs.
:param robust:
If ``True`` and ``vmin``/``vmax`` are absent, the colormap range
will be computed based on 2nd and 98th percentiles, minimising
the influence of extreme values. Used for single-band arrays
only; ignored for RGBA arrays.
If ``True`` (and ``vmin`` and ``vmax`` are absent), the colormap
range will be computed based on 2nd and 98th percentiles,
minimising the influence of extreme values. Used for single-band
arrays only; ignored for multi-band inputs.
:param tiles:
Map tileset to use for the map basemap. Supports any option
supported by :py:mod:`folium`, including "OpenStreetMap",
Expand All @@ -213,10 +221,10 @@ def explore(
Additional keyword arguments to pass to ``folium.Map()``.
:param reproject_kwds:
Additional keyword arguments to pass to ``.odc.reproject()``.
:param **kwargs:
:param \**kwargs:
Additional keyword arguments to pass to ``.odc.add_to()``.
:return: A :py:mod:`folium` map object.
:return: A :py:mod:`folium` map containing the plotted xarray data.
"""
assert have.folium

Expand All @@ -233,26 +241,43 @@ def explore(
map_kwds.update(tiles=tiles, attr=attr)
reproject_kwds.update(resampling=resampling)

# If input is a dataset, convert to an RGBA array
if isinstance(xx, xr.Dataset):
try:
xx = xx.odc.to_rgba(bands=bands, vmin=vmin, vmax=vmax)
except ValueError as e:
raise ValueError(
f"Unable to automatically guess RGB colours ({e}). "
f"Manually specify bands to plot using the `bands` "
"parameter, e.g. `ds.odc.explore(bands=['a', 'b', 'c'])`"
) from e

# Check if correct number of dimensions and raise error early
# to avoid needlessly running expensive reprojection
is_2d = not is_rgb(xx) and xx.ndim == 2
is_3d_rgba = is_rgb(xx) and xx.ndim == 3
if not (is_2d or is_3d_rgba):
raise ValueError(
"Only 2D single-band (x, y) or 3D multi-band (x, y, band) "
"arrays are supported by `.explore()`. Please reduce the "
"dimensions in your array, for example by using `.isel()` "
"or `.sel()`: `da.isel(time=0).odc.explore()`."
)

# Create folium Map if required
if map is None:
map = Map(**map_kwds)

# Reproject to map CRS
# If necessary, reproject to map CRS
_crs = map_crs(map)
xx_reprojected = xx.odc.reproject(_crs, **reproject_kwds)
layer_name = xx.name # copy out name as it is lost in reprojection
if _crs != xx.odc.crs:
xx = xx.odc.reproject(_crs, **reproject_kwds)

# Add to map, and raise helpful error if wrong number of dims
try:
xx_reprojected.odc.add_to(map, name=xx.name, **kwargs)
except ValueError as e:
raise ValueError(
"Only 2D (x, y) or 3D (x, y, band) arrays are supported "
"by `.explore()`. Please reduce the dimensions in your "
"array, for example by using `.isel()` or `.sel()`:"
"`da.isel(time=0).odc.explore()`."
) from e
# Add data to map
xx.odc.add_to(map, name=layer_name, **kwargs)

# Zoom map to bounds
# Zoom map to extent of data
map.fit_bounds(xx.odc.map_bounds())

# Add a layer control if requested and not already added
Expand Down
16 changes: 9 additions & 7 deletions odc/geo/_xr_interop.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

from ._interop import have, is_dask_collection
from ._rgba import colorize, to_rgba
from ._map import explore
from .crs import CRS, CRSError, SomeCRS, norm_crs_or_error
from .gcp import GCPGeoBox, GCPMapping
from .geobox import Coordinate, GeoBox
Expand All @@ -44,7 +43,7 @@
if have.rasterio:
from ._cog import to_cog, write_cog
from ._compress import compress
from ._map import add_to
from ._map import add_to, explore
from .warp import rio_reproject

XarrayObject = Union[xarray.DataArray, xarray.Dataset]
Expand Down Expand Up @@ -253,8 +252,9 @@ def mask(
xx: XrT, poly: Geometry, invert: bool = False, all_touched: bool = True
) -> XrT:
"""
Apply a polygon geometry as a mask, setting all xr.Dataset
or xr.DataArray pixels outside the rasterized polygon to ``NaN``.
Apply a polygon geometry as a mask, setting all
:py:class:`xarray.Dataset` or :py:class:`xarray.DataArray` pixels
outside the rasterized polygon to ``NaN``.
:param xx:
:py:class:`~xarray.Dataset` or :py:class:`~xarray.DataArray`.
Expand Down Expand Up @@ -303,8 +303,8 @@ def crop(
xx: XrT, poly: Geometry, apply_mask: bool = True, all_touched: bool = True
) -> XrT:
"""
Crops and optionally mask an xr.Dataset or xr.DataArray to the
spatial extent of a geometry.
Crops and optionally mask an :py:class:`xarray.Dataset` or
:py:class:`xarray.DataArray` to the spatial extent of a geometry.
:param xx:
:py:class:`~xarray.Dataset` or :py:class:`~xarray.DataArray`.
Expand Down Expand Up @@ -795,7 +795,9 @@ def map_bounds(self) -> Tuple[Tuple[float, float], Tuple[float, float]]:

mask = _wrap_op(mask)
crop = _wrap_op(crop)
explore = _wrap_op(explore)

if have.rasterio:
explore = _wrap_op(explore)


@xarray.register_dataarray_accessor("odc")
Expand Down
5 changes: 2 additions & 3 deletions odc/geo/xr.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
colorize,
crop,
mask,
explore,
rasterize,
register_geobox,
spatial_dims,
Expand Down Expand Up @@ -50,12 +49,11 @@
"to_rgba",
"crop",
"mask",
"explore",
]

# pylint: disable=import-outside-toplevel,unused-import
if have.rasterio:
from ._xr_interop import add_to, compress, rio_reproject, to_cog, write_cog
from ._xr_interop import add_to, compress, rio_reproject, to_cog, write_cog, explore

__all__.extend(
[
Expand All @@ -64,5 +62,6 @@
"add_to",
"rio_reproject",
"compress",
"explore",
]
)

0 comments on commit a26c406

Please sign in to comment.