✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

Put a geographical map on the floor of a Scatter3D to track coronavirus infection events

To help people track possible Covid/Coronavirus infection events, I’m trying to use Google Location History Data from multiple individuals to show intersections in time and space.

In the upper figure, you see my location history on a conventional map. In the lower figure, you see a Scatter3D plot with x=latitude, y=longitude, z=time. (Yes I went to Costa Rica recently).

Now, I’d like a conventional map to be laid on the “floor” of the 3d plot.

It’s possible in principle, but before I try to go down that rat hole,

Does anyone know (or wish to provide), a solution?


fig = go.Figure(go.Scattermapbox(
    mode = "lines",
    lon = lons,
    lat = lats,
    marker = {'size': 10}))

fig.update_layout(
    margin ={'l':0,'t':0,'b':0,'r':0},
    mapbox = {
        'center': {'lon': 10, 'lat': 10},
        'style': "stamen-terrain",
        'center': {'lon': -20, 'lat': -20},
        'zoom': 1})
map2d = fig

Thanks!

@jonschull

You cannot draw a styled mapbox on the floor of a 3d plot. You can only plot the boundaries of a map

like in this plot: https://plot.ly/~empet/14375https://plot.ly/~empet/14375,
reading a geojson file of the region of interest, and drawing the boundaries as Scatter3d, mode='lines' :

import json
import urllib.request
import numpy as np
import plotly.graph_objects as go


#Load and read the geojson file for your map

map_url = "https://raw.githubusercontent.com/openpolis/geojson-italy/master/geojson/limits_IT_regions.geojson"  #here is a geojson file for Italy
with urllib.request.urlopen(map_url) as url:
        jdata = json.loads(url.read().decode())
              
pts = []#list of points defining boundaries of polygons
for  feature in jdata['features']:
    if feature['geometry']['type'] == 'Polygon':
        pts.extend(feature['geometry']['coordinates'][0])    
        pts.append([None, None])#mark the end of a polygon   
        
    elif feature['geometry']['type'] == 'MultiPolygon':
        for polyg in feature['geometry']['coordinates']:
            pts.extend(polyg[0])
            pts.append([None, None])#end of polygon
    elif feature['geometry']['type'] == 'LineString': 
        points.extend(feature['geometry']['coordinates'])
        points.append([None, None])
    else: pass           
    #else: raise ValueError("geometry type irrelevant for map")
x, y = zip(*pts)    
h=0
z=  h*np.ones(len(x))
fig = go.Figure()
fig.add_scatter3d(x=x, y=y, z=z, mode='lines', line_color='#999999', line_width=1.5)
fig.show()

Perfect!

Thank you so much!

Sorry to be dense, but would you mind sharing the python code you used for your example (in which one trace is lines, and the other is markers).?

@jonschull

After definning the Scatter3d trace of map boundaries I recorded positions to mark North, South, Est, West as follows:

xmin, xmax = np.nanmin(np.array(x, dtype=np.float)), np.nanmax(np.array(x, dtype=np.float))
ymin, ymax = np.nanmin(np.array(y, dtype=np.float)), np.nanmax(np.array(y, dtype=np.float))
N = [0.5*(xmin+xmax), ymax]
S = [0.5*(xmin+xmax), ymin]
W = [xmin, 0.5*(ymin+ymax)]
E = [xmax, 0.5*(ymin+ymax)]

and defined an associated trace:

NSEW =  go.Scatter3d(
            x=[N[0], S[0], W[0], E[0]],
            y=[N[1], S[1], W[1], E[1]],
            z=[h]*4,
            mode='markers+text',
            marker=dict(size=0.5, color='rgb(230,230,230)'),
            text=['N', 'S', 'W', 'E'])

From a pandas DataFrame, df, with the columns ‘Region’, ‘Lon’, ‘Lat’, where for each index, Lon and Lat are geographical
coordinates of the corresponding Region center, has been set up the Scatter3d trace for markers (mode='markers'):

marker_t = go.Scatter3d(
             x=df['Lon'],
             y=df['Lat'],
             z=[h]*len(df),
             mode='markers',
             marker=dict(size=4, color='blue'))

yes, and then how exactly do you marry that with with the other graph (in my hands, fig.add_trace is producing an emtpy graph)

Thanks again(again)

never mind, got it:

fig=go.Figure()
fig.add_scatter3d(…)
fig.add_scatter3d(…)
fig;

@jonschull

You can insert all traces as follows:

fig= go.Figure(data=[trace_boundaries, NSEW, marker_t])

If you fix h=0 (the floor level), then the following layout updates are recommended:

fig.update_layout(width=800, height=800)  #you can tune these dims 
fig.update_scenes(xaxis_title= 'lon',
                  yaxis_title= 'lat',
                  zaxis_range=[h-0.01, H])  #H is the height of the third axis to be set by you