Skip to content

上海地区底图项目

动手实践:从 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 每日构建的全球底图地址格式:

https://build.protomaps.com/YYYYMMDD.pmtiles

两种方式找最新构建日期(官方指南推荐网页方式):

方式 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

执行过程说明:

  1. CLI 通过 HTTP Range Requests 只读取全球文件的元数据和上海区域的瓦片
  2. 根据网络情况,上海区域(zooms 0-15)只需几十秒到几分钟
  3. 输出文件 shanghai.pmtiles20~50MB(视 bbox 大小)

原理: PMTiles 的目录结构经过优化,Region Requests 可以跳过不相关的瓦片数据。

与官方指南一致,extract 也支持只提取部分 zoom 范围(不需要 bbox):

# 提取全球 zoom 0-6 子集(约 60MB)
pmtiles extract https://build.protomaps.com/20260805.pmtiles planet_z6.pmtiles --maxzoom=6

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
  • 想让文件更贴合边界可用下面的 --region GeoJSON 方式,但低 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

  1. 浏览器打开 https://pmtiles.io/
  2. shanghai.pmtiles 拖入页面 Drop Zone(或点击选择文件)
  3. 即可交互预览:缩放平移、图层开关、坐标信息——无需安装任何东西

选择 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 项目核心原理与代码阅读指南"
Links (2)