From 8f4efd9a59849f1258d0ea4e37d1a069d1bf390a Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Mon, 27 Jan 2025 23:38:00 -0500 Subject: [PATCH 1/2] Add support for Mapillary street level imagery --- docs/maplibre/create_vector.ipynb | 42 ++++++++++++++++++++-- leafmap/maplibregl.py | 58 ++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/docs/maplibre/create_vector.ipynb b/docs/maplibre/create_vector.ipynb index f2f4cd356c..883c179b90 100644 --- a/docs/maplibre/create_vector.ipynb +++ b/docs/maplibre/create_vector.ipynb @@ -37,9 +37,28 @@ "metadata": {}, "outputs": [], "source": [ + "import os\n", "import leafmap.maplibregl as leafmap" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use Mapillary street level imagery, you will need to sign up for a free account at [Mapillary](https://www.mapillary.com/) and get an access token. Please visit [the Mapillary API page](https://www.mapillary.com/developer/api-documentation) for more information on how to get an access token. \n", + "\n", + "Once you have an access token, uncomment the following line and replace `YOUR_ACCESS_TOKEN` with your actual access token." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# os.environ[\"MAPILLARY_API_KEY\"] = \"YOUR_ACCESS_TOKEN\"" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -55,6 +74,7 @@ "source": [ "m = leafmap.Map(center=[-74.1935, 40.6681], zoom=15, style=\"liberty\")\n", "m.add_basemap(\"Satellite\")\n", + "m.add_mapillary()\n", "m.add_layer_control()\n", "m.add_draw_control(\n", " controls=[\"point\", \"polygon\", \"line_string\", \"trash\"], position=\"top-right\"\n", @@ -95,7 +115,9 @@ "metadata": {}, "outputs": [], "source": [ - "widget = leafmap.create_vector_data(m, properties, file_ext=\"geojson\")\n", + "widget = leafmap.create_vector_data(\n", + " m, properties, file_ext=\"geojson\", add_mapillary=True\n", + ")\n", "widget" ] }, @@ -115,10 +137,24 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "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": 2 + "nbformat_minor": 4 } diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index dcc01de61b..301d1d6aed 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -3940,6 +3940,7 @@ def add_mapillary( maxzoom: int = 14, sequence_lyr_name: str = "sequence", image_lyr_name: str = "image", + before_id: str = None, sequence_paint: dict = None, image_paint: dict = None, image_minzoom: int = 17, @@ -3954,6 +3955,7 @@ def add_mapillary( 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". + before_id (str): The ID of an existing layer to insert the new layer before. Defaults to None. 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. @@ -4014,9 +4016,8 @@ def add_mapillary( "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) + self.add_layer(sequence_lyr, name=sequence_lyr_name, before_id=before_id) + self.add_layer(image_lyr, name=image_lyr_name, before_id=before_id) if add_popup: self.add_popup(sequence_lyr_name) self.add_popup(image_lyr_name) @@ -4046,8 +4047,7 @@ def create_mapillary_widget( radius (float): Search radius for Mapillary images. Defaults to 0.00005. bbox (Optional[Union[str, List[float]]]): Bounding box for the search. Defaults to None. image_id (Optional[str]): ID of the Mapillary image. Defaults to None. - style (str): Style of the Mapillary image. Defaults to "classic". - width (int): Width of the iframe. Defaults to 560. + style (str): Style of the Mapillary image. Can be "classic", "photo", and "split". Defaults to "classic". height (int): Height of the iframe. Defaults to 600. frame_border (int): Frame border of the iframe. Defaults to 0. link (bool): Whether to link the widget to map clicks. Defaults to True. @@ -4061,9 +4061,11 @@ def create_mapillary_widget( if image_id is None: if lon is None or lat is None: - if len(self.center) > 0: - lon = self.center["lng"] - lat = self.center["lat"] + if "center" in self.view_state: + center = self.view_state + if len(center) > 0: + lon = center["lng"] + lat = center["lat"] else: lon = 0 lat = 0 @@ -5212,6 +5214,12 @@ def create_vector_data( out_dir: Optional[str] = None, filename_prefix: str = "", file_ext: str = "geojson", + add_mapillary: bool = False, + style: str = "photo", + radius: float = 0.00005, + width: int = 300, + height: int = 420, + frame_border: int = 0, **kwargs: Any, ) -> widgets.VBox: """Generates a widget-based interface for creating and managing vector data on a map. @@ -5237,6 +5245,16 @@ def create_vector_data( filename_prefix (str, optional): A prefix to be added to the exported filename. Defaults to "". file_ext (str, optional): The file extension for the exported file. Defaults to "geojson". + add_mapillary (bool, optional): Whether to add a Mapillary image widget that displays the + nearest image to the clicked point on the map. Defaults to False. + style (str, optional): The style of the Mapillary image widget. Can be "classic", "photo", + or "split". Defaults to "photo". + radius (float, optional): The radius (in degrees) used to search for the nearest Mapillary + image. Defaults to 0.00005 degrees. + width (int, optional): The width of the Mapillary image widget. Defaults to 300. + height (int, optional): The height of the Mapillary image widget. Defaults to 420. + frame_border (int, optional): The width of the frame border for the Mapillary image widget. + Defaults to 0. **kwargs (Any): Additional keyword arguments that may be passed to the function. Returns: @@ -5279,6 +5297,8 @@ def create_default_map(): prop_widgets = widgets.VBox() + image_widget = widgets.HTML() + if isinstance(properties, dict): for key, values in properties.items(): @@ -5336,6 +5356,26 @@ def draw_change(lng_lat): m.observe(draw_change, names="draw_features_selected") + def log_lng_lat(lng_lat): + lon = lng_lat.new["lng"] + lat = lng_lat.new["lat"] + image_id = common.search_mapillary_images(lon, lat, radius=radius, limit=1) + if len(image_id) > 0: + content = f""" + + """ + image_widget.value = content + else: + image_widget.value = "No Mapillary image found." + + if add_mapillary: + m.observe(log_lng_lat, names="clicked") + button_layout = widgets.Layout(width="97px") save = widgets.Button( description="Save", button_style="primary", layout=button_layout @@ -5349,6 +5389,7 @@ def draw_change(lng_lat): def on_save_click(b): + output.clear_output() if len(m.draw_features_selected) > 0: feature_id = m.draw_features_selected[0]["id"] for prop_widget in prop_widgets.children: @@ -5400,6 +5441,7 @@ def on_reset_click(b): prop_widgets, widgets.HBox([save, export, reset]), output, + image_widget, ] left_col_layout = v.Col( From 127866725d5a17dad8ab84f4cdc32377dc05238a Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Mon, 27 Jan 2025 23:54:23 -0500 Subject: [PATCH 2/2] Update Mapillary notebook example --- docs/maplibre/mapillary.ipynb | 22 ++++++++++++++++++++++ leafmap/maplibregl.py | 2 ++ 2 files changed, 24 insertions(+) diff --git a/docs/maplibre/mapillary.ipynb b/docs/maplibre/mapillary.ipynb index e67988b665..9c895c97c0 100644 --- a/docs/maplibre/mapillary.ipynb +++ b/docs/maplibre/mapillary.ipynb @@ -58,6 +58,7 @@ "outputs": [], "source": [ "m = leafmap.Map(style=\"bright\", center=[-73.99941, 40.71194], zoom=13)\n", + "m.add_basemap(\"Satellite\")\n", "m.add_mapillary(minzoom=6, maxzoom=14, add_popup=True)\n", "m.add_layer_control()\n", "m" @@ -126,6 +127,27 @@ "source": [ "common.get_mapillary_image_widget(image_ids[0], style=\"photo\", width=1000)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"bright\", center=[-73.99941, 40.71194], zoom=13)\n", + "m.add_basemap(\"Satellite\")\n", + "m.add_mapillary(minzoom=6, maxzoom=14, add_popup=True)\n", + "m.add_layer_control()\n", + "widget = m.create_mapillary_widget(style=\"photo\", width=500)\n", + "widget" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![image](https://github.com/user-attachments/assets/a67a44ba-b15c-4916-9d88-14a744645364)" + ] } ], "metadata": { diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index 301d1d6aed..fd1f716a4a 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -4098,6 +4098,8 @@ def log_lng_lat(lng_lat): """ widget.value = content + else: + widget.value = "No Mapillary image found." self.observe(log_lng_lat, names="clicked")