Reputation: 143
I am trying to add a Scattergeo
trace or overlay on top of a white-bg
density mapbox to get a heat map over a generic USA states outline.
The reason for my use of scattergeo
is I'd like to plot a star symbol on top of the density mapbox, and the only symbol accepted via add_scattermapbox
is a dot. If you choose the star
symbol, there is no symbol added.
I'm also aware that star symbols are acceptable for the p mapbox_styles
of add_scattermapbox
or density_scattermapbox
but at the present time I am not in the position to pay per web load after the trial amount runs out.
Is there a clever way to add a star symbol on top of a density_mapbox
plot?
Working ScatterGeo
fig = go.Figure(go.Scattergeo())
fig.add_scattergeo(lat = [30, 40]
,lon = [-90, -80]
,hoverinfo = 'none'
,marker_size = 10
,marker_color = 'rgb(65, 105, 225)' # blue
,marker_symbol = 'star'
,showlegend = False
)
fig.update_geos(
visible=False, resolution=110, scope="usa",
showcountries=True, countrycolor="Black",
showsubunits=True, subunitcolor="Black"
)
fig.show()
Working Density Mapbox
d = {'Location': ['Point A', 'Point B'], 'lat': [30, 40], 'long': [-90, -80], 'z': [100,200]}
df = pd.DataFrame(data=d)
fig = px.density_mapbox(df
,lat='lat'
,lon='long'
,z='z'
,hover_name='Location'
,center=dict(lat=38.5, lon=-96)
,range_color = [0, 200]
,zoom=2
,radius=50
,opacity=.5
,mapbox_style='open-street-map')
fig.add_scattermapbox(lat = [30, 40]
,lon = [-90, -80]
,hoverinfo = 'none'
,marker_size = 6
,marker_color = 'rgb(0, 0, 0)'
# ,marker_symbol = 'star'
,showlegend = False
)
fig.show()
Attempt #1 - Just set marker_symbol = 'star'
Un-commenting the marker_symbol = 'star'
, which would work for the premium styles of mapbox
, completely removes the scatter point.
d = {'Location': ['Point A', 'Point B'], 'lat': [30, 40], 'long': [-90, -80], 'z': [100,200]}
df = pd.DataFrame(data=d)
fig = px.density_mapbox(df
,lat='lat'
,lon='long'
,z='z'
,hover_name='Location'
,center=dict(lat=38.5, lon=-96)
,range_color = [0, 200]
,zoom=2
,radius=50
,opacity=.5
,mapbox_style='open-street-map')
fig.add_scattermapbox(lat = [30, 40]
,lon = [-90, -80]
,hoverinfo = 'none'
,marker_size = 6
,marker_color = 'rgb(0, 0, 0)'
,marker_symbol = 'star'
,showlegend = False
)
fig.show()
Attempt #2 - Adding a density mapbox on top of the scatter geo
Adding a density_mapbox
on top of the scattergeo
produces the same geo plot, but nothing more. The density mapbox legend is there, but no heat map.
d = {'Location': ['Point A', 'Point B'], 'lat': [30, 40], 'long': [-90, -80], 'z': [100,200]}
df = pd.DataFrame(data=d)
fig = go.Figure(go.Scattergeo())
fig.add_scattergeo(lat = [30, 40]
,lon = [-90, -80]
,hoverinfo = 'none'
,marker_size = 10
,marker_color = 'rgb(65, 105, 225)' # blue
,marker_symbol = 'star'
,showlegend = False
)
fig.add_densitymapbox(lat=df['lat'],
lon=df['long'],
z=df['z'],
radius=50,
opacity=.5
)
fig.update_geos(
visible=False, resolution=110, scope="usa",
showcountries=True, countrycolor="Black",
showsubunits=True, subunitcolor="Black"
)
fig.show()
Upvotes: 6
Views: 12151
Reputation: 31226
tile maps and layer maps do not work together. Hence you cannot use markers from geo on mapbox
thinking laterally, you can add your own geojson layers onto mapbox plots
generate geometry. Have provided two options for this
get_geom(df["long"], df["lat"], marker=None, size=k)
get_geom(df["long"], df["lat"], marker="star", size=k)
where marker is the MAKI icon name. NB icons with holes can be filled in - for example cautionadding layers to mapbox figure layout. This is parameterised to generate multiple layers to support different zoom levels. More layers, more overhead.
import geopandas as gpd
import pandas as pd
import shapely.geometry
import math
import json
import plotly.express as px
import svgpath2mpl
import requests
import numpy as np
d = {
"Location": ["Point A", "Point B"],
"lat": [30, 40],
"long": [-90, -80],
"z": [100, 200],
}
df = pd.DataFrame(data=d)
fig = px.density_mapbox(
df,
lat="lat",
lon="long",
z="z",
hover_name="Location",
center=dict(lat=38.5, lon=-96),
range_color=[0, 200],
zoom=2,
radius=50,
opacity=0.5,
mapbox_style="open-street-map",
)
# https://stackoverflow.com/questions/23411688/drawing-polygon-with-n-number-of-sides-in-python-3-2
def polygon(sides, radius=1, rotation=0, translation=None):
one_segment = math.pi * 2 / sides
points = [(math.sin(one_segment * i + rotation) * radius,
math.cos(one_segment * i + rotation) * radius,)
for i in range(sides)]
if translation:
points = [[sum(pair) for pair in zip(point, translation)] for point in points]
return shapely.geometry.Polygon(points)
def makimarker(makiname="star", geo=(0, 0), size=0.1):
url = f"https://raw.githubusercontent.com/mapbox/maki/main/icons/{makiname}.svg"
svgpath = pd.read_xml(requests.get(url).text).loc[0, "d"]
p = svgpath2mpl.parse_path(svgpath).to_polygons()
# need centroid to adjust marked to be centred on geo location
c = shapely.affinity.scale(
shapely.geometry.Polygon(p[0]), xfact=size, yfact=size
).centroid
# centre and place marker
marker = shapely.geometry.Polygon(
[[sum(triple) for triple in zip(point, geo, (-c.x, -c.y))] for point in p[0]]
)
# finally size geometry
return shapely.affinity.scale(marker, xfact=size, yfact=size)
def get_geom(long_a: list, lat_a: list, marker=None, size=0.15) -> list:
if marker:
geo = [
makimarker(marker, geo=(long, lat), size=size)
for long, lat in zip(long_a, lat_a)
]
else:
geo = [
polygon(3, translation=(long, lat), radius=size*10)
for long, lat in zip(long_a, lat_a)
]
return json.loads(gpd.GeoDataFrame(geometry=geo).to_json())
# basing math on this https://wiki.openstreetmap.org/wiki/Zoom_levels
# dict is keyed by size with min/max zoom levels covered by this size
MINZOOM=.1
MAXZOOM=18
LAYERS=7
zoom = 512**np.linspace(math.log(MINZOOM,512), math.log(MAXZOOM, 512), LAYERS)
zoom = {
(200/(2**(np.percentile(zoom[i:i+2],25)+9))): {"minzoom":zoom[i], "maxzoom":zoom[i+1], "name":i}
for i in range(LAYERS-1)
}
# add a layers to density plot that are the markers
fig.update_layout(
mapbox={
"layers": [
{
"source": get_geom(df["long"], df["lat"], marker="star", size=k),
"type": "fill",
"color": "blue",
**zoom[k],
}
for k in zoom.keys()
]
},
margin={"t": 0, "b": 0, "l": 0, "r": 0},
)
fig
Upvotes: 2