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