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.
const loader = new seismogramloader.SeismogramLoader(stationQuery, eventQuery);
loader.startOffset = -300;
loader.endOffset = 1200;
loader.markedPhaseList = "PcP,SS";
loadPromise = loader.load();
dataset.Dataset.fromSeismogramLoader(loader).then(dataset => dataset.saveToZipFile());
} else {
loadPromise = dataset.load('tutorial4_dataset.zip').then(ds => [ ds.inventory, ds.catalog, ds.waveforms]);
}
loadPromise.then(([ networkList, quakeList, seismogramDataList]) => {
seismogramDataList = sorting.reorderXYZ(seismogramDataList);
mymap.seisData = seismogramDataList;
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.
let queryTimeWindow = luxon.Interval.fromDateTimes(util.isoToDateTime('2019-07-01'), util.isoToDateTime('2019-07-31'));
let eventQuery = new fdsnevent.EventQuery()
.timeWindow(queryTimeWindow)
.minMag(7)
.latitude(35).longitude(-118)
.maxRadius(3);
let stationQuery = new fdsnstation.StationQuery()
.networkCode('CO')
.stationCode('HODGE')
.locationCode('00')
.channelCode('LH?')
.timeWindow(queryTimeWindow);
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.
let div = document.querySelector('div#myseismograph');
let graphList = [];
let commonSeisConfig = new seismographconfig.SeismographConfig();
commonSeisConfig.linkedAmpScale = new scale.LinkedAmplitudeScale();
commonSeisConfig.linkedTimeScale = new scale.LinkedTimeScale();
commonSeisConfig.wheelZoom = false;
commonSeisConfig.doGain = true;
commonSeisConfig.centeredAmp = false;
for( let sdd of seismogramDataList) {
let seisConfig = commonSeisConfig.clone();
let graph = new seismograph.Seismograph([ sdd ], seisConfig);
graphList.push(graph);
div.appendChild(graph);
}
return Promise.all([ quakeList, networkList, seismogramDataList, graphList ]);
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="myparticlemotion">
</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. We add some flags to the seismographs to show the time range plotted.
}).then( ( [ quakeList, networkList, seismogramDataList, graphList ] ) => {
let pmdiv = document.querySelector("div#myparticlemotion");
let firstS = seismogramDataList[0].traveltimeList.find(a => a.phase.startsWith("S"));
let windowDuration = 60;
let firstSTimeWindow = luxon.Interval.after(
quakeList[0].time.plus({seconds: firstS.time,}).minus({seconds: windowDuration/4}),
luxon.Duration.fromMillis(1000*windowDuration));
seismogramDataList.forEach(sdd => sdd.addMarkers({
name: "pm start",
time: firstSTimeWindow.start,
type: "other",
description: "pm start"}));
seismogramDataList.forEach(sdd => sdd.addMarkers({
name: "pm end",
time: firstSTimeWindow.end,
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);
const doGain = true;
const centeredAmp = true;
let minMax = seismogram.findMinMax([ xSeisData, ySeisData, zSeisData], doGain, centeredAmp);
let pmSeisConfig = new particlemotion.createParticleMotionConfig(firstSTimeWindow);
pmSeisConfig.fixedYScale = minMax;
pmSeisConfig.doGain = doGain;
pmSeisConfig.centeredAmp = centeredAmp;
let pmpA = new particlemotion.ParticleMotion(xSeisData, ySeisData, pmSeisConfig);
pmdiv.appendChild(pmpA);
//pmpA.draw();
let pmpB = new particlemotion.ParticleMotion(xSeisData, zSeisData, pmSeisConfig);
pmdiv.appendChild(pmpB);
//pmpB.draw();
let pmpC = new particlemotion.ParticleMotion(ySeisData, zSeisData, pmSeisConfig);
pmdiv.appendChild(pmpC);
//pmpC.draw();
return Promise.all([ quakeList, networkList, seismogramDataList, graphList ]);
}).catch( function(error) {
const div = document.querySelector('div#myseismograph');
div.innerHTML = `
<p>Error loading data. ${error}</p>
`;
console.assert(false, error);
});
Previous: Quakes and Channels