'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
    1. used geo_interface to pass geojson
    2. opacity is not valid as a mapbox parameter
    3. 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