Source code for odc.geo._map

from typing import Any, Dict, Optional, Tuple

import xarray as xr

from ._interop import have
from ._rgba import colorize, is_rgb
from .converters import map_crs
from .gcp import GCPGeoBox
from .geobox import GeoBox


# pylint: disable=import-outside-toplevel, redefined-builtin, too-many-locals
def _add_to_folium(url, bounds, map, name=None, index=None, **kw):
    assert have.folium

    from folium.raster_layers import ImageOverlay

    img_overlay = ImageOverlay(url, bounds, name=name, **kw)
    if map is not None:
        img_overlay.add_to(map, name=name, index=index)
    return img_overlay


def _add_to_ipyleaflet(url, bounds, map, name=None, index=None, **kw):
    assert have.ipyleaflet

    from ipyleaflet import ImageOverlay, Map

    assert isinstance(map, Map)

    if name is not None:
        kw.update(name=name)

    img_overlay = ImageOverlay(url=url, bounds=bounds, **kw)
    if map is not None:
        map.add(img_overlay, index=index)

    return img_overlay


def _get_add_to_method(map):
    if map is None:
        return None

    map_module = getattr(map, "__module__", "")

    if "folium" in map_module:
        return _add_to_folium

    if "ipyleaflet" in map_module:
        return _add_to_ipyleaflet

    raise ValueError(f"Not sure how to add image to: '{type(map)}'")


[docs] def add_to( xx: Any, map: Any, *, name: Optional[str] = None, index: Optional[int] = None, fmt: str = "png", max_size: int = 4096, resampling: str = "nearest", # jpeg options: transparent_pixel: Optional[Tuple[int, int, int]] = None, # RGB conversion parameters cmap: Optional[Any] = None, clip: bool = False, vmin: Optional[float] = None, vmax: Optional[float] = None, robust: Optional[bool] = None, # passed to ImageOverlay constructor **kw, ) -> Any: """ Add image to an interactive map. If map is not supplied, image data url and bounds are returned instead. :param xx: The :py:class:`~xarray.DataArray` to display :param map: Map object, :py:mod:`folium` and :py:mod:`ipyleaflet` are understood; can also be ``None`` which will return an image data url and bounds instead. :param name: The name of the layer as it will appear in :py:mod:`folium` and :py:mod:`ipyleaflet` Layer Controls. The default ``None`` will use the input array name (e.g. ``xx.name``) if it exists. :param fmt: Compress image format. Defaults to "png"; also supports "webp", "jpeg". :param max_size: If longest dimension is bigger than this, shrink it down before compression; defaults to 4096. :param resampling: Custom resampling method to use when reprojecting ``xx`` to the map CRS; defaults to "nearest". :param transparent_pixel: Replace transparent pixels with this value, needed for "jpeg". :param cmap: If supplied array is not RGB use this colormap to turn it into one. :param clip: When converting to RGB clip input values to fit ``cmap``. :param vmin: Used with matplotlib colormaps :param vmax: Used with matplotlib colormaps :param robust: Used with matplotlib colormaps, ``vmin=2%, vmax=98%`` :raises ValueError: when map object is not understood :return: ImageLayer that was added to a map :return: ``(url, bounds)`` when ``map is None``. .. seealso:: :py:meth:`~odc.geo.xr.colorize`, :py:meth:`~odc.geo.xr.to_rgba` """ from .xr import ODCExtensionDa assert isinstance(xx, xr.DataArray) assert isinstance(xx.odc, ODCExtensionDa) _add_to = _get_add_to_method(map) # raises on error _crs = map_crs(map) # If array xx has a name (xx.name), use it by default if (name is None) and (xx.name is not None): name = xx.name if isinstance(xx.name, str) else str(xx.name) gbox0 = xx.odc.geobox assert gbox0 is not None native_crs = gbox0.crs assert native_crs is not None gbox = gbox0 if isinstance(gbox, GCPGeoBox): if _crs is not None: gbox = xx.odc.output_geobox(_crs, tight=True) else: gbox = xx.odc.output_geobox(native_crs, tight=True) if _crs is not None and gbox.crs != _crs: gbox = gbox.to_crs(_crs, tight=True) if not gbox.axis_aligned: gbox = GeoBox.from_bbox( gbox.boundingbox, resolution=gbox.resolution, tight=True ) if max(*gbox.shape) > max_size: gbox = gbox.zoom_to(max_size) if gbox is not gbox0: xx = xx.odc.reproject(gbox, resampling=resampling) if not is_rgb(xx): xx = colorize(xx, cmap=cmap, clip=clip, vmin=vmin, vmax=vmax, robust=robust) compress_opts = [fmt] for opt in ["zlevel", "quality"]: if (v := kw.pop(opt, None)) is not None: compress_opts.append(v) url = xx.odc.compress( *compress_opts, as_data_url=True, transparent=transparent_pixel ) bounds = gbox.map_bounds() if _add_to is None: return url, bounds return _add_to(url, bounds, map, name=name, index=index, **kw)
def explore( xx: Any, map: 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, layer_control: bool = True, resampling: str = "nearest", map_kwds: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: """ Plot xarray data on an interactive :py:mod:`folium` leaflet map for rapid data exploration. :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.Dataset` or :py:class:`~xarray.DataArray` to plot on the map. :param map: 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 used for the color stretch. :param vmax: 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`` 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", "CartoDB positron", "CartoDB dark_matter" or a custom XYZ URL. :param attr: Map tile attribution; only required if passing custom tile URL. :param layer_control: Whether to add a control to the map to show or hide map layers. If a layer control already exists, this will be skipped. :param resampling: Custom resampling method to use when reprojecting ``xx`` to the map CRS; defaults to "nearest". :param map_kwds: Additional keyword arguments to pass to :py:class:`folium.Map`. :param kwargs: Additional keyword arguments to pass to ``.odc.add_to()``. :return: A :py:mod:`folium` map containing the plotted xarray data. """ # pylint: disable=too-many-arguments, protected-access if not have.folium: raise ModuleNotFoundError( "'folium' is required but not installed. " "Please install it before using `.explore()`." ) from folium import LayerControl, Map map_kwds = {} if map_kwds is None else map_kwds new_map = map is None # If input is a dataset, convert to an RGBA array if isinstance(xx, xr.Dataset): xx = xx.odc.to_rgba(bands=bands, vmin=vmin, vmax=vmax) # Create folium Map if required if map is None: map = Map(tiles=tiles, attr=attr, **map_kwds) # Add to map and raise a friendly error if data has unsuitable dims try: xx.odc.add_to( map, cmap=cmap, vmin=vmin, vmax=vmax, robust=robust, resampling=resampling, **kwargs, ) except ValueError as e: 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()`." ) from e if new_map: # Zoom map to extent of data map.fit_bounds(xx.odc.map_bounds()) # Add a layer control if requested if layer_control: LayerControl().add_to(map) return map