Seisplotjs Tutorial 3.1.5-SNAPSHOT

Realtime Data

See it live in tutorial7.html .

Now for something else completely different. A realtime plot can be a crowd pleaser, but it is considered very rude to rapidly request the same seismogram over and over from the FDSN dataselect web service, and so we will use a web socket to the IRIS ringserver using the DataLink protocol. If you run your own ringserver and wish to configure it to allow websocket access, some additional information is here .

First we need to set up a bunch of variables we will use to keep track of the realtime data, including the regular expression pattern to match channels we wish to receive and the time window of our display.


const matchPattern = `CO_BIRD_00_HH./MSEED`;
document.querySelector("span#channel").textContent = matchPattern;
const duration = sp.luxon.Duration.fromISO("PT5M");

let numPackets = 0;
let paused = false;
let stopped = true;
let realtimeDiv = document.querySelector("div#realtime");

Here is the timer that will periodically update the current time. Not required, but perhaps helpful.


const n_span = document.querySelector("#nt");
setInterval(() => {
  const seisConfig = rtDisp.organizedDisplay.seismographConfig;
  const lts = seisConfig.linkedTimeScale;
  n_span.textContent = sp.luxon.DateTime.utc().toISO();
}, 1000);

Here we create the organized display element to show the seismographs, and a timer that will periodically refresh the displays. The field minRedrawMillis on the animationScaler sets the timer interval so that hopefully it updates the display just often enough to move the image over by one pixel. This must be calculated after the organized display is drawn for the first time to know how many pixels wide it is. We then start the animation, although with no data this will not actually do anything useful.


const rtConfig = {
  duration: sp.luxon.Duration.fromISO("PT5M"),
};
const rtDisp = sp.animatedseismograph.createRealtimeDisplay(rtConfig);
realtimeDiv.appendChild(rtDisp.organizedDisplay);
rtDisp.organizedDisplay.draw();
rtDisp.animationScaler.minRedrawMillis =
  sp.animatedseismograph.calcOnePixelDuration(rtDisp.organizedDisplay);

rtDisp.animationScaler.animate();

We will also need a couple of functions for error handling.


function updateNumPackets() {
  numPackets++;
  document.querySelector("#numPackets").textContent = numPackets;
}
function addToDebug(message) {
  const debugDiv = document.querySelector("div#debug");
  if (!debugDiv) {
    return;
  }
  const pre = debugDiv.appendChild(document.createElement("pre"));
  const code = pre.appendChild(document.createElement("code"));
  code.textContent = message;
}
function errorFn(error) {
  console.assert(false, error);
  if (datalink) {
    datalink.close();
  }
  addToDebug("Error: " + error);
}

Now we create function to toggle on and off the the actual Datalink connection to the IRIS ringserver . This creates the Datalink connection the first time it is toggled on. We are using a secure web socket, via wss:// , which is effectively a web socket over HTTPS.


let datalink = null;
const IRIS_DATALINK = "wss://rtserve.iris.washington.edu/datalink";
const SCSN_DATALINK = "wss://eeyore.seis.sc.edu/ringserver/datalink";

let toggleConnect = function () {
  stopped = !stopped;
  if (stopped) {
    document.querySelector("button#disconnect").textContent = "Reconnect";
    if (datalink) {
      datalink.endStream();
      datalink.close();
    }
  } else {
    document.querySelector("button#disconnect").textContent = "Disconnect";
    if (!datalink) {
      datalink = new sp.datalink.DataLinkConnection(
        SCSN_DATALINK,
        (packet) => {
          rtDisp.packetHandler(packet);
          updateNumPackets();
        },
        errorFn,
      );
    }
    if (datalink) {
      datalink
        .connect()
        .then((serverId) => {
          return datalink.match(matchPattern);
        })
        .then((response) => {
          addToDebug(`match response: ${response}`);
          if (response.isError()) {
            addToDebug(`response is not OK, ignore... ${response}`);
          }
          const start = sp.luxon.DateTime.utc().minus(duration);
          console.log(`start datalink at : ${start}`);
          return datalink.positionAfter(start);
        })
        .then((response) => {
          if (response.isError()) {
            addToDebug(
              `Oops, positionAfter response is not OK, ignore... ${response}`,
            );
            // bail, ignore, or do something about it...
          }
          return datalink.stream();
        });
    }
  }
};

We wire up the pause button.


document
  .querySelector("button#pause")
  .addEventListener("click", function (evt) {
    togglePause();
  });

let togglePause = function () {
  paused = !paused;
  if (paused) {
    document.querySelector("button#pause").textContent = "Play";
    rtDisp.animationScaler.pause();
  } else {
    document.querySelector("button#pause").textContent = "Pause";
    rtDisp.animationScaler.animate();
  }
};

And wire up the disconnect button


document
  .querySelector("button#disconnect")
  .addEventListener("click", function (evt) {
    toggleConnect();
  });

And then we start it going!


toggleConnect();

See it live in tutorial7.html .

Previous: Helicorder

Next: ...and more