Black Lives Matter. Please consider donating to Black Girls Code today.

How to plot a scattermapbox with pandas?

I’m trying to plot lat/lon points on a scattermapbox with a geojson file. I threw the geojson file in a panada’s data frame and made an empty list for lat and lon since thats all scattermapbox accepts. It didn’t turn out as expected. Haha

Dataframe:

0      [[[-105.077274, 40.514625], [-105.077005, 40.5...
1      [[[-105.024284, 40.509791], [-105.024274, 40.5...
2      [[[-105.087928, 40.578187], [-105.087939, 40.5...
3      [[[-105.11976, 40.589318], [-105.11977, 40.587...
4      [[[-105.083718, 40.568761], [-105.08459, 40.56...
                             ...
995    [[[-105.05362, 40.525161], [-105.053607, 40.52...
996    [[[-105.030003, 40.62114], [-105.030012, 40.62...
997    [[[-105.123316, 40.560645], [-105.123353, 40.5...
998    [[[-105.070162, 40.580083], [-105.070175, 40.5...
999    [[[-105.120617, 40.560044], [-105.120637, 40.5...
Name: geometry_coordinates, Length: 1000, dtype: object
import plotly.graph_objects as go
import pandas as pd
from pandas.io.json import json_normalize
import json

geojson = json.load(open("Data/High Comfort Bike Lanes.geojson"))
geojson = json_normalize(geojson['features'], sep="_")

lon_comfortBike = []
lat_comfortBike = []

for l1 in geojson['geometry_coordinates']:
    for l2 in l1:
        for l3 in l2:
            lon_comfortBike.append(l2[0])
            lat_comfortBike.append(l2[1])

fig = go.Figure(
       data=[
            go.Scattermapbox(
            name='High Comfort Bike Route',
            hoverinfo='name',
            lat=lat_comfortBike,
            lon=lon_comfortBike,
            mode="lines",
            line=dict(width=3, color="#F00")
            ]
        )

Any suggestions on how to loop through the data frame correctly and plot these lines?

@prime90 You get cluttered lines because of json_normalize, and no end line marked by a point of coordinates [None, None].
I suggest to avoid saving the geometry in a dataframe and operate on geojson data, taking into account the geometry type of each feature in geojson[‘features’] (from your pasted dataframe rows it seems that your bike lanes are defined by 'Polygon'(s)), but I consider below 'MultiPolygon' and 'LineString', too. The definition of each geometry type can be found here https://en.wikipedia.org/wiki/GeoJSON.

geojson = json.load(open("Data/High Comfort Bike Lanes.geojson"))
points = []

for  feature in geojson['features']:
    if feature['geometry']['type'] == 'Polygon':
        points.extend(feature['geometry']['coordinates'][0])    
        points.append([None, None]) # mark the end of a polygon   
        
    elif feature['geometry']['type'] == 'MultiPolygon':
        for polyg in feature['geometry']['coordinates']:
            points.extend(polyg[0])
            points.append([None, None]) #end of polygon
    elif feature['geometry']['type'] == 'LineString': 
        points.extend(feature['geometry']['coordinates'])
        points.append([None, None])
    else: pass   
    
lons, lats = zip(*points)  

fig = go.Figure(
            go.Scattermapbox(
            name='High Comfort Bike Route',
            hoverinfo='name',
            lat=lats
            lon=lons
            mode="lines",
            line=dict(width=3, color="#F00")
        )

Thank you for the detailed reply @empet! However, I’m receiving an error on line elif feature['geometry']['type'] == "LineString': it reads (i did change LineString to MultiLineString but received the same error on both.)

SyntaxError: EOL while scanning string literal

I can’t seem to find anything wrong with my geojson file. It plots correctly on git hub: hc_bikelanes.geojson

To see what would happen I commented out

# elif feature['geometry']['type'] == "MultiLineString': 
       #  points.extend(feature['geometry']['coordinates'])
       #  points.append([None, None])

and received the following error which occurred on line lons, lats = zip(*points) (this might because I have geometry type: MultiLineString in my geojson and not Polygon or Multipolygon?)

ValueError: not enough values to unpack (expected 2, got 0)

Thanks so much for your help!

@prime90
The error is caused by "LineString'. Take either " " or ' ' . Perhaps the rightmost quote was deleted during pasting of the code, and I inserted the one which is not identical with that at the begining of the string.

@empet Nice eye! I thought it had to do something with the geojson file. Getting closer to success…The map loads but nothing plots except the legend. I’m guessing the points list isn’t being appended correctly? I took a peek at print(points) and I see [None, None] at the end of each line so it does seem to be correct right?

[[-105.0527, 40.57039], [-105.05267, 40.5704], [-105.05264, 40.57041], [-105.05262, 40.57041], [-105.0526, 40.57042], [-105.05257, 40.57043], [-105.05255, 40.57044], [-105.05253, 40.57045], [-105.0525, 40.57045], [-105.05249, 40.57046], [-105.05248, 40.57046], [-105.05246, 40.57047], [-105.05244, 40.57048], [-105.05241, 40.57049], [-105.05181, 40.57075]], [None, None], [[-105.11086, 40.56166], [-105.11087, 40.56166], [-105.11089, 40.56167], [-105.11092, 40.56168], [-105.11094, 40.56169], [-105.11096, 40.5617], [-105.11098, 40.56171], [-105.11101, 40.56172], [-105.11103, 40.56174], [-105.11121, 40.56183], [-105.11123, 40.56184], [-105.11124, 40.56185], [-105.11126, 40.56186]], [None, None], [[-105.06749, 40.57805], [-105.06735, 40.57805], [-105.06717, 40.57804], [-105.0666, 40.57804]], [None, None], [[-105.10591, 40.59646], [-105.10587, 40.59827], [-105.10588, 40.59832], [-105.10592, 40.59836], [-105.10599, 40.5984], [-105.10605, 40.59841], [-105.10913, 40.59842], [-105.10918, 40.59841], [-105.10924, 40.59836], [-105.10927, 40.5983], [-105.10931, 40.59648]], [None, None], [[-105.06755, 40.5419], [-105.06756, 40.54185], [-105.06756, 40.54182], [-105.06756, 40.54179], [-105.06756, 40.54175], [-105.06756, 40.54172], [-105.06756, 40.54168], [-105.06756, 40.54165], [-105.06757, 40.54127]], [None, None], [[-105.07565, 40.571], [-105.07492, 40.571]], [None, None],
import plotly.graph_objects as go
import pandas as pd
from pandas.io.json import json_normalize
import json

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

points = []

for  feature in geojson['features']:
    if feature['geometry']['type'] == 'Polygon':
        points.extend(feature['geometry']['coordinates'][0])    
        points.append([None, None]) # mark the end of a polygon   
    elif feature['geometry']['type'] == 'MultiPolygon':
        for polyg in feature['geometry']['coordinates']:
            points.extend(polyg[0])
            points.append([None, None]) #end of polygon
    elif feature['geometry']['type'] == 'MultiLineString': 
        points.extend(feature['geometry']['coordinates'])
        points.append([None, None])
    else: pass   
    
lons, lats = zip(*points)  

fig = go.Figure(
        go.Scattermapbox(
            name='High Comfort Bike Lane',
            hoverinfo='name',
            lat=lats,
            lon=lons,
            mode="lines",
            line=dict(width=3, color="#F00")
        )
)
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="stamen-terrain", 
        zoom=12, 
        center_lat = 40.55,
        center_lon = -105.08,
    )
)
fig.show()

@prime90

Inspect the list points to see if it is empty or not. I read my own geojson file and the list points was well defined.

Check also the structure of your geojson file, to identify the geometry type and whether the list of coordinates is written as the in the standard geojson file (whose geometries are described on wikipedia) or not.

In case you cannot identify why the lines are not plotted, please upload somewhere your geojson file or a part of it to run my code with that file.

Thanks! You’re really helping me understand python and plotly…I did validate my geojson in http://geojsonlint.com/ . It passed and plotted after I removed 'name' and 'crs' lines at the top of the file. I linked my geojson on my previous post but here it is again.

geojson file

I’ll continue to scrub through it and see if I find anything.

@prime90

You added the geometry type 'MultiLineString' but did not extract correctly the corresponding points in each (sub)line. Your geojson file contains only this geometry type.

Hence with this code:

points = []

for  feature in geojson['features']:
    if feature['geometry']['type'] == 'Polygon':
        points.extend(feature['geometry']['coordinates'][0])    
        points.append([None, None]) # mark the end of a polygon   
        
    elif feature['geometry']['type'] == 'MultiPolygon':
        for polyg in feature['geometry']['coordinates']:
            points.extend(polyg[0])
            points.append([None, None]) #end of polygon
    elif feature['geometry']['type'] == 'LineString': 
        points.extend(feature['geometry']['coordinates'])
        points.append([None, None])
    elif feature['geometry']['type'] == 'MultiLineString':  
        for line  in feature['geometry']['coordinates']:
            points.extend(line)
            points.append([None, None])
    else: pass   

you get the right list of points and this map:

@empet Awesome!! Very grateful for your help. I spent probably 48hrs trying to figure this out.

That makes sense to add a nested for loop because it’s a nested list. I’m going to make a function for this json extraction and save it in my notes for future use.

I don’t understand lons, lats = zip(*points) but I haven’t researched it yet. I’ll be sure to ask if I haven’t figured it out.

Thanks again!

@prime90 Here https://appdividend.com/2019/04/09/python-zip-example-python-zip-function-tutorial/ you find examples on how to, use and interpret zip().