pythonleafletplotly-dash

Change property of GeoJSON by clicking on a Map using Dash and Leaflet


Currently my code looks like this:

import dash
from dash import Output, Input, State, html, no_update
import dash_leaflet as dl
import osmnx as ox
import json

from numpy import dtype


# Style-Funktion für GeoJSON-Features
def get_style(feature):
    return {
        "color": "rgba(0,0,0,0)",  # Transparent
        "weight": 5,
        "opacity": 1,
        "fillOpacity": 0
    }

# create a style for the GeoJSON features where the color is dependent on the "use" attribute
def get_style_with_use(feature):
    if feature['properties']['use'] == True:
        return {
            "color": "rgba(0,0,0,0)",
            "weight": 2,
            "opacity": 0.7,
            "fillOpacity": 0
        }
    else:
        return {
            "color": "green",
            "weight": 5,
            "opacity": 0.9,
            "fillOpacity": 0
        }


# Hover-Style (blau)
hover_style = {
    "color": "blue",
    "weight": 7,
    "opacity": 1,
    "fillOpacity": 0.5
}

# GeoJSON with Dresdner streets
G = ox.load_graphml("dresden_saxony_germanyroad_network.graphml")

# add a attribute to the edges called "use" and set it to "TRUE"
for u, v, key, data in G.edges(keys=True, data=True):
    data['use'] = True

geojson_data = ox.graph_to_gdfs(G, nodes=False, edges=True, fill_edge_geometry=True).to_json()
data = json.loads(geojson_data)
meta = data['features']

mapping = {}
for i in range(len(meta)):
    mapping[meta[i]['id']] = i


# print the Name of the Street on the Dash App as text below the map
# Dash-App mit Leaflet-Karte
app = dash.Dash(__name__)

app.layout = html.Div([
    html.H1("Dresden Streets Map"),
    dl.Map([
        dl.TileLayer(),
        dl.GeoJSON(
            data=data,
            id="geojson-dresden-streets",
            style={
                "color": "rgba(0,0,0,0)",
                "weight": 5,
                "opacity": 1,
                "fillOpacity": 0
            },
            hoverStyle=hover_style
        )
    ], style={'width': '1000px', 'height': '500px'}, center=[51.0504, 13.7373], zoom=12),
    html.Div(id='container-output-text',
             children='Enter a value and press submit')
])


@app.callback(
    Output('container-output-text', 'children'),
    Input('geojson-dresden-streets', 'n_clicks'),
    State('geojson-dresden-streets', 'clickData'),
    prevent_initial_call=True
)
def f1(input_, state_):
    print(json.dumps(input_))
    print(json.dumps(state_))

    # id = mapping.get(str(state_['id']))
    # print(state_['id'])
    # print(id)
    # # print datatype of data['features']
    # print(type(data['features']))
    # print(data['features'][id])
    # data['features'][id]['properties']['use'] = False

    print()


if __name__ == "__main__":
    app.run(debug=True)

What I want to do:

What I tried for 'Clicking on a street':

I have tried a lot, but I have no idea how to get it to work...


Solution

  • Using Highlight a selected feature from the Geojson Tutorial page as a reference, it looks like you can use a hideout property to store state, and then use style_handle = assign"""function(feature, context){...}""" for the javascript rendering logic to color a selection depending on its state.

    Below is a fully reproducible example (note that I defined G using a downloadable graph of Dresden, Saxony instead of your file):

    import dash
    from dash import Output, Input, State, html, no_update
    from dash_extensions.javascript import assign
    import dash_leaflet as dl
    import osmnx as ox
    import json
    
    from numpy import dtype
    
    
    # Style-Funktion für GeoJSON-Features
    def get_style(feature):
        return {
            "color": "rgba(0,0,0,0)",  # Transparent
            "weight": 5,
            "opacity": 1,
            "fillOpacity": 0
        }
    
    # create a style for the GeoJSON features where the color is dependent on the "use" attribute
    def get_style_with_use(feature):
        if feature['properties']['use'] == True:
            return {
                "color": "rgba(0,0,0,0)",
                "weight": 2,
                "opacity": 0.7,
                "fillOpacity": 0
            }
        else:
            return {
                "color": "green",
                "weight": 5,
                "opacity": 0.9,
                "fillOpacity": 0
            }
    
    
    # Hover-Style (blau)
    hover_style = {
        "color": "blue",
        "weight": 7,
        "opacity": 1,
        "fillOpacity": 0.5
    }
    
    # GeoJSON with Dresdner streets
    # G = ox.load_graphml("dresden_saxony_germanyroad_network.graphml")
    
    ## to reproduce the graph G
    G = ox.graph_from_place("Dresden, Saxony, Germany", network_type="drive")
    
    # add a attribute to the edges called "use" and set it to "TRUE"
    for u, v, key, data in G.edges(keys=True, data=True):
        data['use'] = True
    
    geojson_data = ox.graph_to_gdfs(G, nodes=False, edges=True, fill_edge_geometry=True).to_json()
    data = json.loads(geojson_data)
    meta = data['features']
    
    mapping = {}
    for i in range(len(meta)):
        mapping[meta[i]['id']] = i
    
    
    # print the Name of the Street on the Dash App as text below the map
    # Dash-App mit Leaflet-Karte
    app = dash.Dash(__name__)
    
    style_handle = assign("""function(feature, context){
        const {selected} = context.hideout;
        if (selected.includes(feature.id)) {
            return {color: 'red', weight: 5, opacity: 1};
        }
        return {color: 'rgba(0,0,0,0)', weight: 5, opacity: 1, fillOpacity: 0};
    }""")
    
    app.layout = html.Div([
        html.H1("Dresden Streets Map"),
        dl.Map([
            dl.TileLayer(),
            dl.GeoJSON(
                data=data,
                id="geojson-dresden-streets",
                hoverStyle=hover_style,
                hideout=dict(selected=[]), 
                style=style_handle,
            )
        ], style={'width': '1000px', 'height': '500px'}, center=[51.0504, 13.7373], zoom=12),
        html.Div(id='container-output-text',
                 children='Enter a value and press submit')
    ])
    
    @app.callback(
        Output("geojson-dresden-streets", "hideout"),
        Input("geojson-dresden-streets", "n_clicks"),
        State("geojson-dresden-streets", "clickData"),
        State("geojson-dresden-streets", "hideout"),
        prevent_initial_call=True,
    )
    def f1(_, feature, hideout):
        selected = hideout["selected"]
        feature_id = feature["id"]
        if feature_id in selected:
            selected.remove(feature_id)
        else:
            selected.append(feature_id)
        return hideout
    
    if __name__ == "__main__":
        app.run(debug=True)
    

    enter image description here