独立开发者做带地图的应用,最怕的往往不是没人用,而是突然走红。像 Mapbox 这样的商业地图 API,一旦超出免费额度就要按每千次加载 5 美元计费,一夜爆火很容易变成一张天价账单。苏黎世联邦理工学院(ETH Zurich)计算机系学生 Bruce Röttgers 在开发旅行规划白板工具 Mapboard 时,用开源生态 Protomaps 的 PMTiles 格式配合廉价 CDN 服务商 Bunny.net,把 139GB 的全球地图单文件托管到了公网上。这套方案的全球多大陆 5 倍复制存储费每月约 5 美元,加载欧洲地图并放大到城市的 5MB 流量成本仅 0.00005 美元(折合每千次加载 0.05 美元),账面带宽成本比 Mapbox 超额单价便宜了 100 倍。但这并不意味着所有人都该立刻抛弃商业 API,因为算清真实的盈亏平衡线、换掉前端绑定的 SDK 并堵上百 GB 文件的流量敞口,才是这套方案从玩具走向生产环境的真正门槛。
单文件架构如何砍掉每月 500 美元的操作费
过去自托管基于 OpenStreetMap 数据的开源地图,是一场繁重的运维苦役。开发者要么得自己维护 PostgreSQL 与 PostGIS 数据库并跑着实时渲染服务器,要么就得在云端对象存储里上传数以亿计的碎瓦片文件,结果被高昂的磁盘 IOPS 和 API 请求费拖垮。

Protomaps 推出的 PMTiles 格式直接改变了底层逻辑。它把全球矢量瓦片打包进单一静态文件中,依靠内部目录结构和 HTTP Range(字节范围请求)机制,让客户端只按需拉取特定字节,把复杂的动态地图服务降维成了纯粹的静态对象存储与 CDN 分发问题。Protomaps 官方每日构建(Version 4)覆盖缩放级别 0 到 15 的全球文件大小通常在 120GB 左右(随每日构建动态波动,Röttgers 实测存储体积为 139GB),提供 BLAKE3 校验,官方存储桶还会保留过去一周的所有构建及各补丁版本的最新构建。
这种单文件设计对云存储账单是降维打击。在 Hacker News 社区里,有开发者把原本包含大量碎文件、每月耗费约 500 美元 S3 操作费的地图部署替换为单个 PMTiles 文件后,操作成本直接降到了几美分。
在实际分发环节,不同云厂商的脾气截然不同。如果选 Cloudflare,开发者需要把文件存入 R2,并部署官方 Worker 来把传统的 /world/{z}/{x}/{y}.mvt 端点和 /world.json 转换成 Range 请求,而且它的缓存机制要求必须绑定自定义域名,不能直接用默认的 workers.dev。相比之下,总部位于欧盟、默认丢弃 IP 地址最后一个字节以保护隐私的 Bunny.net 原生支持大文件 Range 请求缓存,省去了写边缘函数的麻烦。
便宜 100 倍背后的真实账本与前端计费陷阱
声称自托管比 Mapbox 便宜 100 倍很容易让人热血沸腾,但如果把两套计费模型拆开细算,你会发现不同流量规模和交互深度下的账单完全是两码事。

Bunny.net 的价格确实极低,但并非零成本。它实行账户级每月 1 美元的最低消费门槛(如果所有活跃区域月度总消耗只有 0.30 美元,依然按 1 美元扣费;消耗 3 美元则按 3 美元计费),好在免收 CDN 请求费、存储 API 费以及从 Bunny Storage 到 Bunny CDN 的内部传输费。其标准 HDD 存储单区域为 0.01 美元/GB/月,双区域 0.02 美元/GB/月,每增加一个 HDD 区域加收 0.005 美元/GB/月(最高复制级别 0.055 美元/GB/月)。带宽方面,欧美标准网络为 0.01 美元/GB(更小的 Volume 网络为 0.005 美元/GB),而亚太为 0.03 美元/GB、南美 0.045 美元/GB、中东与非洲 0.06 美元/GB。
| 计费维度 | Protomaps + Bunny.net + MapLibre | Mapbox 官方服务 (Mapbox GL JS) |
|---|---|---|
| 基础固定门槛 | 1 美元/月最低消费;139GB 五倍复制存储约 5 美元/月 | 月加载 5 万次以内 0 美元 |
| 超额/流量单价 | 欧美 0.01 美元/GB,亚太 0.03 美元/GB,南美 0.045 美元/GB,中东非洲 0.06 美元/GB | 5 万-10 万次 5 美元/千次;10 万-20 万次 4 美元/千次 |
| 100 万次月加载估算 | 约 21 美元(按每次加载消耗 2MB 欧美流量加存储测算) | 高达 3050 美元 |
| 重度交互成本敞口 | 随用户平移缩放产生的几十 MB 流量线性增加 | 单次会话最长 12 小时内无限平移缩放不加收费用 |
从上表能清晰看出真实的盈亏平衡点。如果你的项目月加载量低于 5 万次,直接用 Mapbox 的免费层其实是 0 美元,比需要交存储费和最低消费的 Bunny.net 更划算;只有当月加载量冲到 100 万次量级时,自托管方案(约 21 美元)才会对 Mapbox(3050 美元)展现出碾压级的成本优势。况且 Mapbox 是按单次访问一口价收费,如果遇到重度用户在 12 小时会话内疯狂平移缩放,Mapbox 不会多收一分钱,而自托管方案的流量表会随着几十 MB 的数据传输不停跳字。
只换瓦片不换前端内核,省下的带宽费依然会变成商业授权账单。
更隐蔽的坑在于前端渲染器。Mapbox GL JS 的计费在 Map 对象初始化那一刻就已经触发。哪怕你千辛万苦把 PMTiles 自托管到了外部 CDN,只要前端代码里还在引用 Mapbox GL JS 来渲染画面,超过 5 万次免费额度后依然会按阶梯被收取每千次 4 到 5 美元的加载费。开发者必须把前端彻底替换成开源 BSD 许可的 MapLibre GL JS 并挂载 pmtiles 协议插件,才能真正斩断按次计费的锁链。
跨越百 GB 单文件上线的五个工程暗礁
把一个上百 GB 的大文件直接扔上公网当生产环境用,远没有教程里写得那么轻松。从上传配置、性能延迟到功能补齐,一线开发者已经替后来者踩出了五个必须绕开的工程暗礁。

第一个暗礁是上传与防盗刷配置。直接通过 HTTP API 上传 139GB 文件会在 15 分钟后超时失败,Röttgers 给出的实战解法是开启存储区的 S3 兼容模式,在 tmux 会话或临时 VPS 里用 rclone copyurl 配合 256M 分块(--s3-chunk-size 256M)流式直传。文件传上去后,必须在拉取区(Pull Zone)设置 CORS 允许的 Referrer 域名,并开启大文件分发优化(Optimize for large object delivery)来激活 Range 缓存。更要命的是防止有人恶意拖走整个 139GB 文件给你留下一笔巨额流量账单。Röttgers 采用了一个巧妙的工程限制,即在网络限制里设置单次请求传输满 10MB 后,自动将下载速度掐死在 10kB/s(正常浏览地图绝不会触发单连接 10MB),而 Protomaps 作者也建议在前端加一层 Lambda 或轻量级瓦片服务器来彻底隔离风险。
第二个暗礁是冷启动性能争议。OpenFreeMap 的作者曾在 2024 年 9 月指出,全球 PMTiles 演示版冷加载耗时高达 8 到 10 秒;Protomaps 作者随后回应解释,那是因为演示版直连了未加边缘缓存的 S3 us-west-2 节点。而在 2025 年 8 月针对 BunnyCDN 的实测中,开启缓存切片后,缓存命中的 PMTiles Range 请求单次耗时仅在 20 到 35 毫秒之间,相比缓存独立瓦片仅增加 39 毫秒的页面总加载时间。
最后三个暗礁关乎日常运维与功能完整性:
- 缺乏增量更新机制.PMTiles 目前无法只更新局部变动数据,每次更新全球地图都意味着要重新上传上百 GB 的新快照并切换客户端源。
- 缺失地址搜索(Geocoding):PMTiles 只负责把地图画面画出来,不包含地理编码与地址搜索索引,开发者如果做旅行规划或门店查询,还得额外部署像 Photon 这样的独立服务来实现搜索框自动补全。
- 风险.无脑托管 139GB 全球包在冷缓存淘汰和全量更新时代价太高,除非你的业务真的需要让用户随时放大查看地球上任何一个角落的街道。
对于绝大多数中小型团队来说,最理性的做法是利用官方的 go-pmtiles 命令行工具按经纬度边界框(Bounding Box)或特定缩放级别裁切数据。就像社区里那位开发澳大利亚门店定位器的工程师一样,用约 50MB 的全球概览图搭配 900MB 的澳大利亚区域高精度归档,既能把账单死死按在每月几美分,又能彻底避开百 GB 巨型文件的运维泥潭。
