It would be nice to know where the P and S wave are predicted to
arrive. We can use the IRIS
traveltime
web service to to get travel times for seismic phases. The traveltime
web service uses The TauP Toolkit
under the hood, and so the documentation for it can help. We will
keep things simple and just ask for P and S phases.
Again, this is a remote service, therefore asynchronous, and we
will need to
use promises again. We put an additional
then()
call
after we get the quake and station but before we ask for the
seismograms. This allows us to use the predicted travel times to
pick the time window starting 30 seconds prior to the first P
arrival.
Promise.all( [ eventQuery.query(), stationQuery.queryChannels() ] )
.then( ( [ quakeList, networks ] ) => {
let taupQuery = new seisplotjs.traveltime.TraveltimeQuery()
.latLonFromStation(networks[0].stations[0])
.latLonFromQuake(quakeList[0])
.phases("P,S");
return Promise.all( [ quakeList, networks, taupQuery.queryJson() ] );
Now in the next then
, we can use add the travel time to the
origin time to get a start for our request.
The little flags for phase arrivals are Marker
objects
and we put them and the quake into our
SeismogramDisplayData
objects.
One important thing
to keep in mind with time is that the moment objects from the
momentjs library that we
use are mutible, and so you should always create a copy before
modifying like seisplotjs.moment.utc(quakeList[0].time)
as
otherwise you will change the origin time of the quake. The
postQuerySeismograms()
will parse the miniseed in the response and then create seismograms
within each
SeismogramDisplayData
object,
making it easy to associate the new waveform with the request time
window, channel, and quake.
}).then( ( [ quakeList, networks, ttimes ] ) => {
let phaseMarkers = seisplotjs.seismograph.createMarkersForTravelTimes(quakeList[0], ttimes);
phaseMarkers.push({
markertype: 'predicted',
name: "origin",
time: seisplotjs.moment.utc(quakeList[0].time)
});
let allChannels = Array.from(seisplotjs.stationxml.allChannels(networks));
let firstP = ttimes.arrivals.find(a => a.phase.startsWith('P'));
let startTime = seisplotjs.moment.utc(quakeList[0].time)
.add(firstP.time, 'seconds').subtract(30, 'seconds');
let timeWindow = new seisplotjs.util.StartEndDuration(startTime, null, 1800);
let seismogramDataList = allChannels.map(c => {
let sdd = seisplotjs.seismogram.SeismogramDisplayData.fromChannelAndTimeWindow(c, timeWindow);
sdd.addQuake(quakeList);
sdd.addMarkers(phaseMarkers);
return sdd;
});
let dsQuery = new seisplotjs.fdsndataselect.DataSelectQuery();
return Promise.all( [ quakeList, networks, ttimes, dsQuery.postQuerySeismograms(seismogramDataList) ] );
Now that we have travel times and seismograms, we can plot both. We also link the seismographs so that they stay aligned with each other in time and amplitude.
}).then( ( [ quakeList, networks, ttimes, seismogramDataList ] ) => {
let div = seisplotjs.d3.select('div#myseismograph');
let graphList = [];
for( let sdd of seismogramDataList) {
let seisConfig = new seisplotjs.seismographconfig.SeismographConfig();
seisConfig.title = sdd.channel.codes();
seisConfig.wheelZoom = false;
seisConfig.doGain = true;
let subdiv = div.append('div').classed('seismograph', true);
let graph = new seisplotjs.seismograph.Seismograph(subdiv,
seisConfig,
sdd);
graphList.forEach(g => graph.linkXScaleTo(g));
graphList.forEach(g => graph.linkYScaleTo(g));
graphList.push(graph);
graph.draw();
}
seisplotjs.d3.select('span#stationCode').text(networks[0].stations[0].codes());
seisplotjs.d3.select('span#earthquakeDescription').text(quakeList[0].description);
return Promise.all([ quakeList, networks, ttimes, seismogramDataList ]);
For a little extra, we also can plot the particle motion around the P wave for these seismograms. First we need to add a div to to the html.
<div id="particlemotion">
</div>
And some styling in the <style> at the top.
div.particlemotionContainer {
float:left;
height: 300px;
width: 320px;
}
And then javascript to create the particle motion plots. This uses 60 seconds around the S wave.
}).then( ( [ quakeList, networks, ttimes, seismogramDataList, graphList ] ) => {
let div = seisplotjs.d3.select('div#particlemotion');
let firstS = ttimes.arrivals.find(a => a.phase.startsWith('S'));
let windowDuration = 60;
let firstSTimeWindow = new seisplotjs.util.StartEndDuration(
seisplotjs.moment.utc(quakeList[0].time).add(firstS.time, 'seconds').subtract(windowDuration/4, 'seconds'),
null,
windowDuration);
seismogramDataList.forEach(sdd => sdd.addMarkers({
name: "pm start",
time: firstSTimeWindow.startTime,
type: "other",
description: "pm start"}));
seismogramDataList.forEach(sdd => sdd.addMarkers({
name: "pm end",
time: firstSTimeWindow.endTime,
type: "other",
description: "pm end"}));
graphList.forEach(g => g.drawMarkers());
let xSeisData = seismogramDataList[0].cut(firstSTimeWindow);
let ySeisData = seismogramDataList[1].cut(firstSTimeWindow);
let zSeisData = seismogramDataList[2].cut(firstSTimeWindow);
let spanA = div.append('div').classed('particlemotionContainer', true);
let spanB = div.append('div').classed('particlemotionContainer', true);
let spanC = div.append('div').classed('particlemotionContainer', true);
let minMax = seisplotjs.seismogram.findMinMax([ xSeisData, ySeisData, zSeisData]);
let seismographConfig = new seisplotjs.seismographconfig.SeismographConfig();
seismographConfig.margin.left = 60;
seismographConfig.margin.top = seismographConfig.margin.bottom;
seismographConfig.margin.right = seismographConfig.margin.left;
seismographConfig.fixedTimeScale = firstSTimeWindow;
seismographConfig.fixedYScale = minMax;
let seisConfigA = seismographConfig.clone();
seisConfigA.title = xSeisData.channelCode+" "+ySeisData.channelCode;
seisConfigA.xLabel = xSeisData.channelCode;
seisConfigA.yLabel = ySeisData.channelCode;
let pmpA = new seisplotjs.particlemotion.ParticleMotion(spanA, seisConfigA, xSeisData, ySeisData);
pmpA.draw();
let seisConfigB = seismographConfig.clone();
seisConfigB.title = xSeisData.channelCode+" "+zSeisData.channelCode;
seisConfigB.xLabel = xSeisData.channelCode;
seisConfigB.yLabel = zSeisData.channelCode;
let pmpB = new seisplotjs.particlemotion.ParticleMotion(spanB, seisConfigB, xSeisData, zSeisData);
pmpB.draw();
let seisConfigC = seismographConfig.clone();
seisConfigC.title = ySeisData.channelCode+" "+zSeisData.channelCode;
seisConfigC.xLabel = ySeisData.channelCode;
seisConfigC.yLabel = zSeisData.channelCode;
let pmpC = new seisplotjs.particlemotion.ParticleMotion(spanC, seisConfigC, ySeisData, zSeisData);
pmpC.draw();
return Promise.all([ quakeList, networks, ttimes, seismogramDataList ]);
}).catch( function(error) {
seisplotjs.d3.select("div#myseismograph").append('p').html("Error loading data." +error);
console.assert(false, error);
});
Previous: Quakes and Channels