上海地区底图项目
动手实践:从 Protomaps 全球底图远程裁剪上海地区,不需要下载 128GB 全量文件。 本文是 Protomaps 研究的核心项目。
项目目标
- 用
pmtiles extract从远程全球底图裁剪上海地区 →shanghai.pmtiles - 本地验证:
pmtiles show+ 浏览器 MapLibre 渲染 - 最终部署:R2 / S3 静态托管
1. 安装 pmtiles CLI
安装方式见 环境准备:pmtiles CLI
2. 找到最新的全球底图并裁剪
本步目的: 确定最新构建号(
https://build.protomaps.com/YYYYMMDD.pmtiles), 然后用pmtiles extract远程裁剪出上海区域——全程不下载 128GB 全量文件。
Protomaps 每日构建的全球底图地址格式:
两种方式找最新构建日期(官方指南推荐网页方式):
方式 1:网页查看 maps.protomaps.com/builds/
浏览器打开即可看到每日构建列表,取最新日期的构建号(如 20260805)。
方式 2:API 查询
# 取最新一条构建信息
curl -sL https://build-metadata.protomaps.dev/builds.json | python3 -c "
import sys, json
data = json.load(sys.stdin)
latest = data[-1]
print(f'最新构建: {latest[\"key\"]}')
print(f'版本: {latest.get(\"version\", \"N/A\")}')
print(f'日期: {latest.get(\"uploaded\", \"N/A\")}')
print(f'大小: {latest[\"size\"] / 1024**3:.1f} GB')
"
# 或直接取最新构建文件名
curl -sL https://build-metadata.protomaps.dev/builds.json | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data[-1]['key'])
"
确定构建号后,可先用 pmtiles show 远程验证该构建的元数据(OSM 数据截止日期等):
pmtiles show https://build.protomaps.com/20260805.pmtiles
# planetiler:osm:osmosisreplicationtime: 2026-08-05T05:00:00Z ← OSM 数据截止日期
⚠️ 以下命令中的
20260805均为示例构建日期,执行前请替换为第二步查到的实际日期。注意:全球底图约 128GB,不要下载到本地,直接使用远程 URL 裁剪。 参考官方指南:Getting Started
pmtiles extract 支持直接从远程 URL 读取并裁剪,无需下载完整文件:
# bbox 格式:minLng,minLat,maxLng,maxLat
# 上海市区范围约 120.8,30.6,122.2,31.9(含崇明岛,见下方说明)
pmtiles extract \
https://build.protomaps.com/20260805.pmtiles \
shanghai.pmtiles \
--bbox=120.8,30.6,122.2,31.9
执行过程说明:
- CLI 通过 HTTP Range Requests 只读取全球文件的元数据和上海区域的瓦片
- 根据网络情况,上海区域(zooms 0-15)只需几十秒到几分钟
- 输出文件
shanghai.pmtiles约 20~50MB(视 bbox 大小)
原理: PMTiles 的目录结构经过优化,Region Requests 可以跳过不相关的瓦片数据。
与官方指南一致,
extract也支持只提取部分 zoom 范围(不需要 bbox):
bbox 参考(中国主要城市群)
| 区域 | bbox | 预估文件大小 |
|---|---|---|
| 上海市区 | 120.8,30.6,122.2,31.9 | ~20MB |
| 长三角 | 118.5,29.5,123.0,32.5 | ~80MB |
| 京津冀(北京) | 115.0,38.5,118.0,41.5 | ~70MB |
| 珠三角(广州) | 112.5,21.5,115.5,24.0 | ~60MB |
| 成都/重庆 | 103.0,29.0,107.0,31.5 | ~50MB |
| 香港 | 113.8,22.1,114.5,22.6 | ~10MB |
上海的 bbox 是怎么来的
bbox 是手工框出来的外接矩形(不是算法计算),用 bboxfinder 在上海市域图上画框复制坐标,或从行政边界取外包矩形。
对照上海市域官方范围(常用表述:东经 120°51′—122°12′,北纬 30°40′—31°53′ ≈ 120.85,30.67,122.2,31.88):
| bbox 值 | 对应地理边界 | 取法 |
|---|---|---|
120.8(西界) | 青浦/松江西部,市域西界约 120.85°E | 留 ~0.05° 白(覆盖到昆山边缘) |
30.6(南界) | 金山/奉贤南端,杭州湾北岸约 30.67°N | 留 ~0.07° 白(覆盖到平湖边缘) |
122.2(东界) | 长江口/东海,市域东界约 122.2°E | 与官方东界一致,含外海 |
31.9(北界) | 崇明岛北端约 31.88°N | 略大于官方值,完整覆盖崇明岛 |
要点:
- 行政边界不是矩形,外接矩形必然包含邻省边缘(青浦西边是江苏、南边是浙江)——这是正常现象
- 旧值北界曾用
31.8,会切掉崇明最北端 ~0.08°,已改为31.9 - 想让文件更贴合边界可用下面的
--regionGeoJSON 方式,但低 zoom 瓦片仍是矩形网格、无法精确贴合 - 重新框选工具:bboxfinder.com(画矩形复制坐标)、geojson.io(导入边界自动显示 bounding box)
进阶:GeoJSON 不规则区域提取
pmtiles extract \
https://build.protomaps.com/20260805.pmtiles \
shanghai-area.pmtiles \
--region=shanghai-region.geojson \
--maxzoom=14
GeoJSON 可用 geojson.io 在线绘制导出,或从 openstreetmap.org 导出行政边界。
3. 预览下载的 .pmtiles 地图
下载/裁剪得到 shanghai.pmtiles 后,选择 1:在线工具 pmtiles.io:
- 浏览器打开 https://pmtiles.io/
- 把
shanghai.pmtiles拖入页面 Drop Zone(或点击选择文件) - 即可交互预览:缩放平移、图层开关、坐标信息——无需安装任何东西
选择 2:本地查看(静态服务器 + MapLibre pmtiles:// 协议)见下方第 5 步——test-map.html 已实测通过。
4. 验证裁剪结果
# 查看文件信息
pmtiles show shanghai.pmtiles
# 输出示例:
# tile_type: mvt
# tiles: <实际瓦片数>(如 12945)
# tile_compression: gzip
# min_zoom: 0
# max_zoom: 15
# bounds: 120.8, 30.6, 122.2, 31.9
# center: 121.5, 31.2
# description: Extracted from build.protomaps.com/20260805.pmtiles
# 查看包含哪些图层
pmtiles show --metadata shanghai.pmtiles | python3 -c "import sys,json; d=json.load(sys.stdin); print('图层:', [l.get('id') for l in d.get('vector_layers', [])])"
# 输出示例:图层: ['boundaries', 'buildings', 'earth', 'landcover', 'landuse', 'places', 'pois', 'roads', 'water']
5. 本地查看 .pmtiles(test-map.html)
已实测通过。静态服务器 + MapLibre
pmtiles://协议(参考官方 PMTiles in MapLibre GL)。
cd /path/to/pmtiles
# npx 免全局安装,首次运行自动下载 http-server
npx -y http-server . --cors --port 8080
# 验证:浏览器访问 http://localhost:8080/shanghai.pmtiles 可下载
保存 test-map.html 到 http-server 根目录:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Protomaps Test</title>
<style>body { margin: 0 } #map { width: 100vw; height: 100vh }</style>
<script src="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/pmtiles@3/dist/pmtiles.js"></script>
<script src="https://unpkg.com/@protomaps/basemaps@5/dist/basemaps.js"></script>
</head>
<body>
<div id="map"></div>
<script>
const protocol = new pmtiles.Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
const map = new maplibregl.Map({
container: "map",
style: {
version: 8,
glyphs: "https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf",
sprite: "https://protomaps.github.io/basemaps-assets/sprites/v4/light",
sources: {
protomaps: {
type: "vector",
url: "pmtiles:///shanghai.pmtiles", // http-server 下用相对路径
attribution: '© <a href="https://protomaps.com">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
},
},
layers: basemaps.layers("protomaps", basemaps.namedFlavor("light"), { lang: "zh" }),
},
center: [121.47, 31.23],
zoom: 12,
});
</script>
</body>
</html>
访问 http://localhost:8080/test-map.html 即可看到上海地图。
原理:
pmtiles://协议由 pmtiles.js 在客户端实现(header → 目录 → 瓦片三步 Range 请求), 详见 PMTiles 格式与工具链。
部署方案
PMTiles 最大的优势是部署简单——不需要地图服务器:
| 平台 | 说明 | 成本 |
|---|---|---|
| Cloudflare R2 | 兼容 S3 API,免流量费 | 存储费 ~$0.015/GB/月 |
| AWS S3 | 需开启 CORS | 标准 S3 定价 |
| 自建 Web Server | 需支持 Range Requests(Nginx / Caddy / Apache 等均可) | 取决于服务器 |
部署后 MapLibre 中改用完整 URL:
url: "pmtiles://https://cdn.example.com/shanghai.pmtiles"参考官方:Cloud Storage(S3 / R2 / 自建对象存储部署指南)
常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
pmtiles extract 很慢 | 远程全球文件大,Range 请求有延迟 | 加 --maxzoom=12 限制最大缩放级别 |
| 能看到上海以外的区域 | 正常:extract 按瓦片复制,低 zoom 瓦片覆盖全球/大洲(zoom 0 一张瓦片=全球);pmtiles show 的 bounds 仍是上海 | 不影响使用;想精简可加 --minzoom=6(缩到 minzoom 以下会空白) |
| 裁剪文件过大 | bbox 太大或 zoom 太高 | 缩小 bbox 或加 --maxzoom 限制 |
| 地图白屏 | pmtiles 协议未注册 | 确认调用了 maplibregl.addProtocol("pmtiles", protocol.tile) |
| CORS 错误 | http-server 未加 --cors | 重新启动:npx -y http-server . --cors |
| 字体不显示 | glyphs URL 不可达 | 使用 Protomaps 托管的字体 |
| 中文标签乱码 | 未设置 lang 或 OSM 无中文名 | 在 layers() 中加 { lang: "zh" } |
参考
Backlinks (3)
flowchart LR
n0["AI"]
n1["Database"]
n2["Dev Tools"]
n3["Emoji"]
n4["Frontend"]
n5["Game Dev"]
n6["Collection"]
n7["Languages"]
n8["Maps"]
n9["Media"]
n10["Monitor"]
n11["Plans"]
n12["Cloud"]
n13["对象存储基本用法(Bucket / Object / 常用操作)"]
n14["对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"]
n15["Object Storage"]
n16["挂载 Bucket 为本地文件系统(FUSE Mount)"]
n17["对象存储签名 URL(Signed URL)原理与实战"]
n18["对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"]
n19["Prototypes"]
n20["Research"]
n21["Better Auth 源码阅读指南"]
n22["DuckDB 环境与基本使用"]
n23["DuckDB 实战研究"]
n24["DuckDB 模拟数据"]
n25["PostgreSQL 数据用 DuckDB 加速查询"]
n26["HK 角色阅读"]
n27["HK 台词阅读"]
n28["Hollow Knight 英语主题"]
n29["HK 物品阅读"]
n30["HK 地点阅读"]
n31["HK 世界观 / Lore 阅读"]
n32["HK 英语学习素材清单"]
n33["英语学习 Dashboard"]
n34["English Scraps Archive"]
n35["English Scraps 使用指南"]
n36["Jellyfin 源码阅读指南"]
n37["Lux 资料整理"]
n38["Nest Commander 学习资料"]
n39["NestJS 源码阅读指南"]
n40["Protomaps 自建底图研究"]
n41["自制 PMTiles 地图(最简单例子)"]
n42["MapLibre 集成 Protomaps"]
n43["PMTiles 格式与工具链"]
n44["上海地区底图项目"]
n45["Redash 源码阅读指南"]
n46["Rust 学习计划"]
n47["shadcn/ui 源码阅读指南"]
n48["TRIP 项目核心原理与代码阅读指南"]
n1 --> n23
n6 --> n0
n6 --> n1
n6 --> n2
n6 --> n3
n6 --> n4
n6 --> n5
n6 --> n7
n6 --> n8
n6 --> n9
n6 --> n10
n6 --> n11
n6 --> n20
n12 --> n15
n13 --> n14
n13 --> n17
n13 --> n18
n13 --> n19
n14 --> n13
n14 --> n16
n14 --> n17
n14 --> n18
n15 --> n13
n15 --> n14
n15 --> n16
n15 --> n17
n15 --> n18
n16 --> n13
n16 --> n17
n16 --> n18
n17 --> n19
n18 --> n13
n18 --> n14
n18 --> n17
n18 --> n19
n19 --> n13
n19 --> n16
n19 --> n17
n19 --> n18
n19 --> n40
n20 --> n21
n20 --> n23
n20 --> n33
n20 --> n36
n20 --> n37
n20 --> n38
n20 --> n39
n20 --> n40
n20 --> n45
n20 --> n46
n20 --> n47
n20 --> n48
n22 --> n24
n23 --> n1
n23 --> n20
n23 --> n22
n23 --> n24
n23 --> n25
n24 --> n22
n24 --> n25
n25 --> n23
n25 --> n24
n28 --> n26
n28 --> n27
n28 --> n29
n28 --> n30
n28 --> n31
n28 --> n32
n28 --> n35
n32 --> n27
n33 --> n28
n33 --> n34
n33 --> n35
n35 --> n33
n35 --> n34
n40 --> n8
n40 --> n41
n40 --> n42
n40 --> n43
n40 --> n44
n41 --> n43
n42 --> n19
n42 --> n44
n43 --> n41
n43 --> n44
n44 --> n42
n44 --> n43
click n0 "../../../../collection/ai/" "AI"
click n1 "../../../../collection/database/" "Database"
click n2 "../../../../collection/dev-tools/" "Dev Tools"
click n3 "../../../../collection/emoji/" "Emoji"
click n4 "../../../../collection/frontend/" "Frontend"
click n5 "../../../../collection/game-dev/" "Game Dev"
click n6 "../../../../collection/" "Collection"
click n7 "../../../../collection/languages/" "Languages"
click n8 "../../../../collection/maps/" "Maps"
click n9 "../../../../collection/media/" "Media"
click n10 "../../../../collection/monitor/" "Monitor"
click n11 "../../../../collection/scraps/plans/" "Plans"
click n12 "../../../../knowledge/infrastructure/cloud/" "Cloud"
click n13 "../../../../knowledge/infrastructure/cloud/object-storage/basic-usage/" "对象存储基本用法(Bucket / Object / 常用操作)"
click n14 "../../../../knowledge/infrastructure/cloud/object-storage/data-migration/" "对象存储数据迁移(R2 → MinIO / 跨厂商搬迁)"
click n15 "../../../../knowledge/infrastructure/cloud/object-storage/" "Object Storage"
click n16 "../../../../knowledge/infrastructure/cloud/object-storage/mount-bucket/" "挂载 Bucket 为本地文件系统(FUSE Mount)"
click n17 "../../../../knowledge/infrastructure/cloud/object-storage/signed-url/" "对象存储签名 URL(Signed URL)原理与实战"
click n18 "../../../../knowledge/infrastructure/cloud/object-storage/vendors-comparison/" "对象存储供应商对比(S3 / R2 / OSS / Supabase / MinIO)"
click n19 "../../../../prototypes/" "Prototypes"
click n20 "../../../" "Research"
click n21 "../../better-auth/" "Better Auth 源码阅读指南"
click n22 "../../duckdb/basic-usage/" "DuckDB 环境与基本使用"
click n23 "../../duckdb/" "DuckDB 实战研究"
click n24 "../../duckdb/mock-data/" "DuckDB 模拟数据"
click n25 "../../duckdb/postgresql-acceleration/" "PostgreSQL 数据用 DuckDB 加速查询"
click n26 "../../english/hollow-knight/characters/" "HK 角色阅读"
click n27 "../../english/hollow-knight/dialogues/" "HK 台词阅读"
click n28 "../../english/hollow-knight/" "Hollow Knight 英语主题"
click n29 "../../english/hollow-knight/items/" "HK 物品阅读"
click n30 "../../english/hollow-knight/locations/" "HK 地点阅读"
click n31 "../../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
click n32 "../../english/hollow-knight/resources/" "HK 英语学习素材清单"
click n33 "../../english/" "英语学习 Dashboard"
click n34 "../../english/scraps/archive/" "English Scraps Archive"
click n35 "../../english/scraps/" "English Scraps 使用指南"
click n36 "../../jellyfin/" "Jellyfin 源码阅读指南"
click n37 "../../lux/" "Lux 资料整理"
click n38 "../../nest-commander/" "Nest Commander 学习资料"
click n39 "../../nestjs/" "NestJS 源码阅读指南"
click n40 "../" "Protomaps 自建底图研究"
click n41 "../make-own-map/" "自制 PMTiles 地图(最简单例子)"
click n42 "../maplibre/" "MapLibre 集成 Protomaps"
click n43 "../pmtiles/" "PMTiles 格式与工具链"
click n44 "./" "上海地区底图项目"
click n45 "../../redash/" "Redash 源码阅读指南"
click n46 "../../rust/" "Rust 学习计划"
click n47 "../../shadcn-ui/" "shadcn/ui 源码阅读指南"
click n48 "../../trip/" "TRIP 项目核心原理与代码阅读指南"