凌晨睡不着,我给台风巴威写了个追踪网站

19 阅读9分钟

这是一份写给开发者、设计师、气象爱好者的共建指南。
也是一篇碎碎念,关于我为什么要做这个东西。

仓库:github.com/Trade-Offf/…
线上:chinaupdated.com


一、缘起

超强台风"巴威"逼近的那个凌晨,我睡不着。

不是因为怕,是白天当了一天牛马,脑子转不动了,躺床上习惯性打开手机,想看看台风到底什么情况。结果越刷越乱:

  • 这个博主说"史上最强,千万别出门",
  • 那个博主说"没那么严重,都是标题党",
  • 划两下刷到一条三天前的旧闻, 还有人在评论区吵"到底信谁的"。

刷了二十分钟,我一个消息没搞清楚, 倒是学会了三种"台风穿搭"和两首"台风BGM"。 躺在出租屋的床上,我脑子里其实只剩三个问题

  1. 台风现在在哪、多强?
  2. 离我家还有多久?
  3. 我现在到底该干嘛?

就这三个问题,翻遍手机里的软件, 居然没有一个地方能一次讲清楚。

索性爬起来,打开电脑,用 Claude Fable 5 把想法一点点敲了出来。把基于中央气象台、日本气象厅、美国 JTWC、台湾气象署四家的路径数据放在同一张地图上之后,很多事一眼就看明白了——比如此刻,几家的预测登陆点正在往温州附近收敛。

然后顺手加了几样东西:

  • 实时新闻聚合。比官方数据更快,分布式数据的二次验证,但不一定准,请自行甄别;
  • 城市波及倒计时。大概还有多久,巴威到你所在的城市,基于风速和地图向量估算的,不一定准;
  • 应急指南和紧急电话。真到了要用的时候,不用再去搜。

这个网站纯粹是自己做的:没有广告,不收集数据,数据来源全是官方,防灾决策请以官方预警为准(不信谣、不传谣)。

做这个的时候就一个念头:能让身边人早知道一点,心里也能踏实一点。

如果你有江浙沪的朋友,欢迎转给 ta。如果你是开发者,往下读——我会把它怎么运转的、哪里还很粗糙、你能怎么帮忙,都摊开讲清楚。


二、它长什么样

先看图,再聊技术。

打开就是一张会动的地图

01-overview-city-countdown.png

实测路径按国标六级强度配色,从深海一路"烧"到近海。左边挂着一列城市倒计时,最紧迫的排最前面。顶上一条预警横幅,告诉你现在谁最危险。

点一下你关心的城市

02-city-popup.png

镜头飞过去,弹出一张小卡片:风圈预计什么时候到、最近距离多少公里、现在该做什么。分享链接也帮你想好了——?city=温州,发给温州的朋友,打开直接聚焦到 ta 家。

四家机构的预报,摆在一起看

截屏2026-07-07 12.09.07.png

中、日、美、台四家的预报虚线可以独立开关。预报本来就有分歧,分歧本身就是信息——四条线收得越拢,说明路径越确定;散得越开,越要多留个心眼。

新闻和指南,都在手边

03-news-panel.png

04-guide-panel.png

新闻是媒体视角,比官方报文快,但请自行甄别。指南是静态内容,从"来临前 48 小时"写到"过后安全确认",紧急电话都列好了——真正慌乱的时刻,没人有耐心再去搜索。


三、它是怎么转起来的

架构简单到有点不好意思:一个 Cloudflare Worker,一堆纯函数,没有数据库,没有服务器。

┌─────────────────────────────────────────────────────────────┐
│  用户浏览器(MapLibre GL + TypeScript)                        │
│  · 路径回放 / 风圈动画 / 城市标记 / HUD / 分享深链            │
└──────────────────────────┬──────────────────────────────────┘
                           │ /api/typhoon/:id  /api/news
┌──────────────────────────▼──────────────────────────────────┐
│  Cloudflare Worker(边缘代理 + 缓存 + 容灾)                   │
│  · normalize.ts  双源归一化                                   │
│  · news.ts       RSS 资讯聚合                                 │
│  · 缓存:台风 5min / 资讯 10min                               │
└──────────┬─────────────────────────────┬────────────────────┘
           │                             │
   浙江水利厅 API(主)              Google/Bing News RSS
   中央气象台 NMC(备)

但简单是刻意的。灾害面前,系统越简单越可靠。 没有数据库可以宕机,没有服务器需要半夜爬起来重启,边缘节点挂了还有下一个。一个防灾工具首先要做到的,是它自己别先倒下。

选型时想的也是同一件事:

层级选型为什么
边缘计算Cloudflare Workers零运维、全球 CDN、国内可访问
前端地图MapLibre GL开源、灵活,不依赖 Google
底图高德卫星 + 中文注记国内 CDN 稳,中文标注,合规踏实
构建Vite + TypeScript快,且类型兜底
数据无数据库源站返回全量轨迹,边缘缓存就够了

目录一览

worker/
  index.ts       # 路由、边缘缓存、双源容灾
  normalize.ts   # CMA/ZJ 数据 → TyphoonData(纯函数)
  news.ts        # RSS 解析、聚合与快照合并(纯函数)

src/
  app.ts         # 入口:回放控制、HUD、分享、倒计时刷新
  map.ts         # MapLibre 图层、城市标记、风圈动画
  impact.ts      # 城市波及倒计时算法(纯函数)
  geo.ts         # 球面距离、风圈多边形、轨迹插值
  guide.ts       # 应对指南内容
  news.ts        # 资讯面板渲染
  mobile.ts      # 移动端布局:顶部实况条 + 底部三档抽屉
  slogan.ts      # 正能量口号轮播

几个写代码时守着的习惯:

  • 数据归一化和算法尽量写成纯函数normalize.tsimpact.tsgeo.ts),好测、好复用,出了问题也好定位;
  • 画图和动画都关在 map.ts 里,业务逻辑不跟渲染纠缠;
  • Worker 只做代理和缓存。它越"笨",越不容易坏。

一个踩过的坑,分享给你

新闻聚合上过一次当。原来的逻辑是每次抓取后覆盖缓存快照——某个时刻 Google 被限流、Bing 恰好只回了 1 条,这份贫瘠的结果就把之前积累的 30 条完整快照冲掉了。用户打开一看,资讯页只剩孤零零一条。

修复之后,刷新改成了合并:新条目优先,历史条目去重补位,七天窗口内只增不减。这个教训值得写在这里:对一个防灾工具来说,一次糟糕的抓取,永远不该毁掉已有的积累。


四、如何上手?

git clone git@github.com:Trade-Offf/typhoon-bavi-tracker.git
cd typhoon-bavi-tracker
npm install
npm run dev          # Vite 开发服务器
npm run typecheck    # TypeScript 检查
npm run preview      # 构建 + wrangler 本地模拟 Worker

本地 vite dev 时,/api/typhoon 会代理到浙江水利厅接口。想看完整的 Worker 行为(缓存、容灾、新闻聚合),用 npm run preview


五、这些事,等你来做

下面的方向按"能多帮到一个人"排序,不按技术难度。在这个项目里,加一个城市比换一个框架更有价值。

防灾价值直接提升

方向说明从哪改
增加城市CITIES 数组加 { name, lng, lat },就这么简单src/impact.ts
10/12 级风圈倒计时扩展 computeImpacts 返回多级 ETAsrc/impact.ts
按定位排序城市navigator.geolocation + 距离排序src/app.ts
多台风切换TYPHOON_ID 改成路由参数——台风年年来,这个网站不该只为巴威活一次src/app.ts · worker/index.ts
地方化指南各省防汛细则、避险点。你比我更懂你的城市src/guide.ts

体验与传播

方向说明
i18n 繁体/英文面向港澳台与外籍居民
预警等级接入对接中央气象台预警信号
PWA 离线缓存断网前的最后一次数据,离线也能看——台风天最容易断网
分享卡片动态生成OG 图按城市倒计时实时渲染

数据源扩展(需合规评估)

现状思路
小红书反爬,无法匿名抓取需用户授权或官方 API
微博需访客 CookieWorker 端谨慎实现,注意 ToS
地方气象局多数有公开接口worker/ 加 fetcher 模块

性能待办(欢迎认领)

  • 风圈多边形点数按需简化(低缩放级别)
  • Service Worker 缓存最近一次 API 响应
  • 截图/OG 图 WebP 压缩
  • 单元测试:impact.ts · normalize.ts · geo.ts

六、提 MR 的姿势

流程很平常,Fork、开分支、自测、发 PR:

git checkout -b feat/your-feature-name
npm run typecheck   # 必须过
npm run build       # 必须过
npm run preview     # 本地把 Worker + 前端跑通

几个小约定:

  • commit message 讲"为什么",别只写"做了什么";
  • 一个 MR 只做一件事。加城市、修数据源、改样式,分开提;
  • 附上验证方式。截图或录屏,让 review 的人两分钟能确认。

比如这样:

feat(impact): 新增厦门、泉州城市倒计时

- 在 CITIES 数组补充闽南沿海城市
- 验证:打开 /?city=厦门 可看到倒计时弹窗

Review 时我会看类型安全、依赖体积、数据源的超时与容灾。但有一条比所有代码规范都重要:

这个项目的每一行文案,都可能在某个深夜被一个正在担心家人的人读到。 我们提供确定性,不制造焦虑;传递紧迫感,不贩卖恐慌。免责声明必须保留:倒计时是估算,防灾决策以官方预警为准。


七、为什么开源

防灾信息不应该被锁在任何人的服务器里——包括我的。

这个项目诞生于一个睡不着的凌晨,但它不该只属于那个凌晨。台风年年来,数据源会失效,城市名单会过时,指南会需要更新。一个人的热情撑不起一件需要长期存在的事,能撑起它的只有社区。

开源之后,事情就变得可以想象:数据源挂了,有人半小时内提个 fix;福建的朋友补上福建的避险点;有人 fork 一份部署到自己的域名,哪怕有一天 chinaupdated.com 不在了,代码还在,还能救急。

写代码的人常常怀疑自己做的东西有没有意义。但防灾这件事给了一个罕见的、清晰的答案——

技术人的理想主义,有时候就是写一段代码,让多一个人提前看到危险。

你修的每一个 bug,可能就是某个用户在断网前抢到的最后一次刷新;你加的每一个城市,背后都是真实住在那里的人。这里没有 KPI,没有增长曲线,只有一个朴素的度量:有没有人因为它,提前把窗户加固了、把水囤上了、把老人接走了。

欢迎 Star、Fork、提 Issue、提 MR。一个人力量有限,众人拾柴,总能多照亮一段路。

台风会走,代码会留下。愿它下一次被用到时,比这一次更好。


附录

自定义域名在 wrangler.jsoncroutes 中配置,当前绑定 chinaupdated.comwww.chinaupdated.com

截图索引

文件说明
docs/screenshots/01-overview-city-countdown.png总览:高德底图 + 城市列表 + 预警条
docs/screenshots/02-city-popup.png点击城市:倒计时弹窗 + 行动建议
docs/screenshots/03-news-panel.png实时资讯:多源媒体报道
docs/screenshots/04-guide-panel.png应对指南:五段式清单
docs/screenshots/05-forecast-compare.png四机构预报路径对比