Seisplotjs Tutorial

Filtering, Deconvolution and FFT

See it live in tutorial5.html .

Maybe we would like to see the body waves more clearly and filter out the surface waves. And instead of the raw velocity data, lets apply the full instrument response to correct to displacement. First, lets switch from the LH? channels, recorded at one sample per second, to the HH? channels, recorded at 100 sps to give us a wider frequency band to play with. Since we will need the response information as well, we will need to change from using queryChannels() to queryResponses() in the stationQuery . This will return stationXML with the full response filled in for each channel. We can also the time window used in our dataselect query to use offsets from the predicted arrivals, windowing in on just the range we need around the P wave. This is all possible by modifying the functionality shown in the previous tutorial. However, because getting seismograms for an earthquake at a channel based on predicted arrival times is such a common activity, there is a tool, SeismogramLoader that does this for us.

The first steps will look a lot like the previous tutorial, where we set up the stationQuery and eventQuery with our search parameters.


let queryTimeWindow = seisplotjs.luxon.Interval.fromDateTimes(
  seisplotjs.util.isoToDateTime('2019-07-01'),
  seisplotjs.util.isoToDateTime('2019-07-31'));
let eventQuery = new seisplotjs.fdsnevent.EventQuery()
  .timeWindow(queryTimeWindow)
  .minMag(7)
  .latitude(35).longitude(-118)
  .maxRadius(3);
let stationQuery = new seisplotjs.fdsnstation.StationQuery()
  .networkCode('CO')
  .stationCode('HODGE')
  .locationCode('00')
  .channelCode('HH?')
  .timeWindow(queryTimeWindow);

Of course 100 sps means that we will have 100 times as many samples, so lets reduce the time window to just the region where the P arrival is, say 300 seconds instead of 1800.


let loader = new seisplotjs.seismogramloader.SeismogramLoader(stationQuery, eventQuery);
loader.startOffset = -30;
loader.endOffset = 270;
loader.markedPhaseList = "origin,PP,pP";
loader.withResponse = true;
let loaderSDDPromise = loader.loadSeismograms();

We are not displaying maps here, but if we were then we can add a then function on loader.stationList and loader.quakeList, which are both Promises set up by the loadSeismograms method. These are useful when we need direct access to intermediate values as SeismogramLoader works across the various remote call. We can set the text in the span elements here just as an example. Note that the quake and channel are saved within the list of SeismogramDisplayData objects we will get at the end, so they are not lost even if we do not make use of these extra then methods.


let stationsPromise = loader.networkList.then(networkList => {
  let staText = "";
  for (let s of seisplotjs.stationxml.allStations(networkList)) {
    staText += s.codes();
  }
  seisplotjs.d3.select('span#stationCode').text(staText);
});
let quakePromise = loader.quakeList.then( quakeList => {
  let quakeText="";
  for (const q of quakeList) {
    quakeText+=quakeList[0].description+" ";
  }
  seisplotjs.d3.select('span#earthquakeDescription').text(quakeText);
});

Now we insert the filtering code after the seismograms have arrived but before we plot them. We will insert another then() call on the actual loader Promise. We create a butterworth filter using the sampling rate of the seismogram, with a passband of 0.5 to 10 Hz. Removing the mean is usually a good idea, then we apply the filter. Tapering is important before a deconvolution and then we transfer the instrument response for the channel.


loaderSDDPromise.then((seismogramDataList) => {
    seismogramDataList.forEach(sdd => {
      let butterworth = seisplotjs.filter.createButterworth(
                             2, // poles
                             seisplotjs.filter.BAND_PASS,
                             .5, // low corner
                             10, // high corner
                             1/sdd.seismogram.sampleRate // delta (period)
                    );
      let rmeanSeis = seisplotjs.filter.rMean(sdd.seismogram);
      let filteredSeis = seisplotjs.filter.applyFilter(butterworth, rmeanSeis);
      let taperSeis = seisplotjs.taper.taper(filteredSeis);
      let correctedSeis = seisplotjs.transfer.transfer(taperSeis,
          sdd.channel.response, .001, .02, 250, 500);
      sdd.seismogram = correctedSeis;
    });
    return seismogramDataList ;
Configure the display so that the amplitude and time axis are linked.

  }).then( seismogramDataList  => {
    let div = document.querySelector('div#myseismograph');
    let seisConfig = new seisplotjs.seismographconfig.SeismographConfig();
    seisConfig.linkedTimeScale = new seisplotjs.scale.LinkedTimeScale();
    seisConfig.linkedAmplitudeScale = new seisplotjs.scale.LinkedAmplitudeScale();
    seisConfig.wheelZoom = false;

Then, just to make sure we don"t correct the data twice, we disable the gain correction and create the plots.


    seisConfig.doGain = false;
    seisConfig.centeredAmp = false;
    for( let sdd of seismogramDataList) {
      let graph = new seisplotjs.seismograph.Seismograph([sdd],
                                                         seisConfig);
      div.appendChild(graph);
    }
    return seismogramDataList;

We can also plot the amplitude spectra for the three seismograms. We need to add an additional div to hold them.


            <div id="fftplot">
            </div>
          

And an additional style to size it.


            div#fftplot {
              height: 600px;
            }
          

Then we calculate the fft and plot it.


  }).then(seismogramDataList => {
    let div = seisplotjs.d3.select('div#fftplot');
    let fftList = seismogramDataList.map(sdd => {
      if (sdd.seismogram.isContiguous()) {
        return seisplotjs.fft.fftForward(sdd.seismogram)
      } else {
        return null; // can't do fft for non-contiguouus
      }
    }).filter(x => x); // to remove nulls
    let seisConfig = new seisplotjs.seismographconfig.SeismographConfig();
    let fftPlot = new seisplotjs.spectraplot.SpectraPlot(fftList, seisConfig);
    document.querySelector('div#fftplot').appendChild(fftPlot);
    return seismogramDataList;
  });

Then just a little bit of cleanup to make sure everything finished correctly.


Promise.all( [ quakePromise, stationsPromise, loaderSDDPromise ] )
.catch( function(error) {
    seisplotjs.d3.select("div#myseismograph").append('p').html("Error loading data." +error);
    console.assert(false, error);
  });

See it live in tutorial5.html .

Previous: Predicted phase arrival times

Next: Helicorder