用seisplotjs在浏览器端解析MiniSEED并绘制交互式地震波形
发布时间:2026/9/26 16:57:26来源:尧图网络
简介seisplotjs是一个用于解析、处理和绘制地震数据的JavaScript模块面向地震学科研人员、数据开发者及需要快速接入FDSN服务的Web工程师。它基于npm安装拆分为多个子模块覆盖数据链路WebSocket协议、日期选择器、距离计算、FDSN数据可用性/数据中心注册表查询以及dataselect、event、station等标准服务还集成了OregonDSP的FFT变换与d3绘图功能能够实现从数据请求、信号处理到可视化展示的完整流程。压缩包共184个文件以js逻辑代码和html示例页面为主辅以css样式、sac地震数据文件以及少量Python工具脚本和元数据文件整体仅1.02MB结构清晰、便于直接阅读和二次开发。资源内置大量教程示例从基础解析到FFT振幅谱绘制均有涉及适合作为学习seisplotjs模块化设计、FDSN Web服务调用与前端地震可视化的参考。已有319人学习下载。1. 用 seisplotjs 解析地震数据先搞清楚它到底解决了什么做地震数据展示的前端经常会遇到一个场景明明后端已经把 MiniSEED 文件解析好、处理完、生成 PNG 了前端只要贴图就行但一旦要加拖拽缩放、多道对比、实时刷新这些需求就全落到后端接口越开越多服务器越来越重。seisplotjs 是纯 Javascript 实现的地震数据模块把解析、处理、绘制三件事全部搬到浏览器端前端拿到原始二进制文件就能直接画波形。它可以解决两个核心痛点一是把地震数据从后端黑匣子里拿出来二是让交互式波形展示不再依赖后端频繁出图。适合做台网可视化、教研演示和轻量分析工具的人不要求你是地震学专家但需要懂一点 Javascript 基础。理解这个定位后面再调滤波参数、做实时看板时你就知道该往哪个方向折腾。2. 核心数据模型MiniSEED、QuakeML 与 Seismogram 的三角关系在动手写代码前先把 seisplotjs 眼中的数据模型讲清楚。它处理的不是一张张图片而是带有时间轴和采样信息的对象。你真正要操作的是 Seismogram。这个模型由三层组成解析层把 MiniSEED 或 QuakeML 的二进制文本变成内存对象处理层在内存对象上做数值运算绘制层把这些数值映射成 canvas 像素。很多人初次接触时容易把三层混在一起解析出来的对象直接拿去画图画不出来就一头扎进绘图代码里其实问题往往出在上面某一层的数据结构上。2.1 MiniSEED 是二进制波形QuakeML 是地震事件元数据地震台网记录到的原始数据通常是 MiniSEED 格式。MiniSEED 是一条连续的二进制数据流每个数据包有固定长度的记录头里面写清了台站代码、信道代码、采样率、起始时间、样本数量紧接着是数据体。数据体里存的是整数或浮点样本。读这种数据不像读文本你必须按字节偏移去读头字段。比如网络代码占 2 字节、站点代码占 5 字节、信道代码占 3 字节时间部分用 BCD 或二进制编码采样率又由因子和乘数组合而成。所以解析 MiniSEED 本身就是一件麻烦事。另一种常见格式是 QuakeML它描述的是地震事件本身震级、震源位置、到时不是波形。QuakeML 是一份 XML 文档里面嵌了台站、震相、振幅等信息。seisplotjs 分别提供了解析这两种格式的子模块。你要解析波形原始数据走 MiniSEED 解析器要获取震相信息走 QuakeML 解析器。两者的数据模型并不互通但在画图时经常要配合使用。区分它们最大的意义在于波形数据和事件元数据经常要联动。画波形图时要在某个震相到时处画一条竖线就需要把 QuakeML 里的到达时间映射到波形时间轴上。如果只把 MiniSEED 解析出来时间坐标是连续的 UTC 时间QuakeML 里的事件时间也是 UTC 时间两者可以直接对齐不需要做时区换算。这是 seisplotjs 统一时间模型带来的好处。你不需要在代码里做“北京时间转 UTC 再转毫秒”这种手工换算。2.2 Seismogram 是处理后的统一对象seisplotjs 解析完 MiniSEED 后返回的不是原始字节而是一个个 Seismogram 对象。这个对象把样本数据、采样率、起始时间、台站信息、信道信息打包在一起。结构大致是这样{ data: Float32Array, sampleRate: 100, // 采样率单位 Hz startTime: 2024-01-01T00:00:00.000Z, stationCode: ANMO, channelCode: BHZ, networkCode: IU, locationCode: 00 }data 里每个元素是一个样本点的幅度采样率决定相邻样本的时间间隔startTime 是第一个样本的时间。这个统一对象的好处是你不必关心原始文件是 MiniSEED 还是某种私有格式后续所有处理都写在 Seismogram 上。你拿到的数据可能是多个包拼接的seisplotjs 会尽量把它们合成连续的 Seismogram并检查重叠和时间连续性。要理解的是Seismogram 表示的是一段连续波形不是某个瞬间。参数说明sampleRate 是后续滤波的关键很多滤波器参数都依赖它。startTime 建议统一用 UTC 的 ISO 字符串避免时区歧义。channelCode 会告诉你这是垂直向Z、南北向N还是东西向E信道旋转处理时需要。如果你拿到的 Seismogram 没有 data 字段说明解析阶段就已经出了问题先别急着画图。2.3 绘图为什么要单独成层解析和处理留下来的都是数值和时间轴绘图则是把它们映射成 canvas 上的像素点。seisplotjs 把绘图单独拆出来是为了让同一个 Seismogram 对象可以用在不同场景可以画成单道波形可以画成多道谱图也可以叠加滤波器前后的曲线。你不必为了画图去改数据本身。绘图层接收 Seismogram 和 DOM 容器读取 data 数组的长度和 sampleRate纵向是幅度范围横向是时间轴。缩放时它会重新计算可视区间的样本点而不是把整段数据都画出来。这种分层设计在调试时很有用数据解析出问题不拖累绘制绘制显示异常也不会去碰数据。实际上我常用它做这样一件事把原始波形和滤波后的波形放到同一个坐标轴里对比。因为 Seismogram 之间天然共享时间基准绘图层只需要根据时间 startTime 和采样率把样本平移到同一个坐标系即可。理解完这个模型下面进入可运行的代码。2.4 从原始字节到 Seismogram 的解析管线seisplotjs 的解析不是一次性读入所有字节。MiniSEED 是由多个固定长度记录头组成每个记录头后面跟数据段。解析器会按记录头里的长度和样本数把每个记录的数据体解码成 Float32Array再去拼接、去重叠、检查采样率一致性。这个过程如果其中一个记录出现异常常见策略是把异常跳过返回前面已解析完的对象并附带错误信息。所以解析函数接收的是 ArrayBuffer返回的是 Promise因为解析过程里可能涉及流式数据的积累。提示如果你看到解析结果里缺了一段波形先怀疑是源文件本身断记而不是解析器 bug。断记在台网数据里很常见不要一缺数据就怪代码。3. 从安装到绘制第一张波形图最小可复现流程与参数说明这一章不走捷径直接带你从零起一个最小项目。目标是选一个本地 MiniSEED 文件解析它然后在浏览器里画出来。你不需要后端不需要配置数据库只需要一个能跑 npm 的环境和一个文件。3.1 安装与模块引入在项目里安装npm install seisplotjs然后在代码里引入import * as seisplotjs from seisplotjs;说明seisplotjs 是纯 ESM 模块现代浏览器和 Node.js 都能用。如果项目是 CommonJS 环境可以用动态 import 或者按官方文档调整打包配置。不要用 script 标签直接引整个文件因为它的模块依赖比较多直接引可能缺依赖运行时才会发现。安装完成后可以先在终端里打印一下导出的 APIconst keys Object.keys(seisplotjs); console.log(keys);如果你能看到 miniseed、seismogram、quakeml 这样的子模块说明安装正常。这一步可以帮你确认你用的版本里有没有你要的接口。不同版本 API 名不完全一致以你安装的包实际导出为准。3.2 用文件输入解析本地 MiniSEED最稳妥的开始方式是用一个文件选择框选本地 MiniSEED 文件。const fileInput document.getElementById(mseedFile); fileInput.addEventListener( change, async (e) { const file e.target.files[0]; if (!file) return; const arrayBuffer await file.arrayBuffer(); const seismograms await seisplotjs.miniseed.parse(arrayBuffer); drainSeismograms(seismograms); }, false ); function drainSeismograms(seismograms) { for (const sgm of seismograms) { const numSamples sgm.data.length; console.log(sgm.startTime, sgm.sampleRate, numSamples); drawOne(sgm); } }逻辑说明先通过 File API 把文件读成 ArrayBuffer这是浏览器读取二进制文件的标准方式然后交给 seisplotjs 的 miniseed.parse 解析。parse 返回的是 Promise等待解析完成得到一个 Seismogram 数组。这里用 drainSeismograms 逐个处理而不是直接把数组渲染到某个固定 div目的是方便你在 console 里先确认解析结果。参数说明file.arrayBuffer() 读入的是原始二进制不要用 text 或 json 方式读否则解析器拿到的是乱码parse 返回数组因为一个 MiniSEED 文件可能包含多个台站/信道的多段波形。可以先打印 startTime 和 data.length确认有没有真的解析出数据。如果 data.length 为 0说明文件不是标准 MiniSEED或者被截断了。3.3 绘制波形图的三个配置参数绘制前准备一个 div 容器然后调用 seisplotjs 的绘图接口。不同版本的函数命名略有差异我这里给一个常见写法关键看参数function drawOne(sgm) { const container document.getElementById(waveform); container.innerHTML ; const plot seisplotjs.seismogram.plot(sgm, container, { height: 320, // 画布高度像素 lineWidth: 1, // 波形线宽 showScale: true, // 是否显示幅度范围 showTimeAxis: true, // 是否显示时间轴 }); plot.redraw(); }逻辑说明plot 函数接收 Seismogram 对象和 DOM 容器配置对象只影响展示方式不改变数据。调用 redraw 才是真正触发 canvas 绘制。有的版本返回一个 plot 对象有的版本直接在容器里生成 canvas。如果你拿到的版本没有 redraw 方法可以忽略这一步绘制函数通常内部已经画完。参数说明height 太大会让 canvas 分辨率吃紧频繁缩放会卡lineWidth 在显示密集波形时建议用 1否则线条糊成一片showTimeAxis 打开后你会看到时间刻度用了 UTC这正好和解析结果的时间戳对应。如果波形显示成一条直线先去看数据幅度是不是非常小不是绘图 bug。3.4 用 Node 脚本做命令行验证不是所有人都想在浏览器里调试。你也可以用 Node.js 快速跑通解析这里有一个最小脚本const fs require(fs); const seisplotjs require(seisplotjs); (async () { const buf fs.readFileSync(example.mseed); const arrayBuffer buf.buffer.slice( buf.byteOffset, buf.byteOffset buf.byteLength ); const seismograms await seisplotjs.miniseed.parse(arrayBuffer); for (const sgm of seismograms) { console.log(sgm.stationCode, sgm.channelCode, sgm.data.length, sgm.sampleRate); } })();说明在 Node 中 Buffer 需要转换成 ArrayBuffer 再传给 parse注意内存偏移处理。直接传 buf 会报参数类型错误这是血泪经验。这样可以不用浏览器快速验证解析是否正常。如果你拿到的数据文件比较大建议只在命令行里做解析测试绘图仍然放到浏览器里Node 端没有 canvas。至此第一条链路已经通了文件 - ArrayBuffer - Seismogram - canvas。后面所有处理都是在这个链路的 Seismogram 对象上做文章。4. 数据处理模块去趋势、去均值、滤波与旋转的参数边界解析只是第一步。台站记录的原始数据里通常带有仪器零点漂移、长周期噪声还有可能的台站方位偏差。不做处理直接画波形可能扭曲做了处理又不知道参数怎么设也很容易翻车。这一章讲处理链里的四个核心操作。4.1 先去趋势还是先滤波处理链顺序拿到 Seismogram 后data 是 Float32Array。第一步通常是去均值/去趋势因为地震仪会有零点漂移对后续低频段滤波影响很大。seisplotjs 的 Seismogram 对象可能自带 detrend 方法我们也可以用最小二乘拟合直线去掉线性趋势。function detrend(data) { const n data.length; if (n 2) return data; let sumX 0, sumY 0, sumXY 0, sumXX 0; for (let i 0; i n; i) { sumX i; sumY data[i]; sumXY i * data[i]; sumXX i * i; } const slope (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); const intercept (sumY - slope * sumX) / n; for (let i 0; i n; i) { data[i] data[i] - (slope * i intercept); } return data; } const sgm seismograms[0]; detrend(sgm.data);逻辑说明这里直接在原 data 上做处理省去 copy。线性回归的系数用普通最小二乘算出。i 是样本序号不是实际时间所以 slope 单位是“每样本”不是“每秒”。参数说明如果源采样率是 100Hz那么 i 增长 1 代表 0.01 秒slope 的含义要换算。若想以秒为单位拟合用 i / sampleRate 作为 x。不过仅用来去趋势用序号关系不大。去趋势之后再滤波可以避免陡峭边缘放大低频信号。如果你要保留物理单位建议处理完后再把 amplitude 的单位信息写回你自己的元数据里Seismogram 本身不一定带单位。4.2 带通滤波器的截止频率与采样率关系带通滤波是地震数据处理里最常用的操作。如果 seisplotjs 没有直接内置可以自己写一个二阶 Butterworth。常见参数是低切和高切频率比如对宽频带地震仪记录保留 0.01Hz 到 10Hz。需要注意 Nyquist 频率是采样率的一半截止频率必须小于 Nyquist。function butterworthBandpass(data, sampleRate, lowCut, highCut) { // 简化的二阶段巴特沃斯带通使用双线性变换 const nyq sampleRate / 2; const w1 Math.tan(Math.PI * lowCut / nyq); const w2 Math.tan(Math.PI * highCut / nyq); // 这里只给出骨架实际系数推导较长 // 建议使用成熟的滤波器库比如 fft 变换或直接下载Matlab算法移植 return data; }说明给定低切和高切后先把频率转换成数字角频率再做双线性变换。这个骨架不完整只是为了让你看到参数映射关系。真正落地时不要自己手搓用社区里的 Butterworth 实现或者用 Web Audio API 的 BiquadFilter 串联。BiquadFilter 有 type 为 bandpass但 Q 值和频率设置是另一套概念。参数要点低切不要设得太低比如采样率 100HzNyquist 是 50Hz低切设 0.001Hz滤波器系数会非常极端结果容易出现数值不稳定。高切也不能太接近 Nyquist一般不超过 0.4 倍采样率。还有一个玄学参数是滤波器的阶数阶数越高频率越陡但相位延迟也越大初学者优先用二阶跑通后再升级。4.3 旋转 ZNE 到 RTZ 时需要的两个角度多分量地震计通常记录 Z、N、E。但分析常用 R、T、Z。转换需要背方位角 backAzimuth 和入射角。function rotateZNEToRTZ(z, n, e, baz, inc) { const bazRad baz * Math.PI / 180; const incRad inc * Math.PI / 180; const r -n * Math.sin(bazRad) e * Math.cos(bazRad); const t -n * Math.cos(bazRad) - e * Math.sin(bazRad); const zR z; return { r, t, zR }; }说明这是地震学里标准旋转。背方位角是震中相对于台站的方向入射角是射线在台站处的入射角。如果这两个角度给错旋转后的 R 和 T 分量会互相泄漏。参数说明baz 范围 0-360 度inc 是 0-90 度。seisplotjs 解析出的 QuakeML 中可能包含 backAzimuth 和 takeoffAngle要注意取的是 origin 到 station 的方位角不是 station 到 origin。4.4 处理链的顺序与手动处理处理完数据后可以直接改 Seismogram 对象的 data 和 metadata再调用绘制。常见做法是先解析然后去趋势、滤波、旋转最后绘制。处理链顺序影响结果很大先去趋势再去滤波滤波结果更稳定先旋转再滤波可能让旋转后的分量保留更多原始噪声。建议多对比几组参数。还有一个容易被忽略的点数据本身是否已做过仪器响应去除。如果台网返回的是原始 counts滤波前需要考虑仪器响应否则波形会被仪器自身频率特性扭曲。仪器响应通常属于 FDSN 的 station 服务不在 Seismogram 内部需要另外加载。如果留恋“一条命令出图”这一层躲不掉。我的建议是在数据处理链路中单独留一个 instrumentCorrection 开关默认关等你需要研究低频段时再打开别为了严谨一上来就开不然数据噪声会大得让你怀疑人生。5. 避坑指南解析失败、时标错位、滤波毛刺的排查记录这一章是前面所有经验里最容易让人浪费时间的部分。我把踩过的坑按“现象 - 原因 - 解决”写下来每一条都对应真实会遇到的问题。5.1 时间戳总是差 8 小时现象解析出来的 startTime 明明是 UTC但画图的横轴显示的是本地时区的时间波形和地震目录对不上。原因绘图组件默认用浏览器本地时区格式化时间轴。中国用户在 UTC8 环境下看到的时间会整体偏移 8 小时。解决绘制时显式指定时区为 UTC。在 seisplotjs 的绘图配置里找 timeZone 或 dateFormat 参数设置为 UTC或者在传给 Seismogram 的 startTime 上手动加时区标识。不要试图在时间戳上减 8 小时那是最容易留下隐患的做法。波形时间轴的统一时区原则是解析、处理、绘图全部走 UTC只在显示成人类可读字符串时再做时区转换。5.2 MiniSEED 解析出来是空数组现象parse 返回了一个空数组代码一行没报错但画布上什么都没有。原因文件不是标准 MiniSEED或者文件被截断。很多台网导出的文件带有自定义头需要在导出时指定为 MiniSEED 格式。还有一个常见原因用了 fetch 读取数据但没有声明 response.arrayBuffer()直接当 JSON 用了。解决先用十六进制查看器打开文件前几十个字节确认有 MiniSEED 的 Magic 或 record header 特征。再在代码里打印 buffer.byteLength确认文件没有被提前截断。如果确定文件没问题尝试用第二个解析器交叉验证隔离模块问题还是文件问题。5.3 滤波后波形两端出现大尾巴现象滤波后的波形在开始和结束位置出现明显的大幅振荡像一条尾巴。原因滤波器是因果系统处理到边缘时需要填充数据。简单粗暴地补零会让滤波器认为信号突然截断产生瞬态响应。解决滤波前对波形两端做 taper半余弦斜坡把两端慢慢衰减到零然后再滤波。滤波后再把 taper 的边界截掉。t 的典型长度是滤波器时长或几个主周期比如 5 秒。另一种方案是用前后数据镜像延拓但效果依赖数据本身平稳性。新手先做 taper 就好别一上来就学镜像。5.4 移动缩放时画布卡顿现象波形图正常绘制但拖动缩放时掉帧明显CPU 占用高。原因绘图层每次重绘都把整段数据重新走一遍甚至用了很高的 canvas 分辨率。样本量一大canvas 扛不住。解决不要直接提高画布分辨率改为固定画布尺寸绘制时做抽稀。比如当前可视区间有 100 万样本但 canvas 只有 1000 像素宽每 1000 个样本取一个极值或平均值再画。seisplotjs 内部可能自带抽稀但如果你自己写绘图层这步必须做。还有一个小技巧绘制期间用 requestAnimationFrame 合并多次重绘调用避免每个鼠标事件都触发一次重绘。5.5 数据拼接处波形跳变现象把一个小时的记录按 10 分钟切片解析每段单独绘制拼接起来后连接处波形不连续甚至有时间重叠。原因MiniSEED 数据包里可能存在少量重叠或缺失解析器如果按包独立处理不更新内部累计时间就会产生跳变。解决在解析时保留每个包的连续标志和时间字段拼接时按 startTime data.length / sampleRate 推进预期时间。如果下一个包的 startTime 与预期偏差超过一个采样间隔就标记为断记不要再强行拼。如果你用 seisplotjs 已经拼过的 Seismogram直接信任它的结果但要在画多道对比时用同一个时间参考点不要每道单独设置起始时间。这五条基本覆盖了从解析到绘制最常见的翻车场景。最后提醒一句遇到奇怪现象时先用最小数据集复现再把处理步骤一个一个关掉定位到具体哪一层。6. 进阶用 seisplotjs 搭实时波形看板时我会怎么做日常用的其实是一个轻量轮询方案每隔几百毫秒从数据服务拉取最近一两个钟头的 MiniSEED 片段解析后和上一段做时间对齐再追加到当前显示窗口。这里有两个关键参数轮询间隔和窗口大小。轮询太频繁会给数据服务造成压力太慢又失去实时性窗口太大canvas 抽稀效果变差窗口太小地震事件还没到就看丢了。我先做一个滑动缓冲区预先分配一个足够大的 Float32Array每次新数据到达后把旧数据往前挪尾部写入新样本。然后绘制时只取可视区间的索引段交给抽稀函数。这样即使缓冲区里有几百万样本canvas 每次绘制也只处理可视区间那几千个点。早期我偷懒每次都把整个缓冲区传给 plot结果拖动一次波形要等两秒才出画面后来改成只传可视区间体验立刻不同。验证这块我建议写一个自动化测试解析一段已知波形把滤波前后的某些统计量断言到一个误差范围内。比如去趋势后前 10% 样本的均值应接近零带通滤波后某些频段的能量应明显增强。不要只靠肉眼。我现在的习惯是所有显示组件都在 UTC 时间轴上工作任何时区转换都限制在页面顶部的时间格式化函数里数据处理链入口固定走“解析 - 去趋势 - 可选滤波 - 可选旋转 - 绘制”每步都打日志方便回查。有一段时间我把滤波和旋转顺序写反又在时间戳里减了 8 小时四五个小时的数据全部白调从那以后我宁可每次打印完整元数据也不愿节省那几行 console.log。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网