How do I plot a line on a map from a geojson file?

I’m really surprised to see there is no geojson attribute when working with plotly maps. It took me hours looking through the documentation to figure out what I have now. I’m currently receiving the following error in the web browser console when I add the mapbox attribute to display my geojson layer.

Error:

Error: No valid mapbox style found, please set mapbox.style to one of: open-street-map, white-bg, carto-positron, carto-darkmatter, stamen-terrain, stamen-toner, stamen-watercolor or register a Mapbox access token to use a Mapbox-served style.

Python:

import plotly.graph_objects as go
import pandas as pd
import json

with open('fcRailroad.geojson') as json_file:
    fcRailroad = json.load(json_file)

fig = go.Figure(go.Scattermapbox())

fig.update_layout(mapbox_style="stamen-terrain", 
                mapbox_zoom=10, 
                mapbox_center_lat = 40.58,
                mapbox_center_lon = -105.08,
                margin={"r":0,"t":0,"l":0,"b":0},
                mapbox=go.layout.Mapbox(
                    layers=[{
                        'sourcetype': 'geojson',
                        'source': fcRailroad,
                        'type': 'line',
                    }]
                ))
fig.show()

geoJSON:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {},
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [
                        -825.0913953781128,
                        40.49348616373978
                    ],
                    [
                        -825.0906443595885,
                        40.49508532104079
                    ],
                    [
                        -825.0863313674927,
                        40.502411585011934
                    ]
                ]
            }
        }
    ]
}

Any help would be greatly appreciated. Thank you.

  1. The first issue is that you are redefining the layout mapbox object after you updated some of its fields. To display a map you should do the following:
fig = go.Figure(data=[go.Scattermapbox(lat=[0], lon=[0])])

fig.update_layout(
    margin={"r":0,"t":0,"l":0,"b":0},
    mapbox=go.layout.Mapbox(
        style="stamen-terrain", 
        zoom=10, 
        center_lat = 40.5,
        center_lon = -105.08,
        layers=[{
            'sourcetype': 'geojson',
            'source': fcRailroad,
            'type': 'line',
        }]
    )
)
fig.show()
  1. The second issue is with the geojson file, these are not standard longitude and latitude so nothing will be shown on the map.

  2. Thirdly I would recommend to convert your geojson to Scattermapbox rather than using layers, especially if you want to have some hover actions with the line:

fig = go.Figure(
    data=[
        go.Scattermapbox(
            lat=np.array(feature["geometry"]["coordinates"])[:, 1],
            lon=np.array(feature["geometry"]["coordinates"])[:, 0],
            mode="lines",
            line=dict(width=8, color="#F00")
        )
        for feature in fcRailroad["features"]
    ]
)

fig.update_layout(
    margin={"r":0,"t":0,"l":0,"b":0},
    mapbox=go.layout.Mapbox(
        style="stamen-terrain", 
        zoom=10, 
        center_lat = 40.5,
        center_lon = -105.08,
    )
)
fig.show()

And here is a geojson with proper latitude and longitude:

fcRailroad = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {},
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [
                        -105.0913953781128,
                        40.49348616373978
                    ],
                    [
                        -105.0906443595885,
                        40.49508532104079
                    ],
                    [
                        -105.0863313674927,
                        40.502411585011934
                    ]
                ]
            }
        }
    ]
}
3 Likes

Thank you so much for a detailed response RenaudLN! I learned more from this post than the documentation I spent hours looking over. I didn’t even notice the longitudes were wrong I drew them from geojson.io . I’m coming from leaflet map making because I like working with data in python more than javascript and the potential with dash is intriguing.

I have another question if you don’t mind.

Why did you import numpy? I understand you’re using an array to pull the lat/lons but I’m a little confused on

lat=np.array(feature["geometry"]["coordinates"])[:, 1],
lon=np.array(feature["geometry"]["coordinates"])[:, 0],

specifically [:, 1] and [:, 0]

Hi @prime90, glad this was helpful :slight_smile:

Regarding the use of numpy array, here’s how it goes. You have this list of lists of lon/lat in the coordinates field of the feature:

[
    [-105.0913953781128, 40.49348616373978],
    [-105.0906443595885, 40.49508532104079],
    [-105.0863313674927, 40.502411585011934]
]

Transforming a list of lists into an array allows you to select a row or a column. In our case we want the first column for the longitudes and the second column for the latitudes. Selecting the i-th column of an array can be done like so:

column = array[:, i] #the : means select all values in this dimension so here the whole column
1 Like

Oh I get it now. Thanks for clarifying, I never used numpy for arrays before. Once more question…I’m having trouble adding a second layer. It seems the for loop is throwing a syntax error when I add a second layer. The docs say data is a tuple but the for loop is getting in the way and throws a syntax error. Any last suggestions?

fig = go.Figure(
    data=[
        go.Scattermapbox(
            name='Rail Road',
            hoverinfo='name',
            lat=np.array(feature["geometry"]["coordinates"])[:, 1],
            lon=np.array(feature["geometry"]["coordinates"])[:, 0],
            mode="lines",
            line=dict(width=3, color="#F00")
        ),
        for feature in fcRailroad["features"]
        go.Scattermapbox(
            name='High Comfort Bike Route',
            hoverinfo='name',
            lat=np.array(feature["geometry"]["coordinates"])[:, 1],
            lon=np.array(feature["geometry"]["coordinates"])[:, 0],
            mode="lines",
            line=dict(width=3, color="#F00")
        )
        for feature in comfortBike["features"]
    ]
)

You need to pass a list of traces in the data argument of the figure. For your example you could do for instance:

fig = go.Figure(
    data=[
        go.Scattermapbox(
            name='Rail Road',
            hoverinfo='name',
            lat=np.array(feature["geometry"]["coordinates"])[:, 1],
            lon=np.array(feature["geometry"]["coordinates"])[:, 0],
            mode="lines",
            line=dict(width=3, color="#F00")
        )
        for feature in fcRailroad["features"]
    ] + [
        go.Scattermapbox(
            name='High Comfort Bike Route',
            hoverinfo='name',
            lat=np.array(feature["geometry"]["coordinates"])[:, 1],
            lon=np.array(feature["geometry"]["coordinates"])[:, 0],
            mode="lines",
            line=dict(width=3, color="#F00")
        )
        for feature in comfortBike["features"]
    ]
)

This works because the sum of two lists is their concatenation.

1 Like

I thought I had this figured out RenaudLN! However, the second geojson layer throws the following error.

IndexError: too many indices for array

After some research, I found it is because the second geosjon file has all different size of arrays where the first only had one array.

{
    "type": "FeatureCollection",
    "name": "easybike",
    "crs": {
        "type": "name",
        "properties": {
            "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
        }
    },
    "features": [
		{
            "type": "Feature",
            "properties": {
                "BIKEMAPCLA": "High Comfort Bike Lane or Buffered Bike Lane"
            },
            "geometry": {
                "type": "MultiLineString",
                "coordinates": [
                    [
                        [
                            -105.024284,
                            40.509791
                        ],
                        [
                            -105.024274,
                            40.509197
                        ]
                    ]
                ]
            }
        },
        {
            "type": "Feature",
            "properties": {
                "BIKEMAPCLA": "High Comfort Bike Lane or Buffered Bike Lane"
            },
            "geometry": {
                "type": "MultiLineString",
                "coordinates": [
                    [
                        [
                            -105.087928,
                            40.578187
                        ],
                        [
                            -105.087939,
                            40.576423
                        ]
                    ]
                ]
            }
        },
        {
            "type": "Feature",
            "properties": {
                "BIKEMAPCLA": "High Comfort Bike Lane or Buffered Bike Lane"
            },
            "geometry": {
                "type": "MultiLineString",
                "coordinates": [
                    [
                        [
                            -105.11976,
                            40.589318
                        ],
                        [
                            -105.11977,
                            40.587695
                        ],
                        [
                            -105.119794,
                            40.587632
                        ],
                        [
                            -105.119843,
                            40.587581
                        ],
                        [
                            -105.119935,
                            40.587512
                        ],
                        [
                            -105.120033,
                            40.58742
                        ],
                        [
                            -105.120063,
                            40.587369
                        ],
                        [
                            -105.120076,
                            40.587285
                        ],
                        [
                            -105.120061,
                            40.586775
                        ],
                        [
                            -105.120067,
                            40.58671
                        ],
                        [
                            -105.120061,
                            40.586664
                        ],
                        [
                            -105.120092,
                            40.58659
                        ],
                        [
                            -105.120141,
                            40.586511
                        ],
                        [
                            -105.120215,
                            40.586423
                        ],
                        [
                            -105.120722,
                            40.586072
                        ],
                        [
                            -105.120789,
                            40.58604
                        ]
                    ]
                ]
            }
        },
        {
            "type": "Feature",
            "properties": {
                "BIKEMAPCLA": "High Comfort Bike Lane or Buffered Bike Lane"
            },
            "geometry": {
                "type": "MultiLineString",
                "coordinates": [
                    [
                        [
                            -105.083718,
                            40.568761
                        ],
                        [
                            -105.08459,
                            40.568766
                        ],
                        [
                            -105.085324,
                            40.56877
                        ],
                        [
                            -105.086462,
                            40.568776
                        ],
                        [
                            -105.086573,
                            40.568776
                        ],
                        [
                            -105.086917,
                            40.568778
                        ]
                    ]
                ]
            }
        },
        {
            "type": "Feature",
            "properties": {
                "BIKEMAPCLA": "High Comfort Bike Lane or Buffered Bike Lane"
            },
            "geometry": {
                "type": "MultiLineString",
                "coordinates": [
                    [
                        [
                            -105.080333,
                            40.571508
                        ],
                        [
                            -105.080332,
                            40.570821
                        ]
                    ]
                ]
            }
        },
        {
            "type": "Feature",
            "properties": {
                "BIKEMAPCLA": "High Comfort Bike Lane or Buffered Bike Lane"
            },
            "geometry": {
                "type": "MultiLineString",
                "coordinates": [
                    [
                        [
                            -105.080346,
                            40.568743
                        ],
                        [
                            -105.08254,
                            40.568755
                        ],
                        [
                            -105.083718,
                            40.568761
                        ]
                    ]
                ]
            }
        }
    ]
}

I started to see if I could use pandas to achieve the same thing but I can’t figure out how to iterate through geometry.coordinates correctly. Gahh I feel so close.

import json
import pandas as pd 
from pandas.io.json import json_normalize

with open('Data/Bike Lanes.geojson') as f:
    d = json.load(f)

df = json_normalize(d['features'])
print(df)

The coordinates seem to be enclosed in multiple lists? I’m thinking I need to make a new row for lat and a new column for lon in order to place those values in the plotly attributes. I tried several variations of this to no avail.

for lat in df['geometry.coordinates']:
    print(df['geometry.coordinates'][:,0])

Any further suggestions? Thanks again!

I don’t know if you’ve figured it out yet but the difference between this one and the previous geojson is that this one is a MultiLineString instead of a LineString. What this means is that your coordinates have one more nesting level: instead of a list of lists, it is a list of lists of lists. Each element in a MultiLineString can be treated the same way I showed you for the LineString.
So what you need to do is to take each LineString in your MultiLineString and concatenate them into a list separated by None so that the different lines are shown as separate.
Let me know if you struggle to do that.

I did figure it out. Thanks!

Hi RenaudLN,

I have also encountered the same issue. Where I have a geojson with the list of MultiLineString. Please can you guide me ?