emilhe / dash-leaflet

MIT License
213 stars 37 forks source link

EditControl Features not clickable when deleting #115

Closed prl900 closed 2 years ago

prl900 commented 2 years ago

Hi,

I apologise for bringing this question here but I couldn't make it simple enough to work for StackOverflow.

I have come up with a tool to allow users to draw polygons and indicate regions of different categories on a map. Using EditControl and a radio button users can select the category and the polygons are represented using different colours.

To do this, polygons are rendered on top of the EditControl GeoJSON using new GeoJSON layers with specific styles. Everything seems to work fine except the delete functionality in EditControl. The original polygons cannot be clicked as they apparently sit behind the coloured layers.

Does anyone know if there is a simple way of making these polygons clickable? I have tried modifying the order of the layers, Panes and z-indices but I cannot work it out.

Here is a simple example that reproduces this problem -- this could be useful to anyone looking at doing map classification tasks for training machine learning models.

Thanks for your help!

import dash_leaflet as dl
from dash import Dash
from dash import dcc
from dash import html
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate
from dash_extensions.javascript import assign
import dash_bootstrap_components as dbc

# Create example app.
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

red = dl.GeoJSON(id="red",
                     data={},
                     options=dict(style=dict(opacity=0.8, color='red', fillOpacity=0.8)))

blue = dl.GeoJSON(id="blue",
                     data={},
                     options=dict(style=dict(opacity=0.8, color='blue', fillOpacity=0.8)))

lmap = dl.Map(center=[56, 10], zoom=4, children=[dl.TileLayer(), red, blue, dl.FeatureGroup([
        dl.EditControl(id="edit_control")])], 
        style={'width': '100%', 'height': '100vh', 'margin': "auto", "display": "inline-block"}, id="map")

radio_buttons = dbc.RadioItems(
    id="radios",
    className="btn-group",
    inputClassName="btn-check",
    labelClassName="btn btn-outline-primary",
    labelCheckedClassName="active",
    options=[
        {"label": "Red", "value": 'red'},
        {"label": "Blue", "value": 'blue'},
    ],
    value='red',
)

app.layout = html.Div([
    dbc.Container([
        dcc.Store(id="masks", data={}),
        dbc.Row(
            [
                dbc.Col(lmap, width=9),
                dbc.Col(radio_buttons, width=3),
            ]
        ),
    ], fluid=True)
])

@app.callback(Output("masks", "data"), Output("red", "data"), Output("blue", "data"), Input("edit_control", "geojson"), State("masks", "data"), State("radios", "value"))
def colorise(feats, classes, class_id):
    if not feats:
        raise PreventUpdate

    # Add category if not already there
    leaf_ids = []
    for feat in feats['features']:
        leaf_id = str(feat['properties']['_leaflet_id'])
        leaf_ids.append(leaf_id)
        if leaf_id not in classes:
            classes[leaf_id] = class_id

    # Remove id if not in feats
    for leaf_id in list(classes):
        if leaf_id not in leaf_ids:
            del classes[leaf_id]

    r = [feat for feat in feats['features'] if classes[str(feat['properties']['_leaflet_id'])] == 'red']
    b = [feat for feat in feats['features'] if classes[str(feat['properties']['_leaflet_id'])] == 'blue']

    return classes, {'type': 'FeatureCollection', 'features': r}, {'type': 'FeatureCollection', 'features': b}

if __name__ == '__main__':
    app.run_server(debug=True)
prl900 commented 2 years ago

Ok, solved this by wrapping each layer in its own Pane.

Here is the code in case it's useful for others running into a similar issue:

lmap = dl.Map(center=[56, 10], zoom=4, children=[dl.Pane(children=[dl.TileLayer()], style={"zIndex": 1}), dl.Pane(children=[red, blue], style={"zIndex": 2}), dl.Pane(children=[dl.FeatureGroup([
        dl.EditControl(id="edit_control")])], style={"zIndex": 3})], 
        style={'width': '100%', 'height': '100vh', 'margin': "auto", "display": "inline-block"}, id="map")