Scattermapbox plot curved lines like Scattergeo

Hi,

In this tutorial (https://plotly.com/python/lines-on-maps/), the lines connecting the points are curved. However I can’t seem to recreate this behavior when using mapbox, i.e. go.Scattermapbox. With Scattermapbox the lines created are straight as shown on this page https://plotly.com/python/lines-on-mapbox/. Are there any options or workarounds to join points using curved lines when using mapbox?

Thanks in advance!

Hi @dee214 did you find any solution for this?

Those curved lines on maps are shortest paths on the shere, representing the Earth, projected on a flat map.
Here is defined the function slerp which returns such a path, and must work on mapbox, too::
https://community.plotly.com/t/prevent-lines-in-scattergeo-plot-to-be-drawn-over-pacific/59196/2

Thanks for your response @empet, I’ll try out the function.

@dee214 @dataturnsmeon

Curved lines representing shortest path between two locations on a mapbox, can be plotted using spherical liniar interpolation, slerp:

import numpy as np
from numpy import pi, sin, cos
import plotly.graph_objects as go
def point_sphere(lon, lat):
    #associate the cartesian coords (x, y, z) to a point on the  globe of given lon and lat
    #lon longitude
    #lat latitude
    lon = lon*pi/180
    lat = lat*pi/180
    x = cos(lon) * cos(lat) 
    y = sin(lon) * cos(lat) 
    z = sin(lat) 
    return np.array([x, y, z])

def slerp(A=[100, 45], B=[-50, -25], dir=-1, n=100):
    #Spherical "linear" interpolation
    """
    A=[lonA, latA] lon lat given in degrees; lon in  (-180, 180], lat in ([-90, 90])
    B=[lonB, latB]
    returns n points on the great circle of the globe that passes through the  points A, B
    #represented by lon and lat
    #if dir=1 it returns the shortest path; for dir=-1 the complement of the shortest path
    """
    As = point_sphere(A[0], A[1])
    Bs = point_sphere(B[0], B[1])
    alpha = np.arccos(np.dot(As,Bs)) if dir==1 else  2*pi-np.arccos(np.dot(As,Bs))
    if abs(alpha) < 1e-6 or abs(alpha-2*pi)<1e-6:
        return A
    else:
        t = np.linspace(0, 1, n)
        P = sin((1 - t)*alpha) 
        Q = sin(t*alpha)
        #pts records the cartesian coordinates of the points on the chosen path
        pts =  np.array([a*As + b*Bs for (a, b) in zip(P,Q)])/sin(alpha)
        #convert cartesian coords to lons and lats to be recognized by go.Scattergeo
        lons = 180*np.arctan2(pts[:, 1], pts[:, 0])/pi
        lats = 180*np.arctan(pts[:, 2]/np.sqrt(pts[:, 0]**2+pts[:,1]**2))/pi
        return lons, lats

lons, lats = slerp(A=[-111.5, 25], B=[-56, 49], dir=1)
fig = go.Figure(go.Scattermapbox(lon=lons, lat=lats, mode="lines", line_color="red"))
fig.update_layout(mapbox= dict(accesstoken=open(".mapbox_token").read().rstrip(),
                  center_lat= 37.72199960022625, center_lon= -94.65000604447964,
                  zoom=2.5,
                  style="basic" ))

1 Like