diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index c574064c26..5415c3083f 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -22,6 +22,7 @@ jobs: GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} EARTHDATA_USERNAME: ${{ secrets.EARTHDATA_USERNAME }} EARTHDATA_PASSWORD: ${{ secrets.EARTHDATA_PASSWORD }} + MAPILLARY_API_KEY: ${{ secrets.MAPILLARY_API_KEY }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 65455c5632..7ec1d53d36 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,6 +22,7 @@ jobs: GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} EARTHDATA_USERNAME: ${{ secrets.EARTHDATA_USERNAME }} EARTHDATA_PASSWORD: ${{ secrets.EARTHDATA_PASSWORD }} + MAPILLARY_API_KEY: ${{ secrets.MAPILLARY_API_KEY }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/py313.yml b/.github/workflows/py313.yml index d986d0e7df..7ca8fbbe49 100644 --- a/.github/workflows/py313.yml +++ b/.github/workflows/py313.yml @@ -25,6 +25,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + MAPILLARY_API_KEY: ${{ secrets.MAPILLARY_API_KEY }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index e4a57bbd16..7a6d04065a 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -25,6 +25,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + MAPILLARY_API_KEY: ${{ secrets.MAPILLARY_API_KEY }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 27cc907f69..bb298466eb 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -22,6 +22,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + MAPILLARY_API_KEY: ${{ secrets.MAPILLARY_API_KEY }} steps: - uses: actions/checkout@v4 diff --git a/docs/maplibre/mapillary.ipynb b/docs/maplibre/mapillary.ipynb new file mode 100644 index 0000000000..485df2b655 --- /dev/null +++ b/docs/maplibre/mapillary.ipynb @@ -0,0 +1,94 @@ +{ + "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/mapillary.ipynb)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/maplibre/mapillary.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/opengeos/leafmap/HEAD)\n", + "\n", + "**Visualizing Mapillary Vector Tiles**\n", + "\n", + "[Mapillary](https://www.mapillary.com/) provides a global dataset of street-level images, with coverage in many countries. The Mapillary vector tiles provide a way to access the data in a vector format, which can be used to create custom maps and visualizations.\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": "markdown", + "metadata": {}, + "source": [ + "To run this notebook, you will need an [access token](https://www.mapillary.com/developer) from Mapillary. Once you have the API key, you can uncomment the following code block and replace `YOUR_API_KEY` with your actual API key. Then, run the code block code to set the API key as an environment variable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# import os\n", + "# os.environ[\"MAPILLARY_API_KEY\"] = \"YOUR_API_KEY\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"bright\", center=[-73.99941, 40.71194], zoom=13)\n", + "m.add_mapillary(minzoom=6, maxzoom=14, add_popup=True)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![image](https://github.com/user-attachments/assets/db9fac4f-4d67-4ccb-8f2d-06d665bdd521)" + ] + } + ], + "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 763cfe7507..fb6b075c85 100644 --- a/docs/maplibre/overview.md +++ b/docs/maplibre/overview.md @@ -411,6 +411,12 @@ Add a third-party raster source to the map. [![](https://i.imgur.com/GX7reQP.png)](https://leafmap.org/maplibre/map_tiles) +## Visualize Mapillary data + +Visualize Mapillary vector tiles on a map. + +[![image](https://github.com/user-attachments/assets/db9fac4f-4d67-4ccb-8f2d-06d665bdd521)](https://leafmap.org/maplibre/mapillary) + ## Use MapTiler styles Use MapTiler styles to customize the look of your map. diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index b0bb28a36f..848d536a16 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -3931,6 +3931,93 @@ def add_data( **legend_args, ) + def add_mapillary( + self, + minzoom: int = 6, + maxzoom: int = 14, + sequence_lyr_name: str = "sequence", + image_lyr_name: str = "image", + sequence_paint: dict = None, + image_paint: dict = None, + image_minzoom: int = 17, + add_popup: bool = True, + access_token: str = None, + ) -> None: + """ + Adds Mapillary layers to the map. + + Args: + minzoom (int): Minimum zoom level for the Mapillary tiles. Defaults to 6. + maxzoom (int): Maximum zoom level for the Mapillary tiles. Defaults to 14. + sequence_lyr_name (str): Name of the sequence layer. Defaults to "sequence". + image_lyr_name (str): Name of the image layer. Defaults to "image". + sequence_paint (dict, optional): Paint properties for the sequence layer. Defaults to None. + image_paint (dict, optional): Paint properties for the image layer. Defaults to None. + image_minzoom (int): Minimum zoom level for the image layer. Defaults to 17. + add_popup (bool): Whether to add popups to the layers. Defaults to True. + access_token (str, optional): Access token for Mapillary API. Defaults to None. + + Raises: + ValueError: If no access token is provided. + + Returns: + None + """ + + if access_token is None: + access_token = common.get_api_key("MAPILLARY_API_KEY") + + if access_token is None: + raise ValueError("An access token is required to use Mapillary.") + + url = f"https://tiles.mapillary.com/maps/vtp/mly1_public/2/{{z}}/{{x}}/{{y}}?access_token={access_token}" + + source = { + "type": "vector", + "tiles": [url], + "minzoom": minzoom, + "maxzoom": maxzoom, + } + self.add_source("mapillary", source) + + if sequence_paint is None: + sequence_paint = { + "line-opacity": 0.6, + "line-color": "#35AF6D", + "line-width": 2, + } + if image_paint is None: + image_paint = { + "circle-radius": 4, + "circle-color": "#3388ff", + "circle-stroke-color": "#ffffff", + "circle-stroke-width": 1, + } + + sequence_lyr = { + "id": sequence_lyr_name, + "type": "line", + "source": "mapillary", + "source-layer": "sequence", + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": sequence_paint, + } + image_lyr = { + "id": image_lyr_name, + "type": "circle", + "source": "mapillary", + "source-layer": "image", + "paint": image_paint, + "minzoom": image_minzoom, + } + + first_symbol_id = self.find_first_symbol_layer()["id"] + self.add_layer(sequence_lyr, name=sequence_lyr_name, before_id=first_symbol_id) + self.add_layer(image_lyr, name=image_lyr_name, before_id=first_symbol_id) + if add_popup: + self.add_popup(sequence_lyr_name) + self.add_popup(image_lyr_name) + class Container(v.Container): diff --git a/mkdocs.yml b/mkdocs.yml index 1a6976bf4f..1abf46a59f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -214,6 +214,7 @@ nav: - maplibre/local_raster.ipynb - maplibre/locate_user.ipynb - maplibre/map_tiles.ipynb + - maplibre/mapillary.ipynb - maplibre/maptiler_styles.ipynb - maplibre/mouse_position.ipynb - maplibre/multiple_geometries.ipynb