Skip to content

Commit

Permalink
Improve support for drawing items in ui.leaflet (#3586)
Browse files Browse the repository at this point in the history
* show drawn layers

* avoid circular references when emitting draw events

* wait a tick for more event arguments

* update documentation

* allow hiding drawn items

* add demos for editing drawn items and drawing with custom options
  • Loading branch information
falkoschindler authored Aug 28, 2024
1 parent c69db4d commit 0ad7478
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 12 deletions.
26 changes: 21 additions & 5 deletions nicegui/elements/leaflet.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { loadResource } from "../../static/utils/resources.js";
import { cleanObject } from "../../static/utils/json.js";

export default {
template: "<div></div>",
Expand All @@ -8,6 +9,7 @@ export default {
options: Object,
draw_control: Object,
resource_path: String,
hide_drawn_items: Boolean,
},
async mounted() {
await this.$nextTick(); // NOTE: wait for window.path_prefix to be set
Expand Down Expand Up @@ -85,10 +87,18 @@ export default {
if (this.draw_control) {
for (const key in L.Draw.Event) {
const type = L.Draw.Event[key];
this.map.on(type, (e) => {
this.map.on(type, async (e) => {
await this.$nextTick(); // NOTE: allow drawn layers to be added
const cleanedObject = cleanObject(e, [
"_map",
"_events",
"_eventParents",
"_handlers",
"_mapToAdd",
"_initHooksCalled",
]);
this.$emit(type, {
...e,
layer: e.layer ? { ...e.layer, editing: undefined, _events: undefined } : undefined,
...cleanedObject,
target: undefined,
sourceTarget: undefined,
});
Expand All @@ -97,10 +107,16 @@ export default {
const drawnItems = new L.FeatureGroup();
this.map.addLayer(drawnItems);
const drawControl = new L.Control.Draw({
edit: { featureGroup: drawnItems },
...this.draw_control,
draw: this.draw_control.draw,
edit: {
...this.draw_control.edit,
featureGroup: drawnItems,
},
});
this.map.addControl(drawControl);
if (!this.hide_drawn_items) {
this.map.on("draw:created", (e) => drawnItems.addLayer(e.layer));
}
}
const connectInterval = setInterval(async () => {
if (window.socket.id === undefined) return;
Expand Down
3 changes: 3 additions & 0 deletions nicegui/elements/leaflet.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self,
*,
options: Dict = {}, # noqa: B006
draw_control: Union[bool, Dict] = False,
hide_drawn_items: bool = False,
) -> None:
"""Leaflet map
Expand All @@ -35,6 +36,7 @@ def __init__(self,
:param zoom: initial zoom level of the map (default: 13)
:param draw_control: whether to show the draw toolbar (default: False)
:param options: additional options passed to the Leaflet map (default: {})
:param hide_drawn_items: whether to hide drawn items on the map (default: False)
"""
super().__init__()
self.add_resource(Path(__file__).parent / 'lib' / 'leaflet')
Expand All @@ -49,6 +51,7 @@ def __init__(self,
self._props['zoom'] = zoom
self._props['options'] = {**options}
self._props['draw_control'] = draw_control
self._props['hide_drawn_items'] = hide_drawn_items

self.on('init', self._handle_init)
self.on('map-moveend', self._handle_moveend)
Expand Down
26 changes: 26 additions & 0 deletions nicegui/static/utils/json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Remove keysToRemove, functions, and circular references from obj
export function cleanObject(obj, keysToRemove = [], seen = new WeakSet()) {
if (obj === null || typeof obj !== "object") {
return obj;
}

if (typeof value === "function") {
return undefined;
}

if (seen.has(obj)) {
return undefined;
}

seen.add(obj);

if (Array.isArray(obj)) {
return obj.map((item) => cleanObject(item, keysToRemove, seen));
}

return Object.fromEntries(
Object.entries(obj)
.filter(([key, value]) => !keysToRemove.includes(key) && typeof value !== "function" && !seen.has(value))
.map(([key, value]) => [key, cleanObject(value, keysToRemove, seen)])
);
}
48 changes: 41 additions & 7 deletions website/documentation/content/leaflet_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,29 +94,63 @@ def disable_pan_zoom() -> None:
You can enable a toolbar to draw on the map.
The `draw_control` can be used to configure the toolbar.
This demo adds markers and polygons by clicking on the map.
By setting "edit" and "remove" to `True` (the default), you can enable editing and deleting drawn shapes.
''')
def draw_on_map() -> None:
from nicegui import events

def handle_draw(e: events.GenericEventArguments):
if e.args['layerType'] == 'marker':
m.marker(latlng=(e.args['layer']['_latlng']['lat'],
e.args['layer']['_latlng']['lng']))
if e.args['layerType'] == 'polygon':
m.generic_layer(name='polygon', args=[e.args['layer']['_latlngs']])
layer_type = e.args['layerType']
coords = e.args['layer'].get('_latlng') or e.args['layer'].get('_latlngs')
ui.notify(f'Drawn a {layer_type} at {coords}')

draw_control = {
'draw': {
'polygon': True,
'marker': True,
'circle': True,
'rectangle': True,
'polyline': True,
'circlemarker': True,
},
'edit': {
'edit': True,
'remove': True,
},
}
m = ui.leaflet(center=(51.505, -0.09), draw_control=draw_control)
m.classes('h-96')
m.on('draw:created', handle_draw)
m.on('draw:edited', lambda: ui.notify('Edit completed'))
m.on('draw:deleted', lambda: ui.notify('Delete completed'))


@doc.demo('Draw with Custom Options', '''
You can draw shapes with custom options like stroke color and weight.
To hide the default rendering of drawn items, set `hide_drawn_items` to `True`.
''')
def draw_custom_options():
from nicegui import events

def handle_draw(e: events.GenericEventArguments):
options = {'color': 'red', 'weight': 1}
m.generic_layer(name='polygon', args=[e.args['layer']['_latlngs'], options])

draw_control = {
'draw': {
'polygon': True,
'marker': False,
'circle': False,
'rectangle': False,
'polyline': False,
'circlemarker': False,
},
'edit': False,
'edit': {
'edit': False,
'remove': False,
},
}
m = ui.leaflet(center=(51.505, -0.09), zoom=13, draw_control=draw_control)
m = ui.leaflet(center=(51.5, 0), draw_control=draw_control, hide_drawn_items=True)
m.on('draw:created', handle_draw)


Expand Down

0 comments on commit 0ad7478

Please sign in to comment.