绵竹深夜连震,iPhone 用户只能刷朋友圈?我写了一个开源地震预警系统
7 月 5 日晚,四川绵竹半小时内连发 3 次 4 级以上地震。当国产手机自动弹出预警时,iPhone 用户的第一反应往往是——一脸懵逼感受到地震后打开朋友圈刷刷看看“朋友圈预警”。
一、那个晚上,朋友圈比地震局还快
2026 年 7 月 5 日,深夜 23 点。
四川绵竹,第一次地震来了——4.5 级,震源深度 20 公里。
17 分钟后,第二次——4.0 级。
又过 10 分钟,第三次——还是 4.5 级。
半小时内连震三次,成都、乐山、内江、宜宾等多地震感明显。
有意思的是,地震发生后最先"播报"的不是电视,不是广播,而是朋友圈。
“刚才摇了没?”
“我杯子里的水在晃!”
“又来了一次!”
而 iPhone 用户——手机安安静静,什么都没发生。直到打开朋友圈,才知道刚刚地震了。
这不是 iPhone 用户第一次感到无奈。事实上,华为、小米、OPPO、vivo 等国产手机早已内置地震预警功能,接入的是成都高新减灾研究所的地震预警网络。但 iPhone 因为系统封闭和一些已知原因、未接入中国地震预警网络,至今无法像国产手机那样自动弹出预警通知。
作为一个写了多年代码的人,我在想:能不能自己动手,做一个能跑的预警系统?
答案是:可以。
二、系统能做什么
一句话概括:实时监听中国地震台网数据,计算你所在城市的预计到达时间和影响程度,在破坏性地震波到达前通过手机推送通知你。
具体来说:
- 📡数据源:通过 WebSocket 实时接收 wolfx.jp 转发的中国地震台网(CENC)数据,WebSocket 断线自动切换 HTTP 轮询兜底。
关于 Wolfx.jp:这是一个基于 P2P 网络的全球地震监测数据聚合平台(可以理解为地震界的"广播站"),它从各国地震台网采集原始数据并以 WebSocket 实时转发,个人开发者无需自建台站即可获取高质量的地震波形和事件数据。
- 🧮计算引擎:基于 IASPEI-91 全球一维地球速度模型,计算 P 波(纵波)和 S 波(横波/破坏性波)到你所在城市的精确到达时间
- 📊烈度预估:基于 GB 18306-2015《中国地震动参数区划图》东部衰减模型 + 场地放大系数修正,估算你所在位置的本地烈度
- 📱推送通知:通过 Bark 将预警推送到你的 iPhone/Android,通知包含震级、地点、距你多远、还有多少秒 S 波到达、建议行动
- 🔊语音倒计时:打开详情页,系统会用中文语音播报倒计时,根据地震强度自动匹配三种模式
三种语音模式
| 地震强度 | 影响等级 | 播报方式 | 示例 |
|---|---|---|---|
| 几乎无感 | 0-1 | 纯数字报数 | “10……9……8……7……” |
| 有感地震 | 2-3 | 数字 + 一声"滴" | “10滴9滴8滴……” |
| 破坏性地震 | 4-5 | 数字 + 两声"滴滴" | “10滴滴9滴滴8滴滴……” |
这个设计的逻辑是:强度越大,警报声越急促,大脑对"滴滴"次数的感知比看数字更快。
关于误报和深夜骚扰的考量:为了避免普通的无感地震频繁触发语音吵醒用户,系统对本地烈度 1-2 级(几乎无感)的地震仅在通知栏静默提示,不会触发语音倒计时。只有本地烈度达到 4 级以上(破坏性地震)时,才会激活最高优先级的"滴滴"急促警报。这个阈值可以通过RuntimeConfigService中的voiceWarningThreshold自由调整——身处地震带上、对震动敏感的用户可以设得更低一些。
三、技术架构
Wolfx CENC WebSocket → 实时地震数据 ↓ [走时计算] IASPEI-91 射线追踪 → P波/S波到达时间 [烈度计算] GB 18306-2015 衰减模型 → 基岩烈度 [场地修正] 场地放大系数 → 本地影响烈度 ↓ [影响分级] 0 无感 → 5 毁灭性 ↓ [推送决策] 震级≥阈值 && 影响等级≥阈值 ↓ [Bark 推送] → 📱 iOS/Android 实时通知 [Web 详情页] → 语音倒计时 + 波到达可视化技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Java | 17 | 运行时 |
| Spring Boot | 3.3.5 | 后端框架 |
| Java-WebSocket | 1.5.7 | 实时数据接收 |
| OkHttp | 4.12.0 | HTTP 代理客户端 |
| SpringDoc OpenAPI | 2.6.0 | API 文档 |
| HTML/CSS/JS | - | 原生前端,零框架依赖 |
前端没有用任何框架(Vue/React),纯原生 HTML + CSS + JavaScript。原因很简单——地震预警页面的第一要务是速度,任何 JS 框架的加载时间都可能是致命的。
四、核心计算模型
这是这个系统最有技术含量的部分。
4.1 P 波 / S 波走时计算
地震发生时产生两种体波:
- P 波(纵波):速度快、破坏力小,先到达
- S 波(横波):速度慢、破坏力大,后到达
两者的到达时间差,就是我们能用来预警的黄金时间窗口。
系统引入IASPEI-91 国际标准地球模型进行射线追踪。该模型将地球内部分层,每层有不同的 P 波速度(Vp)和 S 波速度(Vs),通过 Snell 定律计算射线路径和走时:
走时 = Σ ( 层厚 / 层速度 × cos(入射角) )对于地壳范围内的地震(深度通常 10-30 km),系统简化处理为水平分层模型,根据震源深度和震中距查 IASPEI-91 走时表进行插值计算。
4.2 烈度衰减模型
系统基于GB 18306-2015 中国东部烈度衰减公式:
I = a + b×M - c×log(R+R0) - d×R其中:
- I:基岩场地地震烈度
- M:面波震级
- R:震中距(km)
- a, b, c, d, R0:经验常数(中国东部参数)
4.3 场地放大修正
同一震中距下,软土场地的震感会比基岩场地强烈得多。系统根据《建筑抗震设计规范》GB 50011-2010 的场地类别对基岩烈度进行修正:
- I 类场地(基岩):放大系数 1.0(不放大)
- II 类场地(中硬土):放大系数 1.1
- III 类场地(中软土):放大系数 1.25
- IV 类场地(软弱土):放大系数 1.4
五、WebSocket 双通道容灾设计
系统最棘手的问题不是计算,而是数据链路的可靠性。
数据源 wolfx.jp 的 WebSocket 在境外服务器,国内直连不稳定。系统做了三层容灾:
优先级 1: WebSocket 直连(低延迟,实时推送) ↓ 断线 优先级 2: WebSocket + SOCKS5 代理(墙内可达) ↓ 断线 优先级 3: HTTP 轮询(最可靠,每 5 秒请求一次)每层自动降级、自动恢复,确保数据不丢失。控制面板实时显示当前连接方式和状态。
HTTP 轮询时的"数据去重"问题
HTTP 轮询拿到的数据是时间窗口列表,可能和上一轮有重叠。系统通过ConcurrentHashMap<String, EarthquakeRecord>配合事件 ID 去重,保证同一次地震不会重复触发预警。
六、移动端语音倒计时的踩坑之路
语音倒计时是整个项目中踩坑最多的功能,这里分享几个典型问题。
坑 1:浏览器自动播放策略
Chrome、Safari、Edge 都严禁在没有用户手势的情况下播放音频。AudioContext默认状态是suspended,speechSynthesis.speak()会被直接拦截。
解决方案:添加全屏透明遮罩,用户轻触屏幕任意位置即可解锁音频。移动端用户打开页面的自然行为(点击查看)恰好触发了解锁。这是目前 Web 标准下能做到的最接近"自动播放"的方案。
坑 2:事件双重触发
按钮的 HTML 模板中同时写了onclick内联属性和addEventListener,点击一次触发两个回调。结果:第一个回调解锁音频 → 第二个回调立刻把音频重新静音。
教训:永远不要在同一个元素上同时使用 onclick 和 addEventListener。
坑 3:setInterval 的首次延迟
// ❌ 错误写法:第一次执行在 1 秒后setInterval(countdownTick,1000);// ✅ 正确写法:先立即执行,再启动定时器countdownTick();setInterval(countdownTick,1000);setInterval的第一个回调在delay毫秒后才触发。如果倒计时从 10 开始,用户看到的第一个数字是 10,但要等 1 秒才变 9,造成"停顿感"且整体延迟 1 秒。
七、Web 控制台展示
系统自带一个完整的 Web 管理控制台(原生 HTML,零依赖):
- 📡 实时连接状态(绿色 WebSocket / 蓝色 HTTP 轮询)
- ⚙️ 全量运行时配置(无需重启,即时生效)
- 📍 监测城市管理(添加/删除/编辑,支持场地类别配置)
- 📱 Bark 设备管理(多设备同时推送)
- 🧪 模拟测试(输入虚拟震中,预览所有城市的预警结果)
- 📊 Swagger UI(完整 REST API 文档)
八、开源 & 部署
项目已开源在 GitHub,采用 MIT 协议:
🔗GitHub 仓库:跳转链接
快速启动
# 1. 克隆仓库gitclone<your-repo-url>cdearthquake-warning# 2. 配置监测城市和 Bark Device Key# 编辑 application.yml 和 RuntimeConfigService.java# 详见 README → 部署前必读# 3. 构建 & 运行java-jartarget/earthquake-warning-5.6.0.jar# 4. 打开控制台# http://localhost:5050⚠️ 部署前必读
代码中的以下内容已脱敏,需要替换为你自己的值:
- Bark Device Key(
RuntimeConfigService.java第 161 行)— 在 iOS/Android 安装 Bark App 即可获取 - 详情页跳转地址(
application.yml)— 填你的服务器 IP 或域名 - SOCKS5 代理(按需)— 如果 wolfx.jp 被你的服务器屏蔽
- 推送图标 URL(可选)
详细配置说明见 README 中的「⚠️ 部署前必读:脱敏内容配置」章节。
💡 不想折腾服务器?
坦白说,这个项目需要一台能跑 Java 的服务器,对非技术用户有一定门槛。如果你只是想体验预警功能——可以关注评论区或 GitHub Issues,后续整一个公益服务器供大家使用。
九、写在最后
这个项目从想法到 v5.6.0 大约是七天的工作量,其中写代码大概占 40%,剩下 60% 全在踩坑——Maven 构建兼容性、SSH 部署、浏览器自动播放策略、事件双重触发、setInterval 时序问题……
但最终它跑起来了,而且真的能在地震发生后几秒内计算出你所在城市的预警信息。
当然,这只是一个个人项目,它无法替代专业的应急广播,但希望能成为你手机里最后一道防线。精度无法和成都高新减灾研究所的专业系统相比。但它的意义在于:
- 证明了个人开发者也可以搭建一套可用的地震预警系统
- 所有计算过程和代码完全透明,没有任何黑箱
- 开源免费,任何人都可以部署、修改、改进
如果你也受够了 iPhone 在地震时的沉默,或者只是在技术上有兴趣,欢迎来 GitHub 看看源码,提 Issue。
📝 欢迎在评论区和 GitHub 交流。
🔗 GitHub 仓库:跳转链接
📖 完整踩坑记录:
TROUBLESHOOTING.md📋 更新日志:
CHANGELOG.md
