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 EarthScope 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 . There are similar examples using Seedlink version 3 and Seedlink version 4 . Datalink is better if a simple regular expression can match your channels, Seedlink is better if you need to match channels from a list of stations.
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.
// Datalink on older versions of Ringserver, DLPROTO1.0, use NSLC ids like:
const matchPatternV3 = `CO_HAW_00_HH./MSEED`;
// while datalink on version 4 of Ringserver, DLPROTO1.1, uses FDSN Source ids like:
const matchPatternV4 = `FDSN:CO_HAW_00_H_H_./MSEED`;
// this regex pattern will match both, but is a little clunky
const matchPatternBoth = `(FDSN:)?CO_HAW_00_H_?H_?./MSEED`;
// An alternative is to use a different pattern, V3 or V4, based on the
// returned DLPTOTO version from the serverId. See below for an example.
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 now = sp.luxon.DateTime.utc().toISO();
n_span.textContent = `Now: ${now}`;
}, 1000);
Here we create the organized display element to show the seismographs, and a timer that will periodically refresh the displays. The optional field minRedrawMillis in the config sets the timer interval for redraws. Ideally it is set so that it updates the display just often enough to move the image over by one pixel. The default, if the config value is not set, is to calculate the best value when the component is added and again when resized. Unless you want to reduce CPU usage, it is probably best to not set this.
const rtConfig = {
duration: sp.luxon.Duration.fromISO("PT5M"),
};
const rtDisp = sp.animatedseismograph.createRealtimeDisplay(rtConfig);
realtimeDiv.appendChild(rtDisp.organizedDisplay);
rtDisp.organizedDisplay.draw();
rtDisp.animationScaler.animate();
We will also need a couple of functions for error handling and such.
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
EarthScope ringserver
, also updating the text for the two buttons.
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 DATALINK_URL = IRIS_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) {
addToDebug("Datalink URL: " + DATALINK_URL);
datalink = new sp.datalink.DataLinkConnection(
DATALINK_URL,
(packet) => {
rtDisp.packetHandler(packet);
updateNumPackets();
},
errorFn,
);
}
if (datalink) {
datalink
.connect()
.then((serverId) => {
addToDebug("ServerId: " + serverId);
// here we use the match pattern corresponding to the version of
// ringserver (via DLPROTO) that we are connecting to
if (serverId.includes("DLPROTO:1.0")) {
document.querySelector("span#channel").textContent = matchPatternV3;
return datalink.match(matchPatternV3);
} else {
document.querySelector("span#channel").textContent = matchPatternV4;
return datalink.match(matchPatternV4);
}
})
.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();
Previous: Helicorder
Next: ...and more