emilhe / dash-leaflet

MIT License
213 stars 39 forks source link

Error: The function [dash_props.module.point_to_layer] was not found in the global window object #59

Closed manjeet87 closed 3 years ago

manjeet87 commented 3 years ago

I am newbie on dash leaflet programing, predominantly python coders, with poor knowledge in javascript. What i wanted is to implement custom icon, as marker, to be displayed for GeoJSON point layer. I followed the following link: http://dash-leaflet.herokuapp.com/#func_props. Instead i wanted to use custom icon for local .png file storred in /assets/ folder. I have created a icon.js, in my /assets/ folder. Following is the content: `

var icon_bnk = L.Icon({
                    options: {
                        iconUrl: "/assets/bank.png",
                        iconSize:  [10, 10],
                        iconAnchor: [10, 10]
                    }
                });

var icon_po = L.Icon({
                    options: {
                        iconUrl: "/assets/icons/postoffice.png",
                        iconSize:  [10, 10],
                        iconAnchor: [10, 10]
                    }
                });

window.dash_props = Object.assign({}, window.dash_props, {
    module: {
        point_to_layer: function(feature, latlng, context) {
            return L.Marker(latlng, {icon: icon_bnk})
        }
    }
});

Following is the python code i have used, based on this :

import json
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_leaflet as dl
import dash_leaflet.express as dlx
import pandas as pd
import numpy as np
from dash.dependencies import Output, Input

# region Data

df = pd.read_csv(r"assets/uscities.csv")  # data from https://simplemaps.com/data/us-cities
color_prop = 'population'

def get_data(state):
    df_state = df[df["state_id"] == state]  # pick one state
    df_state = df_state[['lat', 'lng', 'city', 'population', 'density']]  # drop irrelevant columns
    df_state = df_state[df_state[color_prop] > 0]  # drop abandoned cities
    df_state[color_prop] = np.log(df_state[color_prop])  # take log as the values varies A LOT
    dicts = df_state.to_dict('rows')
    for item in dicts:
        item["tooltip"] = "{:.1f}".format(item[color_prop])  # bind tooltip
        item["popup"] = item["city"]  # bind popup
    geojson = dlx.dicts_to_geojson(dicts, lon="lng")  # convert to geojson
    geobuf = dlx.geojson_to_geobuf(geojson)  # convert to geobuf
    return geobuf

def get_minmax(state):
    df_state = df[df["state_id"] == state]  # pick one state
    return dict(min=0, max=np.log(df_state[color_prop].max()))

# Setup a few color scales.
csc_map = {"Rainbow": ['red', 'yellow', 'green', 'blue', 'purple'],
           "Hot": ['yellow', 'red', 'black'],
           "Viridis": "Viridis"}
csc_options = [dict(label=key, value=json.dumps(csc_map[key])) for key in csc_map]
default_csc = "Rainbow"
dd_csc = dcc.Dropdown(options=csc_options, value=json.dumps(csc_map[default_csc]), id="dd_csc", clearable=False)
# Setup state options.
states = df["state_id"].unique()
state_names = [df[df["state_id"] == state]["state_name"].iloc[0] for state in states]
state_options = [dict(label=state_names[i], value=state) for i, state in enumerate(states)]
default_state = "CA"
dd_state = dcc.Dropdown(options=state_options, value=default_state, id="dd_state", clearable=False)

# endregion
minmax = get_minmax(default_state)
# Create geojson.
geojson = dl.GeoJSON(data=get_data(default_state), id="geojson", format="geobuf",
                     zoomToBounds=True,  # when true, zooms to bounds when data changes
                     cluster=True,  # when true, data are clustered
                     clusterToLayer=dlx.scatter.cluster_to_layer,  # how to draw clusters
                     zoomToBoundsOnClick=True,  # when true, zooms to bounds of feature (e.g. cluster) on click
                     options=dict(pointToLayer="window.dash_props.module.point_to_layer"),  # how to draw points
                     superClusterOptions=dict(radius=150,disableClusteringAtZoom=6),  # adjust cluster size
                     hideout=dict(colorscale=csc_map[default_csc], color_prop=color_prop, **minmax))
# Create a colorbar.
colorbar = dl.Colorbar(colorscale=csc_map[default_csc], id="colorbar", width=20, height=150, **minmax)
# Create the app.
chroma = "https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js"
app = dash.Dash(__name__)

app.layout = html.Div([
    dl.Map([dl.TileLayer(), geojson, colorbar]), html.Div([dd_state, dd_csc],
             style={"position": "relative", "bottom": "80px", "left": "10px", "z-index": "1000", "width": "200px"})
], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block", "position": "relative"})

@app.callback([Output("geojson", "hideout"),
               Output("geojson", "data"),
               Output("colorbar", "colorscale"),
               Output("colorbar", "min"),
               Output("colorbar", "max")],
              [Input("dd_csc", "value"), Input("dd_state", "value")])
def update(csc, state):
    csc, data, mm = json.loads(csc), get_data(state), get_minmax(state)
    hideout = dict(colorscale=csc, color_prop=color_prop, popup_prop='city', **mm)
    return hideout, data, csc, mm["min"], mm["max"]

if __name__ == '__main__':
    app.run_server(port=9999,debug=True)

It is throwing error: The function [dash_props.module.point_to_layer] was not found in the global window object.

emilhe commented 3 years ago

You have a few syntax errors in you JavaScript code, i think that't why it doesn't work. Here is an example of a JavaScript snippet that creates a marker with a custom icon,

const iconPlane = L.icon({
        iconUrl: "/assets/icon_plane.png",
        iconSize: [32, 32],
        iconAnchor: [16, 16]
});

window.myNamespace = Object.assign({}, window.myNamespace, {
    mySubNamespace: {
        pointToLayer: function(feature, latlng, context) {
            return L.marker(latlng, {icon: iconPlane})
        }
    }
});

I have just pushed a new version (dash-leaflet==0.1.10 and dash-extensions==0..0.32) that changes the syntax slightly. Using the new syntax, you could use the icon function like this,

import random
import dash
import dash_html_components as html
import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash_extensions.javascript import Namespace

# Create some markers.
points = [dict(lat=55.5 + random.random(), lon=9.5 + random.random(), value=random.random()) for i in range(100)]
data = dlx.dicts_to_geojson(points)
# Create geojson.
ns = Namespace("myNamespace", "mySubNamespace")
geojson = dl.GeoJSON(data=data, options=dict(pointToLayer=ns("pointToLayer")))
# Create the app.
app = dash.Dash()
app.layout = html.Div([
    dl.Map([dl.TileLayer(), geojson], center=(56, 10), zoom=8, style={'height': '50vh'}),
])

if __name__ == '__main__':
    app.run_server()
manjeet87 commented 3 years ago

Thanks a lot for your instant response. I tried your implementation, the said error went away... but now it throws a new error: Cannot read property leafletElement of null

What can be the reason for this?? Should i am required to install specific version. These are my installed version.

dash-leaflet==0.1.5
dash-extensions==0.0.32
emilhe commented 3 years ago

The above code is for the latest version, dash-leaflet 0.1.10, try to see if that works.