GeekHub

自建一张全国天气地图

我写了个小项目,把全国 372 个地级市的 7 天预报都放进一张地图上,公网上是 weather.gooney.fun。打开是张暗色的全国地图,每个城市一个标记,颜色随天气走,温度给的是区间,底下一条 7 天的时间轴能一天一天往后翻。

技术栈就三样:Node.js 后端、Leaflet 前端、和风天气 API,没数据库也没框架。下面讲讲它是怎么搭的,以及踩过的几个坑。

数据从哪来,怎么喂到页面上

数据源是和风天气。免费档限流很紧(一天 1000 次),372 个城市每个拉一次 7 天预报就是 372 次,不能每次用户打开页面都去拉。所以后端定死了一个规则:前端永远只读缓存,点地图不触发任何 API 调用。后台每天 06:00、11:00、18:00(北京时间)各拉一轮,拉完存进磁盘上的 weather-cache.json,下次服务重启也直接从这里恢复。

整体链路是这样的:

浏览器
  → <a href="https://weather.gooney.fun">weather.gooney.fun</a>   (nginx 反代 + SSL)
  → 127.0.0.1:18899            (node server.js,systemd 托管)
  → /api/weather               (只读内存缓存)
  → 和风天气 API                (后台定时拉,不随用户请求)

拉取逻辑本身不难,难的是保证地图永远完整。免费档限流加上家里宽带爱抖,一轮拉下来总有几个城市失败。为了不让”这一轮某个城市挂了”变成”地图上这个城市没了”,后端在拉取时做了几层保护:批量限速避免打爆限流、单个城市出错不牵连整批、失败的城市再补一轮重试、实在没拉到的就沿用上一轮的旧数据。这样顶多某些城市的数据旧一两个刷新周期,但绝不会出现整城空白。

前端长什么样

前端就是一个 index.html,Leaflet 画图。

底图用的是高德瓦片,用 CSS 滤镜压成暗色,跟整页的深色背景统一。每个城市的标记是自己拼的 DOM,里面是天气图标、温度区间、城市名,背景色按天气类型分——晴/多云 橙色,中到大雨 蓝色,暴雨/雷暴 红色,雪类白色,阴/雾/霾 灰色。温度显示的是 23°-25° 这种区间,不是单值。

时间轴前三天标”今天/明天/后天”,后面几天标周几,点开单个城市会弹出它当天的详情。

坐标这块最容易出问题

这是整个项目里最隐蔽、也返工最多次的一块。

高德地图的瓦片用的是 GCJ-02(俗称火星坐标),而不少数据源给的是 WGS-84。两种坐标不转换的话,所有城市会整体偏移,南方城市偏得最明显:南平能漂 37 公里,菏泽 30 公里,昆明 17 公里。所以从 WGS-84 来源加城市时,必须过一遍 WGS-84 → GCJ-02 的转换。

麻烦在于,和风天气在国内返回的坐标其实已经是 GCJ-02 了,对它再转一次就是二次偏移。代码里那段转换函数一直留着(给别的坐标源用),但对和风天气的数据必须关掉,否则城市又漂回去。之前有一次就是栽在这儿:本地代码手动改动之后把已经修好的转换逻辑回退了,线上坐标就又漂了。所以动坐标相关代码之前,一定要先确认本地文件和仓库里那版是一致的。

部署

后端跑成一个 systemd 服务,托管 node server.jsRestart=on-failure 加开机自启。改后端代码才需要重启,只改前端页面不用。

公网那层是 nginx 反代,weather.gooney.fun 接 SSL,转到后端。排查”页面打不开了”的时候,有个反直觉的点:公网返回 HTTP 200 不代表正常,如果返回的是 nginx 的目录列表页,说明请求根本没落到真正的服务上——要么 nginx 丢了这个域名的配置掉回了默认,要么后端端口被别的进程占了、真正的服务已经死了。这类”看着是 200、其实没开”的情况,得从公网入口、本地后端、服务状态一层层查过去,不能在第一个绿灯处停手。

现状

  • 城市数 372,按名称去重,覆盖 28 个省。
  • 预报 7 天,是免费档的上限,10/15/30 天得升级。
  • 每天 06:00 / 11:00 / 18:00 刷新。
  • 服务常驻,内存大约 50MB。

项目不大,没框架没数据库,但把免费 API 限流、家宽抖动、坐标系这三个真实世界的麻烦都处理得比较干净。后面再做类似的”外部数据代理 + 定时刷新 + 磁盘缓存”的东西,这套结构基本可以直接套用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注