Skip to content

Commit

Permalink
refactor: retain old DrawControl for migration period
Browse files Browse the repository at this point in the history
  • Loading branch information
iisakkirotko committed Apr 11, 2024
1 parent b8863cc commit 014358c
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 3 deletions.
37 changes: 34 additions & 3 deletions python/ipyleaflet_core/ipyleaflet/leaflet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,37 @@ def clear_markers(self):
"""Clear all markers."""
self.send({"msg": "clear_markers"})


class DrawControl(DrawControlBase):
"""DrawControl class.
Drawing tools for drawing on the map.
"""

_view_name = Unicode("LeafletDrawControlView").tag(sync=True)
_model_name = Unicode("LeafletDrawControlModel").tag(sync=True)

# Enable each of the following drawing by giving them a non empty dict of options
# You can add Leaflet style options in the shapeOptions sub-dict
# See https://github.com/Leaflet/Leaflet.draw#polylineoptions and
# https://github.com/Leaflet/Leaflet.draw#polygonoptions
circlemarker = Dict({"shapeOptions": {}}).tag(sync=True)

last_draw = Dict({"type": "Feature", "geometry": None})
last_action = Unicode()

def __init__(self, **kwargs):
super(DrawControl, self).__init__(**kwargs)
self.on_msg(self._handle_leaflet_event)

def _handle_leaflet_event(self, _, content, buffers):
if content.get("event", "").startswith("draw"):
event, action = content.get("event").split(":")
self.last_draw = content.get("geo_json")
self.last_action = action
self._draw_callbacks(self, action=action, geo_json=self.last_draw)


class GeomanDrawControl(DrawControlBase):
"""GeomanDrawControl class.
Expand Down Expand Up @@ -2253,10 +2284,10 @@ def clear_text(self):
self.send({'msg': 'clear_text'})


class DrawControl(DrawControlBase):
class DrawControlCompatibility(DrawControlBase):
"""DrawControl class.
Old drawing tools powered by leaflet-draw for backwards compatibility. Use GeomanDrawControl instead.
Python side compatibility layer for old DrawControls, using the new Geoman front-end but old Python API.
"""

_view_name = Unicode("LeafletGeomanDrawControlView").tag(sync=True)
Expand All @@ -2271,7 +2302,7 @@ class DrawControl(DrawControlBase):
last_action = Unicode()

def __init__(self, **kwargs):
super(DrawControl, self).__init__(**kwargs)
super(DrawControlCompatibility, self).__init__(**kwargs)
self.on_msg(self._handle_leaflet_event)

def _handle_leaflet_event(self, _, content, buffers):
Expand Down
201 changes: 201 additions & 0 deletions python/jupyter_leaflet/src/controls/DrawControl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { unpack_models, WidgetView } from '@jupyter-widgets/base';
import { DrawEvents, GeoJSON } from 'leaflet';
import { LayerShapes } from '../definitions/leaflet-extend';
import L from '../leaflet';
import { LeafletControlModel, LeafletControlView } from './Control';

export class LeafletDrawControlModel extends LeafletControlModel {
defaults() {
return {
...super.defaults(),
_view_name: 'LeafletDrawControlView',
_model_name: 'LeafletDrawControlModel',
polyline: { shapeOptions: {} },
polygon: { shapeOptions: {} },
circle: {},
circlemarker: {},
rectangle: {},
marker: {},
data: [],
edit: true,
remove: true,
};
}
}

LeafletDrawControlModel.serializers = {
...LeafletControlModel.serializers,
layer: { deserialize: unpack_models },
};

export class LeafletDrawControlView extends LeafletControlView {
feature_group: GeoJSON;

initialize(
parameters: WidgetView.IInitializeParameters<LeafletControlModel>
) {
super.initialize(parameters);
this.map_view = this.options.map_view;
}

create_obj() {
this.feature_group = L.geoJson([], {
style: function (feature) {
return feature?.properties.style;
},
});
this.data_to_layers();
this.map_view.obj.addLayer(this.feature_group);
let polyline = this.model.get('polyline');
if (!Object.keys(polyline).length) {
polyline = false;
}
let polygon = this.model.get('polygon');
if (!Object.keys(polygon).length) {
polygon = false;
}
let circle = this.model.get('circle');
if (!Object.keys(circle).length) {
circle = false;
}
let circlemarker = this.model.get('circlemarker');
if (!Object.keys(circlemarker).length) {
circlemarker = false;
}
let rectangle = this.model.get('rectangle');
if (!Object.keys(rectangle).length) {
rectangle = false;
}
let marker = this.model.get('marker');
if (!Object.keys(marker).length) {
marker = false;
}
this.obj = new L.Control.Draw({
position: this.model.get('position'),
edit: {
featureGroup: this.feature_group,
edit: this.model.get('edit'),
remove: this.model.get('remove'),
},
draw: {
polyline: polyline,
polygon: polygon,
circle: circle,
circlemarker: circlemarker,
rectangle: rectangle,
marker: marker,
},
});
this.map_view.obj.on('draw:created', (e: DrawEvents.Created) => {
const layer = e.layer;
const geo_json = layer.toGeoJSON();
geo_json.properties.style = layer.options;
this.send({
event: 'draw:created',
geo_json: geo_json,
});
this.feature_group.addLayer(layer);
this.layers_to_data();
});
this.map_view.obj.on('draw:edited', (e: DrawEvents.Edited) => {
const layers = e.layers;
layers.eachLayer((layer) => {
const geo_json = (layer as LayerShapes).toGeoJSON();
geo_json.properties.style = layer.options;
this.send({
event: 'draw:edited',
geo_json: geo_json,
});
});
this.layers_to_data();
});
this.map_view.obj.on('draw:deleted', (e: DrawEvents.Deleted) => {
const layers = e.layers;
layers.eachLayer((layer) => {
const geo_json = (layer as LayerShapes).toGeoJSON();
geo_json.properties.style = layer.options;
this.send({
event: 'draw:deleted',
geo_json: geo_json,
});
});
this.layers_to_data();
});
this.model.on('msg:custom', this.handle_message.bind(this));
this.model.on('change:data', this.data_to_layers.bind(this));
}

remove() {
this.map_view.obj.removeLayer(this.feature_group);
this.map_view.obj.off('draw:created');
this.map_view.obj.off('draw:edited');
this.map_view.obj.off('draw:deleted');
this.model.off('msg:custom');
this.model.off('change:data');
}

data_to_layers() {
const data = this.model.get('data');
this.feature_group.clearLayers();
this.feature_group.addData(data);
}

layers_to_data() {
let newData: LayerShapes[] = [];
this.feature_group.eachLayer((layer) => {
const geoJson = (layer as LayerShapes).toGeoJSON();
geoJson.properties.style = layer.options;
newData.push(geoJson);
});
this.model.set('data', newData);
this.model.save_changes();
}

handle_message(content: { msg: string }) {
if (content.msg == 'clear') {
this.feature_group.eachLayer((layer) => {
this.feature_group.removeLayer(layer);
});
} else if (content.msg == 'clear_polylines') {
this.feature_group.eachLayer((layer) => {
if (layer instanceof L.Polyline && !(layer instanceof L.Polygon)) {
this.feature_group.removeLayer(layer);
}
});
} else if (content.msg == 'clear_polygons') {
this.feature_group.eachLayer((layer) => {
if (layer instanceof L.Polygon && !(layer instanceof L.Rectangle)) {
this.feature_group.removeLayer(layer);
}
});
} else if (content.msg == 'clear_circles') {
this.feature_group.eachLayer((layer) => {
if (layer instanceof L.CircleMarker) {
this.feature_group.removeLayer(layer);
}
});
} else if (content.msg == 'clear_circle_markers') {
this.feature_group.eachLayer((layer) => {
if (layer instanceof L.CircleMarker && !(layer instanceof L.Circle)) {
this.feature_group.removeLayer(layer);
}
});
} else if (content.msg == 'clear_rectangles') {
this.feature_group.eachLayer((layer) => {
if (layer instanceof L.Rectangle) {
this.feature_group.removeLayer(layer);
}
});
} else if (content.msg == 'clear_markers') {
this.feature_group.eachLayer((layer) => {
if (layer instanceof L.Marker) {
this.feature_group.removeLayer(layer);
}
});
}
this.layers_to_data();
}
}
1 change: 1 addition & 0 deletions python/jupyter_leaflet/src/jupyter-leaflet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export * from './layers/WMSLayer';
//Controls
export * from './controls/AttributionControl';
export * from './controls/Control';
export * from './controls/DrawControl';
export * from './controls/GeomanDrawControl';
export * from './controls/FullScreenControl';
export * from './controls/LayersControl';
Expand Down
44 changes: 44 additions & 0 deletions ui-tests/notebooks/GeomanDrawControl.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ipyleaflet import Map, basemaps, GeomanDrawControl\n",
"\n",
"center = [48.265643898808236, 336.7529296875]\n",
"\n",
"m = Map(center=center, zoom=7)\n",
"\n",
"draw_control = GeomanDrawControl()\n",
"draw_control.polyline = {\n",
" \"shapeOptions\": {\"color\": \"#6bc2e5\", \"weight\": 8, \"opacity\": 1.0}\n",
"}\n",
"draw_control.polygon = {\n",
" \"shapeOptions\": {\"fillColor\": \"#6be5c3\", \"color\": \"#6be5c3\", \"fillOpacity\": 1.0},\n",
" \"drawError\": {\"color\": \"#dd253b\", \"message\": \"Oups!\"},\n",
" \"allowIntersection\": False,\n",
"}\n",
"draw_control.circle = {\n",
" \"shapeOptions\": {\"fillColor\": \"#efed69\", \"color\": \"#efed69\", \"fillOpacity\": 1.0}\n",
"}\n",
"draw_control.rectangle = {\n",
" \"shapeOptions\": {\"fillColor\": \"#fca45d\", \"color\": \"#fca45d\", \"fillOpacity\": 1.0}\n",
"}\n",
"\n",
"m.add(draw_control)\n",
"\n",
"m"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

0 comments on commit 014358c

Please sign in to comment.