Black Lives Matter. Please consider donating to Campaign Zero's mission of ending police violence in America.
https://www.joincampaignzero.org

Possible bug plotting MultiPolygons in scattermapbox?

I recently noticed my multi polygons aren’t plotting correctly in scattermapbox. In scattermapbox it seems to be only plotting the outer lines and filling in the entire polygon, compared to the plot on github which is using the same geojson.

Github plot: geojson link

polygonhole

Scattermapbox plot:

import plotly.graph_objects as go
import json
import requests

rr_buffer_1mi = json.load(open("Data/rr_buffer_1mi.geojson"))

buffer_1mi_coords = []

# extracts coordinates from geojson and puts them in an empty list
def coordinate_extract(geojson_file, empty_list):
    for feature in geojson_file['features']:
        if feature['geometry']['type'] == 'Polygon':
            empty_list.extend(feature['geometry']['coordinates'][0])    
            empty_list.append([None, None]) # mark the end of a polygon   
        elif feature['geometry']['type'] == 'MultiPolygon':
            for polyg in feature['geometry']['coordinates']:
                empty_list.extend(polyg[0])
                empty_list.append([None, None]) #end of polygon
        elif feature['geometry']['type'] == 'LineString': 
            empty_list.extend(feature['geometry']['coordinates'])
            empty_list.append([None, None])
        elif feature['geometry']['type'] == 'MultiLineString': 
            for line in feature['geometry']['coordinates']:
                empty_list.extend(line)
                empty_list.append([None, None])
        else: pass 
        
# lat & lon for railroad 1mi buffer
coordinate_extract(rr_buffer_1mi, buffer_1mi_coords)
buff_1mi_lons, buff_1mi_lats = zip(*buffer_1mi_coords)

fig = go.Figure(
    data=[
        go.Scattermapbox(
            name='Railroad Noise (1 mile)',
            hoverinfo='name',
            lat=buff_1mi_lats,
            lon=buff_1mi_lons,
            mode="lines",
            fill='toself',
            line=dict(width=1, color="#abebc6")
        )
    ]
)
mapLegend = go.layout.Legend(
        x=0,
        y=1,
        traceorder="normal",
        font=dict(
            family="sans-serif",
            size=12,
            color="black"
        ),
        bgcolor="LightSteelBlue",
        bordercolor="Black",
        borderwidth=2
    )
fig.update_layout(
    showlegend=True,
    legend=mapLegend,
    margin={"r":0,"t":0,"l":0,"b":0},
    mapbox=go.layout.Mapbox(
        style="carto-positron", 
        zoom=12, 
        center_lat = 40.55,
        center_lon = -105.08,
    )
)
fig.show()

Hey again @prime90, when it comes to polygons and multipolygons you may want to use Choroplethmapbox instead of Scattermapbox.
The problem with polygons is that they have both an outer ring (boundaries of the polygon) and inner ring (wholes inside the polygon) and the way you extract the coordinates may not allow you to get the inner ring.

Using Choroplethmapbox you can use directly the geojson dict. If you need help on how to start with that let me know.

I’m afraid I do! I tried several different ways trying to follow the documentation. But I can’t get go.Choroplethmapbox to plot. I’m receiving this error

ValueError: Mixing dicts with non-Series may lead to ambiguous ordering.

import plotly.graph_objects as go
import json
import requests
import pandas as pd
from pandas.io.json import json_normalize

rr_buffer_1mi = json.load(open("Data/rr_buffer_1mi.geojson"))

rr_location = rr_buffer_1mi['features'][0]['geometry']['coordinates']

fig = go.Figure(
    data=[
        go.Choroplethmapbox(
            geojson=rr_buffer_1mi,
            locations=rr_location,
            name='1 Mile radius',
            hoverinfo='name',
            
        )
    ]
)
mapLegend = go.layout.Legend(
        x=0,
        y=1,
        traceorder="normal",
        font=dict(
            family="sans-serif",
            size=12,
            color="black"
        ),
        bgcolor="LightSteelBlue",
        bordercolor="Black",
        borderwidth=2
    )
fig.update_layout(
    showlegend=True,
    legend=mapLegend,
    margin={"r":0,"t":0,"l":0,"b":0},
    mapbox=go.layout.Mapbox(
        style="carto-positron", 
        zoom=12, 
        center_lat = 40.55,
        center_lon = -105.08,
    )
)
fig.show()

Any suggestions?

The locations keyword in Choroplethmapbox should be a list of the id fields from your geojson. Try:

locations = [f["id"] for f in rr_buffer_1mi["features"]]

The features in your geojson should have id fieldsbut if they don’t you’ll have to manually add them.

Looks like your geojson does not have id fields. Below is a working example:

import plotly.graph_objects as go
import json
import requests
import pandas as pd
from pandas.io.json import json_normalize

rr_buffer_1mi = json.load(open("Data/rr_buffer_1mi.geojson"))
for i in range(len(rr_buffer_1mi ["features"])):
    # id needs to be str
    rr_buffer_1mi ["features"][i]["id"] = str(i)


figure = go.Figure(
    data=[go.Choroplethmapbox(
        geojson=rr_buffer_1mi ,
        locations=[f["id"] for f in geojson["features"]],
        # You don't care about the z value here
        z=[1] * len(geojson["features"]),
        marker=dict(opacity=.8, line=dict(color="#000", width=2)),
        # You need to put a colorscale so just put the same color everywhere for instance
        colorscale=[[0, "rgb(50, 80, 220)"], [1, "rgb(50, 80, 220)"]],
        showscale=False
    )],
    layout=go.Layout(
        margin=dict(b=0, t=0, r=0, l=0),
        mapbox=dict(
            style="carto-positron",
            zoom=8, 
            center_lat = 40.55,
            center_lon = -105.08,
        )
    )
)

figure.show()

And the result:

1 Like

@RenaudLN you da man! This weekend I’m going to study the responses I had to my questions this week. I thought I was close on this one, but I wasn’t! I thought for sure I wanted locations to access ['coordinates'] but I didn’t understand the error I was getting. I find working on projects to be more enjoyable learning python than completing some silly practice problems, but unfortunately, it causes me to ask more questions than I probably should.

How long have you been working with python and plotly to be so familiar with everything?

for i in range(len(rr_buffer_1mi ["features"])):
    # id needs to be str
    rr_buffer_1mi ["features"][i]["id"] = str(i)

This for loop is making the ['id'] a string in 'features' so it can later be accessed in the locations attribute correct?

And then locations=[f["id"] for f in rr_buffer_1mi["features"]], is looping through all the id’s in 'features'? I’ll need to research the placement of f["id"] I haven’t seen any variables placed ahead of a for loop before.

Lastly, I’m a little confused by z=[1] * len(rr_buffer_1mi["features"]). I see in the docs it sets the color value but why did you multiply the length of 'features' by a list?

Also it doesn’t appear this layer can be added to a legend and be turned on/off according to the go.Choropleth attributes, unless I’m missing something.

Again, thanks so much!

For sure practicing on something real is one of the best ways to learn but don’t neglect to learn some the python basics :wink:

I’ve been working with Python for a while now and with Ploly for about a year. I can tell you their documentation is really good so you should really start by checking some of the examples they give to get going. For things that require a little more than the examples shown, check out the full API reference for poorly figures. It would have told you for instance that the Choroplethmapbox locations argument is a list of the feature ids.

Regarding your questions:

  • Correct I just set a unique I’d for each feature so I can access it within the Choroplethmapbox (imo it would probably be easier if there was an option to leave this blank to take all the features but for now it’s not possible).
  • For the locations argument, his is called list comprehension, it’s a Python thing
  • For the z argument, I just create a list of ones with size the number of features. There are other ways to do this that may be more readable for instance np.ones(len(rr_buffer_1mi["features"])). The idea is just to set a z value for each of your polygons, that’s how you would get a nice Choroplethmap.
  • I believe they cannot be seen and turned on/off in the legend. If you really want to have this option you will need to create a layout button that can toggle the visibility of your trace, this is becoming slightly advanced but doable.

Always happy to help fellow Plotly learners :slight_smile: