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