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

397 lines (396 loc) 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SeismogramSegment = exports.COUNT_UNIT = void 0; /* * Philip Crotwell * University of South Carolina, 2019 * https://www.seis.sc.edu */ const luxon_1 = require("luxon"); const utils_1 = require("../utils"); const scale_1 = require("./scale"); const seedcodec = __importStar(require("../waveform/seed-codec")); const source_id_1 = require("../fdsn/source-id"); exports.COUNT_UNIT = "count"; /** * A contiguous segment of a Seismogram. * * @param yArray array of Y sample values, ie the timeseries * @param sampleRate sample rate of the seismogram, hertz * @param startTime start time of seismogrm as a luxon DateTime in utc or a string that can be parsed */ class SeismogramSegment { /** Array of y values */ _y; _compressed; /** * the sample rate in hertz * * @private */ _sampleRate; /** @private */ _startTime; _endTime_cache; _endTime_cache_numPoints; _sourceId; yUnit; _highlow; constructor(yArray, sampleRate, startTime, sourceId) { if (yArray instanceof Int32Array || yArray instanceof Float32Array || yArray instanceof Float64Array) { this._y = yArray; this._compressed = null; } else if (Array.isArray(yArray) && yArray.every((ee) => ee instanceof seedcodec.EncodedDataSegment)) { this._compressed = yArray; this._y = null; } else if (Array.isArray(yArray) && yArray.every((ee) => typeof ee === "number")) { // numbers in js are 64bit, so... this._y = Float64Array.from(yArray); this._compressed = null; } else { this._compressed = null; this._y = null; } if (sampleRate <= 0) { throw new Error(`SampleRate must be positive number: ${sampleRate}`); } this._sampleRate = sampleRate; this._startTime = (0, utils_1.checkStringOrDate)(startTime); this.yUnit = exports.COUNT_UNIT; this._sourceId = sourceId ? sourceId : source_id_1.FDSNSourceId.createUnknown(sampleRate); // to avoid recalc of end time as it is kind of expensive this._endTime_cache = null; this._endTime_cache_numPoints = 0; } /** * Y data of the seismogram. Decompresses data if needed. * * @returns y data as typed array */ get y() { let out; if (this._y) { out = this._y; } else { if (!this.isEncoded()) { throw new Error("Seismogram not y as TypedArray or encoded."); } // data is still compressed const outLen = this.numPoints; if (this._compressed === null) { throw new Error("Seismogram not y as TypedArray or encoded."); } if (this._compressed[0].compressionType === seedcodec.DOUBLE) { out = new Float64Array(outLen); } else if (this._compressed[0].compressionType === seedcodec.FLOAT) { out = new Float32Array(outLen); } else { out = new Int32Array(outLen); } let currIdx = 0; for (const c of this._compressed) { const cData = c.decode(); for (let i = 0; i < c.numSamples; i++) { out[currIdx + i] = cData[i]; } currIdx += c.numSamples; } this._y = out; this._compressed = null; } return out; } set y(value) { this._y = value; this._invalidate_endTime_cache(); } get start() { return this.startTime; } set start(value) { this.startTime = value; } get startTime() { return this._startTime; } set startTime(value) { this._startTime = (0, utils_1.checkStringOrDate)(value); this._invalidate_endTime_cache(); } get end() { return this.endTime; } get endTime() { if (!this._endTime_cache || this._endTime_cache_numPoints !== this.numPoints) { // array length modified, recalc cached end time this._endTime_cache_numPoints = this.numPoints; this._endTime_cache = this.timeOfSample(this._endTime_cache_numPoints - 1); } return this._endTime_cache; } get timeRange() { return luxon_1.Interval.fromDateTimes(this.startTime, this.endTime); } get sampleRate() { return this._sampleRate; } set sampleRate(value) { this._sampleRate = value; this._invalidate_endTime_cache(); } get samplePeriod() { return 1.0 / this.sampleRate; } get numPoints() { let out = 0; if (this._y) { out = this._y.length; } else if (this._compressed) { for (const c of this._compressed) { out += c.numSamples; } } return out; } get networkCode() { return this._sourceId.networkCode; } get stationCode() { return this._sourceId.stationCode; } get locationCode() { return this._sourceId.locationCode; } get channelCode() { return this._sourceId.formChannelCode(); } /** * Checks if the data is encoded * * @returns true if encoded, false otherwise */ isEncoded() { if (this._y && this._y.length > 0) { return false; } else if (this._compressed && this._compressed.length > 0) { return true; } else { return false; } } /** * Gets encoded data, if it is. * * @returns array of encoded data segments * @throws Error if data is not encoded */ getEncoded() { const compressed = this._compressed; if (this.isEncoded() && compressed != null) { return compressed; } else { throw new Error("Data is not encoded."); } } yAtIndex(i) { if (i >= 0) { return this.y[i]; } else { return this.y[this.numPoints + i]; } } /** * Finds the min and max values of a SeismogramSegment, with an optional * accumulator for use with gappy data. * * @param minMaxAccumulator optional initialized accumulator as an array * of two numbers, min and max * @returns min, max as arry of length two */ findMinMax(minMaxAccumulator) { let minAmp = Number.MAX_SAFE_INTEGER; let maxAmp = -1 * minAmp; if (minMaxAccumulator) { minAmp = minMaxAccumulator.min; maxAmp = minMaxAccumulator.max; } const yData = this.y; for (let n = 0; n < yData.length; n++) { if (minAmp > yData[n]) { minAmp = yData[n]; } if (maxAmp < yData[n]) { maxAmp = yData[n]; } } return new scale_1.MinMaxable(minAmp, maxAmp); } /** * Time of the i-th sample, indexed from zero. * If i is negative, counting from end, so * timeOfSample(-1) is time of last data point; * * @param i sample index * @returns time */ timeOfSample(i) { if (i >= 0) { return this.startTime.plus(luxon_1.Duration.fromMillis((1000 * i) / this.sampleRate)); } else { return this.startTime.plus(luxon_1.Duration.fromMillis((1000 * (this.numPoints + i)) / this.sampleRate)); } } indexOfTime(t) { if (t < this.startTime || t > this.endTime.plus(luxon_1.Duration.fromMillis(1000 / this.sampleRate))) { return -1; } return Math.round((t.diff(this.startTime).toMillis() * this.sampleRate) / 1000); } hasCodes() { return (0, utils_1.isDef)(this._sourceId); } /** * return network, station, location and channels codes as one string. * Uses this.channel if it exists, this.seismogram if not. * * @returns nslc codes separated by '.' */ get nslc() { return this.codes(); } get nslcId() { return this._sourceId.asNslc(); } /** * return network, station, location and channels codes as one string * * @param sep separator, defaults to '.' * @returns nslc codes separated by sep */ codes(sep = ".") { return ((this.networkCode ? this.networkCode : "") + sep + (this.stationCode ? this.stationCode : "") + sep + (this.locationCode ? this.locationCode : "") + sep + (this.channelCode ? this.channelCode : "")); } seisId() { const out = `${this.sourceId.toString()}_${this.startTime.toISO()}_${this.endTime.toISO()}`; return out.replace(/\./g, "_").replace(/:/g, ""); } /** * return FDSN source id. * * @returns FDSN source id */ get sourceId() { return this._sourceId; } set sourceId(sid) { this._sourceId = sid; } clone() { let out; if ((0, utils_1.isDef)(this._y)) { out = this.cloneWithNewData(this._y.slice()); } else if (this.isEncoded()) { // shallow copy array, assume Encoded is immutable out = this.cloneWithNewData(Array.from(this.getEncoded())); } else { throw new Error("no _y and no _compressed"); } return out; } cloneWithNewData(clonedData, clonedStartTime = this._startTime) { const out = new SeismogramSegment(clonedData, this.sampleRate, clonedStartTime, this._sourceId.clone()); out.yUnit = this.yUnit; return out; } cut(timeRange) { (0, utils_1.checkLuxonValid)(timeRange); if (timeRange.start == null || timeRange.end == null || timeRange.end < this._startTime || timeRange.start > this.endTime) { return null; } let sIndex = 0; if (timeRange.start > this._startTime) { const milliDiff = timeRange.start.diff(this._startTime).toMillis(); const offset = (milliDiff * this.sampleRate) / 1000.0; sIndex = Math.floor(offset); } let eIndex = this.y.length; if (timeRange.end < this.endTime) { const milliDiff = this.endTime.diff(timeRange.end).toMillis(); const offset = (milliDiff * this.sampleRate) / 1000.0; eIndex = this.y.length - Math.floor(offset); } const cutY = this.y.slice(sIndex, eIndex); const out = this.cloneWithNewData(cutY, this._startTime.plus(luxon_1.Duration.fromMillis((1000 * sIndex) / this.sampleRate))); return out; } _invalidate_endTime_cache() { this._endTime_cache = null; this._endTime_cache_numPoints = 0; } } exports.SeismogramSegment = SeismogramSegment;