'Plotly Dash - TypeError: Object of type Polygon is not JSON serializable
I am attempting to plot choropleth map using dash and mapbox. At runtime, I am getting an error "TypeError: Object of type Polygon is not JSON serializable"
.
sample data and code:
url = "https://drive.google.com/file/d/1bP_SdwrU7pMtCPnOd5XfA3oQYKWYlNjn/view?usp=sharing"
import geopandas as gpd
from shapely.geometry import Point
from shapely.geometry import Polygon
import fiona
from shapely import wkt
import dash
from dash import html
import geojson
import json
MAPBOX_KEY="pk.xxx...."
token = MAPBOX_KEY
with urlopen(url) as response:
geo_json = json.load(response)
df_geo = gpd.read_file(url)
layout = html.Div([
dcc.Graph(id="map"),
html.Div(id="dummy")
])
@app.callback(
[
Output("map", "figure")
],
[
Input("dummy", "value")
],
)
def update_map(d):
s = df_geo['avg_price'].astype(float)
datad = []
datad.append({
"type": "choroplethmapbox",
"geojson": geo_json,
"locations": df_geo['tract'],
"z": s,
"featureidkey": "properties.tract",
"autocolorscale":False,
"colorscale":"YlOrRd",
"colorbar":dict(
title = label,
orientation = 'h',
x= -0.15,
xanchor= "left",
y= 0,
yanchor= "bottom",
showticklabels=True,
thickness= 20,
tickformatstops=dict(dtickrange=[0,10]),
titleside= 'top',
ticks= 'outside'
),
"zmin": s.min(),
"zmax": s.max(),
"marker_line_width": 0,
"opacity": 0.2,
"labels": label,
"title": "Choropleth - Census Tract Level"
}
)
layout = {
"autosize": True,
"datarevision": 0,
"hovermode": "closest",
"mapbox": {
"accesstoken": MAPBOX_KEY,
"bearing": 0,
"center": {
"lat": 33.6050991,
"lon": -112.4052438
},
"pitch": 0,
"opacity": 0.2,
"zoom": zoom,
"style": "streets",
},
"margin": {
"r": 0,
"t": 0,
"l": 0,
"b": 0,
"pad": 0
}
}
return ({"data": datad, "layout": layout})
Full Traceback:
Exception on /_dash-update-component [POST]
Traceback (most recent call last):
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/_callback.py", line 191, in add_context
jsonResponse = to_json(response)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/_utils.py", line 21, in to_json
return to_json_plotly(value)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/plotly/io/_json.py", line 124, in to_json_plotly
return json.dumps(plotly_object, cls=PlotlyJSONEncoder, **opts)
File "/Applications/Anaconda/anaconda3/lib/python3.9/json/__init__.py", line 234, in dumps
return cls(
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/_plotly_utils/utils.py", line 59, in encode
encoded_o = super(PlotlyJSONEncoder, self).encode(o)
File "/Applications/Anaconda/anaconda3/lib/python3.9/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/Applications/Anaconda/anaconda3/lib/python3.9/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/_plotly_utils/utils.py", line 136, in default
return _json.JSONEncoder.default(self, obj)
File "/Applications/Anaconda/anaconda3/lib/python3.9/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Polygon is not JSON serializable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/dash.py", line 1336, in dispatch
response.set_data(func(*args, outputs_list=outputs_list))
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/_callback.py", line 193, in add_context
_validate.fail_callback_output(output_value, output)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/_validate.py", line 297, in fail_callback_output
_validate_value(val, index=i)
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/_validate.py", line 287, in _validate_value
_raise_invalid(
File "/Applications/Anaconda/anaconda3/lib/python3.9/site-packages/dash/_validate.py", line 226, in _raise_invalid
raise exceptions.InvalidCallbackReturnValue(
dash.exceptions.InvalidCallbackReturnValue: The callback for `[<Output `map.figure`>]`
returned a value having type `list` which is not JSON serializable.
Solution 1:[1]
Have used data provided on google drive
- have simplified geometry to make it more performant. Plus removed spurious column tract_geom. This column was probably causing your error on JSON serialisation, but data provided for this question is WKT not objects.
- made GeoDataFrame compatible with callback code and defined referenced variables
- fixes
- used geo_interface to pass geojson
- opacity is not valid as a mapbox parameter
- callback was using arrays as parameters. This was causing errors, so switched to
@app.callback(
Output("map", "figure"),
Input("dummy", "value"),
)
def update_map(d):
full working code
import pandas as pd
import geopandas as gpd
from dash import dcc, html, Input, Output
import pandas as pd
from jupyter_dash import JupyterDash
url = (
"https://drive.google.com/file/d/1bP_SdwrU7pMtCPnOd5XfA3oQYKWYlNjn/view?usp=sharing"
)
df = pd.read_csv("https://drive.google.com/uc?id=" + url.split("/")[-2], index_col=0)
# construct geodataframe from provided data
df_geo = gpd.GeoDataFrame(
df, geometry=gpd.GeoSeries.from_wkt(df["geometry"]), crs="epsg:4386"
)
# reduce size of dataframe geometry....
df_geo["geometry"] = (
df_geo.to_crs(df_geo.estimate_utm_crs()).simplify(5000).to_crs(df_geo.crs).drop(columns=["tract_geom"])
)
# make compatible with code in call back
df_geo["tract"] = df_geo["tract_ce"]
zoom = 5
label = "Average Price"
app = JupyterDash(__name__)
app.layout = html.Div([dcc.Graph(id="map"), html.Div(id="dummy")])
@app.callback(
Output("map", "figure"),
Input("dummy", "value"),
)
def update_map(d):
s = df_geo["avg_price"].astype(float)
datad = []
datad.append(
{
"type": "choroplethmapbox",
"geojson": df_geo.__geo_interface__,
"locations": df_geo["tract"],
"z": s,
"featureidkey": "properties.tract",
"autocolorscale": False,
"colorscale": "YlOrRd",
"colorbar": dict(
title=label,
orientation="h",
x=-0.15,
xanchor="left",
y=0,
yanchor="bottom",
showticklabels=True,
thickness=20,
tickformatstops=dict(dtickrange=[0, 10]),
titleside="top",
ticks="outside",
),
"zmin": s.min(),
"zmax": s.max(),
"marker_line_width": 0,
"opacity": 0.2,
"labels": label,
"title": "Choropleth - Census Tract Level",
}
)
layout = {
"autosize": True,
"datarevision": 0,
"hovermode": "closest",
"mapbox": {
"accesstoken": MAPBOX_KEY,
"bearing": 0,
"center": {"lat": 33.6050991, "lon": -112.4052438},
"pitch": 0,
# "opacity": 0.2,
"zoom": zoom,
"style": "streets",
},
"margin": {"r": 0, "t": 0, "l": 0, "b": 0, "pad": 0},
}
return {"data": datad, "layout": layout}
if __name__ == "__main__":
app.run_server(mode="inline")
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |