Robert Allan
Robert Allan

Reputation: 115

Marker Icons In Dash Leaflet

I have been working with both Folium and Dash Leaflet and one thing that I wish is that they were more consistent on how to do the same thing. As the title of the question suggests, I am looking to achieve changing the marker icons in Dash Leaflet. I have been able to change the marker to an image (example below)

import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash import html, Dash
from dash_extensions.javascript import assign
import json
locations = '''{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Point",
        "coordinates": [
          -74.00570869445801,
          40.713175131022695
        ]
      }
    }
  ]
}'''
use_icon = assign("""function(feature, latlng){
const i = L.icon({iconUrl: `https://cdn4.iconfinder.com/data/icons/standard-free-icons/139/Checkin01-512.png`, iconSize: [40, 40]});
return L.marker(latlng, {icon: i});
}""")
app = Dash()
app.layout = html.Div(children=[
                        dl.Map(center=[39, -98], zoom=4, children=[
                            dl.TileLayer(),
                            dl.GeoJSON(data=dlx.geojson_to_geobuf(json.loads(locations)), 
                                format='geobuf', 
                                options=dict(pointToLayer=use_icon))],
                            style={'width': '100%', 'height': '100vh'})])

if __name__ == "__main__":
    app.run_server(debug=False)

And here is what it looks like

enter image description here

This is great and all, but one of the things that is not desirable is that the coordinate for the maker is in the middle of the image. So as you zoom out the center of the image is where the coordinate is. What I want is the functionality of the regular marker where the base is always where the coordinate is.

In addition, what I would like is to do the same thing that you can do in Folium. Folium allows you to change the color of the regular marker as well as the marker icon from a selection given by Font-Awesome 4.0, Bootstrap 3, and (I believe) Ionicons 1.5.2.. Below is an example of what I am talking about

import folium
m = folium.Map()
folium.Marker((40.713175131022695, -74.00570869445801), 
              icon=folium.Icon(color='black', icon='fire', prefix='fa')).add_to(m)
m

And here is the result from the code

enter image description here

The behavior of this marker is what I would like to do in Dash Leaflet. Now, before I finish this question, I do want to add one thing. I am looking to use these markers for filtering data (hence why I am using Dash Leaflet), so I am not looking for a solution where you simply use an iframe tag to have the Folium map with the desired marker in the dashboard.

If someone out there is familiar with implementing this, I would appreciate it.

Thanks

Upvotes: 0

Views: 3063

Answers (2)

Robert Allan
Robert Allan

Reputation: 115

To follow up on the comment I made, here is where I have gotten. I had used the asset folder where you can add css and javascript. Downloading awesome-markers, I moved this into the assets folder (see below)

import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash import html, Dash
from dash_extensions.javascript import assign
import json
import geopandas as gpd
awesome_markers = 'https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js'
locations = '''{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Point",
        "coordinates": [
          -74.00570869445801,
          40.713175131022695
        ]
      }
    }
  ]
}'''
locations = gpd.GeoDataFrame.from_features(json.loads(locations).get('features'))
use_icon = assign("""function(feature, latlng){
return L.marker(latlng, {icon: L.AwesomeMarkers.icon({icon: 'fire', prefix:'fa', markerColor: 'orange'})});
}""")
app = Dash(external_scripts=[awesome_markers])
app.layout = html.Div(children=[
                        dl.Map(center=[39, -98], zoom=4, children=[
                            dl.TileLayer(),
                            dl.GeoJSON(data=dlx.geojson_to_geobuf(json.loads(locations.to_json())), 
                                format='geobuf', 
                                options=dict(pointToLayer=use_icon),
                                ),
                            ],
                            style={'width': '100%', 'height': '100vh'})])

if __name__ == "__main__":
    app.run_server(debug=False)

This does work to a certain extent, but I am not able to get the fire icon to show (below is what the map looks like)

enter image description here

In my code you might notice that I also try referencing the code using external scripts. This did not result in anything changing (I think the changes you see are from the files in my asset folder), but I was trying to mimic what is done on the dash leaflet documentation under the "Scatter plot" example where they use chroma as an external script.

While I have been playing with this, I also figured out how to pass parameters through the GeoDataFrame into the javascript so I eventually could have different colors and icons based on calculations done in the GeoDataFrame. Here is what that would look like (see below)

import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash import html, Dash
from dash_extensions.javascript import assign
import json
import geopandas as gpd
awesome_markers = 'https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js'
locations = '''{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Point",
        "coordinates": [
          -74.00570869445801,
          40.713175131022695
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Point",
        "coordinates": [
          -87.62695312499999,
          41.87774145109676
        ]
      }
    }
  ]
}'''
locations = gpd.GeoDataFrame.from_features(json.loads(locations).get('features'))
locations['marker_color'] = ['orange', 'green']
locations['icon'] = ['fire', 'bolt']
use_icon = assign("""function(feature, latlng){
return L.marker(latlng, {icon: L.AwesomeMarkers.icon({icon: feature.properties.icon, prefix:'fa', markerColor: feature.properties.marker_color})});
}""")
app = Dash(external_scripts=[awesome_markers])
app.layout = html.Div(children=[
                        dl.Map(center=[39, -98], zoom=4, children=[
                            dl.TileLayer(),
                            dl.GeoJSON(data=dlx.geojson_to_geobuf(json.loads(locations.to_json())), 
                                format='geobuf', 
                                options=dict(pointToLayer=use_icon),
                                ),
                            ],
                            style={'width': '100%', 'height': '100vh'})])

if __name__ == "__main__":
    app.run_server(debug=False)

And here is the result of that code

enter image description here

This doesn't fully answer my question since the icons do not work, but it is a step in the right direction. If someone wants to build on what I have shown, they are more than welcome to answer. In the meantime I am going to continue to figure out how to get the javascript from awesome markers into the javascript in dash leaflet (since the code for it assumes L is being referenced correctly). If I am successful, I will update this answer

Upvotes: 0

emher
emher

Reputation: 6024

You can adjust the anchor of the icon via the iconAnchor property (see the docs for more details). Hence, if you update you use_icon function to something like,

use_icon = assign("""function(feature, latlng){
const i = L.icon({
iconUrl: `https://cdn4.iconfinder.com/data/icons/standard-free-icons/139/Checkin01-512.png`, 
iconSize: [40, 40],
iconAnchor: [20, 40]
});
return L.marker(latlng, {icon: i});
}""")

you should get the desired behaviour.

The main reason that the interface of dash-leaflet and Folium are so different is that dash-leaflet is built on top of React Leaflet to enable seamless integration with Dash (which is built on top of React).

Upvotes: 0

Related Questions