⚡ OpsAILab.cc

中国 2,200 年,被压进一个网页,还能拖着看

18 个朝代、164 张疆域快照,纯前端零依赖。更狠的是它让 AI 不用知道经纬度,也能定位一句诗里的地点——拆解「江山时序」的三个设计。

· 8 分钟阅读 · 数海独航
#项目拆解#历史地图#MCP#AI 工具

读古文时,脑子里那张地图是空的

「即从巴峡穿巫峡,便下襄阳向洛阳。」杜甫写这句是公元 763 年,安史之乱刚平,他在梓州听到官军收复失地的消息,想立刻顺江东下回中原。

这句诗你大概背过。但巴峡、巫峡、襄阳、洛阳分别在今天哪里,中间隔多远,走水路要几天——多半是模糊的。

这不是记忆力的问题。中国历史文本里塞满了地名,而其中一大半今天已经不存在了:州、郡、道、路、府,每一级都随朝代改。你记住的是名字,脑子里缺的是坐标。

「江山时序」解决的就是这件事。它目前 3,357 Star、398 Fork,GPL-3.0 协议,纯 JavaScript——没有后端,没有构建步骤。数据是我从 GitHub API 和站点自己的数据文件里核的,不是转述别人的介绍。

江山时序首屏:底部时间轴与战国疆域地图
战国首屏:拖动底部时间轴,疆域色块、政权名、右侧大事记会同步变化

它只做三件事,但三件事咬得很紧

打开首页,最先看到的是底部那条时间轴:战国、秦、西汉、东汉、三国、西晋、东晋十六国、南北朝、隋、唐、五代、北宋、南宋、元、明、清、民国、现代,18 个朝代一字排开,下面是年份刻度,从公元前 403 年一直排到今天。

拖一下滑块,地图上的色块跟着变大变小。到这儿还不算稀奇——静态的疆域 GIF 早就有了。它多做的是两件事。

点地溯源

点地图上任何一座城,弹出一张沿革卡:从最早见于记载的名字,到历代所属的郡、州、路、府,再到今天的省市,中间夹着发生在这块地上的大事。

唐开元二十九年长安沿革卡:十三朝建都与 41 条史事
唐开元二十九年点开长安:十三朝建都、41 条史事,右侧同步列出该年的事件

长安那张卡里,从西周一路到唐,每一朝的名字变更和建都时间都在。它把「地名沿革」做成了一条能点开的时间线,而不是一张干巴巴的对照表。

点人溯事

搜一个人的名字,它把这个人一生走过的地点按年份连成路线,同时自动切到对应的朝代底图。

苏轼一生行迹地图:从眉州到儋州
苏轼一生行迹:眉州出蜀,经汴京、凤翔、杭州、密州、徐州、湖州,一路贬到黄州、惠州、儋州

苏轼这条线,教科书上一句「一贬再贬」就带过了。画在地图上才发现:从湖州到儋州横跨整个中国,而走这条路时他已经六十岁。

作者在自述里说,这三个功能不是一次想好的,是一条接一条长出来的:先把时间这根横轴做出来,就想能不能从一座城出发、走地理这根纵轴;横纵都有了,人自然就走进来了。

有了横纵两条坐标轴,天与地都有了,那作为历史主角的人,是不是也能自然而然地走进这张地图?

—— 作者自述

最漂亮的一个决定:让 AI 不知道经纬度

这是我认为整个项目最值得抄的设计。

现在的通行做法是给大模型一个地图 API,让它自己查坐标。问题是模型会编经纬度——给一个看着像那么回事的坐标,然后在撒哈拉边上插面旗。

江山时序反过来:它不要坐标,只要两个语义参数——年份和地名。

于是有了这套「零坐标 URL 协议」,三种精度:

  • 单点定位:?year=819&place=蓝关 —— 跳到唐元和十四年,在秦岭上标出韩愈「雪拥蓝关马不前」那座关
  • 同年多点:?year=763&place=剑外,巴峡,巫峡,襄阳,洛阳 —— 在 763 年的唐图上连出杜甫那条回乡路线
  • 跨年时空:?year=1079,1080,1094,1097&place=湖州,黄州,惠州,儋州 —— 年份与地点一一配对,苏轼四次贬谪按年排开
杜甫 763 年回乡路线:剑外、巴峡、巫峡、襄阳、洛阳五站相连
杜甫 763 年回乡路线:五站编号相连,全程约 991 公里,可逐站查看沿革

上面这张就是第二条链接直接渲染出来的结果。没有任何人工标注,只给了年份和五个地名,它自己算出了 991 公里的全程和各站位置。

这背后是一整套给 AI 用的接口:

入口作用
llms.txt机器可读的规范,告诉模型能传什么参数
本地 MCP Server零 npm 依赖,3 个工具:查地点、查人物、全文搜索,离线响应 5ms 内
Agent Skill带一个 Python CLI,一条命令直接截出地图 PNG
atlasAPI / WebMCP浏览器里也能被 Agent 直接驱动

那个 MCP Server 走 stdio,接进 Claude Desktop 或 Cursor 只要在配置里加一行路径。没有 npm install,没有依赖树,这点在 MCP 生态里相当少见。

这意味着什么:让 AI 写一篇讲杜甫生平的稿子,它可以自己调这个工具,把每个地点标出来、截成图,不用人肉去截图拼版。

这段不是推测。我把它的 CLI 装上跑了一遍:--person 苏轼 一条命令,8 秒后拿到一张 2880×1800 的图——地图上 12 个节点从眉州一路连到常州,右侧是完整的生平纪事,每个节点都挂着年份、当时的州府名、今天的城市名,还有对应的诗句。

再加一个 --inspect,同一份数据还能拿到 JSON:dynasty、polity、modernProvince、nearestCity 全在字段里,连「成都府路文献名邦,三苏故里」这种考据注都带着。这意味着做历史视频的人,分镜脚本和地图素材可以是同一次调用出来的。

技术底子:一个不装框架的地图

把前端翻一遍,有几个数字值得说。

第一,没有地图库。没有 Leaflet、没有 Mapbox、没有 D3,也没有 Vue 或 React。整个项目就是几个 JS 文件加一份 CSS。README 写得很直白:克隆下来,python3 -m http.server 8000,打开就能用。

第二,省界是自己压的。china_geo_data.js 那 85KB 里,装着全国省级边界的完整多边形——用的是 VLQ 增量编码,跟 Mapbox Vector Tile 压缩坐标是同一套思路:把经纬度差分成变长整数,再用自定义 base64 变体编码。文件头一行写着「Lossless VLQ Delta-Encoded Rings」,无损。

第三,数据全内联。window.DYNASTY_DATA 和 window.ATLAS_LORE_DATA 是两个巨大的 JS 字面量。把里面的条目数出来是这样:

内容数量
朝代阶段18 朝 · 164 张疆域快照
帝王纪年351 条
地点史事687 条
人物档案124 位
政权档案110 个
古今地名186 条 · 87 座城
名关雄隘20 座
历史行迹7 条

这些条目从哪来,值得单独说一句。README 的 Credits 列了四份底本:谭其骧主编《中国历史地图集》、哈佛燕京学社与复旦的 CHGIS V6、Robert M. Hartwell 的中国历史 GIS 数据集,以及 aourednik 的 historical-basemaps;今天的省界用的是 DataV GeoAtlas 公开行政区划。

CONTRIBUTING 里还有条硬规矩:要改边界,必须在 PR 里写明参考的是哪本图集的哪一册哪一幅。这不是随手画的色块。

但这里埋着一处授权陷阱。 郡级边界和战国疆域派生自 CHGIS V6 与 Hartwell 数据集,这两份原始数据限定学术、非商业用途,作者明确写了它们没有随 GPL-3.0 一起再许可。整站是 GPL,但那部分几何不是。看、引用、写文章都没问题,拿去做商业产品之前,得先把这条读明白。

取舍很清楚:没有 API,所以搜一搜爬得到、离线能用、部署成本接近零;代价是首屏要下 2MB 出头的数据,第一次打开不算快。

还有个细节挺有意思:右上角那个「雅乐」按钮,点开会放古琴曲——《流水》《平沙落雁》《阳关三叠》《醉渔唱晚》《酒狂》,五首,数据里就带着音频地址。地图配古琴,这不太像工程师会做的事。

短板也很明显

先说最要紧的一句:它的历史疆域与政区是示意性近似,不是精确测绘边界。这话不是加的,是站点自己写在页面右下角的免责声明。看趋势、找位置没问题,拿去做严肃的边界论证就别了。

数据校勘是靠人力一点点磨的。作者自述里提了两个例子:有一次放大地图才发现,辽国上京被画在了辽国疆域外面;还有一次,横跨欧亚的蒙古帝国在罗布泊那儿莫名破了个洞。这类错误在历史地图里太常见,他的处理方式是翻《资治通鉴》和各朝正史一条条对。

另外它的 GeoJSON 导出只覆盖到 1948 年,之后的部分和现代省界不在导出范围内。想拿去做二次开发,这点得先知道。

还有个挺现实的细节。作者把项目发小红书、发知乎都被限流,投给几个中文 GitHub 推荐社群也没什么动静。最后是 X 上有人转发,几天涨了一千多 star。火了之后,倒有不少人搬到小红书和公众号上,拿到了不错的流量。

这段读起来有点不是滋味,但它确实说明一件事:好项目不等于好传播。

三种用法,和一个彩蛋

想逛的,直接开网页,不用注册:deanchen.me/jiangshan-map,手机端也能用。

想本地跑的,把仓库克隆下来,进目录执行:

python3 -m http.server 8000

浏览器打开 localhost:8000。没别的步骤,不需要 node,不需要装包。

想喂给 AI 的,在 MCP 客户端里加一段配置,把 command 指到 node、args 填仓库里 scripts/mcp_server.js 的绝对路径就行。或者直接用仓库自带的 Agent Skill,里面有 CLI:--year 819 --place 蓝关,跑完直接给你一张图。

另外藏了个彩蛋:古地名挑战。随机给你一个「西汉时期的武威」,让你在地图上点出位置,答完算分给等级。

古地名挑战:根据朝代和地名在地图上标定位置
古地名挑战:给出朝代和地名,要在疆域图上标定位置,共十题

这题真正难的地方在于:你可能记得「河西四郡」这个词,但说不出它们在今天的哪个位置。玩两轮,比看十遍疆域图记得牢。

最后

作者在自述里说,他上学时最爱翻历史课本里的地图,看大唐疆域幻想贞观开元,翻到宋朝为燕云十六州叹气。工作以后读《两晋悲歌》,五胡十六国那团乱麻怎么也拼不出地图,这才自己动手做。

今年 7 月他试过一版,做的是历史地理小游戏,做到一半觉得「我更想看的是那张地图本身」,就搁下了。10 月模型能力上来,他把项目捡回来,只做地图——两周半,两百多次提交,大半在深夜和周末。

评论区有一句话被顶得很高,我觉得比任何产品介绍都准:

拖动时间轴的那一刻,朝代不再是课本上的名词,而是脚下可以反复往返的山河。好工具让记忆有了坐标。

顺便说个数据:10 月 25 号作者写自述时,项目是 1400 star;一周多之后的今天,3,357。增长还在加速。

对我自己来说,这个项目最值得带走的不是那 164 张快照,而是那套「让 AI 传语义、不传坐标」的接口设计——做企业内部知识库、做数据可视化,思路是相通的。