diff --git a/.gitignore b/.gitignore index 3057cb2..f876d8d 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,9 @@ $RECYCLE.BIN/ # NPM # ---- +# Ignoring the package-lock.json file +package-lock.json + **/node_modules/ ipyopenlayers/nbextension/index.* @@ -156,4 +159,6 @@ ipyopenlayers/nbextension/index.* ipyopenlayers/labextension ipyopenlayers/nbextension +!ipyopenlayers/nbextension/extensions.js + .yarn diff --git a/css/widget.css b/css/widget.css index 20fa18b..a02331d 100644 --- a/css/widget.css +++ b/css/widget.css @@ -1,3 +1,29 @@ .custom-widget { padding: 0px 2px; } +.ol-container { + height: 100%; + width: 100%; +} + +.lm-Widget.lm-Panel.jp-OutputArea-child.jp-OutputArea-executeResult{ + height: 100%; + overflow: hidden; + flex: 1 1 auto; +} + +.lm-Widget.lm-Panel.jp-OutputArea-output { + height: 100%; + overflow: hidden; + flex: 1 1 auto; +} + +.lm-Widget.custom-widget { + padding: 0px 2px; + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: 100%; + height: 100%; + min-height: 500px; +} \ No newline at end of file diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index 2be59fe..8239fc7 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -33,7 +33,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3265c9dce2864936a1f98ae5c591f899", + "model_id": "d8f5c32afbf64921bca30f0faf9363ee", "version_major": 2, "version_minor": 0 }, @@ -51,6 +51,26 @@ "m" ] }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.zoom" + ] + }, { "cell_type": "code", "execution_count": 5, @@ -124,7 +144,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3265c9dce2864936a1f98ae5c591f899", + "model_id": "0387237889744294ad4966279e4f39dd", "version_major": 2, "version_minor": 0 }, @@ -172,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -181,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -192,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -202,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -211,7 +231,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -222,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -231,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -242,7 +262,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -251,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -260,7 +280,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -296,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -305,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -314,7 +334,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -323,7 +343,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -335,7 +355,7 @@ " 'properties': {'name': 'Null Island'}}]}" ] }, - "execution_count": 30, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -346,8 +366,12 @@ }, { "cell_type": "code", - "execution_count": 31, - "metadata": {}, + "execution_count": 29, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, "outputs": [], "source": [ "import matplotlib as mpl\n", @@ -369,7 +393,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 30, "metadata": { "jupyter": { "source_hidden": true @@ -385,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 31, "metadata": { "jupyter": { "source_hidden": true @@ -408,8 +432,10 @@ }, { "cell_type": "code", - "execution_count": 34, - "metadata": {}, + "execution_count": 32, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -436,7 +462,7 @@ " '#08306b']" ] }, - "execution_count": 34, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -447,7 +473,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 33, "metadata": { "jupyter": { "source_hidden": true @@ -466,7 +492,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 34, "metadata": { "jupyter": { "source_hidden": true @@ -508,7 +534,7 @@ " 'fillOpacity': 0.5}}}" ] }, - "execution_count": 36, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -519,7 +545,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -528,7 +554,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -537,13 +563,60 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[-68.20312499999996, 36.536794394023914]" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.center" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.131608506719787" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.zoom" + ] + }, + { + "cell_type": "code", + "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "m.remove_layer(g)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, diff --git a/ipyopenlayers/Map.py b/ipyopenlayers/Map.py index 4030ea0..758dea4 100644 --- a/ipyopenlayers/Map.py +++ b/ipyopenlayers/Map.py @@ -9,7 +9,7 @@ """ from ipywidgets import DOMWidget, Widget, widget_serialization, CallbackDispatcher -from traitlets import Unicode, List, Instance, CFloat, Bool, Dict, Any +from traitlets import Unicode, List, Instance, Float, Bool, Dict, Any from ._frontend import module_name, module_version def_loc = [0.0, 0.0] @@ -102,7 +102,7 @@ class Map(DOMWidget): _view_module_version = Unicode(module_version).tag(sync=True) center = List(def_loc).tag(sync=True, o=True) - zoom = CFloat(2).tag(sync=True, o=True) + zoom = Float(2).tag(sync=True, o=True) 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) diff --git a/ipyopenlayers/nbextension/extension.js b/ipyopenlayers/nbextension/extension.js new file mode 100644 index 0000000..e6d713b --- /dev/null +++ b/ipyopenlayers/nbextension/extension.js @@ -0,0 +1,17 @@ +// Entry point for the notebook bundle containing custom model definitions. +// +define(function() { + "use strict"; + + window['requirejs'].config({ + map: { + '*': { + 'ipyopenlayers': 'nbextensions/ipyopenlayers/index', + }, + } + }); + // Export the required load_ipython_extension function + return { + load_ipython_extension : function() {} + }; +}); \ No newline at end of file diff --git a/src/layer.ts b/src/layer.ts index 6c8f40c..3515f77 100644 --- a/src/layer.ts +++ b/src/layer.ts @@ -14,7 +14,6 @@ export class LayerModel extends WidgetModel { _view_name: LayerModel.view_name, _view_module: LayerModel.view_module, _view_module_version: LayerModel.view_module_version, - value: 'Hello World', }; } diff --git a/src/widget.ts b/src/widget.ts index d26eb7b..49e51e4 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -7,9 +7,10 @@ import { unpack_models, ViewList, } from '@jupyter-widgets/base'; -import { TileLayerModel, TileLayerView } from './tilelayer'; +import { LayerModel, LayerView } from './layer'; import { BaseOverlayModel, BaseOverlayView } from './baseoverlay'; import { BaseControlModel, BaseControlView } from './basecontrol'; +import { ViewObjectEventTypes } from 'ol/View'; import { Map } from 'ol'; import TileLayer from 'ol/layer/Tile'; @@ -18,7 +19,7 @@ import 'ol/ol.css'; import { MODULE_NAME, MODULE_VERSION } from './version'; import '../css/widget.css'; import { useGeographic } from 'ol/proj'; - +import { ObjectEvent } from 'ol/Object'; export * from './imageoverlay'; export * from './geojson'; export * from './video_overlay'; @@ -70,10 +71,8 @@ export class MapView extends DOMWidgetView { render() { useGeographic(); this.el.classList.add('custom-widget'); - this.mapContainer = document.createElement('div'); - this.mapContainer.style.height = '500px'; - this.mapContainer.style.width = '100%'; + this.mapContainer.className = 'ol-container'; this.el.appendChild(this.mapContainer); this.layerViews = new ViewList( @@ -102,6 +101,18 @@ export class MapView extends DOMWidgetView { layers: [new TileLayer()], }); + this.map.getView().on('change:center', () => { + this.model.set('center', this.map.getView().getCenter()); + this.model.save_changes(); + }); + + this.map + .getView() + .on('change:resolution' as ViewObjectEventTypes, (event: ObjectEvent) => { + this.model.set('zoom', this.map.getView().getZoom()); + this.model.save_changes(); + }); + this.layersChanged(); this.overlayChanged(); this.controlChanged(); @@ -113,7 +124,7 @@ export class MapView extends DOMWidgetView { } layersChanged() { - const layers = this.model.get('layers') as TileLayerModel[]; + const layers = this.model.get('layers') as LayerModel[]; this.layerViews.update(layers); } @@ -141,7 +152,7 @@ export class MapView extends DOMWidgetView { } } - removeLayerView(child_view: TileLayerView) { + removeLayerView(child_view: LayerView) { this.map.removeLayer(child_view.obj); child_view.remove(); } @@ -158,8 +169,8 @@ export class MapView extends DOMWidgetView { child_view.remove(); } - async addLayerModel(child_model: TileLayerModel) { - const view = await this.create_child_view(child_model, { + async addLayerModel(child_model: LayerModel) { + const view = await this.create_child_view(child_model, { map_view: this, }); this.map.addLayer(view.obj); @@ -198,7 +209,7 @@ export class MapView extends DOMWidgetView { imageElement: HTMLImageElement; mapContainer: HTMLDivElement; map: Map; - layerViews: ViewList; + layerViews: ViewList; overlayViews: ViewList; controlViews: ViewList; } diff --git a/ui-tests/tests/ipyopenlayers.test.ts b/ui-tests/tests/ipyopenlayers.test.ts index 4c9859b..f97f621 100644 --- a/ui-tests/tests/ipyopenlayers.test.ts +++ b/ui-tests/tests/ipyopenlayers.test.ts @@ -45,7 +45,7 @@ const testCellOutputs = async (page: IJupyterLabPageFixture, tmpPath: string, th await page.notebook.save(); for (let c = 0; c < numCellImages; ++c) { - expect(results[c]).toMatchSnapshot(getCaptureImageName(contextPrefix, notebook, c), {threshold: 0.3}); + expect(results[c]).toMatchSnapshot(getCaptureImageName(contextPrefix, notebook, c), {threshold: 0.5}); } await page.notebook.close(true); @@ -86,7 +86,7 @@ const testPlotUpdates = async (page: IJupyterLabPageFixture, tmpPath: string, th await page.notebook.save(); for (let i = 0; i < cellCount; i++) { - expect(results[i]).toMatchSnapshot(getCaptureImageName(contextPrefix, notebook, i), {threshold: 0.3}); + expect(results[i]).toMatchSnapshot(getCaptureImageName(contextPrefix, notebook, i), {threshold: 0.5}); } await page.notebook.close(true);