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 that will put the travel times into text near the bottom
of the page. Note that if you wish to actually process the travel
times instead of just display, then using the setting the
queryJson()
instead of
queryText()
is more useful to avoid parsing the text.
let stationsPromise = stationQuery.queryChannels();
let quakePromise = eventQuery.query();
const allPhases = 'P,S';
const ttimePromise = Promise.all([quakePromise, stationsPromise])
.then(([quakeList, networkList]) => {
document.querySelector("span#stationCode").textContent = networkList[0].stations[0].codes();
document.querySelector("span#earthquakeDescription").textContent = quakeList[0].description;
let quakeTTimesPromiseList = quakeList.map(q => {
const allDistDeg = [];
for (const s of sp.stationxml.allStations(networkList)) {
if (s.timeRange.contains(q.time)) {
const daz = sp.distaz.distaz(
s.latitude,
s.longitude,
q.latitude,
q.longitude,
);
allDistDeg.push(daz.distanceDeg);
}
}
const taupQuery = new sp.traveltime.TraveltimeQuery();
taupQuery.distdeg(allDistDeg);
taupQuery.evdepthInMeter(q.depth);
taupQuery.phases(allPhases);
return taupQuery.queryText();
});
return Promise.all([quakeList, networkList, Promise.all(quakeTTimesPromiseList)]);
}).then(([quakeList, networkList, quakeTTimesList]) => {
const ttdiv = document.querySelector("#traveltimes");
quakeTTimesList.forEach(qtt => {
const preEl = ttdiv.appendChild(document.createElement("pre"));
preEl.textContent = qtt;
});
This would allow us to use the predicted travel times to
pick the time window based on the predicted arrival times and request
data. However, because this is a common need, we have a tool,
SeismogramLoader
, that
wraps all of this in a single call, handling a lot of the bookkeeping
and details. The default starting phase is the first of p,P,Pdiff,PKP
and ending phase is the first of s,S,Sdiff,SKS. Here we get 300
seconds before P to 1200 seconds after S. Also, we can get traveltimes
for other phases that we may wish to mark on the plot. The
loader handles figuring out the travel times for each quake-station,
associating the request with a SeismogramDisplayData and retrieving
the waveforms. The result is populated into a
DataSet
which is a big holder object for waveforms, quakes, station,
channels and other items.
const loader = new sp.seismogramloader.SeismogramLoader(networkList, quakeList);
loader.startOffset = -300;
loader.endOffset = 1200;
loader.markedPhaseList = "PcP,SS";
return loader.load();
}).then(dataset => {
let seismogramDataList = sp.sorting.reorderXYZ(dataset.waveforms);
mymap.seisData = 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.
let div = document.querySelector('div#myseismograph');
let graphList = [];
let commonSeisConfig = new sp.seismographconfig.SeismographConfig();
commonSeisConfig.linkedAmpScale = new sp.scale.LinkedAmplitudeScale();
commonSeisConfig.linkedTimeScale = new sp.scale.LinkedTimeScale();
commonSeisConfig.doGain = true;
for (let sdd of seismogramDataList) {
let graph = new sp.seismograph.Seismograph([sdd], commonSeisConfig);
graphList.push(graph);
div.appendChild(graph);
}
For something a little extra, we also can plot the particle motion around the S 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.
sp-particle-motion {
float:left;
width: 32vw;
height: 32vw;
}
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.
let pmdiv = document.querySelector("div#myparticlemotion");
let firstS = seismogramDataList[0].traveltimeList.find(a => a.phase.startsWith("S"));
let windowDuration = 60;
let windowStart = seismogramDataList[0].quake.time.plus({ seconds: firstS.time, }).minus({ seconds: windowDuration / 4 });
let firstSTimeWindow = sp.util.startDuration(windowStart, windowDuration);
seismogramDataList.forEach(sdd => {
sdd.addMarkers(
[
{
name: "pm start",
time: firstSTimeWindow.start,
type: "other",
description: "pm start"
},
{
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;
let minMax = sp.seismogram.findMinMax([xSeisData, ySeisData, zSeisData], doGain);
let pmSeisConfig = new sp.particlemotion.createParticleMotionConfig(firstSTimeWindow);
pmSeisConfig.fixedYScale = minMax;
pmSeisConfig.doGain = doGain;
let pmpA = new sp.particlemotion.ParticleMotion(xSeisData, ySeisData, pmSeisConfig);
pmdiv.appendChild(pmpA);
let pmpB = new sp.particlemotion.ParticleMotion(xSeisData, zSeisData, pmSeisConfig);
pmdiv.appendChild(pmpB);
let pmpC = new sp.particlemotion.ParticleMotion(ySeisData, zSeisData, pmSeisConfig);
pmdiv.appendChild(pmpC);
return Promise.all([seismogramDataList, graphList, dataset]);
}).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