diff --git a/.gitignore b/.gitignore index 1b96f79..c236667 100644 --- a/.gitignore +++ b/.gitignore @@ -62,8 +62,6 @@ instance/ # Sphinx documentation docs/_build/ docs/_static/ -docs/_static/embed-bundle.js -docs/_static/embed-bundle.js.map docs/build docs/output docs/jupyter_execute @@ -133,6 +131,8 @@ ehthumbs.db # Folder config file Desktop.ini .config.ini +.env.ini + # Recycle Bin used on file shares $RECYCLE.BIN/ @@ -167,9 +167,5 @@ ipyopenlayers/nbextension .yarn -.vscode/ - #jupyter lite .jupyterlite.doit.db -lite_build_output/dist -lite_build_output/.jupyterlite.doit.db diff --git a/examples/GeojsonLayer.ipynb b/examples/GeojsonLayer.ipynb index 5a972f3..813e0ee 100644 --- a/examples/GeojsonLayer.ipynb +++ b/examples/GeojsonLayer.ipynb @@ -466,7 +466,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/examples/RasterLayer.ipynb b/examples/RasterLayer.ipynb index 778dd57..358305d 100644 --- a/examples/RasterLayer.ipynb +++ b/examples/RasterLayer.ipynb @@ -110,7 +110,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/examples/map.ipynb b/examples/map.ipynb index 0699aaa..0d0b51a 100644 --- a/examples/map.ipynb +++ b/examples/map.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "id": "94284d6e", "metadata": { "vscode": { @@ -21,15 +21,15 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "cf72e462ac2847b586c82d5738e01598", + "model_id": "2fe473c94493460082701df614ff2268", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Map(center=[0.0, 0.0], layers=[RasterTileLayer()])" + "Map(center=[0.0, 0.0], zoom=2.0)" ] }, - "execution_count": 1, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -38,6 +38,10 @@ "from ipyopenlayers import (\n", " Map, RasterTileLayer\n", ")\n", + "import configparser\n", + "import requests\n", + "import unicodedata\n", + "\n", "m = Map(center=[0.0, 0.0], zoom=2)\n", "layer= RasterTileLayer()\n", "m.add_layer(layer)\n", @@ -46,11 +50,43 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "6ecd0ff6-93f3-4cd8-bc0a-8520aab07a84", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "config = configparser.ConfigParser()\n", + "config.read('../.env.ini')\n", + "api_key = config['DEFAULT']['api_key']" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c2a997b5-3f80-4e9c-8956-5dba704f7eae", + "metadata": {}, + "outputs": [], + "source": [ + "def get_country_from_coordinates_geoapify(**kwargs):\n", + " lon = kwargs.get('lon')\n", + " lat = kwargs.get('lat')\n", + " url = f\"https://api.geoapify.com/v1/geocode/reverse?lat={lat}&lon={lon}&apiKey={api_key}\"\n", + " \n", + " response = requests.get(url)\n", + " data = response.json()\n", + " \n", + " features = data.get('features', [])\n", + " if features:\n", + " first_feature = features[0]\n", + " properties = first_feature.get('properties', {})\n", + " country = properties.get('country', None)\n", + " normalized_name = country.split(' ')[0]\n", + " normalized_name = unicodedata.normalize('NFKD', normalized_name)\n", + " normalized_name = normalized_name.encode('ASCII', 'ignore').decode('utf-8')\n", + " print(f\"Country: {normalized_name}\")\n", + "\n", + "m.on_click(get_country_from_coordinates_geoapify)" + ] } ], "metadata": { @@ -69,7 +105,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.19" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/ipyopenlayers/openlayers.py b/ipyopenlayers/openlayers.py index 807c24b..d23d335 100644 --- a/ipyopenlayers/openlayers.py +++ b/ipyopenlayers/openlayers.py @@ -4,7 +4,7 @@ # Copyright (c) QuantStack. # Distributed under the terms of the Modified BSD License. -from ipywidgets import DOMWidget, Widget, widget_serialization +from ipywidgets import DOMWidget, Widget, widget_serialization, CallbackDispatcher from traitlets import Unicode, List, Instance, CFloat, Bool, Dict, Int, Float from ._frontend import module_name, module_version @@ -43,7 +43,7 @@ class TileLayer(Layer): Additional format options for the tile source. """ - url = Unicode('').tag(sync=True) + url = Unicode('https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png').tag(sync=True) attribution = Unicode("").tag(sync=True) opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True) visible = Bool(True).tag(sync=True) @@ -263,6 +263,8 @@ class Map(DOMWidget): layers = List(Instance(Layer)).tag(sync=True, **widget_serialization) overlays=List(Instance(BaseOverlay)).tag(sync=True, **widget_serialization) controls=List(Instance(BaseControl)).tag(sync=True, **widget_serialization) + _click_callbacks = Instance(CallbackDispatcher, ()) + @@ -277,6 +279,7 @@ def __init__(self, center=None, zoom=None, **kwargs): The initial zoom level of the map. """ super().__init__(**kwargs) + self.on_msg(self._handle_mouse_events) if center is not None: self.center = center if zoom is not None: @@ -351,4 +354,19 @@ def clear_layers(self): """Remove all layers from the map. """ - self.layers = [] \ No newline at end of file + self.layers = [] + + def _handle_mouse_events(self, _, content, buffers): + """Handle mouse events and trigger click callbacks. + """ + event_type = content.get("type", "") + if event_type == "click": + self._click_callbacks(**content) + + def on_click(self, callback, remove=False): + """Add a click event listener. + """ + self._click_callbacks.register_callback(callback, remove=remove) + + + diff --git a/src/widget.ts b/src/widget.ts index 02cd755..22dd489 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -12,6 +12,7 @@ import { BaseOverlayModel, BaseOverlayView } from './baseoverlay'; import { BaseControlModel, BaseControlView } from './basecontrol'; import { ViewObjectEventTypes } from 'ol/View'; import TileLayer from 'ol/layer/Tile'; +import MapBrowserEvent from 'ol/MapBrowserEvent'; import { Map } from 'ol'; import View from 'ol/View'; import 'ol/ol.css'; @@ -122,6 +123,10 @@ export class MapView extends DOMWidgetView { ], }); + this.map.on('click', (event: MapBrowserEvent) => { + this.handleMapClick(event); + }); + this.map.getView().on('change:center', () => { this.model.set('center', this.map.getView().getCenter()); this.model.save_changes(); @@ -143,6 +148,12 @@ export class MapView extends DOMWidgetView { this.model.on('change:zoom', this.zoomChanged, this); this.model.on('change:center', this.centerChanged, this); } + + handleMapClick(event: MapBrowserEvent) { + const coordinate = event.coordinate; + const [lon, lat] = coordinate; + this.send({ type: 'click', lon, lat }); + } layersChanged() { const layers = this.model.get('layers') as LayerModel[]; this.layerViews.update(layers); diff --git a/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-GeoTIFFTileLayer-update-ipynb-cell-0-linux.png b/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-GeoTIFFTileLayer-update-ipynb-cell-0-linux.png index 5546c31..f5308bd 100644 Binary files a/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-GeoTIFFTileLayer-update-ipynb-cell-0-linux.png and b/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-GeoTIFFTileLayer-update-ipynb-cell-0-linux.png differ diff --git a/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-HeatmapLayer-update-ipynb-cell-0-linux.png b/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-HeatmapLayer-update-ipynb-cell-0-linux.png index c0e957e..c42badc 100644 Binary files a/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-HeatmapLayer-update-ipynb-cell-0-linux.png and b/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-HeatmapLayer-update-ipynb-cell-0-linux.png differ diff --git a/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-VideoOverlay-update-ipynb-cell-1-linux.png b/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-VideoOverlay-update-ipynb-cell-1-linux.png index b8392e4..62dc32d 100644 Binary files a/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-VideoOverlay-update-ipynb-cell-1-linux.png and b/ui-tests/tests/ipyopenlayers.test.ts-snapshots/light-VideoOverlay-update-ipynb-cell-1-linux.png differ diff --git a/webpack.config.js b/webpack.config.js index 364792a..d5fe1b9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -75,7 +75,7 @@ module.exports = [ entry: './src/index.ts', output: { filename: 'embed-bundle.js', - path: path.resolve(__dirname, 'docs', 'source', '_static'), + path: path.resolve(__dirname, 'docs', '_static'), library: 'ipyopenlayers', libraryTarget: 'amd', },