Seisplotjs Tutorial

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

See it live in tutorial4.html .

Previous: Quakes and Channels

Next: Deconvolution and Filtering