Skip to content

draft swipe position #32

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions css/widget.css
Original file line number Diff line number Diff line change
@@ -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;
}
222 changes: 178 additions & 44 deletions examples/RasterLayer.ipynb
Original file line number Diff line number Diff line change
@@ -13,46 +13,34 @@
"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
},
"outputs": [
{
"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 = '<a href=\"https://www.maptiler.com/copyright/\" target=\"_blank\">&copy; MapTiler</a> ' +'<a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\">&copy; OpenStreetMap contributors</a>';\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",
" '&copy; <a href=\"https://www.maptiler.com/copyright/\" target=\"_blank\">MapTiler</a>',\n",
" '&copy; <a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\">OpenStreetMap contributors</a>'\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": {
2 changes: 1 addition & 1 deletion ipyopenlayers/__init__.py
Original file line number Diff line number Diff line change
@@ -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():
44 changes: 37 additions & 7 deletions ipyopenlayers/Map.py → ipyopenlayers/openlayers.py
Original file line number Diff line number Diff line change
@@ -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 = []
self.layers = []




3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -36,8 +36,9 @@ classifiers = [
]
dependencies = [
"ipywidgets>=8.0.0",
"traitlets",
]
version = "0.1.0.dev0"
version = "0.1.0"

[project.optional-dependencies]
docs = [
58 changes: 52 additions & 6 deletions src/rastertilelayer.ts
Original file line number Diff line number Diff line change
@@ -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');
}*/
}
91 changes: 91 additions & 0 deletions src/splitcontrol.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
79 changes: 79 additions & 0 deletions src/splitmapcontrol.ts
Original file line number Diff line number Diff line change
@@ -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,
});
});
}
}
21 changes: 15 additions & 6 deletions src/widget.ts
Original file line number Diff line number Diff line change
@@ -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 };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you don't need this

Suggested change
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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you probably don't need this

Suggested change
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<BaseOverlayView>(
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<BaseOverlayView>(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<LayerView>;
overlayViews: ViewList<BaseOverlayView>;
controlViews: ViewList<BaseControlView>;