UNPKG

@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
"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> `;