Issue
I would like to add a weather contour on top of a plotly
density_mapbox
map, but am unsure of the necessary steps.
First, I created a matplotlib
contour plot to visualize the data.
Then, I used geojsoncontour
to create a geojson
file from said matplotlib
contour plot of the contours.
What I would like to do now, is plot the contours in the same map as the density_mapbox
.
geojson
and .csv files containing data can be found here.
Concerning the .csv file, 'Rand_Data' is the data that goes into the density_mapbox
plot, 'Rain_in' is the data used to generate the contours.
link to data: https://github.com/jkiefn1/Contours_and_plotly
Creating the Mapbox:
# Create the static figure
fig = px.density_mapbox(df
,lat='lat'
,lon='long'
,z='Rand_Data'
,hover_data={
'lat':True # remove from hover data
,'long':True # remove from hover data
,col:True
}
,center=dict(lat=38.5, lon=-96)
,zoom=3
,radius=30
,opacity=0.5
,mapbox_style='open-street-map'
,color_continuous_scale='inferno'
)
fig.show()
Creating the matplotlib contour plot and generating the geojson file
# Load in the DataFrame
path = r'/Users/joe_kiefner/Desktop/Sample_Data.csv'
df = pd.read_csv(path, index_col=[0])
data = []
# Define rain levels to be contours in geojson
levels = [0.25,0.5,1,2.5,5,10]
colors = ['royalblue', 'cyan', 'lime', 'yellow', 'red']
vmin = 0
vmax = 1
cm = branca.colormap.LinearColormap(colors, vmin=vmin, vmax=vmax).to_step(len(levels))
x_orig = (df.long.values.tolist())
y_orig = (df.lat.values.tolist())
z_orig = np.asarray(df['Rain_in'].values.tolist())
x_arr = np.linspace(np.min(x_orig), np.max(x_orig), 500)
y_arr = np.linspace(np.min(y_orig), np.max(y_orig), 500)
x_mesh, y_mesh = np.meshgrid(x_arr, y_arr)
xscale = df.long.max() - df.long.min()
yscale = df.lat.max() - df.lat.min()
scale = np.array([xscale, yscale])
z_mesh = griddata((x_orig, y_orig), z_orig, (x_mesh, y_mesh), method='linear')
sigma = [5, 5]
z_mesh = sp.ndimage.filters.gaussian_filter(z_mesh, sigma, mode='nearest')
# Create the contour
contourf = plt.contourf(x_mesh, y_mesh, z_mesh, levels, alpha=0.9, colors=colors,
linestyles='none', vmin=vmin, vmax=vmax)
# Convert matplotlib contourf to geojson
geojson = geojsoncontour.contourf_to_geojson(
contourf=contourf,
min_angle_deg=3,
ndigits=2,
unit='in',
stroke_width=1,
fill_opacity=0.3)
d = json.loads(geojson)
len_features=len(d['features'])
if not data:
data.append(d)
else:
for i in range(len(d['features'])):
data[0]['features'].append(d['features'][i])
with open('/path/to/Sample.geojson', 'w') as f:
dump(geojson, f)
Solution
- there are two core options
- add as layers https://plotly.com/python/mapbox-layers/
- add as choropleth traces https://plotly.com/python/mapbox-county-choropleth/
- layers-legend - same as layers option with addition of creation of a legend by adding additional traces to figure
- both these options are coded up below. change value of
OPTION
to switch between them - layers means there is no legend or hover text
- choropleth these are present, moved colorbar so it does not overlap legend. More beatification of legend and hover text requires...
import json, requests
import pandas as pd
import geopandas as gpd
import plotly.express as px
txt = requests.get(
"https://raw.githubusercontent.com/jkiefn1/Contours_and_plotly/main/Sample.geojson"
).text
js = json.loads(json.loads(txt))
df = pd.read_csv(
"https://raw.githubusercontent.com/jkiefn1/Contours_and_plotly/main/Sample_Data.csv"
)
col = "Rand_Data"
fig = px.density_mapbox(
df,
lat="lat",
lon="long",
z="Rand_Data",
hover_data={
"lat": True, # remove from hover data
"long": True, # remove from hover data
col: True,
},
center=dict(lat=38.5, lon=-96),
zoom=3,
radius=30,
opacity=0.5,
mapbox_style="open-street-map",
color_continuous_scale="inferno",
)
OPTION = "layers-legend"
if OPTION[0:6]=="layers":
fig.update_traces(legendgroup="weather").update_layout(
mapbox={
"layers": [
{
"source": f,
"type": "fill",
"color": f["properties"]["fill"],
"opacity": f["properties"]["fill-opacity"],
}
for f in js["features"]
],
}
)
if OPTION=="layers-legend":
# create a dummy figure to create a legend for the geojson
dfl = pd.DataFrame(js["features"])
dfl = pd.merge(
dfl["properties"].apply(pd.Series),
dfl["geometry"].apply(pd.Series)["coordinates"].apply(len).rename("len"),
left_index=True,
right_index=True,
)
figl = px.bar(
dfl.loc[dfl["len"].gt(0)],
color="title",
x="fill",
y="fill-opacity",
color_discrete_map={cm[0]: cm[1] for cm in dfl.loc[:, ["title", "fill"]].values},
).update_traces(visible="legendonly")
fig.add_traces(figl.data).update_layout(
xaxis={"visible": False}, yaxis={"visible": False}, coloraxis={"colorbar":{"y":.25}}
)
else:
gdf = gpd.GeoDataFrame.from_features(js)
gdf = gdf.loc[~gdf.geometry.is_empty]
cmap = {
list(d.values())[0]: list(d.values())[1]
for d in gdf.loc[:, ["title", "fill"]].apply(dict, axis=1).tolist()
}
fig2 = px.choropleth_mapbox(
gdf,
geojson=gdf.geometry,
locations=gdf.index,
color="title",
color_discrete_map=cmap,
opacity=.3
)
fig.add_traces(fig2.data).update_layout(coloraxis={"colorbar":{"y":.25}})
fig
layers
traces
Answered By - Rob Raymond
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.