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

1,364 lines (1,363 loc) 46.1 kB
"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, };