diff --git a/css/widget.css b/css/widget.css
index 85bb861..2641c91 100644
--- a/css/widget.css
+++ b/css/widget.css
@@ -19,3 +19,18 @@
overflow: hidden;
flex: 1 1 auto;
}
+
+.swiper-container {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 1000;
+ background: rgba(255, 255, 255, 0.8);
+ padding: 5px;
+ border-radius: 4px;
+}
+
+.swipe {
+ width: 100%;
+ margin: 0;
+}
diff --git a/examples/RasterLayer.ipynb b/examples/RasterLayer.ipynb
index d054f2b..f5958bd 100644
--- a/examples/RasterLayer.ipynb
+++ b/examples/RasterLayer.ipynb
@@ -13,24 +13,12 @@
"metadata": {},
"outputs": [],
"source": [
- "from ipyopenlayers import Map, RasterTileLayer"
+ "from ipyopenlayers import Map, RasterTileLayer,SplitMapControl, ZoomSlider"
]
},
{
"cell_type": "code",
"execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "import configparser\n",
- "config = configparser.ConfigParser()\n",
- "config.read('.config.ini')\n",
- "key = config['DEFAULT']['key']"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
"metadata": {
"scrolled": true
},
@@ -38,21 +26,21 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "bf54f7b270eb4c12ba2fbcaeea2583da",
+ "model_id": "eb8f7885d1b94654942794f633a79e87",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
- "Map(center=[4.299875503991089, 46.85012303279379], zoom=0.0)"
+ "Map(center=[0.0, 0.0])"
]
},
- "execution_count": 3,
+ "execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "m = Map(center=[4.299875503991089, 46.85012303279379], zoom=0)\n",
+ "m = Map()\n",
"m"
]
},
@@ -74,64 +62,210 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
- "m.add_layer(layere) "
+ "layer_b=RasterTileLayer(url='https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png')"
]
},
{
"cell_type": "code",
- "execution_count": 26,
+ "execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
- "attributions = '© MapTiler ' +'© OpenStreetMap contributors';\n",
- "\n",
- "raster = RasterTileLayer(attributions=attributions,url='https://api.maptiler.com/maps/dataviz-dark/{z}/{x}/{y}.png?key=' + key,\n",
- " tileSize= 512)\n",
- "m.add_layer(raster) "
+ "m.add_layer(layere) "
]
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "scrolled": true
- },
+ "execution_count": 4,
+ "metadata": {},
"outputs": [],
"source": [
- "m.remove_layer(raster)"
+ "zoom=ZoomSlider()\n",
+ "m.add_control(zoom)"
]
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "jupyter": {
- "source_hidden": true
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "eb8f7885d1b94654942794f633a79e87",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[0.0, 0.0])"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
}
- },
+ ],
+ "source": [
+ "m.remove(zoom)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
"outputs": [],
"source": [
- "attributions = [\n",
- " '© MapTiler',\n",
- " '© OpenStreetMap contributors'\n",
- "]\n",
- "\n",
- "rasterlay = RasterTileLayer(url=f'https://api.maptiler.com/maps/satellite/{{z}}/{{x}}/{{y}}.jpg?key={key}',attributions=attributions,tileSize=512,maxZoom=20)\n",
- "m.add_layer(rasterlay)"
+ "split = SplitMapControl(left_layer=layer_b)"
]
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
- "m.remove_layer(rasterlay)"
+ "m.add_control(split)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "118567532fa24b9bbfe23392434a10ff",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[0.0, 0.0], layers=[RasterTileLayer()], zoom=3.1446582428318823)"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.remove(split)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[]"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.controls"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "9ca94f4d55c341d88bec99d839f1d744",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[47.167155938883134, 33.72586333359965], layers=[RasterTileLayer()])"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[0.00033276346057838606, 0.011182868644951327], controls=[SplitMapControl(left_layer=RasterTileLaye…"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "m.remove_control(split)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "249f37982ae74162851c0f1b99a36b1a",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[0.00033276346057838606, 0.011182868644951327], layers=[RasterTileLayer()], zoom=1.9997517395318722…"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
diff --git a/ipyopenlayers/__init__.py b/ipyopenlayers/__init__.py
index 4cf0919..0671fce 100644
--- a/ipyopenlayers/__init__.py
+++ b/ipyopenlayers/__init__.py
@@ -4,7 +4,7 @@
# Copyright (c) QuantStack.
# Distributed under the terms of the Modified BSD License.
-from .Map import *
+from .openlayers import *
from ._version import __version__, version_info
def _jupyter_labextension_paths():
diff --git a/ipyopenlayers/Map.py b/ipyopenlayers/openlayers.py
similarity index 83%
rename from ipyopenlayers/Map.py
rename to ipyopenlayers/openlayers.py
index d17b0ec..b3f48f7 100644
--- a/ipyopenlayers/Map.py
+++ b/ipyopenlayers/openlayers.py
@@ -67,9 +67,6 @@ class HeatmapLayer(Layer):
blur =Int(15).tag(sync=True)
radius = Int(8).tag(sync=True)
-
-
-
class BaseOverlay(DOMWidget):
_model_module = Unicode(module_name).tag(sync=True)
@@ -87,7 +84,6 @@ class ImageOverlay (BaseOverlay):
class VideoOverlay (BaseOverlay):
_view_name = Unicode('VideoOverlayView').tag(sync=True)
_model_name = Unicode('VideoOverlayModel').tag(sync=True)
-
video_url = Unicode('').tag(sync=True)
class PopupOverlay (BaseOverlay):
@@ -121,6 +117,14 @@ class MousePosition(BaseControl):
_view_name = Unicode('MousePositionView').tag(sync=True)
_model_name = Unicode('MousePositionModel').tag(sync=True)
+class SplitMapControl(BaseControl):
+ _model_name = Unicode('SplitMapControlModel').tag(sync=True)
+ _view_name = Unicode('SplitMapControlView').tag(sync=True)
+ left_layer = Instance(Layer).tag(sync=True, **widget_serialization)
+ #right_layer = Instance(Layer).tag(sync=True, **widget_serialization)
+ swipe_position = Int(50).tag(sync=True)
+
+
class Map(DOMWidget):
_model_name = Unicode('MapModel').tag(sync=True)
@@ -137,7 +141,6 @@ class Map(DOMWidget):
controls=List(Instance(BaseControl)).tag(sync=True, **widget_serialization)
-
def __init__(self, center=None, zoom=None, **kwargs):
super().__init__(**kwargs)
@@ -163,7 +166,34 @@ def add_control(self, control):
def remove_control(self, control):
self.controls = [x for x in self.controls if x != control]
-
+
+ def remove(self, item):
+ """Remove an item from the map : either a layer or a control.
+
+ Parameters
+ ----------
+ item: Layer or Control instance
+ The layer or control to remove.
+ """
+ if isinstance(item, Layer):
+ self.layers = tuple(
+ [layer for layer in self.layers if layer.model_id != item.model_id]
+ )
+
+ elif isinstance(item, BaseControl):
+ self.controls = tuple(
+ [
+ control
+ for control in self.controls
+ if control.model_id != item.model_id
+ ]
+ )
+ return self
+
def clear_layers(self):
- self.layers = []
\ No newline at end of file
+ self.layers = []
+
+
+
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 70e3887..0a58d87 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,8 +36,9 @@ classifiers = [
]
dependencies = [
"ipywidgets>=8.0.0",
+ "traitlets",
]
-version = "0.1.0.dev0"
+version = "0.1.0"
[project.optional-dependencies]
docs = [
diff --git a/src/rastertilelayer.ts b/src/rastertilelayer.ts
index 68c934f..3726a31 100644
--- a/src/rastertilelayer.ts
+++ b/src/rastertilelayer.ts
@@ -1,12 +1,15 @@
-// Copyright (c) QuantStack
-// Distributed under the terms of the Modified BSD License.
import { DOMWidgetModel, ISerializers } from '@jupyter-widgets/base';
-import TileLayer from 'ol/layer/WebGLTile.js';
+import WebGLTileLayer from 'ol/layer/WebGLTile.js';
import XYZ from 'ol/source/XYZ.js';
import { MODULE_NAME, MODULE_VERSION } from './version';
import { MapView } from './widget';
import { LayerModel, LayerView } from './layer';
+/*
+type WebGLEvent = {
+ context: WebGLRenderingContext;
+};
+*/
export class RasterTileLayerModel extends LayerModel {
defaults() {
return {
@@ -28,7 +31,7 @@ export class RasterTileLayerModel extends LayerModel {
static serializers: ISerializers = {
...DOMWidgetModel.serializers,
- // Add any extra serializers here
+ // Add any extra serializers ici
};
static model_name = 'RasterTileLayerModel';
@@ -41,15 +44,36 @@ export class RasterTileLayerModel extends LayerModel {
export class RasterTileLayerView extends LayerView {
map_view: MapView;
+ tileLayer: WebGLTileLayer;
+ /*
+ private prerenderListener: (event: WebGLEvent) => void;
+ private postrenderListener: (event: WebGLEvent) => void;
+ private previousSwipePosition: number | undefined;
+
+ constructor(options: any) {
+ super(options);
+ this.map_view = options.options.map_view;
+ this.prerenderListener = this.map_view.handlePrerender.bind(this.map_view);
+ this.postrenderListener = this.map_view.handlePostrender.bind(
+ this.map_view,
+ );
+ this.previousSwipePosition = undefined;
+ }*/
render() {
super.render();
this.urlChanged();
this.model.on('change:url', this.urlChanged, this);
+ /*this.model.on(
+ 'change:swipe_position',
+ this.handleSwipePositionChanged,
+ this,
+ );*/
+ //this.updateEventListeners();
}
create_obj() {
- this.obj = this.tileLayer = new TileLayer({
+ this.obj = this.tileLayer = new WebGLTileLayer({
source: new XYZ({
url: this.model.get('url'),
attributions: this.model.get('attributions'),
@@ -74,5 +98,27 @@ export class RasterTileLayerView extends LayerView {
}
}
- tileLayer: TileLayer;
+ /*handleSwipePositionChanged() {
+ const swipePosition = this.model.get('swipe_position');
+ console.log('Swipe Position Changed:', swipePosition);
+
+ if (this.previousSwipePosition !== swipePosition) {
+ this.previousSwipePosition = swipePosition;
+ this.updateEventListeners();
+ this.map_view.map.render();
+ }
+ }
+
+ updateEventListeners() {
+ console.log('Updating event listeners');
+ const swipePosition = this.model.get('swipe_position');
+ (this.tileLayer as any).un('precompose', this.prerenderListener);
+ (this.tileLayer as any).un('postcompose', this.postrenderListener);
+
+ if (swipePosition >= 0) {
+ (this.tileLayer as any).on('precompose', this.prerenderListener);
+ (this.tileLayer as any).on('postcompose', this.postrenderListener);
+ }
+ console.log('Event listeners updated');
+ }*/
}
diff --git a/src/splitcontrol.ts b/src/splitcontrol.ts
new file mode 100644
index 0000000..9868ac1
--- /dev/null
+++ b/src/splitcontrol.ts
@@ -0,0 +1,91 @@
+import Control from 'ol/control/Control';
+import { getRenderPixel } from 'ol/render';
+import 'ol/ol.css';
+import '../css/widget.css';
+import { MapView } from './widget';
+
+// Interface for SplitMapControl options
+interface SplitMapControlOptions {
+ target?: string;
+ map_view?: MapView;
+ swipe_position?: number;
+}
+
+export default class SplitMapControl extends Control {
+ swipe: HTMLInputElement;
+ leftLayer: any;
+ map_view: MapView;
+ private _swipe_position: number;
+
+ constructor(leftLayer: any, options: SplitMapControlOptions = {}) {
+ const element = document.createElement('div');
+ element.className = 'ol-unselectable ol-control split-map-control';
+
+ super({
+ element: element,
+ target: options.target,
+ });
+
+ this.leftLayer = leftLayer;
+
+ if (options.map_view) {
+ this.map_view = options.map_view;
+ } else {
+ throw new Error('MapView is required for SplitMapControl.');
+ }
+
+ this._swipe_position = options.swipe_position ?? 0;
+
+ const swiperContainer = document.createElement('div');
+ swiperContainer.className = 'swiper-container';
+ swiperContainer.style.width = '100%';
+
+ this.swipe = document.createElement('input');
+ this.swipe.type = 'range';
+ this.swipe.className = 'swipe';
+ this.swipe.style.width = '100%';
+ this.updateSwipeValue();
+ swiperContainer.appendChild(this.swipe);
+
+ this.map_view.map_container.style.position = 'relative';
+ this.map_view.map_container.appendChild(swiperContainer);
+
+ const map_view = this.map_view;
+
+ this.leftLayer.on('prerender', (event: any) => {
+ const gl = event.context;
+ gl.enable(gl.SCISSOR_TEST);
+
+ const mapSize = map_view.getSize();
+
+ if (mapSize) {
+ const bottomLeft = getRenderPixel(event, [0, mapSize[1]]);
+ const topRight = getRenderPixel(event, [mapSize[0], 0]);
+
+ const width = Math.round(
+ (topRight[0] - bottomLeft[0]) * (this._swipe_position / 100),
+ );
+ const height = topRight[1] - bottomLeft[1];
+
+ gl.scissor(bottomLeft[0], bottomLeft[1], width, height);
+ }
+ });
+
+ this.leftLayer.on('postrender', (event: any) => {
+ const gl = event.context;
+ gl.disable(gl.SCISSOR_TEST);
+ });
+
+ this.swipe.addEventListener('input', () => {
+ this._swipe_position = parseInt(this.swipe.value, 10);
+ this.updateSwipeValue();
+ map_view.map.render();
+ });
+ }
+
+ private updateSwipeValue() {
+ if (this.swipe) {
+ this.swipe.value = this._swipe_position.toString();
+ }
+ }
+}
diff --git a/src/splitmapcontrol.ts b/src/splitmapcontrol.ts
new file mode 100644
index 0000000..511bbaf
--- /dev/null
+++ b/src/splitmapcontrol.ts
@@ -0,0 +1,79 @@
+// Copyright (c) QuantStack
+// Distributed under the terms of the Modified BSD License.
+import { unpack_models, ISerializers } from '@jupyter-widgets/base';
+import { BaseControlModel, BaseControlView } from './basecontrol';
+import 'ol/ol.css';
+import '../css/widget.css';
+import SplitMapControl from './splitcontrol';
+import { MODULE_NAME, MODULE_VERSION } from './version';
+
+export class SplitMapControlModel extends BaseControlModel {
+ defaults() {
+ return {
+ ...super.defaults(),
+ _model_name: SplitMapControlModel.model_name,
+ _model_module: SplitMapControlModel.model_module,
+ _model_module_version: SplitMapControlModel.model_module_version,
+ _view_name: SplitMapControlModel.view_name,
+ _view_module: SplitMapControlModel.view_module,
+ _view_module_version: SplitMapControlModel.view_module_version,
+ left_layer: undefined,
+ right_layer: undefined,
+ swipe_position: 50,
+ };
+ }
+
+ static serializers: ISerializers = {
+ ...BaseControlModel.serializers,
+ left_layer: { deserialize: unpack_models },
+ right_layer: { deserialize: unpack_models },
+ };
+
+ static model_name = 'SplitMapControlModel';
+ static model_module = MODULE_NAME;
+ static model_module_version = MODULE_VERSION;
+ static view_name = 'SplitMapControlView';
+ static view_module = MODULE_NAME;
+ static view_module_version = MODULE_VERSION;
+}
+
+function asArray(arg: any) {
+ return Array.isArray(arg) ? arg : [arg];
+}
+
+export class SplitMapControlView extends BaseControlView {
+ swipe_position: number;
+
+ initialize(parameters: any) {
+ super.initialize(parameters);
+ this.map_view = this.options.map_view;
+ this.map_view.layer_views = this.options.map_view.layerViews;
+ if (this.map_view && !this.map_view.layerViews) {
+ console.warn(
+ 'Layer views is not initialized. Ensure it is properly set.',
+ );
+ }
+ }
+
+ createObj() {
+ const left_models = asArray(this.model.get('left_layer'));
+ let layersModel = this.map_view.model.get('layers');
+ layersModel = layersModel.concat(left_models);
+
+ return this.map_view.layer_views.update(layersModel).then((views: any) => {
+ const left_views: any[] = [];
+ views.forEach((view: any) => {
+ if (left_models.indexOf(view.model) !== -1) {
+ left_views.push(view.obj);
+ }
+ });
+
+ this.swipe_position = this.model.get('swipe_position');
+
+ this.obj = new SplitMapControl(left_views[0], {
+ map_view: this.map_view,
+ swipe_position: this.swipe_position,
+ });
+ });
+ }
+}
diff --git a/src/widget.ts b/src/widget.ts
index 20f3298..9eaa924 100644
--- a/src/widget.ts
+++ b/src/widget.ts
@@ -11,7 +11,8 @@ import { LayerModel, LayerView } from './layer';
import { BaseOverlayModel, BaseOverlayView } from './baseoverlay';
import { BaseControlModel, BaseControlView } from './basecontrol';
import { ViewObjectEventTypes } from 'ol/View';
-
+import { SplitMapControlModel, SplitMapControlView } from './splitmapcontrol';
+export { SplitMapControlModel, SplitMapControlView };
import { Map } from 'ol';
import View from 'ol/View';
import 'ol/ol.css';
@@ -31,6 +32,8 @@ export * from './heatmap';
export * from './rastertilelayer';
export * from './geotifflayer';
export * from './vectortilelayer';
+export * from './splitmapcontrol';
+export * from './splitcontrol';
const DEFAULT_LOCATION = [0.0, 0.0];
@@ -49,6 +52,7 @@ export class MapModel extends DOMWidgetModel {
overlays: [],
zoom: 2,
center: DEFAULT_LOCATION,
+ swipe_position: 0,
};
}
@@ -96,7 +100,6 @@ export class MapView extends DOMWidgetView {
this.removeLayerView,
this,
);
-
this.overlayViews = new ViewList(
this.addOverlayModel,
this.removeOverlayView,
@@ -128,21 +131,22 @@ export class MapView extends DOMWidgetView {
this.model.set('zoom', this.map.getView().getZoom());
this.model.save_changes();
});
-
this.layersChanged();
- this.overlayChanged();
this.controlChanged();
+ this.overlayChanged();
+ this.zoomChanged();
+ this.centerChanged();
this.model.on('change:layers', this.layersChanged, this);
this.model.on('change:overlays', this.overlayChanged, this);
this.model.on('change:controls', this.controlChanged, this);
this.model.on('change:zoom', this.zoomChanged, this);
this.model.on('change:center', this.centerChanged, this);
}
+
layersChanged() {
const layers = this.model.get('layers') as LayerModel[];
this.layerViews.update(layers);
}
-
overlayChanged() {
const overlay = this.model.get('overlays') as BaseOverlayModel[];
this.overlayViews.update(overlay);
@@ -159,6 +163,10 @@ export class MapView extends DOMWidgetView {
this.map.getView().setZoom(newZoom);
}
}
+ getSize() {
+ const size = this.map.getSize();
+ return size;
+ }
centerChanged() {
const newCenter = this.model.get('center');
@@ -192,9 +200,9 @@ export class MapView extends DOMWidgetView {
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,
@@ -224,6 +232,7 @@ export class MapView extends DOMWidgetView {
imageElement: HTMLImageElement;
map_container: HTMLDivElement;
map: Map;
+ map_view: MapView;
layerViews: ViewList;
overlayViews: ViewList;
controlViews: ViewList;