diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index e7b23e4..c06f769 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -13,7 +13,7 @@ "metadata": {}, "outputs": [], "source": [ - "from ipyopenlayers import Map, TileLayer" + "from ipyopenlayers import Map, TileLayer, ImageOverlay, VideoOverlay,PopupOverlay" ] }, { @@ -24,7 +24,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "757d5de3cec24c4e83d186b710dcfa59", + "model_id": "26ca977cf97241c0b29d81cb48500b97", "version_major": 2, "version_minor": 0 }, @@ -38,27 +38,63 @@ } ], "source": [ - "m = Map()\n", + "m = Map(center=[0.0, 0.0], zoom=2)\n", "m" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "eca839e371ec41e5806221cd8bda94dd", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "TileLayer()" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "m.zoom=4\n", - "\n" + "layer=TileLayer()\n", + "layer" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ - "m.center=[10.0,5.0]" + "layer.url='https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.layers" ] }, { @@ -67,19 +103,27 @@ "metadata": {}, "outputs": [], "source": [ - "layere= TileLayer()\n", - "layere.url=\"https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\"\n" + "m.add_layer(layer)" ] }, { "cell_type": "code", "execution_count": 7, - "metadata": { - "scrolled": true - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[TileLayer(url='https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png')]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "m.add_layer(layere)" + "m.layers" ] }, { @@ -89,8 +133,13 @@ "outputs": [ { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6c598e65b1054e3d87e80d164ef8c5d0", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "[TileLayer(url='https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png')]" + "ImageOverlay(position=[0, 0])" ] }, "execution_count": 8, @@ -99,36 +148,74 @@ } ], "source": [ - "m.layers" + "image=ImageOverlay()\n", + "image" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ - "m.remove_layer(layere)" + "image.image_url=\"https://i.imgur.com/06Q1fSz.png\"" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, + "outputs": [], + "source": [ + "image.image_url=\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "m.add_overlay(image)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "image.position=[-70,70]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[]" + "[ImageOverlay(image_url='https://i.imgur.com/06Q1fSz.png', position=[-70, 70])]" ] }, - "execution_count": 10, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "m.layers" + "m.overlays" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "imaget=ImageOverlay()\n", + "imaget.image_url=\"https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg\"\n" ] }, { @@ -137,73 +224,115 @@ "metadata": {}, "outputs": [], "source": [ - "m.clear_layers()" + "m.add_overlay(imaget)" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 13, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "m.layers" + "m.remove_overlay(image)" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "59aea50b07f14f028e2fb5e01c43a461", + "model_id": "f7453e31240d4770986c2189b947d065", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Map(center=[0.0, 0.0])" + "VideoOverlay(position=[0, 0])" ] }, - "execution_count": 12, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "m1 = Map(center=[0.0, 0.0], zoom=2)\n", - "m1" + "video=VideoOverlay()\n", + "video" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "video.video_url=\"https://www.w3schools.com/html/mov_bbb.webm\"" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "video.video_url=\"https://www.mapbox.com/bites/00188/patricia_nasa.webm\"" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "m.add_overlay(video)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "video.position=[-70,70]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "video2=VideoOverlay()\n", + "video2.video_url=\"https://www.mapbox.com/bites/00188/patricia_nasa.webm\"" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "video2.position=[-80,70]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "m.add_overlay(video2)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2.0" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "m1.zoom\n" + "m.remove_overlay(video)" ] }, { @@ -213,8 +342,13 @@ "outputs": [ { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "786f2fed9b174fa5bbc4829bffabe492", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "[0.0, 0.0]" + "PopupOverlay(position=[0, 0])" ] }, "execution_count": 14, @@ -223,7 +357,35 @@ } ], "source": [ - "m1.center" + "popup=PopupOverlay()\n", + "popup" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "popup.popup_content='hellooooo'" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "m.add_overlay(popup)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "m.remove_overlay(popup)" ] }, { diff --git a/ipyopenlayers/example.py b/ipyopenlayers/example.py index 8ead0e1..1282f9a 100644 --- a/ipyopenlayers/example.py +++ b/ipyopenlayers/example.py @@ -8,9 +8,10 @@ TODO: Add module docstring """ -from ipywidgets import DOMWidget, Widget, widget_serialization -from traitlets import Unicode, List, Instance, CFloat +from ipywidgets import DOMWidget, Widget, widget_serialization, HTML +from traitlets import Unicode, List, Instance, CFloat, Bool,Int, Float from ._frontend import module_name, module_version + def_loc = [0.0, 0.0] class TileLayer(Widget): @@ -22,7 +23,28 @@ class TileLayer(Widget): _view_module = Unicode(module_name).tag(sync=True) _view_module_version = Unicode(module_version).tag(sync=True) url = Unicode('https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png').tag(sync=True) - + +class BaseOverlay(DOMWidget): + _model_name = Unicode('BaseOverlayModel').tag(sync=True) + _model_module = Unicode(module_name).tag(sync=True) + _model_module_version = Unicode(module_version).tag(sync=True) + _view_name = Unicode('BaseOverlayView').tag(sync=True) + _view_module = Unicode(module_name).tag(sync=True) + _view_module_version = Unicode(module_version).tag(sync=True) + overlay_type = Unicode().tag(sync=True) + position = List([0, 0]).tag(sync=True) + +class ImageOverlay (BaseOverlay): + overlay_type = Unicode('image').tag(sync=True) + image_url = Unicode('').tag(sync=True) + +class VideoOverlay (BaseOverlay): + overlay_type = Unicode('video').tag(sync=True) + video_url = Unicode('').tag(sync=True) + +class PopupOverlay (BaseOverlay): + overlay_type = Unicode('popup').tag(sync=True) + popup_content = Unicode('').tag(sync=True) class Map(DOMWidget): _model_name = Unicode('MapModel').tag(sync=True) @@ -36,23 +58,28 @@ class Map(DOMWidget): center = List(def_loc).tag(sync=True, o=True) zoom = CFloat(2).tag(sync=True, o=True) layers = List(Instance(TileLayer)).tag(sync=True, **widget_serialization) + overlays=List(Instance(BaseOverlay)).tag(sync=True, **widget_serialization) + def __init__(self, center=None, zoom=None, **kwargs): super().__init__(**kwargs) if center is not None: self.center = center - if zoom is not None: self.zoom = zoom def add_layer(self, layer): self.layers = self.layers + [layer] + def add_overlay(self, overlay): + self.overlays = self.overlays + [overlay] + def remove_layer(self, layer): self.layers = [x for x in self.layers if x != layer] + + def remove_overlay(self, overlay): + self.overlays = [x for x in self.overlays if x != overlay] def clear_layers(self): - self.layers = [] - - + self.layers = [] \ No newline at end of file diff --git a/src/__tests__/utils.ts b/src/__tests__/utils.ts index 0a61d63..06f6387 100644 --- a/src/__tests__/utils.ts +++ b/src/__tests__/utils.ts @@ -54,7 +54,7 @@ export class DummyManager extends baseManager.ManagerBase { display_view( msg: services.KernelMessage.IMessage, view: widgets.DOMWidgetView, - options: any + options: any, ) { // TODO: make this a spy // TODO: return an html element @@ -68,7 +68,7 @@ export class DummyManager extends baseManager.ManagerBase { protected loadClass( className: string, moduleName: string, - moduleVersion: string + moduleVersion: string, ): Promise { if (moduleName === '@jupyter-widgets/base') { if ((widgets as any)[className]) { @@ -106,7 +106,7 @@ export interface Constructor { export function createTestModel( constructor: Constructor, - attributes?: any + attributes?: any, ): T { const id = widgets.uuid(); const widget_manager = new DummyManager(); diff --git a/src/index.ts b/src/index.ts index 850616a..ef0d215 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ // Copyright (c) QuantStack // Distributed under the terms of the Modified BSD License. - export * from './version'; export * from './widget'; +export * from './tilelayer'; diff --git a/src/plugin.ts b/src/plugin.ts index dbad27f..b469593 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -32,7 +32,7 @@ export default examplePlugin; */ function activateWidgetExtension( app: Application, - registry: IJupyterWidgetRegistry + registry: IJupyterWidgetRegistry, ): void { registry.registerWidget({ name: MODULE_NAME, diff --git a/src/tilelayer.ts b/src/tilelayer.ts new file mode 100644 index 0000000..daffb69 --- /dev/null +++ b/src/tilelayer.ts @@ -0,0 +1,60 @@ +import { WidgetModel, WidgetView, ISerializers } from '@jupyter-widgets/base'; +import TileLayer from 'ol/layer/Tile'; +import XYZ from 'ol/source/XYZ'; +import OSM from 'ol/source/OSM'; +import { MODULE_NAME, MODULE_VERSION } from './version'; + +export class TileLayerModel extends WidgetModel { + defaults() { + return { + ...super.defaults(), + _model_name: TileLayerModel.model_name, + _model_module: TileLayerModel.model_module, + _model_module_version: TileLayerModel.model_module_version, + _view_name: TileLayerModel.view_name, + _view_module: TileLayerModel.view_module, + _view_module_version: TileLayerModel.view_module_version, + value: 'Hello World', + }; + } + + static serializers: ISerializers = { + ...WidgetModel.serializers, + // Add any extra serializers here + }; + + static model_name = 'TileLayerModel'; + static model_module = MODULE_NAME; + static model_module_version = MODULE_VERSION; + static view_name = 'TileLayerView'; + static view_module = MODULE_NAME; + static view_module_version = MODULE_VERSION; +} + +export class TileLayerView extends WidgetView { + render() { + super.render(); + const url = this.model.get('url'); + + this.tileLayer = new TileLayer({ + source: new XYZ({ + url: url, + }), + }); + + this.urlChanged(); + this.model.on('change:url', this.urlChanged, this); + } + + urlChanged() { + const newUrl = this.model.get('url'); + if (newUrl) { + const newSource = new XYZ({ + url: newUrl, + }); + this.tileLayer.setSource(newSource); + } + } + + tileLayer: TileLayer; +} diff --git a/src/widget.ts b/src/widget.ts index 0f845d7..a886270 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -1,6 +1,5 @@ // Copyright (c) QuantStack // Distributed under the terms of the Modified BSD License. - import { DOMWidgetModel, DOMWidgetView, @@ -15,14 +14,17 @@ import { Map } from 'ol'; import OSM from 'ol/source/OSM'; import TileLayer from 'ol/layer/Tile'; import View from 'ol/View'; -import XYZ from 'ol/source/XYZ'; import 'ol/ol.css'; - import { MODULE_NAME, MODULE_VERSION } from './version'; import '../css/widget.css'; import { useGeographic } from 'ol/proj'; +import Overlay from 'ol/Overlay'; + +export * from './tilelayer'; +import { TileLayerModel, TileLayerView } from './tilelayer'; const DEFAULT_LOCATION = [0.0, 0.0]; + export class MapModel extends DOMWidgetModel { defaults() { return { @@ -37,21 +39,21 @@ export class MapModel extends DOMWidgetModel { layers: [], zoom: 2, center: DEFAULT_LOCATION, - }; } static serializers: ISerializers = { ...DOMWidgetModel.serializers, layers: { deserialize: unpack_models }, + overlays: { deserialize: unpack_models }, // Add any extra serializers here }; static model_name = 'MapModel'; static model_module = MODULE_NAME; static model_module_version = MODULE_VERSION; - static view_name = 'MapView'; - static view_module = MODULE_NAME; + static view_name = 'MapView'; + static view_module = MODULE_NAME; static view_module_version = MODULE_VERSION; } @@ -62,131 +64,219 @@ export class MapView extends DOMWidgetView { this.mapContainer = document.createElement('div'); this.mapContainer.style.height = '500px'; - this.el.appendChild(this.mapContainer); - this.layer_views = new ViewList( - this.add_layer_model, - this.remove_layer_view, - this + this.layerViews = new ViewList( + this.addLayerModel, + this.removeLayerView, + this, ); - - this.layers_changed(); - this.model.on('change:layers', this.layers_changed, this); - this.model.on('change:zoom', this.zoom_changed, this) - this.model.on('change:center', this.center_changed, this); - + this.overlayViews = new ViewList( + this.addOverlayModel, + this.removeOverlayView, + this, + ); this.map = new Map({ target: this.mapContainer, view: new View({ - center: this.model.get('center'), - zoom : this.model.get('zoom'), + center: this.model.get('center'), + zoom: this.model.get('zoom'), }), layers: [ new TileLayer({ - source: new OSM() - }) - ] + source: new OSM(), + }), + ], }); + + this.layersChanged(); + this.model.on('change:layers', this.layersChanged, this); + this.model.on('change:overlays', this.overlayChanged, this); + this.model.on('change:zoom', this.zoomChanged, this); + this.model.on('change:center', this.centerChanged, this); } - layers_changed(){ + layersChanged() { const layers = this.model.get('layers') as TileLayerModel[]; - this.layer_views.update(layers); + this.layerViews.update(layers); } - zoom_changed() { + + overlayChanged() { + const overlay = this.model.get('overlays') as BaseOverlayModel[]; + this.overlayViews.update(overlay); + } + + zoomChanged() { const newZoom = this.model.get('zoom'); if (newZoom !== undefined && newZoom !== null) { - this.map.getView().setZoom(newZoom); + this.map.getView().setZoom(newZoom); } -} - center_changed() { + } + + centerChanged() { const newCenter = this.model.get('center'); if (newCenter !== undefined && newCenter !== null) { - this.map.getView().setCenter(newCenter); + this.map.getView().setCenter(newCenter); } -} + } - remove_layer_view(child_view: TileLayerView) { - this.map.removeLayer(child_view.tileLayer); - child_view.remove(); + removeLayerView(child_view: TileLayerView) { + this.map.removeLayer(child_view.tileLayer); + child_view.remove(); + } + + removeOverlayView(child_view: BaseOverlayView) { + if (child_view.overlay) { + this.map.removeOverlay(child_view.overlay); + } + child_view.remove(); } - async add_layer_model(child_model: TileLayerModel) { + async addLayerModel(child_model: TileLayerModel) { const view = await this.create_child_view(child_model, { map_view: this, }); - this.map.addLayer(view.tileLayer); + this.displayed.then(() => { + view.trigger('displayed', this); + }); + return view; + } + async addOverlayModel(child_model: BaseOverlayModel) { + const view = await this.create_child_view(child_model, { + map_view: this, + }); + this.map.addOverlay(view.overlay); this.displayed.then(() => { view.trigger('displayed', this); }); return view; } - mapContainer: HTMLDivElement; + imageElement: HTMLImageElement; + mapContainer: HTMLDivElement; map: Map; - - layer_views: ViewList; + layerViews: ViewList; + overlayViews: ViewList; } - -export class TileLayerModel extends WidgetModel { +export class BaseOverlayModel extends DOMWidgetModel { defaults() { return { ...super.defaults(), - _model_name: TileLayerModel.model_name, - _model_module: TileLayerModel.model_module, - _model_module_version: TileLayerModel.model_module_version, - _view_name: TileLayerModel.view_name, - _view_module: TileLayerModel.view_module, - _view_module_version: TileLayerModel.view_module_version, + _model_name: BaseOverlayModel.model_name, + _model_module: BaseOverlayModel.model_module, + _model_module_version: BaseOverlayModel.model_module_version, + _view_name: BaseOverlayModel.view_name, + _view_module: BaseOverlayModel.view_module, + _view_module_version: BaseOverlayModel.view_module_version, value: 'Hello World', }; } static serializers: ISerializers = { - ...WidgetModel.serializers, - // Add any extra serializers here + ...DOMWidgetModel.serializers, + // Ajoutez ici tous les sérialiseurs supplémentaires }; - static model_name = 'TileLayerModel'; + static model_name = 'BaseOverlayModel'; static model_module = MODULE_NAME; static model_module_version = MODULE_VERSION; - static view_name = 'TileLayerView'; - static view_module = MODULE_NAME; + static view_name = 'BaseOverlayView'; + static view_module = MODULE_NAME; static view_module_version = MODULE_VERSION; } -export class TileLayerView extends WidgetView { +export class BaseOverlayView extends DOMWidgetView { + overlay: Overlay; + element: HTMLElement; + videoElement: HTMLVideoElement; + render() { super.render(); - const url= this.model.get('url') + this.updateElement(); + } + + initialize(parameters: WidgetView.IInitializeParameters) { + super.initialize(parameters); + this.initializeElement(); + this.createOverlay(); + this.model_events(); + } + + initializeElement() { + const overlayType = this.model.get('overlay_type'); + + if (overlayType === 'image') { + this.element = document.createElement('img'); + this.updateImageElement(); + } else if (overlayType === 'video') { + this.element = document.createElement('div'); + this.videoElement = document.createElement('video'); + this.videoElement.controls = true; + this.videoElement.src = this.model.get('video_url'); + this.element.appendChild(this.videoElement); + this.updateVideoElement(); + } else if (overlayType === 'popup') { + this.element = document.createElement('div'); + this.updatePopupElement(); + } + } - this.tileLayer = new TileLayer({ - source: new XYZ({ - url: url - }) + createOverlay() { + const position = this.model.get('position'); + this.overlay = new Overlay({ + position: position, + element: this.element, }); + return this.overlay; + } - this.url_changed(); - this.model.on('change:url', this.url_changed, this); + model_events() { + this.listenTo(this.model, 'change:overlay_type', this.initializeElement); + this.listenTo(this.model, 'change:image_url', this.updateImageElement); + this.listenTo(this.model, 'change:video_url', this.updateVideoElement); + this.listenTo(this.model, 'change:popup_content', this.updatePopupElement); + this.listenTo(this.model, 'change:position', this.updatePosition); } - url_changed() { - const newUrl = this.model.get('url'); - if (newUrl) { - const newSource = new XYZ({ - url: newUrl - }); - this.tileLayer.setSource(newSource); - - }} + updateElement() { + const overlayType = this.model.get('overlay_type'); + if (overlayType === 'image') { + this.updateImageElement(); + } else if (overlayType === 'video') { + this.updateVideoElement(); + } else if (overlayType === 'popup') { + this.updatePopupElement(); + } + } - - tileLayer: TileLayer; + updateImageElement() { + const imageUrl = this.model.get('image_url'); + if (imageUrl) { + (this.element as HTMLImageElement).src = imageUrl; + } + } -} \ No newline at end of file + updateVideoElement() { + const videoUrl = this.model.get('video_url'); + if (videoUrl) { + this.videoElement.src = this.model.get('video_url'); + } + } + + updatePopupElement() { + const popupContent = this.model.get('popup_content'); + if (popupContent) { + this.element.innerHTML = popupContent; + } + } + + updatePosition() { + const position = this.model.get('position'); + this.overlay.setPosition(position); + } +}