From 067d93d2ce174aaab1a430a6c2e71eeb0c058cae Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 22 Dec 2022 23:34:03 -0500 Subject: [PATCH] Added colorbar support for folium (#330) * Added colorbar support for folium #329 * Updated notebooks * Fixed testing error Former-commit-id: 0fc89c09554d409e635c960ba6301cf79649298a --- docs/notebooks/03_cog_stac.ipynb | 10 +- docs/notebooks/62_folium_colorbar.ipynb | 234 ++++++++++++++++++++ docs/tutorials.md | 1 + examples/README.md | 8 +- examples/notebooks/03_cog_stac.ipynb | 10 +- examples/notebooks/62_folium_colorbar.ipynb | 234 ++++++++++++++++++++ leafmap/common.py | 130 +++++++++++ leafmap/foliumap.py | 101 +++++++-- leafmap/leafmap.py | 9 +- tests/test_foliumap.py | 16 +- 10 files changed, 708 insertions(+), 45 deletions(-) create mode 100644 docs/notebooks/62_folium_colorbar.ipynb create mode 100644 examples/notebooks/62_folium_colorbar.ipynb diff --git a/docs/notebooks/03_cog_stac.ipynb b/docs/notebooks/03_cog_stac.ipynb index b1daa34ecc..fd8fab4f2b 100644 --- a/docs/notebooks/03_cog_stac.ipynb +++ b/docs/notebooks/03_cog_stac.ipynb @@ -71,7 +71,8 @@ "metadata": {}, "outputs": [], "source": [ - "os.environ['TITILER_ENDPOINT'] = 'https://titiler.xyz' # Use the TiTiler demo endpoint. Replace this if needed." + "# Use the TiTiler demo endpoint. Replace this if needed.\n", + "os.environ['TITILER_ENDPOINT'] = 'https://titiler.xyz'" ] }, { @@ -393,7 +394,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "31f05ea183a4718249d13ada7f166c6bdba1d00716247af5c11c23af8d5923f1" + } } }, "nbformat": 4, diff --git a/docs/notebooks/62_folium_colorbar.ipynb b/docs/notebooks/62_folium_colorbar.ipynb new file mode 100644 index 0000000000..0735d05827 --- /dev/null +++ b/docs/notebooks/62_folium_colorbar.ipynb @@ -0,0 +1,234 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f43c888-4745-4ce3-a4ce-1b23926b53e7", + "metadata": {}, + "source": [ + "[![image](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://demo.leafmap.org/lab/index.html?path=notebooks/62_folium_colorbar.ipynb)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://githubtocolab.com/giswqs/leafmap/blob/master/examples/notebooks/62_folium_colorbar.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://gishub.org/leafmap-binder)\n", + "\n", + "**Adding colorbars to a folium map**\n", + "\n", + "Uncomment the following line to install [leafmap](https://leafmap.org) if needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb3d4f02-b029-4bc7-867f-4679c040e087", + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap.foliumap as leafmap" + ] + }, + { + "cell_type": "markdown", + "id": "9d67dffc-4c68-4243-a94c-12e3cb7680a9", + "metadata": {}, + "source": [ + "Unlike the ipyleaflet plotting backend, folium does not support adding matplotlib colormap directly. One workaround is to save the maplotlib colormap as an image, then add the image to the map. Let's first create a colormap." + ] + }, + { + "cell_type": "markdown", + "id": "ef94f639-b905-4f1c-aa03-9faf9538f791", + "metadata": {}, + "source": [ + "Create a colormap using specified parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9404e90b-e886-4022-a9b4-87daed979339", + "metadata": {}, + "outputs": [], + "source": [ + "params = {\n", + " 'width': 4.0,\n", + " 'height': 0.3,\n", + " 'vmin': 0,\n", + " 'vmax': 6000,\n", + " 'cmap': 'terrain',\n", + " 'label': 'Elevation (m)',\n", + " 'orientation': 'horizontal',\n", + " 'transparent': False,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "0c0649d0-75b9-49af-b860-6b765dfd7a00", + "metadata": {}, + "source": [ + "Save the colormap as an image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad11fc89-90f6-4579-92ec-78561223af28", + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.save_colorbar('colorbar.png', **params)" + ] + }, + { + "cell_type": "markdown", + "id": "4c7565f6-0c1f-4107-a44c-8de88837b60a", + "metadata": {}, + "source": [ + "You can also create use the `m.add_colormap()` method to add a colormap. Under the hood, it generate a colormap as an image, then add it to the map. You need to specified the position of the colormap using a tuple (x, y), which represents the percentage [0-100] from the lower-left corner. If the map size changes, you might need to change the colormap position as well. " + ] + }, + { + "cell_type": "markdown", + "id": "c49880d0-71a3-4eeb-add1-af8a94135a95", + "metadata": {}, + "source": [ + "Add a horizontal colormap to the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df5c5eb9-1ddf-4c10-8157-f15f055883be", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "m.add_colormap(position=(55, 5), **params)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "3a0cbd26-3a64-4f3d-ab58-651de5471d3d", + "metadata": {}, + "source": [ + "Make the colormap background transparent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be44f1c4-47e7-48d2-b0a8-75a811a7fae4", + "metadata": {}, + "outputs": [], + "source": [ + "params['transparent'] = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b96ffc1-032b-44d5-9e1c-6993de550c14", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "m.add_colormap(position=(55, 5), **params)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "d1b9f9f0-b56e-4e48-a7e7-013f2eecd282", + "metadata": {}, + "source": [ + "Change the orientation to vertical." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80b0d0fb-1e7f-444f-bd9b-ac30b508ee4e", + "metadata": {}, + "outputs": [], + "source": [ + "params = {\n", + " 'width': 0.3,\n", + " 'height': 4,\n", + " 'vmin': 0,\n", + " 'vmax': 6000,\n", + " 'cmap': 'terrain',\n", + " 'label': 'Elevation (m)',\n", + " 'orientation': 'vertical',\n", + " 'transparent': False,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0df43e72-bcc7-447f-9573-b6d759f37a50", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "m.add_colormap(position=(85, 5), **params)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "3eeb7a29-29f0-4633-af4c-e9e4958bd826", + "metadata": {}, + "source": [ + "To make the colormap position fixed at four corners (i.e., `bottomright`, `bottomleft`, `topright`, `topleft`), you need to make the image available through an HTTP URL (e.g., [imgur](https://imgur.com)). Local file paths are not supported. Use `m.add_image()` to add the colormap image to the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5eb3989c-d93d-4c69-8181-3301486337b2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "image = 'https://i.imgur.com/SpmE7Cs.png'\n", + "m.add_image(image, position='bottomright')\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "92ce812e-2256-4561-9cda-eced63233cfe", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/hpHZiqT.png)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials.md b/docs/tutorials.md index 5fd04ebe55..8e0776df2a 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -65,6 +65,7 @@ 59. Creating legends using leafmap with only one line of code ([notebook](https://leafmap.org/notebooks/59_create_legend)) 60. Adding text, images, HTML, and widgets to the map ([notebook](https://leafmap.org/notebooks/60_add_widget)) 61. Creating an animated GIF from a vector dataset ([notebook](https://leafmap.org/notebooks/61_vector_to_gif)) +62. Add colorbars to a folium map ([notebook](https://leafmap.org/notebooks/62_folium_colorbar)) ## Demo diff --git a/examples/README.md b/examples/README.md index 9ee8d523a7..05d4b6a11a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -72,9 +72,11 @@ 55. LiDAR data analysis and visualization with whitebox and leafmap ([notebook](https://leafmap.org/notebooks/55_lidar)) 56. Downloading 10-m National Elevation Dataset (NED) with only one line of code ([notebook](https://leafmap.org/notebooks/56_download_ned)) 57. Download data from The National Map ([notebook](https://leafmap.org/notebooks/57_national_map)) -58. Creating legends using leafmap with only one line of code ([notebook](https://leafmap.org/notebooks/59_create_legend)) -59. Adding text, images, HTML, and widgets to the map ([notebook](https://leafmap.org/notebooks/60_add_widget)) -60. Creating an animated GIF from a vector dataset ([notebook](https://leafmap.org/notebooks/61_vector_to_gif)) +58. Creating interactive maps with bokeh ([notebook](https://leafmap.org/notebooks/58_bokeh)) +59. Creating legends using leafmap with only one line of code ([notebook](https://leafmap.org/notebooks/59_create_legend)) +60. Adding text, images, HTML, and widgets to the map ([notebook](https://leafmap.org/notebooks/60_add_widget)) +61. Creating an animated GIF from a vector dataset ([notebook](https://leafmap.org/notebooks/61_vector_to_gif)) +62. Add colorbars to a folium map ([notebook](https://leafmap.org/notebooks/62_folium_colorbar)) ## Demo diff --git a/examples/notebooks/03_cog_stac.ipynb b/examples/notebooks/03_cog_stac.ipynb index b1daa34ecc..fd8fab4f2b 100644 --- a/examples/notebooks/03_cog_stac.ipynb +++ b/examples/notebooks/03_cog_stac.ipynb @@ -71,7 +71,8 @@ "metadata": {}, "outputs": [], "source": [ - "os.environ['TITILER_ENDPOINT'] = 'https://titiler.xyz' # Use the TiTiler demo endpoint. Replace this if needed." + "# Use the TiTiler demo endpoint. Replace this if needed.\n", + "os.environ['TITILER_ENDPOINT'] = 'https://titiler.xyz'" ] }, { @@ -393,7 +394,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.15" + }, + "vscode": { + "interpreter": { + "hash": "31f05ea183a4718249d13ada7f166c6bdba1d00716247af5c11c23af8d5923f1" + } } }, "nbformat": 4, diff --git a/examples/notebooks/62_folium_colorbar.ipynb b/examples/notebooks/62_folium_colorbar.ipynb new file mode 100644 index 0000000000..0735d05827 --- /dev/null +++ b/examples/notebooks/62_folium_colorbar.ipynb @@ -0,0 +1,234 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f43c888-4745-4ce3-a4ce-1b23926b53e7", + "metadata": {}, + "source": [ + "[![image](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://demo.leafmap.org/lab/index.html?path=notebooks/62_folium_colorbar.ipynb)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://githubtocolab.com/giswqs/leafmap/blob/master/examples/notebooks/62_folium_colorbar.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://gishub.org/leafmap-binder)\n", + "\n", + "**Adding colorbars to a folium map**\n", + "\n", + "Uncomment the following line to install [leafmap](https://leafmap.org) if needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb3d4f02-b029-4bc7-867f-4679c040e087", + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap.foliumap as leafmap" + ] + }, + { + "cell_type": "markdown", + "id": "9d67dffc-4c68-4243-a94c-12e3cb7680a9", + "metadata": {}, + "source": [ + "Unlike the ipyleaflet plotting backend, folium does not support adding matplotlib colormap directly. One workaround is to save the maplotlib colormap as an image, then add the image to the map. Let's first create a colormap." + ] + }, + { + "cell_type": "markdown", + "id": "ef94f639-b905-4f1c-aa03-9faf9538f791", + "metadata": {}, + "source": [ + "Create a colormap using specified parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9404e90b-e886-4022-a9b4-87daed979339", + "metadata": {}, + "outputs": [], + "source": [ + "params = {\n", + " 'width': 4.0,\n", + " 'height': 0.3,\n", + " 'vmin': 0,\n", + " 'vmax': 6000,\n", + " 'cmap': 'terrain',\n", + " 'label': 'Elevation (m)',\n", + " 'orientation': 'horizontal',\n", + " 'transparent': False,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "0c0649d0-75b9-49af-b860-6b765dfd7a00", + "metadata": {}, + "source": [ + "Save the colormap as an image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad11fc89-90f6-4579-92ec-78561223af28", + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.save_colorbar('colorbar.png', **params)" + ] + }, + { + "cell_type": "markdown", + "id": "4c7565f6-0c1f-4107-a44c-8de88837b60a", + "metadata": {}, + "source": [ + "You can also create use the `m.add_colormap()` method to add a colormap. Under the hood, it generate a colormap as an image, then add it to the map. You need to specified the position of the colormap using a tuple (x, y), which represents the percentage [0-100] from the lower-left corner. If the map size changes, you might need to change the colormap position as well. " + ] + }, + { + "cell_type": "markdown", + "id": "c49880d0-71a3-4eeb-add1-af8a94135a95", + "metadata": {}, + "source": [ + "Add a horizontal colormap to the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df5c5eb9-1ddf-4c10-8157-f15f055883be", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "m.add_colormap(position=(55, 5), **params)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "3a0cbd26-3a64-4f3d-ab58-651de5471d3d", + "metadata": {}, + "source": [ + "Make the colormap background transparent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be44f1c4-47e7-48d2-b0a8-75a811a7fae4", + "metadata": {}, + "outputs": [], + "source": [ + "params['transparent'] = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b96ffc1-032b-44d5-9e1c-6993de550c14", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "m.add_colormap(position=(55, 5), **params)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "d1b9f9f0-b56e-4e48-a7e7-013f2eecd282", + "metadata": {}, + "source": [ + "Change the orientation to vertical." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80b0d0fb-1e7f-444f-bd9b-ac30b508ee4e", + "metadata": {}, + "outputs": [], + "source": [ + "params = {\n", + " 'width': 0.3,\n", + " 'height': 4,\n", + " 'vmin': 0,\n", + " 'vmax': 6000,\n", + " 'cmap': 'terrain',\n", + " 'label': 'Elevation (m)',\n", + " 'orientation': 'vertical',\n", + " 'transparent': False,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0df43e72-bcc7-447f-9573-b6d759f37a50", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "m.add_colormap(position=(85, 5), **params)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "3eeb7a29-29f0-4633-af4c-e9e4958bd826", + "metadata": {}, + "source": [ + "To make the colormap position fixed at four corners (i.e., `bottomright`, `bottomleft`, `topright`, `topleft`), you need to make the image available through an HTTP URL (e.g., [imgur](https://imgur.com)). Local file paths are not supported. Use `m.add_image()` to add the colormap image to the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5eb3989c-d93d-4c69-8181-3301486337b2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap('OpenTopoMap')\n", + "image = 'https://i.imgur.com/SpmE7Cs.png'\n", + "m.add_image(image, position='bottomright')\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "92ce812e-2256-4561-9cda-eced63233cfe", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/hpHZiqT.png)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leafmap/common.py b/leafmap/common.py index a0cba49c56..ce6bbf5bb1 100644 --- a/leafmap/common.py +++ b/leafmap/common.py @@ -8440,3 +8440,133 @@ def vector_to_gif( if verbose: print(f"Done. The GIF is saved to {out_gif}.") + + +def save_colorbar( + out_fig=None, + width=4.0, + height=0.3, + vmin=0, + vmax=1.0, + palette=None, + vis_params=None, + cmap="gray", + discrete=False, + label=None, + label_size=10, + label_weight="normal", + tick_size=8, + bg_color="white", + orientation="horizontal", + dpi="figure", + transparent=False, + show_colorbar=True, + **kwargs, +): + """Create a standalone colorbar and save it as an image. + + Args: + out_fig (str): Path to the output image. + width (float): Width of the colorbar in inches. Default is 4.0. + height (float): Height of the colorbar in inches. Default is 0.3. + vmin (float): Minimum value of the colorbar. Default is 0. + vmax (float): Maximum value of the colorbar. Default is 1.0. + palette (list): List of colors to use for the colorbar. It can also be a cmap name, such as ndvi, ndwi, dem, coolwarm. Default is None. + vis_params (dict): Visualization parameters as a dictionary. See https://developers.google.com/earth-engine/guides/image_visualization for options. + cmap (str, optional): Matplotlib colormap. Defaults to "gray". See https://matplotlib.org/3.3.4/tutorials/colors/colormaps.html#sphx-glr-tutorials-colors-colormaps-py for options. + discrete (bool, optional): Whether to create a discrete colorbar. Defaults to False. + label (str, optional): Label for the colorbar. Defaults to None. + label_size (int, optional): Font size for the colorbar label. Defaults to 12. + label_weight (str, optional): Font weight for the colorbar label, can be "normal", "bold", etc. Defaults to "normal". + tick_size (int, optional): Font size for the colorbar tick labels. Defaults to 10. + bg_color (str, optional): Background color for the colorbar. Defaults to "white". + orientation (str, optional): Orientation of the colorbar, such as "vertical" and "horizontal". Defaults to "horizontal". + dpi (float | str, optional): The resolution in dots per inch. If 'figure', use the figure's dpi value. Defaults to "figure". + transparent (bool, optional): Whether to make the background transparent. Defaults to False. + show_colorbar (bool, optional): Whether to show the colorbar. Defaults to True. + **kwargs: Other keyword arguments to pass to matplotlib.pyplot.savefig(). + + Returns: + str: Path to the output image. + """ + import matplotlib as mpl + import matplotlib.pyplot as plt + import numpy as np + from .colormaps import palettes, get_palette + + if out_fig is None: + out_fig = temp_file_path("png") + else: + out_fig = check_file_path(out_fig) + + if vis_params is None: + vis_params = {} + elif not isinstance(vis_params, dict): + raise TypeError("The vis_params must be a dictionary.") + + if palette is not None: + if palette in ["ndvi", "ndwi", "dem"]: + palette = palettes[palette] + elif palette in list(palettes.keys()): + palette = get_palette(palette) + vis_params["palette"] = palette + + orientation = orientation.lower() + if orientation not in ["horizontal", "vertical"]: + raise ValueError("The orientation must be either horizontal or vertical.") + + if "opacity" in vis_params: + alpha = vis_params["opacity"] + if type(alpha) not in (int, float): + raise ValueError("The provided opacity value must be type scalar.") + else: + alpha = 1 + + if cmap is not None: + + cmap = mpl.pyplot.get_cmap(cmap) + norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax) + + if "palette" in vis_params: + hexcodes = to_hex_colors(vis_params["palette"]) + if discrete: + cmap = mpl.colors.ListedColormap(hexcodes) + vals = np.linspace(vmin, vmax, cmap.N + 1) + norm = mpl.colors.BoundaryNorm(vals, cmap.N) + + else: + cmap = mpl.colors.LinearSegmentedColormap.from_list( + "custom", hexcodes, N=256 + ) + norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax) + + elif cmap is not None: + + cmap = mpl.pyplot.get_cmap(cmap) + norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax) + + else: + raise ValueError( + 'cmap keyword or "palette" key in vis_params must be provided.' + ) + + fig, ax = plt.subplots(figsize=(width, height)) + cb = mpl.colorbar.ColorbarBase( + ax, norm=norm, alpha=alpha, cmap=cmap, orientation=orientation, **kwargs + ) + if label is not None: + cb.set_label(label=label, size=label_size, weight=label_weight) + cb.ax.tick_params(labelsize=tick_size) + + if transparent: + bg_color = None + + if bg_color is not None: + kwargs["facecolor"] = bg_color + if "bbox_inches" not in kwargs: + kwargs["bbox_inches"] = "tight" + + fig.savefig(out_fig, dpi=dpi, transparent=transparent, **kwargs) + if not show_colorbar: + plt.close(fig) + return out_fig diff --git a/leafmap/foliumap.py b/leafmap/foliumap.py index 4178f9852c..6b74c50413 100644 --- a/leafmap/foliumap.py +++ b/leafmap/foliumap.py @@ -1799,27 +1799,76 @@ def add_marker( def add_colormap( self, + width=4.0, + height=0.3, + vmin=0, + vmax=1.0, + palette=None, + vis_params=None, cmap="gray", - colors=None, discrete=False, label=None, - width=8.0, - height=0.4, + label_size=10, + label_weight="normal", + tick_size=8, + bg_color="white", orientation="horizontal", - vmin=0, - vmax=1.0, - axis_off=False, - show_name=False, - font_size=12, - transparent_bg=False, - position="bottomright", + dpi="figure", + transparent=False, + position=(70, 5), **kwargs, ): - """Adds a matplotlib colormap to the map.""" - raise NotImplementedError( - "The folium plotting backend does not support this function. Use the ipyleaflet plotting backend instead." + """Add a colorbar to the map. Under the hood, it uses matplotlib to generate the colorbar, save it as a png file, and add it to the map using m.add_image(). + + Args: + width (float): Width of the colorbar in inches. Default is 4.0. + height (float): Height of the colorbar in inches. Default is 0.3. + vmin (float): Minimum value of the colorbar. Default is 0. + vmax (float): Maximum value of the colorbar. Default is 1.0. + palette (list): List of colors to use for the colorbar. It can also be a cmap name, such as ndvi, ndwi, dem, coolwarm. Default is None. + vis_params (dict): Visualization parameters as a dictionary. See https://developers.google.com/earth-engine/guides/image_visualization for options. + cmap (str, optional): Matplotlib colormap. Defaults to "gray". See https://matplotlib.org/3.3.4/tutorials/colors/colormaps.html#sphx-glr-tutorials-colors-colormaps-py for options. + discrete (bool, optional): Whether to create a discrete colorbar. Defaults to False. + label (str, optional): Label for the colorbar. Defaults to None. + label_size (int, optional): Font size for the colorbar label. Defaults to 12. + label_weight (str, optional): Font weight for the colorbar label, can be "normal", "bold", etc. Defaults to "normal". + tick_size (int, optional): Font size for the colorbar tick labels. Defaults to 10. + bg_color (str, optional): Background color for the colorbar. Defaults to "white". + orientation (str, optional): Orientation of the colorbar, such as "vertical" and "horizontal". Defaults to "horizontal". + dpi (float | str, optional): The resolution in dots per inch. If 'figure', use the figure's dpi value. Defaults to "figure". + transparent (bool, optional): Whether to make the background transparent. Defaults to False. + position (tuple, optional): The position of the colormap in the format of (x, y), + the percentage ranging from 0 to 100, starting from the lower-left corner. Defaults to (0, 0). + **kwargs: Other keyword arguments to pass to matplotlib.pyplot.savefig(). + + Returns: + str: Path to the output image. + """ + + colorbar = save_colorbar( + None, + width, + height, + vmin, + vmax, + palette, + vis_params, + cmap, + discrete, + label, + label_size, + label_weight, + tick_size, + bg_color, + orientation, + dpi, + transparent, + show_colorbar=False, + **kwargs, ) + self.add_image(colorbar, position=position) + def add_points_from_xy( self, data, @@ -2445,20 +2494,24 @@ def add_image(self, image, position=(0, 0), **kwargs): """ import base64 - if position == "bottomleft": - position = (5, 5) - elif position == "bottomright": - position = (80, 5) - elif position == "topleft": - position = (5, 60) - elif position == "topright": - position = (80, 60) - if isinstance(image, str): if image.startswith("http"): - image = download_file(image, quiet=False) + html = f'' + if isinstance(position, tuple): + position = "bottomright" + self.add_html(html, position=position, **kwargs) + + elif os.path.exists(image): + + if position == "bottomleft": + position = (5, 5) + elif position == "bottomright": + position = (80, 5) + elif position == "topleft": + position = (5, 60) + elif position == "topright": + position = (80, 60) - if os.path.exists(image): with open(image, "rb") as lf: # open in binary mode, read bytes, encode, decode obtained bytes as utf-8 string b64_content = base64.b64encode(lf.read()).decode("utf-8") diff --git a/leafmap/leafmap.py b/leafmap/leafmap.py index e195ac361e..5791652730 100644 --- a/leafmap/leafmap.py +++ b/leafmap/leafmap.py @@ -35,9 +35,6 @@ def __init__(self, **kwargs): if "scroll_wheel_zoom" not in kwargs: kwargs["scroll_wheel_zoom"] = True - if "attribution_control" not in kwargs: - kwargs["attribution_control"] = True - super().__init__(**kwargs) self.baseclass = "ipyleaflet" self.toolbar = None @@ -1497,14 +1494,14 @@ def add_colormap( colors=None, discrete=False, label=None, - width=8.0, - height=0.4, + width=3, + height=0.25, orientation="horizontal", vmin=0, vmax=1.0, axis_off=False, show_name=False, - font_size=12, + font_size=8, transparent_bg=False, position="bottomright", **kwargs, diff --git a/tests/test_foliumap.py b/tests/test_foliumap.py index 4161251ba6..48e3c9178f 100644 --- a/tests/test_foliumap.py +++ b/tests/test_foliumap.py @@ -57,14 +57,14 @@ def test_add_colorbar(self): out_str = m.to_html() assert "Elevation" in out_str - @patch("matplotlib.pyplot.show") - def test_add_colormap(self, mock_show): - """Check colormap""" - with self.assertRaises(NotImplementedError): - m = leafmap.Map() - m.add_colormap(cmap="gray", label="Elevation") - out_str = m.to_html() - assert "Elevation" in out_str + # @patch("matplotlib.pyplot.show") + # def test_add_colormap(self, mock_show): + # """Check colormap""" + # with self.assertRaises(NotImplementedError): + # m = leafmap.Map() + # m.add_colormap(cmap="gray", label="Elevation") + # out_str = m.to_html() + # assert "Elevation" in out_str def test_add_gdf(self): """Check GeoDataFrame"""