@seismo/core
Version:
This is the package for the core library of Seismo, a JavaScript library for seismic data processing and visualization. It provides utilities for handling seismic data, including FDSN web services, waveform processing, and event handling. The library is d
542 lines (541 loc) • 17.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FAKE_EMPTY_SVG = exports.FAKE_EMPTY_TEXT_HEADERS = exports.FAKE_EMPTY_TEXT_MODEL = exports.TraveltimeQuery = exports.SVG_FORMAT = exports.JSON_FORMAT = exports.TEXT_FORMAT = exports.IRIS_HOST = void 0;
exports.isValidTraveltimeJsonType = isValidTraveltimeJsonType;
exports.isValidTraveltimeArrivalType = isValidTraveltimeArrivalType;
exports.convertTravelTimeLineToObject = convertTravelTimeLineToObject;
exports.createOriginArrival = createOriginArrival;
exports.createEmptyTraveltimeJson = createEmptyTraveltimeJson;
/*
* Philip Crotwell
* University of South Carolina, 2019
* https://www.seis.sc.edu
*/
const common_1 = require("../fdsn/common");
const utils_1 = require("../utils");
exports.IRIS_HOST = "service.iris.edu";
exports.TEXT_FORMAT = "text";
exports.JSON_FORMAT = "json";
exports.SVG_FORMAT = "svg";
/**
* Verifies that JSON matches the types we expect, for typescript.
*
* @param v JSON object, usually from the traveltime web service
* @returns true if matches expected structure
*/
function isValidTraveltimeJsonType(v) {
if (!v || typeof v !== "object") {
return false;
}
const object = v;
// IRIS web service uses sourceDepth, TauP uses sourcedepth
if (!(typeof object.model === "string" &&
(typeof object.sourcedepth === "number" ||
typeof object.sourceDepth === "number") &&
(typeof object.receiverdepth === "number" ||
typeof object.receiverDepth === "number"))) {
return false;
}
// fix to lower d
if (typeof object.sourceDepth === "number") {
// fix IRIS typo D
object.sourcedepth = object.sourceDepth;
object.sourceDepth = undefined;
}
if (typeof object.receiverDepth === "number") {
// fix IRIS typo D
object.receiverdepth = object.receiverDepth;
object.receiverDepth = undefined;
}
if (!Array.isArray(object.phases)) {
return false;
}
if (!Array.isArray(object.arrivals)) {
return false;
}
return true;
}
function isValidTraveltimeArrivalType(v) {
if (!v || typeof v !== "object") {
return false;
}
const object = v;
return (typeof object.distdeg === "number" &&
typeof object.name === "string" &&
typeof object.time === "number" &&
typeof object.rayparam === "number" &&
typeof object.takeoff === "number" &&
typeof object.incident === "number" &&
typeof object.puristdist === "number" &&
typeof object.puristname === "string");
}
/**
* converts a text line from the text format into an
* TraveltimeArrivalType object like what is returned by the json format.
*
* @param ttimeline travel time output line for an arrival
* @returns parsed travel time arrival
*/
function convertTravelTimeLineToObject(ttimeline) {
const items = ttimeline.trim().split(/\s+/);
return {
distdeg: parseFloat(items[0]),
phase: items[2],
time: parseFloat(items[3]),
rayparam: parseFloat(items[4]),
takeoff: parseFloat(items[5]),
incident: parseFloat(items[6]),
puristdist: parseFloat(items[7]),
puristname: items[9],
};
}
/**
* Creates a fake arrival for the origin time, useful to display a flag
* at origin time similar to the P and S arrival.
* @param distdeg earthquake to station distance, in degrees
* @returns an arrival for the origin
*/
function createOriginArrival(distdeg) {
return {
distdeg: distdeg,
phase: "origin",
time: 0,
rayparam: 0,
takeoff: 0,
incident: 0,
puristdist: distdeg,
puristname: "origin",
};
}
/**
* Query to the IRIS traveltime webservice, based on the TauP Toolkit. See
* https://service.iris.edu/irisws/traveltime/1/ and
* https://www.seis.sc.edu/TauP/
*
* @param host optional host to connect to, defaults to IRIS
*/
class TraveltimeQuery extends common_1.FDSNCommon {
/** @private */
_evdepth;
/** @private */
_distdeg;
/** @private */
_model;
/** @private */
_phases;
/** @private */
_stalat;
/** @private */
_stalon;
/** @private */
_receiverdepth;
/** @private */
_evlat;
/** @private */
_evlon;
/** @private */
_format;
/** @private */
_noheader;
constructor(host) {
if (!(0, utils_1.isNonEmptyStringArg)(host)) {
host = exports.IRIS_HOST;
}
super(host);
this._evdepth = 0;
this._format = exports.JSON_FORMAT;
this._noheader = false; // only for text format
}
protocol(value) {
(0, utils_1.doStringGetterSetter)(this, "protocol", value);
return this;
}
getProtocol() {
return this._protocol;
}
host(value) {
(0, utils_1.doStringGetterSetter)(this, "host", value);
return this;
}
getHost() {
return this._host;
}
/**
* Gets/Sets the remote port to connect to.
*
* @param value optional new value if setting
* @returns new value if getting, this if setting
*/
port(value) {
(0, utils_1.doIntGetterSetter)(this, "port", value);
return this;
}
getPort() {
return this._port;
}
/**
* Gets/Sets the nodata parameter, usually 404 or 204 (default), controlling
* the status code when no matching data is found by the service.
*
* @param value optional new value if setting
* @returns new value if getting, this if setting
*/
nodata(value) {
(0, utils_1.doIntGetterSetter)(this, "nodata", value);
return this;
}
getNodata() {
return this._nodata;
}
specVersion(value) {
(0, utils_1.doStringGetterSetter)(this, "specVersion", value);
return this;
}
getSpecVersion() {
return this._specVersion;
}
evdepth(value) {
(0, utils_1.doFloatGetterSetter)(this, "evdepth", value);
return this;
}
evdepthInMeter(value) {
(0, utils_1.doFloatGetterSetter)(this, "evdepth", (0, utils_1.isDef)(value) ? value / 1000 : value);
return this;
}
getEvdepth() {
return this._evdepth;
}
distdeg(value) {
if (typeof value === "number") {
this._distdeg = [value];
}
else {
this._distdeg = value;
}
return this;
}
getDistdeg() {
return this._distdeg;
}
model(value) {
(0, utils_1.doStringGetterSetter)(this, "model", value);
return this;
}
getModel() {
return this._model;
}
phases(value) {
(0, utils_1.doStringGetterSetter)(this, "phases", value);
return this;
}
getPhases() {
return this._phases;
}
stalat(value) {
(0, utils_1.doFloatGetterSetter)(this, "stalat", value);
return this;
}
getStalat() {
return this._stalat;
}
stalon(value) {
(0, utils_1.doFloatGetterSetter)(this, "stalon", value);
return this;
}
getStalon() {
return this._stalon;
}
latLonFromStation(station) {
this.stalat(station.latitude);
this.stalon(station.longitude);
return this;
}
receiverdepth(value) {
(0, utils_1.doFloatGetterSetter)(this, "receiverdepth", value);
return this;
}
receiverdepthInMeter(value) {
(0, utils_1.doFloatGetterSetter)(this, "receiverdepth", (0, utils_1.isDef)(value) ? value / 1000 : value);
return this;
}
receiverdepthFromChannel(channel) {
return this.receiverdepth(channel.depth / 1000);
}
getReceiverdepth() {
return this._receiverdepth;
}
evlat(value) {
(0, utils_1.doFloatGetterSetter)(this, "evlat", value);
return this;
}
getEvlat() {
return this._evlat;
}
evlon(value) {
(0, utils_1.doFloatGetterSetter)(this, "evlon", value);
return this;
}
getEvlon() {
return this._evlon;
}
latLonFromQuake(quake) {
this.evlat(quake.latitude);
this.evlon(quake.longitude);
this.evdepthInMeter(quake.depth);
return this;
}
format(value) {
(0, utils_1.doStringGetterSetter)(this, "format", value);
return this;
}
getFormat() {
return this._format;
}
noheader(value) {
(0, utils_1.doBoolGetterSetter)(this, "noheader", value);
return this;
}
getNoheader() {
return this._noheader;
}
/**
* Get/Set the timeout in seconds for the request. Default is 30.
*
* @param value optional new value if setting
* @returns new value if getting, this if setting
*/
timeout(value) {
(0, utils_1.doFloatGetterSetter)(this, "timeoutSec", value);
return this;
}
getTimeout() {
return this._timeoutSec;
}
queryText() {
this.format(exports.TEXT_FORMAT);
const url = this.formURL();
const fetchInit = (0, utils_1.defaultFetchInitObj)(utils_1.TEXT_MIME);
return (0, utils_1.doFetchWithTimeout)(url, fetchInit, this._timeoutSec * 1000).then((response) => {
if (response.status === 204 ||
((0, utils_1.isDef)(this._nodata) && response.status === this._nodata)) {
// no data, create empty
return (exports.FAKE_EMPTY_TEXT_MODEL +
((0, utils_1.isDef)(this._model) ? this.getModel() : "") +
exports.FAKE_EMPTY_TEXT_HEADERS);
}
else {
return response.text();
}
});
}
queryJson() {
this.format(exports.JSON_FORMAT);
const url = this.formURL();
const fetchInit = (0, utils_1.defaultFetchInitObj)(utils_1.JSON_MIME);
return (0, utils_1.doFetchWithTimeout)(url, fetchInit, this._timeoutSec * 1000)
.then((response) => {
if (response.status === 204 ||
((0, utils_1.isDef)(this._nodata) && response.status === this._nodata)) {
// no data, create empty
return createEmptyTraveltimeJson(this);
}
else {
return response.json();
}
})
.then((jsonValue) => {
if (isValidTraveltimeJsonType(jsonValue)) {
return jsonValue;
}
else {
throw new TypeError(`Oops, we did not get root traveltime JSON!`);
}
});
}
querySvg() {
this.format(exports.SVG_FORMAT);
const url = this.formURL();
const fetchInit = (0, utils_1.defaultFetchInitObj)(utils_1.SVG_MIME);
return (0, utils_1.doFetchWithTimeout)(url, fetchInit, this._timeoutSec * 1000)
.then((response) => {
if (response.status === 200) {
return response.text();
}
else if (response.status === 204 ||
((0, utils_1.isDef)(this._nodata) && response.status === this._nodata)) {
// 204 is nodata, so successful but empty
return exports.FAKE_EMPTY_SVG;
}
else {
throw new Error(`Status not successful: ${response.status}`);
}
})
.then(function (rawXmlText) {
return new DOMParser().parseFromString(rawXmlText, utils_1.SVG_MIME);
})
.then((xml) => {
const elArray = xml.getElementsByTagName("svg");
if (elArray.length > 0) {
return elArray[0];
}
else {
throw new Error("Can't find svg element in response");
}
});
}
queryWadl() {
return fetch(this.formWadlURL()).then((response) => {
if (response.ok) {
return response
.text()
.then((textResponse) => new window.DOMParser().parseFromString(textResponse, "text/xml"));
}
else {
throw new Error(`Fetching over network was not ok: ${response.status} ${response.statusText}`);
}
});
}
query() {
if (this._format === exports.JSON_FORMAT) {
return this.queryJson();
}
else if (this._format === exports.SVG_FORMAT) {
return this.querySvg();
}
else if (this._format === exports.TEXT_FORMAT) {
return this.queryText();
}
else {
throw new Error("Unknown format: " + this._format);
}
}
formBaseURL() {
let colon = ":";
if (this._protocol.endsWith(colon)) {
colon = "";
}
const url = this._protocol +
colon +
"//" +
this._host +
(this._port === 80 ? "" : ":" + this._port) +
"/irisws/traveltime/" +
this._specVersion +
"/";
return url;
}
formURL() {
let url = this.formBaseURL() + "query?";
if ((0, utils_1.isDef)(this._noheader) && this._noheader) {
url = url + "noheader=true&";
}
if ((0, utils_1.isDef)(this._evdepth) && this._evdepth !== 0) {
url = url + (0, utils_1.makeParam)("evdepth", this._evdepth);
}
if ((0, utils_1.isDef)(this._receiverdepth) && this._receiverdepth !== 0) {
url = url + (0, utils_1.makeParam)("receiverdepth", this._receiverdepth);
}
if ((0, utils_1.isDef)(this._stalat) && (0, utils_1.isDef)(this._stalon)) {
url =
url +
(0, utils_1.makeParam)("staloc", "[" + (0, utils_1.stringify)(this._stalat) + "," + (0, utils_1.stringify)(this._stalon) + "]");
}
if ((0, utils_1.isDef)(this._evlat) && (0, utils_1.isDef)(this._evlon)) {
url =
url +
(0, utils_1.makeParam)("evloc", "[" + (0, utils_1.stringify)(this._evlat) + "," + (0, utils_1.stringify)(this._evlon) + "]");
}
if ((0, utils_1.isDef)(this._distdeg)) {
url = url + (0, utils_1.makeParam)("distdeg", this._distdeg.join(","));
}
if ((0, utils_1.isDef)(this._model)) {
url = url + (0, utils_1.makeParam)("model", this._model);
}
if ((0, utils_1.isDef)(this._phases)) {
url = url + (0, utils_1.makeParam)("phases", this._phases);
}
if ((0, utils_1.isDef)(this._format)) {
url = url + (0, utils_1.makeParam)("format", this._format);
}
if ((0, utils_1.isDef)(this._nodata)) {
url = url + (0, utils_1.makeParam)("nodata", this._nodata);
}
if (url.endsWith("&") || url.endsWith("?")) {
url = url.substr(0, url.length - 1); // zap last & or ?
}
return url;
}
queryTauPVersion() {
return fetch(this.formTauPVersionURL()).then((response) => {
if (response.ok) {
return response.text();
}
else {
throw new Error("Fetching over network was not ok: " +
response.status +
" " +
response.statusText);
}
});
}
formTauPVersionURL() {
return this.formBaseURL() + "taupversion";
}
formWadlURL() {
return this.formBaseURL() + "application.wadl";
}
}
exports.TraveltimeQuery = TraveltimeQuery;
exports.FAKE_EMPTY_TEXT_MODEL = `Model: `;
exports.FAKE_EMPTY_TEXT_HEADERS = `
Distance Depth Phase Travel Ray Param Takeoff Incident Purist Purist
(deg) (km) Name Time (s) p (s/deg) (deg) (deg) Distance Name
-----------------------------------------------------------------------------------
`;
function createEmptyTraveltimeJson(ttquery) {
const out = {
model: (0, utils_1.isDef)(ttquery._model) ? ttquery._model : "",
sourcedepth: (0, utils_1.isDef)(ttquery._evdepth) ? ttquery._evdepth : 0,
receiverdepth: (0, utils_1.isDef)(ttquery._receiverdepth) ? ttquery._receiverdepth : 0,
phases: (0, utils_1.isDef)(ttquery._phases) ? ttquery._phases.split(",") : [],
arrivals: [],
};
return out;
}
exports.FAKE_EMPTY_SVG = `
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 14016.2 14016.2">
<!--
This script will plot ray paths generated by TauP using SVG. -->
<defs>
<style type="text/css"><![CDATA[
circle {
vector-effect: non-scaling-stroke;
stroke: grey;
fill: none;
}
polyline {
vector-effect: non-scaling-stroke;
stroke: black;
fill: none;
}
]]></style>
</defs>
<g transform="translate(7008.1,7008.1)" >
<!-- draw surface and label distances.-->
<!-- tick marks every 30 degrees.-->
<polyline points=" 0.00 -6371.00, 0.00 -6689.55" />
<polyline points=" 3185.50 -5517.45, 3344.78 -5793.32" />
<polyline points=" 5517.45 -3185.50, 5793.32 -3344.77" />
<polyline points=" 6371.00 0.00, 6689.55 0.00" />
<polyline points=" 5517.45 3185.50, 5793.32 3344.77" />
<polyline points=" 3185.50 5517.45, 3344.78 5793.32" />
<polyline points=" 0.00 6371.00, 0.00 6689.55" />
<polyline points="-3185.50 5517.45, -3344.77 5793.32" />
<polyline points="-5517.45 3185.50, -5793.32 3344.77" />
<polyline points="-6371.00 0.00, -6689.55 0.00" />
<polyline points="-5517.45 -3185.50, -5793.32 -3344.78" />
<polyline points="-3185.50 -5517.45, -3344.78 -5793.32" />
<circle cx="0.0" cy="0.0" r="6371.0" />
</g>
</svg>
`;