It would be nicer if we could add some useful labels to this instead of just a raw seismogram. Perhaps getting some information about this particular earthquake and the station it was recorded at. We will use the IRIS FDSNWS station web service to get the station and channels and the USGS FDSNWS event web service to get the earthquake. Since we will have the locations for the quake and station, we might as well plot them on a map. We can use Leaflet, a javascript map library that creates nice looking maps and has lots of flexibility, for this.
Within the <head>, we need to add a link for the Leaflet's style sheet.
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
And some additional styling to size the map. Because the quake and the station are more or less east west of each other, we can use a map that is relatively narrow vertically, but spans the entire window.
<style>
div#mapid {
height: 300px;
width: 100%;
}
We just need to include the leaflet javascript file before our code.
<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"
integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
crossorigin=""></script>
First, we will create the map, centering it on 35/-100 and at zoom level 4.
The
World Ocean Base tile layer gives a nice background.
Then we will create the queries for the quake and station.
Again, both of these queries
are asynchronous and so we will have to use promises. We first create
both the EventQuery
and StationQuery
objects.
const centerLat = 35;
const centerLon = -100;
const mapZoomLevel = 4;
const mymap = L.map('mapid').setView([ centerLat, centerLon], mapZoomLevel);
let OpenTopoMap = L.tileLayer('http://services.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 17,
attribution: 'Map data: Esri, Garmin, GEBCO, NOAA NGDC, and other contributors)'
}).addTo(mymap);
let queryTimeWindow = new seisplotjs.util.StartEndDuration('2019-07-01', '2019-07-31');
let eventQuery = new seisplotjs.fdsnevent.EventQuery()
.timeWindow(queryTimeWindow)
.minMag(7)
.latitude(35).longitude(-118)
.maxRadius(3);
let stationQuery = new seisplotjs.fdsnstation.StationQuery()
.networkCode('CO')
.stationCode('HODGE')
.locationCode('00')
.channelCode('LH?')
.timeWindow(queryTimeWindow);
Then we call their query
methods with a Promise.all()
which will wait until both of these return before calling the
then()
function. Once we have
the quake and station results, we plot them on the map. Even though
we only expect one station and one quake to be returned, we will
plot them as if there might be many for easier reuse. The station is
a generic map marker, but the quake is
plotted as a circle with the radius scaled by the magnitude.
We then will use the more powerful
POST
method to get the seismograms. The
SeismogramDisplayData
works just like the
StartEndDuration
but
also holds the channel that the
time rage corresponds to and will contain the resulting seismogram.
In this case the times are all the same, but
might be different if we were using multiple stations and predicted
arrival times. Note that we use the event time as the start and
ask for a duration of 2400 seconds.
Promise.all([ eventQuery.query(), stationQuery.queryChannels()])
.then( ( [ quakeList, networks ] ) => {
for (const q of quakeList) {
let circle = L.circleMarker([q.latitude, q.longitude], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.15,
radius: q.magnitude ? (q.magnitude.mag*5) : 3 // in case no mag
}).addTo(mymap);
circle.bindTooltip(q.time.toISOString()+" "+(q.magnitude ? (q.magnitude.mag+" "+q.magnitude.type) : "unkn"));
}
for (let s of seisplotjs.stationxml.allStations(networks)) {
let m = L.marker([s.latitude, s.longitude]);
m.addTo(mymap);
m.bindTooltip(s.codes());
}
let allChannels = Array.from(seisplotjs.stationxml.allChannels(networks));
let timeWindow = new seisplotjs.util.StartEndDuration(quakeList[0].time, null, 2400);
let seismogramDataList = allChannels.map(c => {
return seisplotjs.seismogram.SeismogramDisplayData.fromChannelAndTimeWindow(c, timeWindow);
});
let dsQuery = new seisplotjs.fdsndataselect.DataSelectQuery();
return Promise.all([ quakeList, networks, dsQuery.postQuerySeismograms(seismogramDataList) ]);
Because we have the
channel, we can also plot the seismograms corrected for overall
gain and in units of m/s. We also added a couple of
<span>
elements to hold the station codes and earthquake description.
The default text coloring colors the seismograms and the corresponding
array of strings in the title the same color, making them easier to
identify. Note the units on the left hand side are now m/s.
}).then( ( [ quakeList, networks, seismogramDataList ] ) => {
let div = seisplotjs.d3.select('div#myseismograph');
let seisConfig = new seisplotjs.seismographconfig.SeismographConfig();
seisConfig.title = seismogramDataList.map((sdd, i) => i===0?sdd.channel.codes():sdd.channel.channelCode);
seisConfig.doGain = true;
let graph = new seisplotjs.seismograph.Seismograph(div,
seisConfig,
seismogramDataList);
graph.draw();
seisplotjs.d3.select('span#stationCode').text(networks[0].stations[0].codes());
seisplotjs.d3.select('span#earthquakeDescription').text(quakeList[0].description);
}).catch( function(error) {
seisplotjs.d3.select("div#myseismograph").append('p').html("Error loading data." +error);
console.assert(false, error);
});
Previous: Let's get some real data