From 3e1cb234a483d80d39224f408ed205ace154ea13 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Tue, 2 Jul 2024 22:59:39 -0400 Subject: [PATCH] Add functions for executing MapLibre notebook (#806) * Add functions for executing MapLibre notebook * Add GIF notebook example * Improve function --- docs/maplibre/add_gif.ipynb | 81 ++++++++++++++++++++++++ docs/maplibre/overview.md | 6 ++ leafmap/common.py | 122 ++++++++++++++++++++++++++++++++++++ leafmap/maplibregl.py | 34 ++++++++-- mkdocs.yml | 1 + 5 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 docs/maplibre/add_gif.ipynb diff --git a/docs/maplibre/add_gif.ipynb b/docs/maplibre/add_gif.ipynb new file mode 100644 index 0000000000..25ebc59b16 --- /dev/null +++ b/docs/maplibre/add_gif.ipynb @@ -0,0 +1,81 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![image](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://demo.leafmap.org/lab/index.html?path=maplibre/add_gif.ipynb)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/maplibre/add_gif.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/opengeos/leafmap/HEAD)\n", + "\n", + "**Add GIF animations to the map**\n", + "\n", + "This example shows how to add GIF animations to the map.\n", + "\n", + "Uncomment the following line to install [leafmap](https://leafmap.org) if needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install \"leafmap[maplibre]\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap.maplibregl as leafmap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "image = \"https://i.imgur.com/KeiAsTv.gif\"\n", + "m.add_image(image=image, width=250, height=250, position=\"bottom-right\")\n", + "text = \"I love sloth!🦥\"\n", + "m.add_text(text, fontsize=35, padding=\"20px\")\n", + "image2 = \"https://i.imgur.com/kZC2tpr.gif\"\n", + "m.add_image(image=image2, bg_color=\"transparent\", position=\"bottom-left\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/auytBtD.png)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/maplibre/overview.md b/docs/maplibre/overview.md index 68efab255c..ebe4322b7c 100644 --- a/docs/maplibre/overview.md +++ b/docs/maplibre/overview.md @@ -44,6 +44,12 @@ Add deck.gl layers to the map. [![](https://i.imgur.com/rQR4687.png)](https://leafmap.org/maplibre/add_deckgl_layer) +## Add GIF animations to the map + +Add GIF animations to the map. + +[![](https://i.imgur.com/auytBtD.png)](https://leafmap.org/maplibre/add_gif) + ## Add HTML content to the map Add HTML content to the map. diff --git a/leafmap/common.py b/leafmap/common.py index 72806fcbe3..399789172c 100644 --- a/leafmap/common.py +++ b/leafmap/common.py @@ -13616,7 +13616,9 @@ def execute_notebook_dir(in_dir): from pathlib import Path in_dir = os.path.abspath(in_dir) + files = list(Path(in_dir).rglob("*.ipynb")) + files.sort() count = len(files) if files is not None: for index, file in enumerate(files): @@ -13625,6 +13627,126 @@ def execute_notebook_dir(in_dir): execute_notebook(in_file) +def execute_maplibre_notebook_dir( + in_dir: str, + out_dir: str, + delete_html: bool = True, + replace_api_key: bool = True, + recursive: bool = False, + keep_notebook: bool = False, + index_html: bool = True, +) -> None: + """ + Executes Jupyter notebooks found in a specified directory, optionally replacing API keys and deleting HTML outputs. + + Args: + in_dir (str): The input directory containing Jupyter notebooks to be executed. + out_dir (str): The output directory where the executed notebooks and their HTML outputs will be saved. + delete_html (bool, optional): If True, deletes any existing HTML files in the output directory before execution. Defaults to True. + replace_api_key (bool, optional): If True, replaces the API key in the output HTML. Defaults to True. + set "MAPTILER_KEY" and "MAPTILER_KEY_PUBLIC" to your MapTiler API key and public key, respectively. + recursive (bool, optional): If True, searches for notebooks in the input directory recursively. Defaults to False. + keep_notebook (bool, optional): If True, keeps the executed notebooks in the output directory. Defaults to False. + index_html (bool, optional): If True, generates an index.html file in the output directory listing all files. Defaults to True. + + Returns: + None + """ + import shutil + + if not os.path.exists(out_dir): + os.makedirs(out_dir) + + if replace_api_key: + os.environ["MAPTILER_REPLACE_KEY"] = "True" + + if delete_html: + html_files = find_files(out_dir, "*.html", recursive=recursive) + for file in html_files: + os.remove(file) + + files = find_files(in_dir, "*.ipynb", recursive=recursive) + for index, file in enumerate(files): + print(f"Processing {index + 1}/{len(files)}: {file} ...") + basename = os.path.basename(file) + out_file = os.path.join(out_dir, basename) + shutil.copy(file, out_file) + + with open(out_file, "r") as f: + lines = f.readlines() + + out_lines = [] + for line in lines: + if line.strip() == '"m"': + out_lines.append(line.replace("m", "m.to_html()")) + else: + out_lines.append(line) + + with open(out_file, "w") as f: + f.writelines(out_lines) + + out_html = os.path.basename(out_file).replace(".ipynb", ".html") + os.environ["MAPLIBRE_OUTPUT"] = out_html + execute_notebook(out_file) + + if not keep_notebook: + all_files = find_files(out_dir, "*", recursive=recursive) + for file in all_files: + if not file.endswith(".html"): + os.remove(file) + + if index_html: + generate_index_html(out_dir) + + +def generate_index_html(directory: str, output: str = "index.html") -> None: + """ + Generates an HTML file named 'index.html' in the specified directory, listing + all files in that directory as clickable links. + + Args: + directory (str): The path to the directory for which to generate the index.html file. + output (str, optional): The name of the output HTML file. Defaults to "index.html". + + Returns: + None + """ + # Get a list of files in the directory + files = sorted( + [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))] + ) + + # Start the HTML content + html_content = """ + + + + + Index of {directory} + + +

Index of {directory}

+ + +""" + + # Write the HTML content to index.html in the specified directory + with open(os.path.join(directory, output), "w") as f: + f.write(html_content) + + def github_get_release_id_by_tag(username, repository, tag_name, access_token=None): """ Fetches the release ID by tag name for a given GitHub repository. diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index 6dab69d9c9..ea690f6f84 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -142,9 +142,10 @@ def show(self) -> None: def _repr_html_(self, **kwargs): """Displays the map.""" - filename = os.environ.get("MAPLIBRE_HTML", None) + filename = os.environ.get("MAPLIBRE_OUTPUT", None) + replace_key = os.environ.get("MAPTILER_REPLACE_KEY", False) if filename is not None: - self.to_html(filename, replace_key=False) + self.to_html(filename, replace_key=replace_key) def add_layer( self, @@ -1177,6 +1178,7 @@ def to_html( height: str = "100%", replace_key: bool = False, preview: bool = False, + overwrite: bool = False, **kwargs, ): """Render the map to an HTML page. @@ -1193,6 +1195,8 @@ def to_html( The public API key is read from the environment variable `MAPTILER_KEY_PUBLIC`. Defaults to False. preview (bool, optional): Whether to preview the HTML file in a web browser. + Defaults to False. + overwrite (bool, optional): Whether to overwrite the output file if it already exists. **kwargs: Additional keyword arguments that are passed to the `maplibre.ipywidget.MapWidget.to_html()` method. @@ -1226,13 +1230,22 @@ def to_html( div_before = f"""
""" html = html.replace(div_before, div_after) - if replace_key: + if replace_key or (os.getenv("MAPTILER_REPLACE_KEY") is not None): key_before = get_api_key("MAPTILER_KEY") key_after = get_api_key("MAPTILER_KEY_PUBLIC") if key_after is not None: html = html.replace(key_before, key_after) + output = os.getenv("MAPLIBRE_OUTPUT", None) + if output: + + if not overwrite and os.path.exists(output): + import glob + + num = len(glob.glob(output.replace(".html", "*.html"))) + output = output.replace(".html", f"_{num}.html") + with open(output, "w") as f: f.write(html) if preview: @@ -1893,8 +1906,21 @@ def add_image( if id is None: id = "image" + style = "" + if isinstance(width, int): + style += f"width: {width}px; " + elif isinstance(width, str) and width.endswith("px"): + style += f"width: {width}; " + if isinstance(height, int): + style += f"height: {height}px; " + elif isinstance(height, str) and height.endswith("px"): + style += f"height: {height}; " + if position is not None: - html = f'' + if style == "": + html = f'' + else: + html = f'' self.add_html(html, position=position, **kwargs) else: if isinstance(image, str): diff --git a/mkdocs.yml b/mkdocs.yml index 93be6c7aac..845e699e72 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -145,6 +145,7 @@ nav: - maplibre/add_colorbar.ipynb - maplibre/add_components.ipynb - maplibre/add_deckgl_layer.ipynb + - maplibre/add_gif.ipynb - maplibre/add_html.ipynb - maplibre/add_image.ipynb - maplibre/add_image_generated.ipynb