Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clicked point to Lonboard map #671

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Binary file added assets/clicked-point.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions examples/clicked-point.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e328a21f-5254-42ee-9683-136561d96886",
"metadata": {},
"source": [
"# Clicked point\n",
"\n",
"This notebook demonstrates how you can access the coordinate of the most recent click on a Lonboard map and update a set of widgets to display the x and y coordinates using the [`ipywidgets.observe`](https://ipywidgets.readthedocs.io/en/8.1.5/examples/Widget%20Events.html#traitlet-events) method."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f986a64e-269f-4966-93b0-cfa5bf1ba882",
"metadata": {},
"outputs": [],
"source": [
"from typing import Tuple\n",
"\n",
"import ipywidgets as widgets\n",
"\n",
"from lonboard import Map\n",
"\n",
"## Create a Lonboard map and two widgets to display the coordinate\n",
"m = Map(layers=[])\n",
"clicked_x_display = widgets.FloatText(description=\"last clicked x:\")\n",
"clicked_y_display = widgets.FloatText(description=\"last clicked y:\")\n",
"\n",
"def on_map_click(coordinate:Tuple[float, float]) -> None:\n",
" x,y = coordinate\n",
" clicked_x_display.value = x\n",
" clicked_y_display.value = y\n",
"m.on_click(on_map_click)\n",
"\n",
"## show the widgets\n",
"widgets.VBox([\n",
" m,\n",
" clicked_x_display,\n",
" clicked_y_display\n",
"])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "lonboard",
"language": "python",
"name": "lonboard"
},
"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.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
2 changes: 1 addition & 1 deletion examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- [Rivers in Asia ![](../assets/rivers-asia.jpg)](../examples/map_challenge/6-asia/) using [`PathLayer`](../api/layers/path-layer)
- [Inflation Reduction Act Projects ![](../assets/column-layer.jpg)](../examples/column-layer/) using [`ColumnLayer`](../api/layers/column-layer)
- [Linked Maps ![](../assets/linked-maps.gif)](../examples/linked-maps/)

- [Clicked Point ![](../assets/clicked-point.png)](../examples/clicked-point/)
</div>

## Integrations
Expand Down
40 changes: 39 additions & 1 deletion lonboard/_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import sys
from io import StringIO
from pathlib import Path
from typing import IO, TYPE_CHECKING, Optional, Sequence, TextIO, Union, overload
from typing import IO, TYPE_CHECKING, List, Optional, Sequence, TextIO, Union, overload

import ipywidgets
import traitlets
import traitlets as t
from ipywidgets import CallbackDispatcher
from ipywidgets.embed import embed_minimal_html

from lonboard._base import BaseAnyWidget
Expand Down Expand Up @@ -100,7 +101,40 @@ def __init__(
if isinstance(layers, BaseLayer):
layers = [layers]

def _handle_anywidget_dispatch(
widget: ipywidgets.Widget, msg: Union[str, list, dict], buffers: List[bytes]
) -> None:
if msg.get("kind") == "on-click":
self._click_handlers(tuple(msg.get("coordinate")))

super().__init__(layers=layers, **kwargs)
self._click_handlers = CallbackDispatcher()
self.on_msg(_handle_anywidget_dispatch)

def on_click(self, callback, remove=False):
"""Register a callback to execute when the map is clicked.

The callback will be called with one argument, a tuple of the coordinate
clicked (x,y)/(Longitude/Latitude).

Parameters
----------
remove: bool (optional)
Set to true to remove the callback from the list of callbacks.

!!! note

If the map is zoomed to a very large scale and can see the earth wrapped
around, it is possible the coordinate's x/Longitude value may be greater
than or less than expected. Example: If you can see Paris, France three
times in the map, and you click on the Paris in the middle, it will show an
X coordinate of 2, but the Paris on the left of the map will report an X
coordinate of -358, and the Paris on the right of the map will report an
X coordinate of 362.

"""
self._click_handlers.register_callback(callback, remove=remove)
self._has_click_handlers = len(self._click_handlers.callbacks) > 0

_esm = bundler_output_dir / "index.js"
_css = bundler_output_dir / "index.css"
Expand Down Expand Up @@ -128,6 +162,10 @@ def __init__(
once it's been initially rendered.

"""
_has_click_handlers = t.Bool(default_value=False, allow_none=False).tag(sync=True)
"""
Indicates if a click handler has been registered.
"""

_height = t.Int(default_value=DEFAULT_HEIGHT, allow_none=True).tag(sync=True)
"""Height of the map in pixels.
Expand Down
5 changes: 5 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ function App() {
const onMapClickHandler = useCallback((info: PickingInfo) => {
// We added this flag to prevent the hover event from firing after a
// click event.
if (typeof info.coordinate !== "undefined") {
if (model.get("_has_click_handlers")) {
model.send({ kind: "on-click", coordinate: info.coordinate });
}
}
setJustClicked(true);
actorRef.send({
type: "Map click event",
Expand Down