Skip to content

bokehmap module

Map

Source code in leafmap/bokehmap.py
class Map:
    def __init__(
        self,
        center: List[float] = [10, 0],
        zoom: float = 2,
        width: float = 800,
        height: float = 400,
        basemap: Optional[str] = "OpenStreetMap",
        grid_visible: bool = False,
        output_notebook: bool = True,
        **kwargs,
    ):
        if "x_axis_type" not in kwargs:
            kwargs["x_axis_type"] = "mercator"
        if "y_axis_type" not in kwargs:
            kwargs["y_axis_type"] = "mercator"
        if "sizing_mode" not in kwargs:
            kwargs["sizing_mode"] = "scale_both"
        if "width" not in kwargs:
            kwargs["width"] = width
        if "height" not in kwargs:
            kwargs["height"] = height

        if "x_range" not in kwargs:
            kwargs["x_range"] = center_zoom_to_xy_range(center, zoom)[0]
        if "y_range" not in kwargs:
            kwargs["y_range"] = center_zoom_to_xy_range(center, zoom)[1]

        fig = figure(**kwargs)
        self.figure = fig

        if basemap is not None:
            if basemap == "OpenStreetMap":
                try:
                    fig.add_tile(xyz.OpenStreetMap.Mapnik, retina=True)
                except:
                    from bokeh.tile_providers import get_provider

                    fig.add_tile(get_provider(xyz.OpenStreetMap.Mapnik))
            else:
                self.add_basemap(basemap)
        fig.toolbar.active_scroll = fig.select_one(WheelZoomTool)

        if not grid_visible:
            fig.grid.visible = False

        self.output_notebook = output_notebook
        self.output_notebook_done = False

    def _repr_mimebundle_(self, **kwargs) -> None:
        """Display the bokeh map. Reference: https://ipython.readthedocs.io/en/stable/config/integrating.html#MyObject._repr_mimebundle_"""
        if self.output_notebook and (os.environ["OUTPUT_NOTEBOOK"] == "False"):
            output_notebook()
            os.environ["OUTPUT_NOTEBOOK"] = "True"
        show(self.figure)

    def add_basemap(
        self,
        basemap: Optional[str] = "OpenStreetMap",
        retina: Optional[bool] = True,
        **kwargs,
    ) -> None:
        """Adds a basemap to the map.

        Args:
            basemap (str, optional): Can be one of string from basemaps. Defaults to 'OpenStreetMap'.
            retina (bool, optional): Whether to use retina tiles. Defaults to True.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
        """
        import xyzservices

        if isinstance(basemap, xyzservices.TileProvider):
            url = basemap.build_url()
            attribution = basemap.attribution
            if "max_zoom" in basemap.keys():
                max_zoom = basemap["max_zoom"]
            else:
                max_zoom = 30
            tile_options = {
                "url": url,
                "attribution": attribution,
                "max_zoom": max_zoom,
            }
            tile_source = WMTSTileSource(**tile_options)
            self.add_tile(tile_source, retina=retina, **kwargs)
        elif isinstance(basemap, WMTSTileSource):
            self.add_tile(basemap, retina=retina, **kwargs)
        elif isinstance(basemap, str):
            if basemap in basemaps.keys():
                self.add_tile(basemaps[basemap], retina=retina, **kwargs)
            else:
                try:
                    self.add_tile(basemap, retina=retina, **kwargs)
                except Exception as e:
                    print(e)
                    raise ValueError(
                        f"Basemap {basemap} is not supported. Please choose one from {basemaps.keys()}"
                    )
        elif basemap is None:
            raise ValueError("Please specify a valid basemap")

    def add_tile(self, tile: str, **kwargs) -> None:
        """Adds a tile to the map.

        Args:
            tile (bokeh.models.tiles.WMTSTileSource): A bokeh tile.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
        """
        try:
            self.figure.add_tile(tile, **kwargs)
        except Exception as e:
            if "retina" in kwargs.keys():
                kwargs.pop("retina")
            self.figure.add_tile(tile, **kwargs)

    def add_cog_layer(
        self,
        url: str,
        attribution: str = "",
        bands: Optional[List[str]] = None,
        titiler_endpoint: Optional[str] = None,
        cog_args: Dict = {},
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Adds a COG TileLayer to the map.

        Args:
            url (str): The URL of the COG tile layer.
            attribution (str, optional): The attribution to use. Defaults to ''.
            bands (list, optional): A list of bands to use for the layer. Defaults to None.
            titiler_endpoint (str, optional): Titiler endpoint. Defaults to "https://titiler.xyz".
            cog_args: Arbitrary keyword arguments, including bidx, expression, nodata, unscale, resampling, rescale,
                color_formula, colormap, colormap_name, return_mask. See https://developmentseed.org/titiler/endpoints/cog/
                and https://cogeotiff.github.io/rio-tiler/colormap/. To select a certain bands, use bidx=[1, 2, 3].
                apply a rescaling to multiple bands, use something like `rescale=["164,223","130,211","99,212"]`.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
        """
        tile_url = cog_tile(url, bands, titiler_endpoint, **cog_args)
        tile_options = {
            "url": tile_url,
            "attribution": attribution,
        }
        tile_source = WMTSTileSource(**tile_options)
        self.figure.add_tile(tile_source, **kwargs)

        if fit_bounds:
            self.fit_bounds(cog_bounds(url, titiler_endpoint))

    def add_raster(
        self,
        source: str,
        indexes: Optional[List] = None,
        colormap: Optional[str] = None,
        vmin: Optional[float] = None,
        vmax: Optional[float] = None,
        nodata: Optional[float] = None,
        attribution: Optional[str] = "",
        fit_bounds: bool = True,
        layer_name="Local COG",
        open_args={},
        **kwargs,
    ) -> None:
        """Add a local raster dataset to the map.
            If you are using this function in JupyterHub on a remote server (e.g., Binder, Microsoft Planetary Computer) and
            if the raster does not render properly, try running the following code before calling this function:

            import os
            os.environ['LOCALTILESERVER_CLIENT_PREFIX'] = 'proxy/{port}'

        Args:
            source (str): The path to the GeoTIFF file or the URL of the Cloud Optimized GeoTIFF.
            indexes (int, optional): The band(s) to use. Band indexing starts at 1. Defaults to None.
            colormap (str, optional): The name of the colormap from `matplotlib` to use when plotting a single band. See https://matplotlib.org/stable/gallery/color/colormap_reference.html. Default is greyscale.
            vmin (float, optional): The minimum value to use when colormapping the colormap when plotting a single band. Defaults to None.
            vmax (float, optional): The maximum value to use when colormapping the colormap when plotting a single band. Defaults to None.
            nodata (float, optional): The value from the band to use to interpret as not valid data. Defaults to None.
            attribution (str, optional): Attribution for the source raster. This defaults to a message about it being a local file. Defaults to None.
            layer_name (str, optional): The layer name to use. Defaults to 'Local COG'.
            fit_bounds (bool, optional): Whether to fit the map bounds to the raster bounds. Defaults to True.
            open_args: Arbitrary keyword arguments for get_local_tile_layer(). Defaults to {}.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
        """

        if source.startswith("http"):
            source = download_file(source)

        tile_layer, client = get_local_tile_layer(
            source,
            indexes=indexes,
            colormap=colormap,
            vmin=vmin,
            vmax=vmax,
            nodata=nodata,
            attribution=attribution,
            layer_name=layer_name,
            return_client=True,
            **open_args,
        )

        tile_options = {
            "url": tile_layer.url,
            "attribution": attribution,
        }
        tile_source = WMTSTileSource(**tile_options)
        self.figure.add_tile(tile_source, **kwargs)

        if fit_bounds:
            bounds = client.bounds()
            bounds = [bounds[2], bounds[0], bounds[3], bounds[1]]
            self.fit_bounds(bounds)

    def add_stac_layer(
        self,
        url: str,
        collection: Optional[str] = None,
        item: Optional[str] = None,
        assets: Optional[str] = None,
        bands: Optional[List[str]] = None,
        titiler_endpoint: Optional[str] = None,
        attribution: Optional[str] = "",
        fit_bounds: Optional[bool] = True,
        open_args={},
        **kwargs,
    ) -> None:
        """Adds a STAC TileLayer to the map.

        Args:
            url (str): HTTP URL to a STAC item, e.g., https://canada-spot-ortho.s3.amazonaws.com/canada_spot_orthoimages/canada_spot5_orthoimages/S5_2007/S5_11055_6057_20070622/S5_11055_6057_20070622.json
            collection (str | Optional): The Microsoft Planetary Computer STAC collection ID, e.g., landsat-8-c2-l2.
            item (str): The Microsoft Planetary Computer STAC item ID, e.g., LC08_L2SP_047027_20201204_02_T1.
            assets (str | list): The Microsoft Planetary Computer STAC asset ID, e.g., ["SR_B7", "SR_B5", "SR_B4"].
            bands (list): A list of band names, e.g., ["SR_B7", "SR_B5", "SR_B4"]
            titiler_endpoint (str, optional): TiTiler endpoint, e.g., "https://titiler.xyz", "https://planetarycomputer.microsoft.com/api/data/v1", "planetary-computer", "pc". Defaults to None.
            attribution (str, optional): The attribution to use. Defaults to ''.
            fit_bounds (bool, optional): Whether to fit the map bounds to the raster bounds. Defaults to True.
            open_args: Arbitrary keyword arguments for get_local_tile_layer(). Defaults to {}.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

        """
        tile_url = stac_tile(
            url, collection, item, assets, bands, titiler_endpoint, **open_args
        )
        tile_options = {
            "url": tile_url,
            "attribution": attribution,
        }
        tile_source = WMTSTileSource(**tile_options)
        self.figure.add_tile(tile_source, **kwargs)

        if fit_bounds:
            self.fit_bounds(stac_bounds(url, collection, item, titiler_endpoint))

    def add_gdf(
        self,
        gdf,
        to_crs: Optional[str] = "epsg:3857",
        tooltips: Optional[list] = None,
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Adds a GeoDataFrame to the map.

        Args:
            gdf (GeoDataFrame): The GeoDataFrame to add to the map.
            to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
            tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
                https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
        """
        import geopandas as gpd

        if not isinstance(gdf, gpd.GeoDataFrame):
            raise TypeError("gdf must be a GeoDataFrame")

        geom_type = gdf_geom_type(gdf)
        gdf_new = gdf.to_crs(to_crs)

        columns = gdf_new.columns.to_list()
        if "geometry" in columns:
            columns.remove("geometry")

        if tooltips is None:
            tooltips = [(col, f"@{col}") for col in columns]

        source = GeoJSONDataSource(geojson=gdf_new.to_json())

        if geom_type in ["Point", "MultiPoint"]:
            self.figure.circle(x="x", y="y", source=source, **kwargs)
        elif geom_type in ["LineString", "MultiLineString"]:
            self.figure.multi_line(xs="xs", ys="ys", source=source, **kwargs)
        elif geom_type in ["Polygon", "MultiPolygon"]:
            if "fill_alpha" not in kwargs:
                kwargs["fill_alpha"] = 0.5
            self.figure.patches(xs="xs", ys="ys", source=source, **kwargs)

        if len(tooltips) > 0:
            hover = HoverTool(tooltips=tooltips)
            self.figure.add_tools(hover)

        if fit_bounds:
            self.fit_bounds(gdf.total_bounds.tolist())

    def add_geojson(
        self,
        filename: str,
        encoding: Optional[str] = "utf-8",
        read_file_args: Dict = {},
        to_crs: Optional[str] = "epsg:3857",
        tooltips: Optional[List] = None,
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Adds a GeoJSON file to the map.

        Args:
            filename (str): The path to the GeoJSON file. Can be a local file or a URL.
            encoding (str, optional): The encoding of the GeoJSON file. Defaults to "utf-8".
            read_file_args (Dict, optional): A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.
            to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
            tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
            fit_bounds (bool, optional): A flag indicating whether to fit the map bounds to the GeoJSON. Defaults to True.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
                https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
        """
        import geopandas as gpd

        if filename.startswith("http"):
            filename = github_raw_url(filename)

        gdf = gpd.read_file(filename, encoding=encoding, **read_file_args)
        self.add_gdf(
            gdf, to_crs=to_crs, tooltips=tooltips, fit_bounds=fit_bounds, **kwargs
        )

    def add_shp(
        self,
        filename: str,
        encoding: Optional[str] = "utf-8",
        read_file_args: Dict = {},
        to_crs: Optional[str] = "epsg:3857",
        tooltips: Optional[List] = None,
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Adds a shapefile to the map.

        Args:
            filename (str): The path to the shapefile.
            encoding (str, optional): The encoding of the shapefile. Defaults to "utf-8".
            read_file_args (dict, optional): A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.
            to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
            tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
            fit_bounds (bool, optional): A flag indicating whether to fit the map bounds to the shapefile. Defaults to True.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
                https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
        """
        import geopandas as gpd

        import glob

        if filename.startswith("http"):
            filename = github_raw_url(filename)

        if filename.startswith("http") and filename.endswith(".zip"):
            out_dir = os.path.abspath("./cache/shp")
            if not os.path.exists(out_dir):
                os.makedirs(out_dir)
            basename = os.path.basename(filename)
            output = os.path.join(out_dir, basename)
            download_file(filename, output)
            files = list(glob.glob(os.path.join(out_dir, "*.shp")))
            if len(files) > 0:
                filename = files[0]
            else:
                raise FileNotFoundError(
                    "The downloaded zip file does not contain any shapefile in the root directory."
                )
        else:
            filename = os.path.abspath(filename)
            if not os.path.exists(filename):
                raise FileNotFoundError("The provided shapefile could not be found.")

        gdf = gpd.read_file(filename, encoding=encoding, **read_file_args)
        self.add_gdf(
            gdf, to_crs=to_crs, tooltips=tooltips, fit_bounds=fit_bounds, **kwargs
        )

    def add_vector(
        self,
        filename: str,
        encoding: Optional[str] = "utf-8",
        read_file_args: Dict = {},
        to_crs: Optional[str] = "epsg:3857",
        tooltips: Optional[List] = None,
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Adds a vector dataset to the map.

        Args:
            filename (str): The path to the vector dataset. Can be a local file or a URL.
            encoding (str, optional): The encoding of the vector dataset. Defaults to "utf-8".
            read_file_args (Dict, optional): A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.
            to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
            tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
            fit_bounds (bool, optional): A flag indicating whether to fit the map bounds to the vector dataset. Defaults to True.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
                https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
        """
        import geopandas as gpd

        if filename.startswith("http"):
            filename = github_raw_url(filename)

        if isinstance(filename, gpd.GeoDataFrame):
            gdf = filename
        else:
            gdf = gpd.read_file(filename, encoding=encoding, **read_file_args)

        self.add_gdf(
            gdf, to_crs=to_crs, tooltips=tooltips, fit_bounds=fit_bounds, **kwargs
        )

    def to_html(
        self, filename: Optional[str] = None, title: Optional[str] = None, **kwargs
    ) -> None:
        """Converts the map to HTML.

        Args:
            filename (str, optional): The filename to save the HTML to. Defaults to None.
            title (str, optional): The title to use for the HTML. Defaults to None.
            **kwargs: Arbitrary keyword arguments for bokeh.figure.save().
        """
        save(self.figure, filename=filename, title=title, **kwargs)

    def fit_bounds(self, bounds: List[float]):
        """Fits the map to the specified bounds in the form of [xmin, ymin, xmax, ymax].

        Args:
            bounds (list): A list of bounds in the form of [xmin, ymin, xmax, ymax].
        """

        bounds = bounds_to_xy_range(bounds)

        self.figure.x_range.start = bounds[0][0]
        self.figure.x_range.end = bounds[0][1]
        self.figure.y_range.start = bounds[1][0]
        self.figure.y_range.end = bounds[1][1]

    def to_streamlit(
        self,
        width: Optional[int] = 800,
        height: Optional[int] = 600,
        use_container_width: bool = True,
        **kwargs,
    ) -> None:
        """Displays the map in a Streamlit app.

        Args:
            width (int, optional): The width of the map. Defaults to 800.
            height (int, optional): The height of the map. Defaults to 600.
            use_container_width (bool, optional): A flag indicating whether to use the full width of the container. Defaults to True.
            **kwargs: Arbitrary keyword arguments for bokeh.plotting.show().
        """
        import streamlit as st  # pylint: disable=E0401

        self.figure.width = width
        self.figure.height = height

        st.bokeh_chart(
            self.figure,
            use_container_width=use_container_width,
            **kwargs,
        )

add_basemap(self, basemap='OpenStreetMap', retina=True, **kwargs)

Adds a basemap to the map.

Parameters:

Name Type Description Default
basemap str

Can be one of string from basemaps. Defaults to 'OpenStreetMap'.

'OpenStreetMap'
retina bool

Whether to use retina tiles. Defaults to True.

True
**kwargs

Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

{}
Source code in leafmap/bokehmap.py
def add_basemap(
    self,
    basemap: Optional[str] = "OpenStreetMap",
    retina: Optional[bool] = True,
    **kwargs,
) -> None:
    """Adds a basemap to the map.

    Args:
        basemap (str, optional): Can be one of string from basemaps. Defaults to 'OpenStreetMap'.
        retina (bool, optional): Whether to use retina tiles. Defaults to True.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
    """
    import xyzservices

    if isinstance(basemap, xyzservices.TileProvider):
        url = basemap.build_url()
        attribution = basemap.attribution
        if "max_zoom" in basemap.keys():
            max_zoom = basemap["max_zoom"]
        else:
            max_zoom = 30
        tile_options = {
            "url": url,
            "attribution": attribution,
            "max_zoom": max_zoom,
        }
        tile_source = WMTSTileSource(**tile_options)
        self.add_tile(tile_source, retina=retina, **kwargs)
    elif isinstance(basemap, WMTSTileSource):
        self.add_tile(basemap, retina=retina, **kwargs)
    elif isinstance(basemap, str):
        if basemap in basemaps.keys():
            self.add_tile(basemaps[basemap], retina=retina, **kwargs)
        else:
            try:
                self.add_tile(basemap, retina=retina, **kwargs)
            except Exception as e:
                print(e)
                raise ValueError(
                    f"Basemap {basemap} is not supported. Please choose one from {basemaps.keys()}"
                )
    elif basemap is None:
        raise ValueError("Please specify a valid basemap")

add_cog_layer(self, url, attribution='', bands=None, titiler_endpoint=None, cog_args={}, fit_bounds=True, **kwargs)

Adds a COG TileLayer to the map.

Parameters:

Name Type Description Default
url str

The URL of the COG tile layer.

required
attribution str

The attribution to use. Defaults to ''.

''
bands list

A list of bands to use for the layer. Defaults to None.

None
titiler_endpoint str

Titiler endpoint. Defaults to "https://titiler.xyz".

None
cog_args Dict

Arbitrary keyword arguments, including bidx, expression, nodata, unscale, resampling, rescale, color_formula, colormap, colormap_name, return_mask. See https://developmentseed.org/titiler/endpoints/cog/ and https://cogeotiff.github.io/rio-tiler/colormap/. To select a certain bands, use bidx=[1, 2, 3]. apply a rescaling to multiple bands, use something like rescale=["164,223","130,211","99,212"].

{}
**kwargs

Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

{}
Source code in leafmap/bokehmap.py
def add_cog_layer(
    self,
    url: str,
    attribution: str = "",
    bands: Optional[List[str]] = None,
    titiler_endpoint: Optional[str] = None,
    cog_args: Dict = {},
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Adds a COG TileLayer to the map.

    Args:
        url (str): The URL of the COG tile layer.
        attribution (str, optional): The attribution to use. Defaults to ''.
        bands (list, optional): A list of bands to use for the layer. Defaults to None.
        titiler_endpoint (str, optional): Titiler endpoint. Defaults to "https://titiler.xyz".
        cog_args: Arbitrary keyword arguments, including bidx, expression, nodata, unscale, resampling, rescale,
            color_formula, colormap, colormap_name, return_mask. See https://developmentseed.org/titiler/endpoints/cog/
            and https://cogeotiff.github.io/rio-tiler/colormap/. To select a certain bands, use bidx=[1, 2, 3].
            apply a rescaling to multiple bands, use something like `rescale=["164,223","130,211","99,212"]`.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
    """
    tile_url = cog_tile(url, bands, titiler_endpoint, **cog_args)
    tile_options = {
        "url": tile_url,
        "attribution": attribution,
    }
    tile_source = WMTSTileSource(**tile_options)
    self.figure.add_tile(tile_source, **kwargs)

    if fit_bounds:
        self.fit_bounds(cog_bounds(url, titiler_endpoint))

add_gdf(self, gdf, to_crs='epsg:3857', tooltips=None, fit_bounds=True, **kwargs)

Adds a GeoDataFrame to the map.

Parameters:

Name Type Description Default
gdf GeoDataFrame

The GeoDataFrame to add to the map.

required
to_crs str

The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".

'epsg:3857'
tooltips list

A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.

None
**kwargs

Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure

{}
Source code in leafmap/bokehmap.py
def add_gdf(
    self,
    gdf,
    to_crs: Optional[str] = "epsg:3857",
    tooltips: Optional[list] = None,
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Adds a GeoDataFrame to the map.

    Args:
        gdf (GeoDataFrame): The GeoDataFrame to add to the map.
        to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
        tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
            https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
    """
    import geopandas as gpd

    if not isinstance(gdf, gpd.GeoDataFrame):
        raise TypeError("gdf must be a GeoDataFrame")

    geom_type = gdf_geom_type(gdf)
    gdf_new = gdf.to_crs(to_crs)

    columns = gdf_new.columns.to_list()
    if "geometry" in columns:
        columns.remove("geometry")

    if tooltips is None:
        tooltips = [(col, f"@{col}") for col in columns]

    source = GeoJSONDataSource(geojson=gdf_new.to_json())

    if geom_type in ["Point", "MultiPoint"]:
        self.figure.circle(x="x", y="y", source=source, **kwargs)
    elif geom_type in ["LineString", "MultiLineString"]:
        self.figure.multi_line(xs="xs", ys="ys", source=source, **kwargs)
    elif geom_type in ["Polygon", "MultiPolygon"]:
        if "fill_alpha" not in kwargs:
            kwargs["fill_alpha"] = 0.5
        self.figure.patches(xs="xs", ys="ys", source=source, **kwargs)

    if len(tooltips) > 0:
        hover = HoverTool(tooltips=tooltips)
        self.figure.add_tools(hover)

    if fit_bounds:
        self.fit_bounds(gdf.total_bounds.tolist())

add_geojson(self, filename, encoding='utf-8', read_file_args={}, to_crs='epsg:3857', tooltips=None, fit_bounds=True, **kwargs)

Adds a GeoJSON file to the map.

Parameters:

Name Type Description Default
filename str

The path to the GeoJSON file. Can be a local file or a URL.

required
encoding str

The encoding of the GeoJSON file. Defaults to "utf-8".

'utf-8'
read_file_args Dict

A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.

{}
to_crs str

The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".

'epsg:3857'
tooltips list

A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.

None
fit_bounds bool

A flag indicating whether to fit the map bounds to the GeoJSON. Defaults to True.

True
**kwargs

Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure

{}
Source code in leafmap/bokehmap.py
def add_geojson(
    self,
    filename: str,
    encoding: Optional[str] = "utf-8",
    read_file_args: Dict = {},
    to_crs: Optional[str] = "epsg:3857",
    tooltips: Optional[List] = None,
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Adds a GeoJSON file to the map.

    Args:
        filename (str): The path to the GeoJSON file. Can be a local file or a URL.
        encoding (str, optional): The encoding of the GeoJSON file. Defaults to "utf-8".
        read_file_args (Dict, optional): A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.
        to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
        tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
        fit_bounds (bool, optional): A flag indicating whether to fit the map bounds to the GeoJSON. Defaults to True.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
            https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
    """
    import geopandas as gpd

    if filename.startswith("http"):
        filename = github_raw_url(filename)

    gdf = gpd.read_file(filename, encoding=encoding, **read_file_args)
    self.add_gdf(
        gdf, to_crs=to_crs, tooltips=tooltips, fit_bounds=fit_bounds, **kwargs
    )

add_raster(self, source, indexes=None, colormap=None, vmin=None, vmax=None, nodata=None, attribution='', fit_bounds=True, layer_name='Local COG', open_args={}, **kwargs)

Add a local raster dataset to the map. If you are using this function in JupyterHub on a remote server (e.g., Binder, Microsoft Planetary Computer) and if the raster does not render properly, try running the following code before calling this function:

1
2
import os
os.environ['LOCALTILESERVER_CLIENT_PREFIX'] = 'proxy/{port}'

Parameters:

Name Type Description Default
source str

The path to the GeoTIFF file or the URL of the Cloud Optimized GeoTIFF.

required
indexes int

The band(s) to use. Band indexing starts at 1. Defaults to None.

None
colormap str

The name of the colormap from matplotlib to use when plotting a single band. See https://matplotlib.org/stable/gallery/color/colormap_reference.html. Default is greyscale.

None
vmin float

The minimum value to use when colormapping the colormap when plotting a single band. Defaults to None.

None
vmax float

The maximum value to use when colormapping the colormap when plotting a single band. Defaults to None.

None
nodata float

The value from the band to use to interpret as not valid data. Defaults to None.

None
attribution str

Attribution for the source raster. This defaults to a message about it being a local file. Defaults to None.

''
layer_name str

The layer name to use. Defaults to 'Local COG'.

'Local COG'
fit_bounds bool

Whether to fit the map bounds to the raster bounds. Defaults to True.

True
open_args

Arbitrary keyword arguments for get_local_tile_layer(). Defaults to {}.

{}
**kwargs

Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

{}
Source code in leafmap/bokehmap.py
def add_raster(
    self,
    source: str,
    indexes: Optional[List] = None,
    colormap: Optional[str] = None,
    vmin: Optional[float] = None,
    vmax: Optional[float] = None,
    nodata: Optional[float] = None,
    attribution: Optional[str] = "",
    fit_bounds: bool = True,
    layer_name="Local COG",
    open_args={},
    **kwargs,
) -> None:
    """Add a local raster dataset to the map.
        If you are using this function in JupyterHub on a remote server (e.g., Binder, Microsoft Planetary Computer) and
        if the raster does not render properly, try running the following code before calling this function:

        import os
        os.environ['LOCALTILESERVER_CLIENT_PREFIX'] = 'proxy/{port}'

    Args:
        source (str): The path to the GeoTIFF file or the URL of the Cloud Optimized GeoTIFF.
        indexes (int, optional): The band(s) to use. Band indexing starts at 1. Defaults to None.
        colormap (str, optional): The name of the colormap from `matplotlib` to use when plotting a single band. See https://matplotlib.org/stable/gallery/color/colormap_reference.html. Default is greyscale.
        vmin (float, optional): The minimum value to use when colormapping the colormap when plotting a single band. Defaults to None.
        vmax (float, optional): The maximum value to use when colormapping the colormap when plotting a single band. Defaults to None.
        nodata (float, optional): The value from the band to use to interpret as not valid data. Defaults to None.
        attribution (str, optional): Attribution for the source raster. This defaults to a message about it being a local file. Defaults to None.
        layer_name (str, optional): The layer name to use. Defaults to 'Local COG'.
        fit_bounds (bool, optional): Whether to fit the map bounds to the raster bounds. Defaults to True.
        open_args: Arbitrary keyword arguments for get_local_tile_layer(). Defaults to {}.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
    """

    if source.startswith("http"):
        source = download_file(source)

    tile_layer, client = get_local_tile_layer(
        source,
        indexes=indexes,
        colormap=colormap,
        vmin=vmin,
        vmax=vmax,
        nodata=nodata,
        attribution=attribution,
        layer_name=layer_name,
        return_client=True,
        **open_args,
    )

    tile_options = {
        "url": tile_layer.url,
        "attribution": attribution,
    }
    tile_source = WMTSTileSource(**tile_options)
    self.figure.add_tile(tile_source, **kwargs)

    if fit_bounds:
        bounds = client.bounds()
        bounds = [bounds[2], bounds[0], bounds[3], bounds[1]]
        self.fit_bounds(bounds)

add_shp(self, filename, encoding='utf-8', read_file_args={}, to_crs='epsg:3857', tooltips=None, fit_bounds=True, **kwargs)

Adds a shapefile to the map.

Parameters:

Name Type Description Default
filename str

The path to the shapefile.

required
encoding str

The encoding of the shapefile. Defaults to "utf-8".

'utf-8'
read_file_args dict

A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.

{}
to_crs str

The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".

'epsg:3857'
tooltips list

A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.

None
fit_bounds bool

A flag indicating whether to fit the map bounds to the shapefile. Defaults to True.

True
**kwargs

Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure

{}
Source code in leafmap/bokehmap.py
def add_shp(
    self,
    filename: str,
    encoding: Optional[str] = "utf-8",
    read_file_args: Dict = {},
    to_crs: Optional[str] = "epsg:3857",
    tooltips: Optional[List] = None,
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Adds a shapefile to the map.

    Args:
        filename (str): The path to the shapefile.
        encoding (str, optional): The encoding of the shapefile. Defaults to "utf-8".
        read_file_args (dict, optional): A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.
        to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
        tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
        fit_bounds (bool, optional): A flag indicating whether to fit the map bounds to the shapefile. Defaults to True.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
            https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
    """
    import geopandas as gpd

    import glob

    if filename.startswith("http"):
        filename = github_raw_url(filename)

    if filename.startswith("http") and filename.endswith(".zip"):
        out_dir = os.path.abspath("./cache/shp")
        if not os.path.exists(out_dir):
            os.makedirs(out_dir)
        basename = os.path.basename(filename)
        output = os.path.join(out_dir, basename)
        download_file(filename, output)
        files = list(glob.glob(os.path.join(out_dir, "*.shp")))
        if len(files) > 0:
            filename = files[0]
        else:
            raise FileNotFoundError(
                "The downloaded zip file does not contain any shapefile in the root directory."
            )
    else:
        filename = os.path.abspath(filename)
        if not os.path.exists(filename):
            raise FileNotFoundError("The provided shapefile could not be found.")

    gdf = gpd.read_file(filename, encoding=encoding, **read_file_args)
    self.add_gdf(
        gdf, to_crs=to_crs, tooltips=tooltips, fit_bounds=fit_bounds, **kwargs
    )

add_stac_layer(self, url, collection=None, item=None, assets=None, bands=None, titiler_endpoint=None, attribution='', fit_bounds=True, open_args={}, **kwargs)

Adds a STAC TileLayer to the map.

Parameters:

Name Type Description Default
url str

HTTP URL to a STAC item, e.g., https://canada-spot-ortho.s3.amazonaws.com/canada_spot_orthoimages/canada_spot5_orthoimages/S5_2007/S5_11055_6057_20070622/S5_11055_6057_20070622.json

required
collection str | Optional

The Microsoft Planetary Computer STAC collection ID, e.g., landsat-8-c2-l2.

None
item str

The Microsoft Planetary Computer STAC item ID, e.g., LC08_L2SP_047027_20201204_02_T1.

None
assets str | list

The Microsoft Planetary Computer STAC asset ID, e.g., ["SR_B7", "SR_B5", "SR_B4"].

None
bands list

A list of band names, e.g., ["SR_B7", "SR_B5", "SR_B4"]

None
titiler_endpoint str

TiTiler endpoint, e.g., "https://titiler.xyz", "https://planetarycomputer.microsoft.com/api/data/v1", "planetary-computer", "pc". Defaults to None.

None
attribution str

The attribution to use. Defaults to ''.

''
fit_bounds bool

Whether to fit the map bounds to the raster bounds. Defaults to True.

True
open_args

Arbitrary keyword arguments for get_local_tile_layer(). Defaults to {}.

{}
**kwargs

Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

{}
Source code in leafmap/bokehmap.py
def add_stac_layer(
    self,
    url: str,
    collection: Optional[str] = None,
    item: Optional[str] = None,
    assets: Optional[str] = None,
    bands: Optional[List[str]] = None,
    titiler_endpoint: Optional[str] = None,
    attribution: Optional[str] = "",
    fit_bounds: Optional[bool] = True,
    open_args={},
    **kwargs,
) -> None:
    """Adds a STAC TileLayer to the map.

    Args:
        url (str): HTTP URL to a STAC item, e.g., https://canada-spot-ortho.s3.amazonaws.com/canada_spot_orthoimages/canada_spot5_orthoimages/S5_2007/S5_11055_6057_20070622/S5_11055_6057_20070622.json
        collection (str | Optional): The Microsoft Planetary Computer STAC collection ID, e.g., landsat-8-c2-l2.
        item (str): The Microsoft Planetary Computer STAC item ID, e.g., LC08_L2SP_047027_20201204_02_T1.
        assets (str | list): The Microsoft Planetary Computer STAC asset ID, e.g., ["SR_B7", "SR_B5", "SR_B4"].
        bands (list): A list of band names, e.g., ["SR_B7", "SR_B5", "SR_B4"]
        titiler_endpoint (str, optional): TiTiler endpoint, e.g., "https://titiler.xyz", "https://planetarycomputer.microsoft.com/api/data/v1", "planetary-computer", "pc". Defaults to None.
        attribution (str, optional): The attribution to use. Defaults to ''.
        fit_bounds (bool, optional): Whether to fit the map bounds to the raster bounds. Defaults to True.
        open_args: Arbitrary keyword arguments for get_local_tile_layer(). Defaults to {}.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

    """
    tile_url = stac_tile(
        url, collection, item, assets, bands, titiler_endpoint, **open_args
    )
    tile_options = {
        "url": tile_url,
        "attribution": attribution,
    }
    tile_source = WMTSTileSource(**tile_options)
    self.figure.add_tile(tile_source, **kwargs)

    if fit_bounds:
        self.fit_bounds(stac_bounds(url, collection, item, titiler_endpoint))

add_tile(self, tile, **kwargs)

Adds a tile to the map.

Parameters:

Name Type Description Default
tile bokeh.models.tiles.WMTSTileSource

A bokeh tile.

required
**kwargs

Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.

{}
Source code in leafmap/bokehmap.py
def add_tile(self, tile: str, **kwargs) -> None:
    """Adds a tile to the map.

    Args:
        tile (bokeh.models.tiles.WMTSTileSource): A bokeh tile.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.add_tile() function, such as alpha, visible, etc.
    """
    try:
        self.figure.add_tile(tile, **kwargs)
    except Exception as e:
        if "retina" in kwargs.keys():
            kwargs.pop("retina")
        self.figure.add_tile(tile, **kwargs)

add_vector(self, filename, encoding='utf-8', read_file_args={}, to_crs='epsg:3857', tooltips=None, fit_bounds=True, **kwargs)

Adds a vector dataset to the map.

Parameters:

Name Type Description Default
filename str

The path to the vector dataset. Can be a local file or a URL.

required
encoding str

The encoding of the vector dataset. Defaults to "utf-8".

'utf-8'
read_file_args Dict

A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.

{}
to_crs str

The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".

'epsg:3857'
tooltips list

A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.

None
fit_bounds bool

A flag indicating whether to fit the map bounds to the vector dataset. Defaults to True.

True
**kwargs

Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure

{}
Source code in leafmap/bokehmap.py
def add_vector(
    self,
    filename: str,
    encoding: Optional[str] = "utf-8",
    read_file_args: Dict = {},
    to_crs: Optional[str] = "epsg:3857",
    tooltips: Optional[List] = None,
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Adds a vector dataset to the map.

    Args:
        filename (str): The path to the vector dataset. Can be a local file or a URL.
        encoding (str, optional): The encoding of the vector dataset. Defaults to "utf-8".
        read_file_args (Dict, optional): A dictionary of arguments to pass to geopandas.read_file. Defaults to {}.
        to_crs (str, optional): The CRS to use for the GeoDataFrame. Defaults to "epsg:3857".
        tooltips (list, optional): A list of column names to use for tooltips in the form of [(name, @column_name), ...]. Defaults to None, which uses all columns.
        fit_bounds (bool, optional): A flag indicating whether to fit the map bounds to the vector dataset. Defaults to True.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.circle, multi_line, and patches. For more info, see
            https://docs.bokeh.org/en/latest/docs/reference/plotting/figure.html#bokeh.plotting.figure
    """
    import geopandas as gpd

    if filename.startswith("http"):
        filename = github_raw_url(filename)

    if isinstance(filename, gpd.GeoDataFrame):
        gdf = filename
    else:
        gdf = gpd.read_file(filename, encoding=encoding, **read_file_args)

    self.add_gdf(
        gdf, to_crs=to_crs, tooltips=tooltips, fit_bounds=fit_bounds, **kwargs
    )

fit_bounds(self, bounds)

Fits the map to the specified bounds in the form of [xmin, ymin, xmax, ymax].

Parameters:

Name Type Description Default
bounds list

A list of bounds in the form of [xmin, ymin, xmax, ymax].

required
Source code in leafmap/bokehmap.py
def fit_bounds(self, bounds: List[float]):
    """Fits the map to the specified bounds in the form of [xmin, ymin, xmax, ymax].

    Args:
        bounds (list): A list of bounds in the form of [xmin, ymin, xmax, ymax].
    """

    bounds = bounds_to_xy_range(bounds)

    self.figure.x_range.start = bounds[0][0]
    self.figure.x_range.end = bounds[0][1]
    self.figure.y_range.start = bounds[1][0]
    self.figure.y_range.end = bounds[1][1]

to_html(self, filename=None, title=None, **kwargs)

Converts the map to HTML.

Parameters:

Name Type Description Default
filename str

The filename to save the HTML to. Defaults to None.

None
title str

The title to use for the HTML. Defaults to None.

None
**kwargs

Arbitrary keyword arguments for bokeh.figure.save().

{}
Source code in leafmap/bokehmap.py
def to_html(
    self, filename: Optional[str] = None, title: Optional[str] = None, **kwargs
) -> None:
    """Converts the map to HTML.

    Args:
        filename (str, optional): The filename to save the HTML to. Defaults to None.
        title (str, optional): The title to use for the HTML. Defaults to None.
        **kwargs: Arbitrary keyword arguments for bokeh.figure.save().
    """
    save(self.figure, filename=filename, title=title, **kwargs)

to_streamlit(self, width=800, height=600, use_container_width=True, **kwargs)

Displays the map in a Streamlit app.

Parameters:

Name Type Description Default
width int

The width of the map. Defaults to 800.

800
height int

The height of the map. Defaults to 600.

600
use_container_width bool

A flag indicating whether to use the full width of the container. Defaults to True.

True
**kwargs

Arbitrary keyword arguments for bokeh.plotting.show().

{}
Source code in leafmap/bokehmap.py
def to_streamlit(
    self,
    width: Optional[int] = 800,
    height: Optional[int] = 600,
    use_container_width: bool = True,
    **kwargs,
) -> None:
    """Displays the map in a Streamlit app.

    Args:
        width (int, optional): The width of the map. Defaults to 800.
        height (int, optional): The height of the map. Defaults to 600.
        use_container_width (bool, optional): A flag indicating whether to use the full width of the container. Defaults to True.
        **kwargs: Arbitrary keyword arguments for bokeh.plotting.show().
    """
    import streamlit as st  # pylint: disable=E0401

    self.figure.width = width
    self.figure.height = height

    st.bokeh_chart(
        self.figure,
        use_container_width=use_container_width,
        **kwargs,
    )