@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
1,364 lines (1,363 loc) • 46.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseUtil = exports.Author = exports.Comment = exports.DataAvailability = exports.Span = exports.Gain = exports.Decimation = exports.CoefficientsFilter = exports.FIR = exports.PolesZeros = exports.AbstractFilterType = exports.Stage = exports.Response = exports.Equipment = exports.InstrumentSensitivity = exports.Channel = exports.Station = exports.Network = exports.STATION_CLICK_EVENT = exports.CHANNEL_CLICK_EVENT = exports.FAKE_EMPTY_XML = exports.FAKE_START_DATE = exports.INVALID_NUMBER = exports.FIX_INVALID_STAXML = exports.COUNT_UNIT_NAME = exports.STAML_NS = void 0;
exports.createChannelClickEvent = createChannelClickEvent;
exports.createStationClickEvent = createStationClickEvent;
exports.parseStationXml = parseStationXml;
exports.convertToNetwork = convertToNetwork;
exports.convertToStation = convertToStation;
exports.convertToChannel = convertToChannel;
exports.convertToDataAvailability = convertToDataAvailability;
exports.convertToComment = convertToComment;
exports.convertToAuthor = convertToAuthor;
exports.convertToEquipment = convertToEquipment;
exports.convertToResponse = convertToResponse;
exports.convertToInstrumentSensitivity = convertToInstrumentSensitivity;
exports.convertToStage = convertToStage;
exports.convertToDecimation = convertToDecimation;
exports.convertToGain = convertToGain;
exports.createInterval = createInterval;
exports.extractComplex = extractComplex;
exports.allStations = allStations;
exports.allChannels = allChannels;
exports.findChannels = findChannels;
exports.uniqueSourceIds = uniqueSourceIds;
exports.uniqueStations = uniqueStations;
exports.uniqueNetworks = uniqueNetworks;
exports.fetchStationXml = fetchStationXml;
/*
* Philip Crotwell
* University of South Carolina, 2019
* https://www.seis.sc.edu
*/
const utils_1 = require("./utils");
const oregondsputil_1 = require("./oregondsputil");
const source_id_1 = require("./fdsn/source-id");
const luxon_1 = require("luxon");
/** xml namespace for stationxml */
exports.STAML_NS = "http://www.fdsn.org/xml/station/1";
exports.COUNT_UNIT_NAME = "count";
exports.FIX_INVALID_STAXML = true;
exports.INVALID_NUMBER = -99999;
exports.FAKE_START_DATE = luxon_1.DateTime.fromISO("1900-01-01T00:00:00Z");
/** a fake, completely empty stationxml document in case of no data. */
exports.FAKE_EMPTY_XML = '<?xml version="1.0" encoding="ISO-8859-1"?> <FDSNStationXML xmlns="http://www.fdsn.org/xml/station/1" schemaVersion="1.0" xsi:schemaLocation="http://www.fdsn.org/xml/station/1 http://www.fdsn.org/xml/station/fdsn-station-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:iris="http://www.fdsn.org/xml/station/1/iris"> </FDSNStationXML>';
exports.CHANNEL_CLICK_EVENT = "channelclick";
exports.STATION_CLICK_EVENT = "stationclick";
/**
* Utility function to create CustomEvent for clicking on a Channel, for example
* in a map or table.
*
* @param sta Channel clicked on
* @param mouseclick original mouse click Event
* @returns CustomEvent populated with channel field in detail.
*/
function createChannelClickEvent(sta, mouseclick) {
const detail = {
mouseevent: mouseclick,
channel: sta,
};
return new CustomEvent(exports.CHANNEL_CLICK_EVENT, { detail: detail });
}
/**
* Utility function to create CustomEvent for clicking on a Station, for example
* in a map or table.
*
* @param sta Station clicked on
* @param mouseclick original mouse click Event
* @returns CustomEvent populated with station field in detail.
*/
function createStationClickEvent(sta, mouseclick) {
const detail = {
mouseevent: mouseclick,
station: sta,
};
return new CustomEvent(exports.STATION_CLICK_EVENT, { detail: detail });
}
// StationXML classes
class Network {
networkCode;
_startDate;
_endDate;
restrictedStatus;
description;
totalNumberStations;
stations;
constructor(networkCode) {
this.networkCode = networkCode;
this._startDate = exports.FAKE_START_DATE;
this._endDate = null;
this.description = "";
this.restrictedStatus = "";
this.stations = [];
this.totalNumberStations = null;
}
get sourceId() {
return new source_id_1.NetworkSourceId(this.networkCode ? this.networkCode : "");
}
get startDate() {
return this._startDate;
}
set startDate(value) {
this._startDate = (0, utils_1.checkStringOrDate)(value);
}
get endDate() {
return this._endDate;
}
set endDate(value) {
if (!(0, utils_1.isDef)(value)) {
this._endDate = null;
}
else {
this._endDate = (0, utils_1.checkStringOrDate)(value);
}
}
get timeRange() {
return createInterval(this.startDate, this.endDate);
}
codes() {
return this.networkCode;
}
isActiveAt(d) {
if (!(0, utils_1.isDef)(d)) {
d = luxon_1.DateTime.utc();
}
return this.timeRange.contains(d);
}
isTempNet() {
const first = this.networkCode.charAt(0);
return (first === "X" ||
first === "Y" ||
first === "Z" ||
(first >= "0" && first <= "9"));
}
}
exports.Network = Network;
class Station {
network;
stationCode;
sourceID;
/** @private */
_startDate;
/** @private */
_endDate;
restrictedStatus;
name;
latitude;
longitude;
elevation;
waterLevel;
comments;
equipmentList;
dataAvailability;
identifierList;
description;
geology;
vault;
channels;
constructor(network, stationCode) {
this.network = network;
this.name = "";
this.description = "";
this.sourceID = null;
this.restrictedStatus = "";
this._startDate = exports.FAKE_START_DATE;
this._endDate = null;
this.stationCode = stationCode;
this.channels = [];
this.latitude = exports.INVALID_NUMBER;
this.longitude = exports.INVALID_NUMBER;
this.elevation = 0;
this.waterLevel = null;
this.comments = [];
this.equipmentList = [];
this.dataAvailability = null;
this.geology = "";
this.vault = "";
this.identifierList = [];
}
get sourceId() {
return new source_id_1.StationSourceId(this.networkCode ? this.networkCode : "", this.stationCode ? this.stationCode : "");
}
get startDate() {
return this._startDate;
}
set startDate(value) {
this._startDate = (0, utils_1.checkStringOrDate)(value);
}
get endDate() {
return this._endDate;
}
set endDate(value) {
if (!(0, utils_1.isDef)(value)) {
this._endDate = null;
}
else {
this._endDate = (0, utils_1.checkStringOrDate)(value);
}
}
get timeRange() {
return createInterval(this.startDate, this.endDate);
}
get networkCode() {
return this.network.networkCode;
}
isActiveAt(d) {
if (!(0, utils_1.isDef)(d)) {
d = luxon_1.DateTime.utc();
}
return this.timeRange.contains(d);
}
codes(sep = ".") {
return this.network.codes() + sep + this.stationCode;
}
}
exports.Station = Station;
class Channel {
station;
/** @private */
_locationCode;
channelCode;
/** @private */
_sourceId;
/** @private */
_startDate;
/** @private */
_endDate;
restrictedStatus;
latitude;
longitude;
elevation;
depth;
azimuth;
dip;
sampleRate;
waterLevel = null;
comments = [];
equipmentList = [];
dataAvailability = null;
identifierList = [];
description = "";
response;
sensor;
preamplifier;
datalogger;
constructor(station, channelCode, locationCode) {
this.station = station;
this._startDate = exports.FAKE_START_DATE;
this._endDate = null;
this.response = null;
this.sensor = null;
this.preamplifier = null;
this.datalogger = null;
this.restrictedStatus = "";
this.azimuth = exports.INVALID_NUMBER;
this.dip = exports.INVALID_NUMBER;
this.latitude = exports.INVALID_NUMBER;
this.longitude = exports.INVALID_NUMBER;
this.depth = 0;
this.elevation = 0;
this.sampleRate = 0;
if (channelCode.length !== 3) {
throw new Error(`Channel code must be 3 chars: ${channelCode}`);
}
this.channelCode = channelCode;
this._locationCode = locationCode;
if (!locationCode) {
// make sure "null" is encoded as empty string
this._locationCode = "";
}
if (!(this._locationCode.length === 2 || this._locationCode.length === 0)) {
throw new Error(`locationCode must be 2 chars, or empty: "${locationCode}"`);
}
}
get sourceId() {
if (this._sourceId) {
return this._sourceId;
}
return source_id_1.FDSNSourceId.fromNslc(this.networkCode, this.stationCode, this.locationCode, this.channelCode);
}
get nslcId() {
return new source_id_1.NslcId(this.networkCode ? this.networkCode : "", this.stationCode ? this.stationCode : "", this.locationCode && this.locationCode !== "--" ? this.locationCode : "", this.channelCode ? this.channelCode : "");
}
get startDate() {
return this._startDate;
}
set startDate(value) {
this._startDate = (0, utils_1.checkStringOrDate)(value);
}
get endDate() {
return this._endDate;
}
set endDate(value) {
if (!(0, utils_1.isDef)(value)) {
this._endDate = null;
}
else {
this._endDate = (0, utils_1.checkStringOrDate)(value);
}
}
get timeRange() {
return createInterval(this.startDate, this.endDate);
}
get locationCode() {
return this._locationCode;
}
set locationCode(value) {
this._locationCode = value;
if (!value) {
// make sure "null" is encoded as empty string
this._locationCode = "";
}
}
get stationCode() {
return this.station.stationCode;
}
get networkCode() {
return this.station.networkCode;
}
/**
* Checks if this channel has sensitivity defined, within the response.
*
* @returns true if instrumentSensitivity exits
*/
hasInstrumentSensitivity() {
return (0, utils_1.isDef)(this.response) && (0, utils_1.isDef)(this.response.instrumentSensitivity);
}
set instrumentSensitivity(value) {
if (!(0, utils_1.isDef)(this.response)) {
this.response = new Response(value);
}
else {
this.response.instrumentSensitivity = value;
}
}
get instrumentSensitivity() {
if ((0, utils_1.isDef)(this.response) && (0, utils_1.isDef)(this.response.instrumentSensitivity)) {
return this.response.instrumentSensitivity;
}
else {
throw new Error("no Response or InstrumentSensitivity defined");
}
}
/**
* return network, station, location and channels codes as one string.
*
* @returns net.sta.loc.chan
*/
get nslc() {
return this.codes();
}
/**
* return network, station, location and channels codes as one string.
*
* @param sep separator, defaults to dot '.'
* @returns net.sta.loc.chan
*/
codes(sep = ".") {
return (this.station.codes(sep) + sep + this.locationCode + sep + this.channelCode);
}
isActiveAt(d) {
if (!(0, utils_1.isDef)(d)) {
d = luxon_1.DateTime.utc();
}
return this.timeRange.contains(d);
}
}
exports.Channel = Channel;
class InstrumentSensitivity {
sensitivity;
frequency;
inputUnits;
outputUnits;
constructor(sensitivity, frequency, inputUnits, outputUnits) {
this.sensitivity = sensitivity;
this.frequency = frequency;
this.inputUnits = inputUnits;
this.outputUnits = outputUnits;
}
}
exports.InstrumentSensitivity = InstrumentSensitivity;
class Equipment {
resourceId;
type;
description;
manufacturer;
vendor;
model;
serialNumber;
installationDate;
removalDate;
calibrationDateList;
constructor() {
this.resourceId = "";
this.type = "";
this.description = "";
this.manufacturer = "";
this.vendor = "";
this.model = "";
this.serialNumber = "";
this.installationDate = null;
this.removalDate = null;
this.calibrationDateList = [];
}
}
exports.Equipment = Equipment;
class Response {
instrumentSensitivity;
stages;
constructor(instrumentSensitivity, stages) {
if (instrumentSensitivity) {
this.instrumentSensitivity = instrumentSensitivity;
}
else {
this.instrumentSensitivity = null;
}
if (stages) {
this.stages = stages;
}
else {
this.stages = [];
}
}
}
exports.Response = Response;
class Stage {
filter;
decimation;
gain;
constructor(filter, decimation, gain) {
this.filter = filter;
this.decimation = decimation;
this.gain = gain;
}
}
exports.Stage = Stage;
class AbstractFilterType {
inputUnits;
outputUnits;
name;
description;
constructor(inputUnits, outputUnits) {
this.inputUnits = inputUnits;
this.outputUnits = outputUnits;
this.description = "";
this.name = "";
}
}
exports.AbstractFilterType = AbstractFilterType;
class PolesZeros extends AbstractFilterType {
pzTransferFunctionType;
normalizationFactor;
normalizationFrequency;
zeros;
poles;
constructor(inputUnits, outputUnits) {
super(inputUnits, outputUnits);
this.pzTransferFunctionType = "";
this.normalizationFactor = 1;
this.normalizationFrequency = 0;
this.zeros = new Array(0);
this.poles = new Array(0);
}
}
exports.PolesZeros = PolesZeros;
class FIR extends AbstractFilterType {
symmetry;
numerator;
constructor(inputUnits, outputUnits) {
super(inputUnits, outputUnits);
this.symmetry = "none";
this.numerator = [1];
}
}
exports.FIR = FIR;
class CoefficientsFilter extends AbstractFilterType {
cfTransferFunction;
numerator;
denominator;
constructor(inputUnits, outputUnits) {
super(inputUnits, outputUnits);
this.cfTransferFunction = "";
this.numerator = [1];
this.denominator = new Array(0);
}
}
exports.CoefficientsFilter = CoefficientsFilter;
class Decimation {
inputSampleRate;
factor;
offset;
delay;
correction;
constructor(inputSampleRate, factor) {
this.inputSampleRate = inputSampleRate;
this.factor = factor;
}
}
exports.Decimation = Decimation;
class Gain {
value;
frequency;
constructor(value, frequency) {
this.value = value;
this.frequency = frequency;
}
}
exports.Gain = Gain;
class Span {
interval;
numberSegments = 0;
maximumTimeTear;
constructor(interval) {
this.maximumTimeTear = null;
this.interval = interval;
}
}
exports.Span = Span;
class DataAvailability {
extent;
spanList;
constructor() {
this.extent = null;
this.spanList = [];
}
}
exports.DataAvailability = DataAvailability;
class Comment {
id = null;
subject = null;
value;
beginEffectiveTime = null;
endEffectiveTime = null;
authorList = [];
constructor(value) {
this.value = value;
}
}
exports.Comment = Comment;
class Author {
name = null;
agency = null;
email = null;
phone = null;
}
exports.Author = Author;
/**
* Parses the FDSN StationXML returned from a query.
*
* @param rawXml parsed xml to extract objects from
* @returns an Array of Network objects.
*/
function parseStationXml(rawXml) {
const top = rawXml.documentElement;
if (!top) {
throw new Error("No documentElement in XML");
}
const netArray = Array.from(top.getElementsByTagNameNS(exports.STAML_NS, "Network"));
const out = [];
for (const n of netArray) {
out.push(convertToNetwork(n));
}
return out;
}
/**
* Parses a FDSNStationXML Network xml element into a Network object.
*
* @param xml the network xml Element
* @returns Network instance
*/
function convertToNetwork(xml) {
let netCode = "";
try {
netCode = _requireAttribute(xml, "code");
const out = new Network(netCode);
out.startDate = _requireAttribute(xml, "startDate");
const rs = _grabAttribute(xml, "restrictedStatus");
if ((0, utils_1.isNonEmptyStringArg)(rs)) {
out.restrictedStatus = rs;
}
const desc = _grabFirstElText(xml, "Description");
if ((0, utils_1.isNonEmptyStringArg)(desc)) {
out.description = desc;
}
if (_grabAttribute(xml, "endDate")) {
out.endDate = _grabAttribute(xml, "endDate");
}
const totSta = xml.getElementsByTagNameNS(exports.STAML_NS, "TotalNumberStations");
if (totSta && totSta.length > 0) {
out.totalNumberStations = _grabFirstElInt(xml, "TotalNumberStations");
}
const staArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Station"));
const stations = [];
for (const s of staArray) {
stations.push(convertToStation(out, s));
}
out.stations = stations;
return out;
}
catch (err) {
throw (0, utils_1.reErrorWithMessage)(err, netCode);
}
}
/**
* Parses a FDSNStationXML Station xml element into a Station object.
*
* @param network the containing network
* @param xml the station xml Element
* @returns Station instance
*/
function convertToStation(network, xml) {
let staCode = ""; // so can use in rethrow exception
try {
staCode = _requireAttribute(xml, "code");
if (!(0, utils_1.isNonEmptyStringArg)(staCode)) {
throw new Error("station code missing in station!");
}
const out = new Station(network, staCode);
out.startDate = _requireAttribute(xml, "startDate");
const rs = _grabAttribute(xml, "restrictedStatus");
if ((0, utils_1.isNonEmptyStringArg)(rs)) {
out.restrictedStatus = rs;
}
const lat = _grabFirstElFloat(xml, "Latitude");
if ((0, utils_1.isNumArg)(lat)) {
out.latitude = lat;
}
const lon = _grabFirstElFloat(xml, "Longitude");
if ((0, utils_1.isNumArg)(lon)) {
out.longitude = lon;
}
const elev = _grabFirstElFloat(xml, "Elevation");
if ((0, utils_1.isNumArg)(elev)) {
out.elevation = elev;
}
const waterLevel = _grabFirstElFloat(xml, "WaterLevel");
if ((0, utils_1.isNumArg)(waterLevel)) {
out.waterLevel = waterLevel;
}
const vault = _grabFirstElText(xml, "Vault");
if ((0, utils_1.isStringArg)(vault)) {
out.vault = vault;
}
const geology = _grabFirstElText(xml, "Geology");
if ((0, utils_1.isStringArg)(geology)) {
out.geology = geology;
}
const name = _grabFirstElText(_grabFirstEl(xml, "Site"), "Name");
if ((0, utils_1.isStringArg)(name)) {
out.name = name;
}
const endDate = _grabAttribute(xml, "endDate");
if ((0, utils_1.isDef)(endDate)) {
out.endDate = _grabAttribute(xml, "endDate");
}
const description = _grabFirstElText(xml, "Description");
if ((0, utils_1.isDef)(description)) {
out.description = description;
}
const identifierList = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Identifier"));
out.identifierList = identifierList.map((el) => {
return el.textContent ? el.textContent : "";
});
const dataAvailEl = _grabFirstEl(xml, "DataAvailability");
if ((0, utils_1.isDef)(dataAvailEl)) {
out.dataAvailability = convertToDataAvailability(dataAvailEl);
}
const commentArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Comment"));
const comments = [];
for (const c of commentArray) {
comments.push(convertToComment(c));
}
out.comments = comments;
const equipmentArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Equipment"));
const equipmentList = [];
for (const c of equipmentArray) {
equipmentList.push(convertToEquipment(c));
}
out.equipmentList = equipmentList;
const chanArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Channel"));
const channels = [];
for (const c of chanArray) {
channels.push(convertToChannel(out, c));
}
out.channels = channels;
return out;
}
catch (err) {
throw (0, utils_1.reErrorWithMessage)(err, staCode);
}
}
/**
* Parses a FDSNStationXML Channel xml element into a Channel object.
*
* @param station the containing staton
* @param xml the channel xml Element
* @returns Channel instance
*/
function convertToChannel(station, xml) {
let locCode = ""; // so can use in rethrow exception
const chanCode = "";
try {
locCode = _grabAttribute(xml, "locationCode");
if (!(0, utils_1.isNonEmptyStringArg)(locCode)) {
locCode = "";
}
const chanCode = _requireAttribute(xml, "code");
const out = new Channel(station, chanCode, locCode);
out.startDate = (0, utils_1.checkStringOrDate)(_requireAttribute(xml, "startDate"));
const rs = _grabAttribute(xml, "restrictedStatus");
if ((0, utils_1.isNonEmptyStringArg)(rs)) {
out.restrictedStatus = rs;
}
const lat = _grabFirstElFloat(xml, "Latitude");
if ((0, utils_1.isNumArg)(lat)) {
out.latitude = lat;
}
const lon = _grabFirstElFloat(xml, "Longitude");
if ((0, utils_1.isNumArg)(lon)) {
out.longitude = lon;
}
const elev = _grabFirstElFloat(xml, "Elevation");
if ((0, utils_1.isNumArg)(elev)) {
out.elevation = elev;
}
const depth = _grabFirstElFloat(xml, "Depth");
if ((0, utils_1.isNumArg)(depth)) {
out.depth = depth;
}
const waterLevel = _grabFirstElFloat(xml, "WaterLevel");
if ((0, utils_1.isNumArg)(waterLevel)) {
out.waterLevel = waterLevel;
}
const azimuth = _grabFirstElFloat(xml, "Azimuth");
if ((0, utils_1.isNumArg)(azimuth)) {
out.azimuth = azimuth;
}
const dip = _grabFirstElFloat(xml, "Dip");
if ((0, utils_1.isNumArg)(dip)) {
out.dip = dip;
}
const desc = _grabFirstElText(xml, "Description");
if (desc) {
out.description = desc;
}
const sampleRate = _grabFirstElFloat(xml, "SampleRate");
if ((0, utils_1.isNumArg)(sampleRate)) {
out.sampleRate = sampleRate;
}
if (_grabAttribute(xml, "endDate")) {
out.endDate = _grabAttribute(xml, "endDate");
}
const sensor = xml.getElementsByTagNameNS(exports.STAML_NS, "Sensor");
if (sensor && sensor.length > 0) {
const sensorTmp = sensor.item(0);
if ((0, utils_1.isDef)(sensorTmp)) {
out.sensor = convertToEquipment(sensorTmp);
}
}
const preamp = xml.getElementsByTagNameNS(exports.STAML_NS, "PreAmplifier");
if (preamp && preamp.length > 0) {
const preampTmp = sensor.item(0);
if ((0, utils_1.isDef)(preampTmp)) {
out.preamplifier = convertToEquipment(preampTmp);
}
}
const datalogger = xml.getElementsByTagNameNS(exports.STAML_NS, "DataLogger");
if (datalogger && datalogger.length > 0) {
const dataloggerTmp = sensor.item(0);
if ((0, utils_1.isDef)(dataloggerTmp)) {
out.datalogger = convertToEquipment(dataloggerTmp);
}
}
const description = _grabFirstElText(xml, "Description");
if ((0, utils_1.isDef)(description)) {
out.description = description;
}
const identifierList = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Identifier"));
out.identifierList = identifierList.map((el) => {
return el.textContent ? el.textContent : "";
});
const dataAvailEl = _grabFirstEl(xml, "DataAvailability");
if ((0, utils_1.isDef)(dataAvailEl)) {
out.dataAvailability = convertToDataAvailability(dataAvailEl);
}
const commentArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Comment"));
const comments = [];
for (const c of commentArray) {
comments.push(convertToComment(c));
}
out.comments = comments;
const equipmentArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Equipment"));
const equipmentList = [];
for (const c of equipmentArray) {
equipmentList.push(convertToEquipment(c));
}
out.equipmentList = equipmentList;
const responseXml = xml.getElementsByTagNameNS(exports.STAML_NS, "Response");
if (responseXml && responseXml.length > 0) {
const r = responseXml.item(0);
if (r) {
out.response = convertToResponse(r);
}
}
return out;
}
catch (err) {
throw (0, utils_1.reErrorWithMessage)(err, `${locCode}.${chanCode}`);
}
}
function convertToDataAvailability(xml) {
const out = new DataAvailability();
const extent = _grabFirstEl(xml, "Extent");
if (extent && "start" in extent && "end" in extent) {
const s = _grabAttribute(extent, "start");
const e = _grabAttribute(extent, "end");
if (s && e) {
out.extent = luxon_1.Interval.fromDateTimes(luxon_1.DateTime.fromISO(s), luxon_1.DateTime.fromISO(e));
}
}
const spanArray = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Span"));
const spanList = [];
for (const c of spanArray) {
const s = _grabAttribute(c, "start");
const e = _grabAttribute(c, "end");
if (s && e) {
const span = new Span(luxon_1.Interval.fromDateTimes(luxon_1.DateTime.fromISO(s), luxon_1.DateTime.fromISO(e)));
const numSeg = _grabAttribute(c, "numberSegments");
if (numSeg) {
span.numberSegments = parseInt(numSeg);
}
const maxTear = _grabAttribute(c, "maximumTimeTear");
if (maxTear) {
span.maximumTimeTear = parseFloat(maxTear);
}
spanList.push(span);
}
}
out.spanList = spanList;
return out;
}
function convertToComment(xml) {
let val = _grabFirstElText(xml, "Value");
if (!val) {
val = "";
}
const out = new Comment(val);
const id = _grabAttribute(xml, "id");
if (id) {
out.id = id;
}
const subject = _grabAttribute(xml, "subject");
if (subject) {
out.subject = subject;
}
const b = _grabFirstElText(xml, "BeginEffectiveTime");
if (b) {
out.beginEffectiveTime = luxon_1.DateTime.fromISO(b);
}
const e = _grabFirstElText(xml, "EndEffectiveTime");
if (e) {
out.endEffectiveTime = luxon_1.DateTime.fromISO(e);
}
const authList = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "Author"));
out.authorList = authList.map((aEl) => convertToAuthor(aEl));
return out;
}
function convertToAuthor(xml) {
const out = new Author();
const name = _grabFirstElText(xml, "Name");
if (name) {
out.name = name;
}
const agency = _grabFirstElText(xml, "Agency");
if (agency) {
out.agency = agency;
}
const phEl = _grabFirstEl(xml, "Phone");
if (phEl) {
out.phone = `${_grabFirstElText(phEl, "CountryCode")}-${_grabFirstElText(phEl, "AreaCode")}-${_grabFirstElText(phEl, "PhoneNumber")}`;
}
return out;
}
function convertToEquipment(xml) {
const out = new Equipment();
let val;
val = _grabFirstElText(xml, "Type");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.type = val;
}
val = _grabFirstElText(xml, "Description");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.description = val;
}
val = _grabFirstElText(xml, "Manufacturer");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.manufacturer = val;
}
val = _grabFirstElText(xml, "Vendor");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.vendor = val;
}
val = _grabFirstElText(xml, "Model");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.model = val;
}
val = _grabFirstElText(xml, "SerialNumber");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.serialNumber = val;
}
val = _grabFirstElText(xml, "InstallationDate");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.installationDate = (0, utils_1.checkStringOrDate)(val);
}
val = _grabFirstElText(xml, "RemovalDate");
if ((0, utils_1.isNonEmptyStringArg)(val)) {
out.removalDate = (0, utils_1.checkStringOrDate)(val);
}
const calibXml = Array.from(xml.getElementsByTagNameNS(exports.STAML_NS, "CalibrationDate"));
out.calibrationDateList = [];
for (const cal of calibXml) {
if ((0, utils_1.isDef)(cal.textContent)) {
const d = (0, utils_1.checkStringOrDate)(cal.textContent);
if ((0, utils_1.isDef)(d)) {
out.calibrationDateList.push(d);
}
}
}
return out;
}
/**
* Parses a FDSNStationXML Response xml element into a Response object.
*
* @param responseXml the response xml Element
* @returns Response instance
*/
function convertToResponse(responseXml) {
let out = new Response();
const inst = responseXml.getElementsByTagNameNS(exports.STAML_NS, "InstrumentSensitivity");
if (inst && inst.item(0)) {
const i = inst.item(0);
if (i) {
out = new Response(convertToInstrumentSensitivity(i));
}
}
if (!(0, utils_1.isDef)(out)) {
// DMC returns empty response element when they know nothing (instead
// of just leaving it out). Return empty object in this case
out = new Response();
}
const xmlStages = responseXml.getElementsByTagNameNS(exports.STAML_NS, "Stage");
if (xmlStages && xmlStages.length > 0) {
const jsStages = Array.from(xmlStages).map(function (stageXml) {
return convertToStage(stageXml);
});
out.stages = jsStages;
}
return out;
}
/**
* Parses a FDSNStationXML InstrumentSensitivity xml element into a InstrumentSensitivity object.
*
* @param xml the InstrumentSensitivity xml Element
* @returns InstrumentSensitivity instance
*/
function convertToInstrumentSensitivity(xml) {
const sensitivity = _grabFirstElFloat(xml, "Value");
const frequency = _grabFirstElFloat(xml, "Frequency");
const inputUnits = _grabFirstElText(_grabFirstEl(xml, "InputUnits"), "Name");
let outputUnits = _grabFirstElText(_grabFirstEl(xml, "OutputUnits"), "Name");
if (exports.FIX_INVALID_STAXML && !(0, utils_1.isDef)(outputUnits)) {
// assume last output unit is count?
outputUnits = exports.COUNT_UNIT_NAME;
}
if (!((0, utils_1.isDef)(sensitivity) &&
(0, utils_1.isDef)(frequency) &&
(0, utils_1.isDef)(inputUnits) &&
(0, utils_1.isDef)(outputUnits))) {
throw new Error(`Not all elements of Sensitivity exist: ${sensitivity} ${frequency} ${inputUnits} ${outputUnits}`);
}
return new InstrumentSensitivity(sensitivity, frequency, inputUnits, outputUnits);
}
/**
* Parses a FDSNStationXML Stage xml element into a Stage object.
*
* @param stageXml the Stage xml Element
* @returns Stage instance
*/
function convertToStage(stageXml) {
const subEl = stageXml.firstElementChild;
let filter = null;
if (!subEl) {
throw new Error("Stage element has no child elements");
}
else if (stageXml.childElementCount === 1 &&
subEl.localName === "StageGain") {
// degenerate case of a gain only stage
// fix the lack of units after all stages are converted.
}
else {
// shoudl be a filter of some kind, check for units
const inputUnits = _grabFirstElText(_grabFirstEl(stageXml, "InputUnits"), "Name");
const outputUnits = _grabFirstElText(_grabFirstEl(stageXml, "OutputUnits"), "Name");
if (!(0, utils_1.isNonEmptyStringArg)(inputUnits)) {
throw new Error("Stage inputUnits required");
}
if (!(0, utils_1.isNonEmptyStringArg)(outputUnits)) {
throw new Error("Stage outputUnits required");
}
// here we assume there must be a filter, and so must have units
if (subEl.localName === "PolesZeros") {
const pzFilter = new PolesZeros(inputUnits, outputUnits);
const pzt = _grabFirstElText(stageXml, "PzTransferFunctionType");
if ((0, utils_1.isNonEmptyStringArg)(pzt)) {
pzFilter.pzTransferFunctionType = pzt;
}
const nfa = _grabFirstElFloat(stageXml, "NormalizationFactor");
if ((0, utils_1.isNumArg)(nfa)) {
pzFilter.normalizationFactor = nfa;
}
const nfr = _grabFirstElFloat(stageXml, "NormalizationFrequency");
if ((0, utils_1.isNumArg)(nfr)) {
pzFilter.normalizationFrequency = nfr;
}
const zeros = Array.from(stageXml.getElementsByTagNameNS(exports.STAML_NS, "Zero")).map(function (zeroEl) {
return extractComplex(zeroEl);
});
const poles = Array.from(stageXml.getElementsByTagNameNS(exports.STAML_NS, "Pole")).map(function (poleEl) {
return extractComplex(poleEl);
});
pzFilter.zeros = zeros;
pzFilter.poles = poles;
filter = pzFilter;
}
else if (subEl.localName === "Coefficients") {
const coeffXml = subEl;
const cFilter = new CoefficientsFilter(inputUnits, outputUnits);
const cft = _grabFirstElText(coeffXml, "CfTransferFunctionType");
if ((0, utils_1.isNonEmptyStringArg)(cft)) {
cFilter.cfTransferFunction = cft;
}
cFilter.numerator = Array.from(coeffXml.getElementsByTagNameNS(exports.STAML_NS, "Numerator"))
.map(function (numerEl) {
return (0, utils_1.isNonEmptyStringArg)(numerEl.textContent)
? parseFloat(numerEl.textContent)
: null;
})
.filter(utils_1.isDef);
cFilter.denominator = Array.from(coeffXml.getElementsByTagNameNS(exports.STAML_NS, "Denominator"))
.map(function (denomEl) {
return (0, utils_1.isNonEmptyStringArg)(denomEl.textContent)
? parseFloat(denomEl.textContent)
: null;
})
.filter(utils_1.isDef);
filter = cFilter;
}
else if (subEl.localName === "ResponseList") {
throw new Error("ResponseList not supported: ");
}
else if (subEl.localName === "FIR") {
const firXml = subEl;
const firFilter = new FIR(inputUnits, outputUnits);
const s = _grabFirstElText(firXml, "Symmetry");
if ((0, utils_1.isNonEmptyStringArg)(s)) {
firFilter.symmetry = s;
}
firFilter.numerator = Array.from(firXml.getElementsByTagNameNS(exports.STAML_NS, "NumeratorCoefficient"))
.map(function (numerEl) {
return (0, utils_1.isNonEmptyStringArg)(numerEl.textContent)
? parseFloat(numerEl.textContent)
: null;
})
.filter(utils_1.isDef);
filter = firFilter;
}
else if (subEl.localName === "Polynomial") {
throw new Error("Polynomial not supported: ");
}
else if (subEl.localName === "StageGain") {
// gain only stage, pick it up below
}
else {
throw new Error("Unknown Stage type: " + subEl.localName);
}
if (filter) {
// add description and name if it was there
const description = _grabFirstElText(subEl, "Description");
if ((0, utils_1.isNonEmptyStringArg)(description)) {
filter.description = description;
}
if (subEl.hasAttribute("name")) {
const n = _grabAttribute(subEl, "name");
if ((0, utils_1.isNonEmptyStringArg)(n)) {
filter.name = n;
}
}
}
}
const decimationXml = _grabFirstEl(stageXml, "Decimation");
let decimation = null;
if (decimationXml) {
decimation = convertToDecimation(decimationXml);
}
const gainXml = _grabFirstEl(stageXml, "StageGain");
let gain = null;
if (gainXml) {
gain = convertToGain(gainXml);
}
else {
throw new Error("Did not find Gain in stage number " +
(0, utils_1.stringify)(_grabAttribute(stageXml, "number")));
}
const out = new Stage(filter, decimation, gain);
return out;
}
/**
* Parses a FDSNStationXML Decimation xml element into a Decimation object.
*
* @param decXml the Decimation xml Element
* @returns Decimation instance
*/
function convertToDecimation(decXml) {
let out;
const insr = _grabFirstElFloat(decXml, "InputSampleRate");
const fac = _grabFirstElInt(decXml, "Factor");
if ((0, utils_1.isNumArg)(insr) && (0, utils_1.isNumArg)(fac)) {
out = new Decimation(insr, fac);
}
else {
throw new Error(`Decimation without InputSampleRate and Factor: ${insr} ${fac}`);
}
out.offset = _grabFirstElInt(decXml, "Offset");
out.delay = _grabFirstElFloat(decXml, "Delay");
out.correction = _grabFirstElFloat(decXml, "Correction");
return out;
}
/**
* Parses a FDSNStationXML Gain xml element into a Gain object.
*
* @param gainXml the Gain xml Element
* @returns Gain instance
*/
function convertToGain(gainXml) {
let out;
const v = _grabFirstElFloat(gainXml, "Value");
const f = _grabFirstElFloat(gainXml, "Frequency");
if ((0, utils_1.isNumArg)(v) && (0, utils_1.isNumArg)(f)) {
out = new Gain(v, f);
}
else {
throw new Error(`Gain does not have value and frequency: ${v} ${f}`);
}
return out;
}
function createInterval(start, end) {
if (end) {
return luxon_1.Interval.fromDateTimes(start, end);
}
else {
return luxon_1.Interval.fromDateTimes(start, utils_1.WAY_FUTURE);
}
}
/**
* Extracts a complex number from an stationxml element.
*
* @param el xml element
* @returns Complex instance
*/
function extractComplex(el) {
const re = _grabFirstElFloat(el, "Real");
const im = _grabFirstElFloat(el, "Imaginary");
if ((0, utils_1.isNumArg)(re) && (0, utils_1.isNumArg)(im)) {
return new oregondsputil_1.Complex(re, im);
}
else {
throw new Error(`Both Real and Imaginary required: ${re} ${im}`);
}
}
/**
* Generator function to access all stations within all networks in the array.
*
* @param networks array of Networks
* @yields generator yeiding stations
*/
function* allStations(networks) {
for (const n of networks) {
for (const s of n.stations) {
yield s;
}
}
}
/**
* Generator function to access all channels within all stations
* within all networks in the array.
*
* @param networks array of Networks
* @yields generator yeiding channels
*/
function* allChannels(networks) {
for (const s of allStations(networks)) {
for (const c of s.channels) {
yield c;
}
}
}
/**
* Extract all channels from all stations from all networks in the input array.
* Regular expressions may be used instaed of exact code matchs.
*
* @param networks Array of networks.
* @param netCode network code to match
* @param staCode station code to match
* @param locCode location code to match
* @param chanCode channel code to match
* @yields Array of channels.
*/
function* findChannels(networks, netCode, staCode, locCode, chanCode) {
const netRE = new RegExp(`^${netCode}$`);
const staRE = new RegExp(`^${staCode}$`);
const locRE = new RegExp(`^${locCode}$`);
const chanRE = new RegExp(`^${chanCode}$`);
for (const n of networks.filter((n) => netRE.test(n.networkCode))) {
for (const s of n.stations.filter((s) => staRE.test(s.stationCode))) {
for (const c of s.channels.filter((c) => locRE.test(c.locationCode) && chanRE.test(c.channelCode))) {
yield c;
}
}
}
}
function uniqueSourceIds(channelList) {
const out = new Map();
for (const c of channelList) {
if (c) {
out.set(c.sourceId.toString(), c.sourceId);
}
}
return Array.from(out.values()).sort(source_id_1.SourceIdSorter);
}
function uniqueStations(channelList) {
const out = new Set();
for (const c of channelList) {
if (c) {
out.add(c.station);
}
}
return Array.from(out.values());
}
function uniqueNetworks(channelList) {
const out = new Set();
for (const c of channelList) {
if (c) {
out.add(c.station.network);
}
}
return Array.from(out.values());
}
/**
* Fetches and parses StationXML from a URL. This can be used in instances where
* a static stationXML file is available on a web site instead of via a web
* service with query paramters.
* @param url the url to download from
* @param timeoutSec timeout in case of failed connection
* @param nodata http code for no data
* @returns Promise to parsed StationXML as an Network array
*/
function fetchStationXml(url, timeoutSec = 10, nodata = 204) {
const fetchInit = (0, utils_1.defaultFetchInitObj)(utils_1.XML_MIME);
return (0, utils_1.doFetchWithTimeout)(url, fetchInit, timeoutSec * 1000)
.then((response) => {
if (response.status === 200) {
return response.text();
}
else if (response.status === 204 ||
((0, utils_1.isDef)(nodata) && response.status === nodata)) {
// 204 is nodata, so successful but empty
return exports.FAKE_EMPTY_XML;
}
else {
throw new Error(`Status not successful: ${response.status}`);
}
})
.then(function (rawXmlText) {
return new DOMParser().parseFromString(rawXmlText, utils_1.XML_MIME);
})
.then((rawXml) => {
return parseStationXml(rawXml);
});
}
// these are similar methods as in seisplotjs.quakeml
// duplicate here to avoid dependency and diff NS, yes that is dumb...
const _grabFirstEl = function (xml, tagName) {
let out = null;
if (xml instanceof Element) {
const el = xml.getElementsByTagName(tagName);
if ((0, utils_1.isObject)(el) && el.length > 0) {
const e = el.item(0);
if (e) {
out = e;
}
}
}
return out;
};
const _grabFirstElText = function _grabFirstElText(xml, tagName) {
let out = null;
const el = _grabFirstEl(xml, tagName);
if (el instanceof Element) {
out = el.textContent;
}
return out;
};
const _grabFirstElFloat = function _grabFirstElFloat(xml, tagName) {
let out = null;
const elText = _grabFirstElText(xml, tagName);
if ((0, utils_1.isStringArg)(elText)) {
out = parseFloat(elText);
}
return out;
};
const _grabFirstElInt = function _grabFirstElInt(xml, tagName) {
let out = null;
const elText = _grabFirstElText(xml, tagName);
if ((0, utils_1.isStringArg)(elText)) {
out = parseInt(elText);
}
return out;
};
const _grabAttribute = function _grabAttribute(xml, tagName) {
let out = null;
if (xml instanceof Element) {
const a = xml.getAttribute(tagName);
if ((0, utils_1.isStringArg)(a)) {
out = a;
}
}
return out;
};
const _requireAttribute = function _requireAttribute(xml, tagName) {
const out = _grabAttribute(xml, tagName);
if (typeof out !== "string") {
throw new Error(`Attribute ${tagName} not found.`);
}
return out;
};
const _grabAttributeNS = function (xml, namespace, tagName) {
let out = null;
if (xml instanceof Element) {
const a = xml.getAttributeNS(namespace, tagName);
if ((0, utils_1.isStringArg)(a)) {
out = a;
}
}
return out;
};
exports.parseUtil = {
_grabFirstEl: _grabFirstEl,
_grabFirstElText: _grabFirstElText,
_grabFirstElFloat: _grabFirstElFloat,
_grabFirstElInt: _grabFirstElInt,
_grabAttribute: _grabAttribute,
_requireAttribute: _requireAttribute,
_grabAttributeNS: _grabAttributeNS,
};