From c967575b7229e1476c938519e6f361f40d01279c Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 5 Nov 2023 23:32:14 -0500 Subject: [PATCH] Add support for lonboard (#588) * Add support for lonboard * Add lonboard notebook example * Fix typo --- docs/deckgl.md | 3 + docs/notebooks/82_pmtiles.ipynb | 6 - docs/notebooks/83_vector_viz.ipynb | 156 ++++++++++++++++++++++++ docs/tutorials.md | 1 + examples/README.md | 1 + examples/notebooks/82_pmtiles.ipynb | 8 +- examples/notebooks/83_vector_viz.ipynb | 156 ++++++++++++++++++++++++ leafmap/deckgl.py | 159 +++++++++++++++++++++++++ mkdocs.yml | 3 + requirements_dev.txt | 1 + 10 files changed, 481 insertions(+), 13 deletions(-) create mode 100644 docs/deckgl.md create mode 100644 docs/notebooks/83_vector_viz.ipynb create mode 100644 examples/notebooks/83_vector_viz.ipynb create mode 100644 leafmap/deckgl.py diff --git a/docs/deckgl.md b/docs/deckgl.md new file mode 100644 index 0000000000..369028a838 --- /dev/null +++ b/docs/deckgl.md @@ -0,0 +1,3 @@ +# deckgl module + +::: leafmap.deckgl diff --git a/docs/notebooks/82_pmtiles.ipynb b/docs/notebooks/82_pmtiles.ipynb index 75384509ec..c354dc1678 100644 --- a/docs/notebooks/82_pmtiles.ipynb +++ b/docs/notebooks/82_pmtiles.ipynb @@ -40,7 +40,6 @@ }, { "cell_type": "markdown", - "id": "6f3406c5", "metadata": {}, "source": [ "## Remote PMTiles\n", @@ -107,7 +106,6 @@ }, { "cell_type": "markdown", - "id": "e1166262", "metadata": {}, "source": [ "### Overture data" @@ -203,7 +201,6 @@ }, { "cell_type": "markdown", - "id": "6b9d11d9", "metadata": {}, "source": [ "### Source Cooperative\n", @@ -214,7 +211,6 @@ { "cell_type": "code", "execution_count": null, - "id": "9c6d5f2c", "metadata": {}, "outputs": [], "source": [ @@ -227,7 +223,6 @@ { "cell_type": "code", "execution_count": null, - "id": "2c2cfcf8", "metadata": {}, "outputs": [], "source": [ @@ -268,7 +263,6 @@ { "cell_type": "code", "execution_count": null, - "id": "789cce99", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/notebooks/83_vector_viz.ipynb b/docs/notebooks/83_vector_viz.ipynb new file mode 100644 index 0000000000..ab3115b31d --- /dev/null +++ b/docs/notebooks/83_vector_viz.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![image](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://demo.leafmap.org/lab/index.html?path=notebooks/83_vector_viz.ipynb)\n", + "[![image](https://studiolab.sagemaker.aws/studiolab.svg)](https://studiolab.sagemaker.aws/import/github/opengeos/leafmap/blob/master/examples/notebooks/83_vector_viz.ipynb)\n", + "[![image](https://img.shields.io/badge/Open-Planetary%20Computer-black?style=flat&logo=microsoft)](https://pccompute.westeurope.cloudapp.azure.com/compute/hub/user-redirect/git-pull?repo=https://github.com/opengeos/leafmap&urlpath=lab/tree/leafmap/examples/notebooks/83_vector_viz.ipynb&branch=master)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://githubtocolab.com/opengeos/leafmap/blob/master/examples/notebooks/01_leafmap_intro.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://gishub.org/leafmap-binder)\n", + "\n", + "**Visualizing large vector datasets with lonboard**\n", + "\n", + "This notebook demonstrates how to visualize large vector datasets with [lonboard](https://github.com/developmentseed/lonboard). Please note that lonboard does not support Visual Studio Code's interactive notebook yet. You will need to run this notebook in Jupyter Notebook or JupyterLab.\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 -U leafmap lonboard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import leafmap.deckgl as leafmap\n", + "import geopandas as gpd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Download sample datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "url = \"https://open.gishub.org/data/duckdb/nyc_data.zip\"\n", + "leafmap.download_file(url, unzip=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an interactive map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[20, 0], zoom=1.2)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add GeoDataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "streets = gpd.read_file('nyc_streets.shp')\n", + "m.add_gdf(streets, zoom_to_layer=True, pickable=True, get_width=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add any vector format supported by GeoPandas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m.add_vector('nyc_subway_stations.shp', get_radius=10, get_fill_color=[255, 0, 0, 180])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Change layer properties." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m.layers[-1].get_fill_color = [0, 0, 255, 255]" + ] + } + ], + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials.md b/docs/tutorials.md index fb4be15547..6557e22ab0 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -96,6 +96,7 @@ 80. Visualizing solar radiation data from Google Solar API ([notebook](https://leafmap.org/notebooks/80_solar)) 81. Downloading Microsoft and Google Building Footprints ([notebook](https://leafmap.org/notebooks/81_buildings)) 82. Visualizing PMTiles with leafmap ([notebook](https://leafmap.org/notebooks/82_pmtiles)) +83. Visualizing large vector datasets with lonboard ([notebook](https://leafmap.org/notebooks/83_vector_viz)) ## Demo diff --git a/examples/README.md b/examples/README.md index 707229da60..891455fd2e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -103,6 +103,7 @@ 80. Visualizing solar radiation data from Google Solar API ([notebook](https://leafmap.org/notebooks/80_solar)) 81. Downloading Microsoft and Google Building Footprints ([notebook](https://leafmap.org/notebooks/81_buildings)) 82. Visualizing PMTiles with leafmap ([notebook](https://leafmap.org/notebooks/82_pmtiles)) +83. Visualizing large vector datasets with lonboard ([notebook](https://leafmap.org/notebooks/83_vector_viz)) ## Demo diff --git a/examples/notebooks/82_pmtiles.ipynb b/examples/notebooks/82_pmtiles.ipynb index 75384509ec..8f8bb89176 100644 --- a/examples/notebooks/82_pmtiles.ipynb +++ b/examples/notebooks/82_pmtiles.ipynb @@ -40,7 +40,6 @@ }, { "cell_type": "markdown", - "id": "6f3406c5", "metadata": {}, "source": [ "## Remote PMTiles\n", @@ -107,7 +106,6 @@ }, { "cell_type": "markdown", - "id": "e1166262", "metadata": {}, "source": [ "### Overture data" @@ -203,7 +201,6 @@ }, { "cell_type": "markdown", - "id": "6b9d11d9", "metadata": {}, "source": [ "### Source Cooperative\n", @@ -214,7 +211,6 @@ { "cell_type": "code", "execution_count": null, - "id": "9c6d5f2c", "metadata": {}, "outputs": [], "source": [ @@ -227,7 +223,6 @@ { "cell_type": "code", "execution_count": null, - "id": "2c2cfcf8", "metadata": {}, "outputs": [], "source": [ @@ -268,7 +263,6 @@ { "cell_type": "code", "execution_count": null, - "id": "789cce99", "metadata": {}, "outputs": [], "source": [ @@ -411,7 +405,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/examples/notebooks/83_vector_viz.ipynb b/examples/notebooks/83_vector_viz.ipynb new file mode 100644 index 0000000000..ab3115b31d --- /dev/null +++ b/examples/notebooks/83_vector_viz.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![image](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://demo.leafmap.org/lab/index.html?path=notebooks/83_vector_viz.ipynb)\n", + "[![image](https://studiolab.sagemaker.aws/studiolab.svg)](https://studiolab.sagemaker.aws/import/github/opengeos/leafmap/blob/master/examples/notebooks/83_vector_viz.ipynb)\n", + "[![image](https://img.shields.io/badge/Open-Planetary%20Computer-black?style=flat&logo=microsoft)](https://pccompute.westeurope.cloudapp.azure.com/compute/hub/user-redirect/git-pull?repo=https://github.com/opengeos/leafmap&urlpath=lab/tree/leafmap/examples/notebooks/83_vector_viz.ipynb&branch=master)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://githubtocolab.com/opengeos/leafmap/blob/master/examples/notebooks/01_leafmap_intro.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://gishub.org/leafmap-binder)\n", + "\n", + "**Visualizing large vector datasets with lonboard**\n", + "\n", + "This notebook demonstrates how to visualize large vector datasets with [lonboard](https://github.com/developmentseed/lonboard). Please note that lonboard does not support Visual Studio Code's interactive notebook yet. You will need to run this notebook in Jupyter Notebook or JupyterLab.\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 -U leafmap lonboard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import leafmap.deckgl as leafmap\n", + "import geopandas as gpd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Download sample datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "url = \"https://open.gishub.org/data/duckdb/nyc_data.zip\"\n", + "leafmap.download_file(url, unzip=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an interactive map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[20, 0], zoom=1.2)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add GeoDataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "streets = gpd.read_file('nyc_streets.shp')\n", + "m.add_gdf(streets, zoom_to_layer=True, pickable=True, get_width=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add any vector format supported by GeoPandas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m.add_vector('nyc_subway_stations.shp', get_radius=10, get_fill_color=[255, 0, 0, 180])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Change layer properties." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "m.layers[-1].get_fill_color = [0, 0, 255, 255]" + ] + } + ], + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leafmap/deckgl.py b/leafmap/deckgl.py new file mode 100644 index 0000000000..86fed2dc35 --- /dev/null +++ b/leafmap/deckgl.py @@ -0,0 +1,159 @@ +from typing import Union, List, Dict, Optional, Tuple, Any +from .common import * + +try: + import lonboard + import geopandas as gpd + +except ImportError: + raise ( + "lonboard needs to be installed to use this module. Use 'pip install lonboard' to install the package." + ) + + +class Map(lonboard.Map): + """The Map class inherits lonboard.Map. + + Returns: + object: lonboard.Map object. + """ + + def __init__( + self, + center: Tuple[float, float] = (20, 0), + zoom: float = 1.2, + height: int = 600, + layers: List = [], + show_tooltip: bool = True, + **kwargs + ) -> None: + """Initialize a Map object. + + Args: + center (tuple, optional): Center of the map in the format of (lat, lon). Defaults to (20, 0). + zoom (float, optional): The map zoom level. Defaults to 1.2. + height (int, optional): Height of the map. Defaults to 600. + layers (list, optional): List of additional layers to add to the map. Defaults to []. + show_tooltip (bool, optional): Flag to show tooltips on the map. Defaults to True. + **kwargs: Additional keyword arguments to pass to lonboard.Map. + + Returns: + None + """ + + kwargs["latitude"] = center[0] + kwargs["longitude"] = center[1] + kwargs["zoom"] = zoom + + super().__init__( + _height=height, + show_tooltip=show_tooltip, + layers=layers, + _initial_view_state=kwargs, + ) + + def add_gdf( + self, + gdf: gpd.GeoDataFrame, + zoom_to_layer: bool = True, + pickable: bool = True, + **kwargs: Any + ) -> None: + """Adds a GeoPandas GeoDataFrame to the map. + + Args: + gdf (GeoDataFrame): A GeoPandas GeoDataFrame with geometry column. + zoom_to_layer (bool, optional): Flag to zoom to the added layer. Defaults to True. + pickable (bool, optional): Flag to enable picking on the added layer. Defaults to True. + **kwargs: Additional keyword arguments that will be passed to the GeoDataFrame. + + Returns: + None + """ + + from lonboard import ScatterplotLayer, PathLayer, SolidPolygonLayer + + geom_type = gdf.geometry.iloc[0].geom_type + kwargs["pickable"] = pickable + + if geom_type in ["Point", "MultiPoint"]: + if "get_radius" not in kwargs: + kwargs["get_radius"] = 10 + if "get_fill_color" not in kwargs: + kwargs["get_fill_color"] = [255, 0, 0, 180] + layer = ScatterplotLayer.from_geopandas(gdf, **kwargs) + elif geom_type in ["LineString", "MultiLineString"]: + if "get_width" not in kwargs: + kwargs["get_width"] = 5 + layer = PathLayer.from_geopandas(gdf, **kwargs) + elif geom_type in ["Polygon", "MultiPolygon"]: + if "get_fill_color" not in kwargs: + kwargs["get_fill_color"] = [0, 0, 255, 128] + layer = SolidPolygonLayer.from_geopandas(gdf, **kwargs) + + self.layers = self.layers + [layer] + + if zoom_to_layer: + from lonboard._viewport import compute_view + + self._initial_view_state = compute_view([self.layers[-1].table]) + + def add_vector( + self, + vector: Union[str, gpd.GeoDataFrame], + zoom_to_layer: bool = True, + pickable: bool = True, + open_args: dict = {}, + **kwargs: Any + ) -> None: + """Adds a vector layer to the map. + + Args: + vector (Union[str, GeoDataFrame]): The file path or URL to the vector data, or a GeoDataFrame. + zoom_to_layer (bool, optional): Flag to zoom to the added layer. Defaults to True. + pickable (bool, optional): Flag to enable picking on the added layer. Defaults to True. + open_args (dict, optional): Additional keyword arguments that will be passed to gpd.read_file() if vector is a file path or URL. Defaults to {}. + **kwargs: Additional keyword arguments that will be passed to the vector layer. + + Returns: + None + """ + + if isinstance(vector, gpd.GeoDataFrame): + gdf = vector + else: + gdf = gpd.read_file(vector, **open_args) + self.add_gdf(gdf, zoom_to_layer=zoom_to_layer, pickable=pickable, **kwargs) + + def add_layer( + self, + layer: Any, + zoom_to_layer: bool = True, + pickable: bool = True, + **kwargs: Any + ) -> None: + """Adds a layer to the map. + + Args: + layer (Any): A lonboard layer object. + zoom_to_layer (bool, optional): Whether to zoom to the layer extent. Defaults to True. + pickable (bool, optional): Flag to enable picking on the added layer if it's a vector layer. Defaults to True. + **kwargs: Additional keyword arguments that will be passed to the vector layer if it's a vector layer. + + Returns: + None + """ + + from lonboard import ScatterplotLayer, PathLayer, SolidPolygonLayer + + if type(layer) in [ScatterplotLayer, PathLayer, SolidPolygonLayer]: + self.layers = self.layers + [layer] + + if zoom_to_layer: + from lonboard._viewport import compute_view + + self._initial_view_state = compute_view([self.layers[-1].table]) + else: + self.add_vector( + layer, zoom_to_layer=zoom_to_layer, pickable=pickable, **kwargs + ) diff --git a/mkdocs.yml b/mkdocs.yml index 3c47df48cd..c905e3cc67 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -109,6 +109,8 @@ nav: - bokehmap module: bokehmap.md - colormaps module: colormaps.md - common module: common.md + - deck module: deck.md + - deckgl module: deckgl.md - examples module: examples.md - foliumap module: foliumap.md - kepler module: kepler.md @@ -210,3 +212,4 @@ nav: - notebooks/80_solar.ipynb - notebooks/81_buildings.ipynb - notebooks/82_pmtiles.ipynb + - notebooks/83_vector_viz.ipynb diff --git a/requirements_dev.txt b/requirements_dev.txt index bfb2041cda..f765eebf86 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -18,6 +18,7 @@ ipysheet ipyvtklink laspy localtileserver +lonboard mapclassify>=2.4.0 mss netcdf4