I've written a program that periodically receives and processes geographic data. I'd like to plot the (latitude, longitude) pairs that are generated by that program in a dash
app providing a plotly
Scattergeo
plot, more or less as the data comes in.
Here's a MWE of a two-threaded program that uses a queue to receive data from the geographic data source and simply print it. The data in this case is simply (lat, lon)
tuples.
import threading
from queue import Queue
from CoordSource import CoordGetter
class Receiver(object):
def __init__(self, q):
while True:
item = q.get()
print("RECEIVED", item)
if __name__ == '__main__':
mainQueue = Queue()
cg = threading.Thread(target=CoordGetter, args=(mainQueue, ))
cg.start()
rt = threading.Thread(target=Receiver, args=(mainQueue, ))
rt.start()
Here's a MWE of a basic dash app that creates a Scattergeo
plot and updates the plotted data every two seconds, but in this case it simply plots two hard-coded points:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly
import plotly.graph_objects as go
import pandas as pd
app = dash.Dash(__name__, prevent_initial_callbacks=True)
app.layout = html.Div(
html.Div([
dcc.Graph(id='live-update-graph'),
dcc.Interval(
id='interval-component',
interval=1*2000,
n_intervals=1
)
])
)
@app.callback(Output('live-update-graph', 'figure'),
Input('interval-component', 'n_intervals'))
def update_graph_live(n):
fig = go.Figure(go.Scattergeo())
fig.update_geos(projection_type="orthographic")
# hard-coded points, would like to receive from queue instead
df = pd.DataFrame({'lat': [0, 20], 'lon': [20, 0]})
fig.add_traces(data=go.Scattergeo(lat=df['lat'], lon=df['lon']))
return fig
if __name__ == '__main__':
app.run_server(debug=True)
Both MWEs, per my reference to them, are tested and working beautifully. However, I have been having trouble sharing a queue between the thread that generates the data and the dash app that plots it.
I've tried quite a few things, read a lot of documentation and examples, and at this point I'm pretty sure I'm misunderstanding something about how dash maintains its thread(s?). So as part of an answer, I'd love some insight into how dash works in this regard.
Also, I should note, when I start a CoordGetter
right before app.run_server()
, it appears to starts twice. That is, any debugging print statements in its __init__()
function are output twice, and it complains the second time about the UDP port that it opens already being in use. That it starts twice and that I expect it to start once suggests an additional misunderstanding of how dash does threads that I would like clarified. I suspect that a good answer to my main question will clarify this too, but I figured I'd mention it explicitly.
Note: I can share CoordSource
if it would help, but it's a bit messy and interacts with both external hardware and software. I think for this question it's okay to simply say that it works and treat it like a black box. I guess it would be just as effective for testing here to make a little class that randomly generates coordinates, which I'm also happy to do to make the question more self-contained.
EDIT: The following class roughly mimics the behaviour of CoordSource.CoordGetter
:
from random import randrange, uniform
from time import sleep
class CoordGetter():
def __init__(self, queue):
self.queue = queue
self.generate()
def generate(self):
while True:
sleep(randrange(10))
for i in range(1, randrange(10)):
lat = uniform(-90,90)
lon = uniform(-180,180)
self.queue.put((lat, lon))
TLDR: How can I get data from CoordSource.CoordGetter
(the third example) to plot as it updates in realtime using Scattergeo
(the second example)? Making a simple threaded script (like the first example) does not succeed.