Live - Earthquake Tracking System Using Dash and Plotly.
Tags: Data Visualization, Towards Data Science, Pandas, Dash, Plotly

Using this real-time data I have come with a user friendly dash app that tracks the earthquakes and lets you view the earthquakes that have happened in a specific region with a certain magnitude.

Procedure

Read the CSV data by “pandas read_csv()” function.

import pandas as pd def GrabOccurrenceData(past_occurrence, mag_value): url = ' https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/' + str(past_occurrence) + '.csv' qdf = pd.read_csv(url)

qdf = qdf[qdf['mag'] > int(mag_value)] return qdf

This will return a Data Frame consisting of earthquakes that are greater than the “mag_value”.

>>> qdf = GrabOccurrenceData("all_hour", 1)

>>> qdf.columns

Index(['time', 'latitude', 'longitude', 'depth', 'mag', 'magType', 'nst', 'gap', 'dmin', 'rms', 'net', 'id', 'updated', 'place', 'type', 'horizontalError', 'depthError', 'magError', 'magNst', 'status', 'locationSource', 'magSource'], dtype='object') >>> qdf['place'].to_list()

['13km E of Little Lake, CA',

'24km E of Waukomis, Oklahoma',

'16km SW of La Quinta, CA',

'9km NNW of Borrego Springs, CA',

'24km WNW of Warm Springs, Nevada',

'South Shetland Islands']

If we notice the “place” columns, we see that it has “some_value, region”. We are more interested in the “region” as we can later segregate the total number of earthquakes as per the region when selected and displaying only those earthquake that have occurred.

To do that we will use the following function.

def GrabSpecificArea(past_occurrence, mag_value):

qdf = GrabOccurrenceData(past_occurrence, mag_value)

places = qdf['place'].to_list() specific_areas = []

for place in places:

area = place.split(', ')

if len(area) == 2:

specific_areas.append(area[1])

if len(area) < 2:

specific_areas.append(area[0]) area_counts = []

for area in specific_areas:

area_counts.append(area+' - '+str(specific_areas.count(area))) specific_areas = list(set(area_counts))



return specific_areas

The following result is obtained by using the above function.

>>> area_list = GrabSpecificArea("all_hour", 1)

>>> area_list

['CA - 3',

'Oklahoma - 1',

'Nevada - 1',

'South Shetland Islands - 1']

Now that we got the specific region with a count of earthquakes, we can visualize this on MapBox map.

For visualizing the regions on the map, we require “latitudes” and “longitudes”. To visualize on real-time basis we have to define a time interval. As the data gets updated every 1 minute. we can set a time interval of 3 minutes just so that the updated data is captured properly.

We will import the required packages.

import dash

import dash_core_components as dcc

import dash_html_components as html

from dash.dependencies import (Input, Output)

Now we will make a dash object and create layout for the application.



external_stylesheets = [' external_scripts = [' https://www.google-analytics.com/analytics.js' external_stylesheets = [' https://codepen.io/chriddyp/pen/bWLwgP.css' app = dash.Dash(__name__,

external_scripts=external_scripts,

external_stylesheets=external_stylesheets)

app.config['suppress_callback_exceptions'] = True app.layout = html.Div([

html.Div([

dcc.RadioItems(

id='map-type',

options=[{'label':s, 'value':s} for s in ['Density Map','Scatter Map']],

value='Density Map',

labelStyle={'display': 'inline-block'}

)

],style={'margin-left':20,'margin-top':20,'margin-bottom':10}

),

html.Div([dcc.Interval(id='output-update', interval=180*1000)]),

html.Div(id='map-quakes'),

html.Div([

html.Div([

html.Div(id='pie-quake-type')

], className='five columns'),

html.Div([

html.Div(id='area-count-plot')

], className='seven columns')

], className='row', style={'margin-left' : 20, 'margin-top' : 30}),

html.Div([])

], style={'margin-top' : 20, 'margin-bottom' : 20})

Let’s create some helper functions for data visualization.

import plotly.graph_objs as go #<DensityMap>

def PlotDensityMap(lat, lon, z, radius, colorscale):

density_map_trace = go.Densitymapbox(

lat=lat,

lon=lon,

z=z,

radius=radius,

colorscale=colorscale,

)

return density_map_trace def LayoutDensity(height, width, mapbox_style, c_lat, c_lon, zoom):

layout_density_map = go.Layout(

height=height,

width=width,

autosize=True,

showlegend=False,

hovermode='closest',

margin=dict(l=0, r=0, t=0, b=0),

mapbox_style=mapbox_style,

mapbox_center_lat=c_lat,

mapbox_center_lon=c_lon,

mapbox=dict(

zoom=zoom

)

)

return layout_density_map

#</DensityMap> #<ScatterMap>

def PlotScatterMap(lat, lon, size, color, colorscale, text):

scatter_map_trace = go.Scattermapbox(

lat=lat,

lon=lon,

mode='markers',

marker=dict(

size=size, color=color, opacity=1,

colorscale=colorscale,

),

text=text, hoverinfo='text', showlegend=True

)

return scatter_map_trace def LayoutScatter(height, width, mapbox_style, c_lat, c_lon, zoom):

layout_scatter_map = go.Layout(

height=height,

width=width,

autosize=True,

showlegend=False,

hovermode='closest',

margin=dict(l=0, r=0, t=0, b=0),

mapbox_style=mapbox_style,

mapbox=dict(

center=dict(

lat=c_lat,

lon=c_lon

),

zoom=zoom

)

)

return layout_scatter_map

#</ScatterMap>

Let’s create a callback decorator that extends the behavior of a function by wrapping it. This is used to trigger the changes automatically whenever the input value is changed.

Note: There are several callbacks that I have used for developing this app. Under certain conditions I am only using some of them. However, to get familiarized here is the callback chart.

Callback chart

MapBox output map for showing the locations of the earthquakes.

import plotly.graph_objs as go

.callback(

Output('map-quakes', 'children'),

[Input('past-occurrence', 'value'), Input('magnitude-range', 'value'), Input('map-type', 'value'), Input('area-list', 'value'),

Input('output-update', 'n_intervals')],

)

def visualize_quakes(past_occurrence, mag_value, map_type, specific_area, n_intervals):

try:

eqdf = GrabOccurrenceData(past_occurrence, mag_value)

eqdf = eqdf[eqdf['place'].str.contains(str(specific_area.split(' - ')[0]))]

zoom = 3

radius = 15

latitudes = eqdf['latitude'].to_list()

longitudes = eqdf['longitude'].to_list()

magnitudes = eqdf['mag'].to_list()

mags = [float(i)*radius_multiplier['outer'] for i in magnitudes]

mags_info = ['Magnitude : ' + str(m) for m in magnitudes]

depths = eqdf['depth'].to_list()

deps_info = ['Depth : ' + str(d) for d in depths]

places = eqdf['place'].to_list() # @app .callback(Output('map-quakes', 'children'),[Input('past-occurrence', 'value'), Input('magnitude-range', 'value'), Input('map-type', 'value'), Input('area-list', 'value'),Input('output-update', 'n_intervals')],def visualize_quakes(past_occurrence, mag_value, map_type, specific_area, n_intervals):try:eqdf = GrabOccurrenceData(past_occurrence, mag_value)eqdf = eqdf[eqdf['place'].str.contains(str(specific_area.split(' - ')[0]))]zoom = 3radius = 15latitudes = eqdf['latitude'].to_list()longitudes = eqdf['longitude'].to_list()magnitudes = eqdf['mag'].to_list()mags = [float(i)*radius_multiplier['outer'] for i in magnitudes]mags_info = ['Magnitude : ' + str(m) for m in magnitudes]depths = eqdf['depth'].to_list()deps_info = ['Depth : ' + str(d) for d in depths]places = eqdf['place'].to_list() center_lat = eqdf[eqdf['mag'] == eqdf['mag'].max()['latitude'].to_list()[0]

center_lon = eqdf[eqdf['mag'] == eqdf['mag'].max()]['longitude'].to_list()[0] if (map_type == 'Density Map'):

map_trace = PlotDensityMap(latitudes, longitudes, magnitudes, radius, 'Electric')

layout_map = LayoutDensity(600, 980, 'stamen-terrain', center_lat, center_lon, zoom)

visualization = html.Div([

dcc.Graph(

id='density-map',

figure={'data' : [map_trace], 'layout' : layout_map}

),

])

return visualization if (map_type == 'Scatter Map'):

quake_info = [places[i] + '<br>' + mags_info[i] + '<br>' + deps_info[i] for i in range(eqdf.shape[0])]

map_trace = PlotScatterMap(latitudes, longitudes, mags, magnitudes, default_colorscale, quake_info)

layout_map = LayoutScatter(600, 980, 'stamen-terrain', center_lat, center_lon, zoom)

visualization = html.Div([

dcc.Graph(

id='scatter-map',

figure={'data' : [map_trace], 'layout' : layout_map}

),

])

return visualization

except Exception as e:

return html.Div([

html.H6('Please select valid magnitude / region ...')

], style={'margin-top' : 150, 'margin-bottom' : 150, 'margin-left' : 200})

#</density_scatter_mapbox>

The above callback visualizes the earthquakes on the map. Density MapBox is a good representation for earthquakes, however I also included Scatter MapBox.

For further visualization we can show the “type” column using pie chart and the total number of counts of the earthquakes as per the area using bar chart.

Pie chart for showing “Disaster type”.



.callback(

Output('pie-quake-type', 'children'),

[Input('past-occurrence', 'value'), Input('magnitude-range', 'value'), Input('output-update', 'n_intervals')]

)

def category_pie_chart(past_occurrence, mag_value, n_intervals):

eqdf = GrabOccurrenceData(past_occurrence, mag_value)

qtype = eqdf['type'].value_counts().to_frame()

qtype.reset_index(inplace=True)

qtype.columns = ['type', 'count']

labels = qtype['type'].to_list()

values = qtype['count'].to_list() # @app .callback(Output('pie-quake-type', 'children'),[Input('past-occurrence', 'value'), Input('magnitude-range', 'value'), Input('output-update', 'n_intervals')]def category_pie_chart(past_occurrence, mag_value, n_intervals):eqdf = GrabOccurrenceData(past_occurrence, mag_value)qtype = eqdf['type'].value_counts().to_frame()qtype.reset_index(inplace=True)qtype.columns = ['type', 'count']labels = qtype['type'].to_list()values = qtype['count'].to_list() pie_type = go.Pie(labels=labels, values=values, hole=0.3, pull=0.03, textposition='outside', rotation=100)

pie_layout = go.Layout(title='Disaster type') pie_chart_type = html.Div([

dcc.Graph(id='disaster-type', figure={'data' : [pie_type], 'layout' : pie_layout})

])

return pie_chart_type

#</earthquake_type_pie>

Bar chart for showing the “Area Count”.



.callback(

Output('area-count-plot', 'children'),

[Input('past-occurrence', 'value'), Input('magnitude-range', 'value'), Input('output-update', 'n_intervals')]

)

def count_area_plot(past_occurrence, mag_value, n_intervals):

counts_area = GrabSpecificArea(past_occurrence, mag_value) # @app .callback(Output('area-count-plot', 'children'),[Input('past-occurrence', 'value'), Input('magnitude-range', 'value'), Input('output-update', 'n_intervals')]def count_area_plot(past_occurrence, mag_value, n_intervals):counts_area = GrabSpecificArea(past_occurrence, mag_value) areas_alone = []; count_vals = []

for area in counts_area:

area = area.split(' - ')

areas_alone.append(area[0])

count_vals.append(int(area[1])) area_counter = go.Bar(x=areas_alone, y=count_vals)

repeat_layout = go.Layout(title='Worldwide - ' + str(past_occurrence)) repetitive_areas = html.Div([

dcc.Graph(id='area-repeat-list', figure={'data':[area_counter], 'layout' : repeat_layout})

])

difficult_message = html.Div([

html.P('Quite difficult to load the graph ...')

], style={'margin-top' : 150, 'margin-bottom' : 50, 'textAlign' : 'center'}) if past_occurrence == 'all_week' and mag_value < 3:

return difficult_message

if past_occurrence == 'all_week' and mag_value >= 3:

return repetitive_areas

return repetitive_areas

#</count_area_plot>

Finally to run the application we use

if __name__ == '__main__':

app.run_server(debug=True, dev_tools_props_check=False, dev_tools_ui=False)

Results

Density maps are a good way of representing earthquakes. The below result shows the earthquakes across the world that had occurred “Yesterday”.

Density map result

Similarly the scatter map result for a specific area (“Indonesia”) can be shown below.

Indonesia earthquakes — last 24 hrs

To show the disaster type we have used pie chart. For this I have considered the “last week’s” data.

Pie chart showing the type of the occurrence

To show the area count plot I have considered the “Yesterday’s” data.

Area count plot

A more efficient and updated result with live seismic report generation can be seen below.