在旧金山任意两点之间找出最平坦的路线
文章摘要
Flatten SF 是一个专门为旧金山做的路线规划网页:输入起点和终点,选择步行或骑车,它会给出从「最短」到「最平」的一整组候选路线,用一个滑块在它们之间切换。作者是 Drew Edwards(GitHub 账号 almostimplemented,HN 账号 almstimplmntd),HN 上的提交者是另一位用户 ishan0102,帖子没有附自述,作者本人在评论区回答问题。帖子拿到 300 多分、110 多条评论。
滑块背后的思路。 旧金山以坡多出名,但从 A 到 B,绕一点路往往能少爬很多坡。网站并不把两个目标硬加权成一个分数,而是求出所有「帕累托最优」路线:每一条都不会在距离和爬升两个指标上同时输给别的路线。滑块往右拖,路线只会更长、爬升只会更少,不会反过来。这里的「爬升」指沿途累计上升的高度,而不是起终点的海拔差。海拔剖面图、路线经过的街道列表和各项数字会随滑块实时变化,地图上还用淡色线画出同一组里的其他路线。
数据和计算。 所有计算都在浏览器里完成,覆盖约 16 万段街道。街道图来自 Overture/OpenStreetMap,高程用的是 USGS 3DEP 的 1 米分辨率「裸地」激光雷达数据,也就是去掉了建筑和树木的地形模型。作者先对高程栅格做 σ=3 米的高斯平滑,去除路缘、停放车辆等造成的噪声,再沿街道每 5 米采样一次。整张街道图压缩后约 4.8MB,作为静态文件放在 GitHub Pages 上,首次加载需要几秒,之后每次查询都在本地完成。地点搜索也是离线的:路口(如「24th & Mission」)、门牌地址和约一万个公园、地标、车站、商店都已打包进页面,不调用任何地理编码 API。
其他功能。 楼梯只在步行模式下可用,骑车模式会避开。骑车时默认开启「偏好安静街道」:根据旧金山交通局(SFMTA)的自行车道网络,有隔离车道或专用路径的路段按实际长度的 0.8 倍计算,普通安静街道按 1 倍,没有车道的繁忙主干道按 1.4 到 2 倍,爬升计算不受影响。作者测试了 20 条日常路线,开启后骑行距离平均只增加约 7%,在无车道主干道上骑行的比例却从 29% 降到 1%。「环线」按钮可以从同一起点规划一条跑步或骑行环线,滑块变成环线长度(1 到 15 英里),页面会尝试几十条方向不同的环线,展示最平的那条。路线可以导出成带高程的 GPX 文件,导入 Strava、Garmin Connect、Komoot 等应用;「复制链接」会生成能还原整个行程的 URL。
背后的分析。 网站只是项目的一部分。仓库 README 介绍了一份对旧金山「隐形平坦路网」的完整分析,几个结论很有意思:
- 对全部 1,260 个有序街区对取平均,爬升最少的步行路线只比最短路线长 14%,却少爬 39%,典型的最陡坡度也从 27% 降到 17%。
- 有两条「脊柱」撑起了全城的平路:南北向的 Mission 谷底(Valencia、Guerrero、Market、16 街一线,6.9 公里,平均坡度 1.3%),以及东西向的 Wiggle、Panhandle、金门公园一线(7.6 公里)。分析事先并不知道这两条线的存在。
- 骑行者熟知的 Wiggle 是模型自己算出来的:从 Market 与 Duboce 路口到 Haight 与 Masonic 路口,两条路线都必须爬升 47 米,但最短路线额外多爬 19.2 米,Wiggle 只多爬 5.9 米,距离只多 0.4%。
- 金门公园里一条无名小路(海拔约 255 英尺)是 630 个街区对中 119 对的瓶颈,东西向穿越全城至少要爬这么高。双子峰一带则没有便宜的翻越方式。
- 「总爬升最少」和「最陡坡度最小」是两个不同的目标:某个例子中,限制坡度的路线总共爬了 325 英尺,比最平路线的 281 英尺还多,但最陡处只有 10.8%,而后者是 17.2%。
此外还有一个「变形城市」视图,把地图上的距离改成按爬升代价计算:被山脊隔开的地方被推远,由平路连接的地方被拉近,双子峰被推开了将近 5 公里。
HN 评论精华
实测反馈与当场修复。 danserfaty 从外 Richmond 区的 Cabrillo 街到第 4 大道,工具让他去爬 25 大道再走 Geary,而不是走完全平坦的 23 大道。jeffbee 一开始怀疑是高程数据的问题。作者 almstimplmntd 查出来是解析器的 bug:Cabrillo 属于旧金山的 Slow Streets(慢行街道)计划,标签是「仅限目的地车辆」,被误读成禁止行人,整条街在路网里消失了。他修复并重新部署后,路线变成了 Cabrillo → 23 大道 → Geary,danserfaty 确认这次对了。不过这条回复也引来了 AI 腔的指控:jazzyjackson 把「genuine bug in the parser」列为 AI 生成的信号,bigmadshoe 指出开头那句干脆的短句和 Unicode 箭头都是 AI 文风的特征,他觉得用 AI 做项目没问题,但直接贴编码代理的回复让人不舒服。dekhn 说没必要这么刻薄。Doctor_Fegg 自己手写骑行路由引擎,他说交通法规、不一致的地图数据和用户偏好叠在一起,这类 bug 本来就很常见。mschuster91 则说,仓库 README 读起来很像 LLM 写的,贡献者列表里也有 Claude Code。
坡度比总爬升更重要。 这是讨论最集中的改进方向。laurencerowe 骑重型货运电助力车,他说坡度对体力的影响是非线性的:12% 的坡吃力但能骑,22% 的坡骑一个街区就快瘫了。他发现从 Mission Bernal 的 Safeway 到 Diamond Heights 的 Safeway,工具和 Google Maps 一样会走 29 街上 22.7% 的陡坡,而绕道 Clipper 街只有 12%。起点往旁边挪半个街区,结果就变得合理。jez 举了从 SOMA 到 Nob Hill 的例子:按总爬升算,直接沿 Taylor 街上去最平,但骑车的人都知道绕 Polk → California 舒服得多。作者给出了数字:Taylor 街爬 229 英尺、坡度 25%,绕 Polk 和 California 爬 251 英尺、坡度 13%。他承认目前只优化总爬升,坡度只显示不参与优化,加一个陡坡惩罚值得考虑。cowthulhu 担心「最小化坡度」会让路线像盘山路一样无限绕,Serishmus 说上山步道本来就是之字形修的,设一个最大坡度上限会很实用;avani 补充说,坡度上限对推轮椅和婴儿车的人意义很大。Terr_ 建议让用户自定义代价曲线。frollogaston 和 maccam94 争论在陡坡上推车走是不是比慢慢骑更快。
自行车道和安全。 geor9e 说工具让他在 Geary 和 Divisadero 骑车,「谢了,我还想活着」。作者回复说当时还没有自行车道数据,准备从 DataSF 获取 SFMTA 的自行车道网络(现在网站已有「偏好安静街道」选项)。qurren 问 Google Maps 骑行模式不就能做到吗,Arainach 和 ikawe 说要满足的条件很多,大多数人宁可爬一段有隔离车道的坡,也不愿走时速 45 英里的平路,而 Google Maps 常把人引到危险的主干道上。atoav 作为多年的城市骑行者说,他选路更看重连续无红绿灯的路段长度、路面质量,以及会不会遇到游客和出租车这类「混乱因素」。fnord77 吐槽 Apple Maps 每次都选最短路线:从市中心到 Bernal 的一家咖啡馆,最平路线只多 0.2 英里,最短路线却要多爬 300 英尺,坡度从 12% 变成 22%。
同类工具推荐。 andalinmicphew 是 bikehopper.org 的维护者之一,这个湾区路线规划工具已经做了约 5 年,综合考虑高程、自行车设施和公共交通。他强调在旧金山必须用 DTM(数字地形模型),因为 DSM(数字表面模型)包含建筑和树木,会让 Market 街看起来像一连串陡峭的山谷。他们对坡度使用加速惩罚,坡度翻倍时惩罚多于翻倍,并认为电助力车需要单独的路由配置。他还介绍了部署架构:原本跑在 k8s 集群上,后来为省电和省运维,把 nginx、Node 后端、fork 自 GraphHopper 的路由引擎、Photon 地理编码和 Nominatim 全部放到一台 6 核 32GB 的家用机器上。jeffbee 推荐 OpenTripPlanner,它有一个可以在时间、坡度和安全之间权衡的三角形调节器。folmar 和 ivanjermakov 推荐可自定义权重的 BRouter。cheeaun 提到自己 2014 年做的 steepless,ikawe 介绍了 maps.earth,ncan3 推荐了 Modemix。
「climb」是什么意思。 ziofill 问,爬升不是和路线无关吗?geor9e 解释:净海拔变化只取决于起终点,但「爬升」指累计上升高度。人腿没有再生制动,每爬一英尺消耗的能量,下坡时拿不回来。lukan 接话说,下一辆电助力车希望带能量回收刹车。voxadam 还咬文嚼字地区分了「平坦」和「水平」。
各地读者的需求。 西雅图、悉尼、伊斯坦布尔、休斯敦的读者都希望有自己城市的版本,作者回复说代码开源,欢迎照着做。secretsatan 住在一座多山的城市,他正在做一个步行版,因为当地人知道哪里有扶梯、电梯和地铁站可以绕开陡坡,而这些设施各有开放时间。作者说起自己在横滨出差时,找到一条约 2.5 公里、全程不碰任何机动车道的步行路线,Google Maps 完全找不到。artisinal 列举了荷兰十几种车辆类别各自能走哪些路,认为阿姆斯特丹更需要按车辆类型规划路线。modo_ 和 k7peak 则想要反向功能:找最陡的路,一个是为了跑步刷爬升,一个是为了玩下坡滑板。fhub 骑电助力车时会故意选起伏的路线,上坡用满助力,下坡冲过助力上限。