Seisplotjs Tutorial 3.1.3

Predicted Phase Arrival Times:

See it live in tutorial4.html .

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);
});

See it live in tutorial4.html .

Previous: Quakes and Channels

Next: Deconvolution and Filtering