MapLibre 集成
本页目的: 讲解如何在前端(MapLibre GL JS)渲染已制作好的
.pmtiles底图(如 上海地区底图项目 裁剪出的shanghai.pmtiles)。 只涉及渲染代码——数据就是那一个.pmtiles文件,无需下载任何额外数据。与「上海地区底图项目」第 5 步(test-map.html 快速验证)不同,本页系统讲解集成到自己应用的方法: 协议原理、
@protomaps/basemaps完整样式、React 组件、样式切换与性能。🚀 本文方案的实战封装见原型 protomaps-map-view:把本页的 React 集成方式封装成通用 MapView 组件(中心/缩放、marker/轨迹、底图切换、可嵌入纯 HTML 或发 S3 分发)。 ⚠️ 3D 地形(TerrainControl)不在本页范围——它需要单独的 DEM 高程数据,与 Protomaps 底图无关。
1. 原理:pmtiles 自定义协议
MapLibre 默认不认识 .pmtiles 文件。通过 pmtiles npm 包的 Protocol 注册一个 自定义 URL 协议(pmtiles://),MapLibre 请求瓦片时就会走该协议处理器, 由它通过 HTTP Range Requests 从 PMTiles 文件中提取所需瓦片:
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { Protocol } from "pmtiles";
// 注册 pmtiles 协议(全局一次即可)
const protocol = new Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
关键点: source 的
url必须以pmtiles://为前缀,MapLibre 才会使用注册的协议处理器。
2. 最简集成
const map = new maplibregl.Map({
container: "map",
style: {
version: 8,
sources: {
protomaps: {
type: "vector",
url: "pmtiles:///shanghai.pmtiles", // 本地相对路径
attribution: '© <a href="https://protomaps.com">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
},
},
layers: [ /* 矢量图层定义 */ ],
},
center: [121.47, 31.23],
zoom: 11,
});
URL 写法:
| 场景 | url |
|---|---|
| 本地相对路径(http-server) | pmtiles:///shanghai.pmtiles |
| 远程对象存储 | pmtiles://https://cdn.example.com/shanghai.pmtiles |
3. 完整底图样式:@protomaps/basemaps(推荐)
手写所有道路、水系、建筑、地名图层太繁琐。Protomaps 提供 npm 包自动生成完整样式:
import { layers, namedFlavor } from "@protomaps/basemaps";
import { Protocol } from "pmtiles";
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
const protocol = new 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",
attribution: '© <a href="https://protomaps.com">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
},
},
layers: layers("protomaps", namedFlavor("light"), { lang: "zh" }),
},
center: [121.47, 31.23],
zoom: 11,
});
layers() 函数签名
layers(
sourceName: string, // 对应 sources 中的 key
flavor: Flavor, // 风格:light / dark / white / black / grayscale
options?: {
lang?: string; // 语言,如 "zh"、"en"、"ja"、"ko",默认英文
labelsOnly?: boolean; // 是否只显示文字标签
}
)
支持的 Flavor(风格)
| 值 | 效果 |
|---|---|
namedFlavor("light") | 浅色(默认) |
namedFlavor("dark") | 深色 |
namedFlavor("white") | 纯白极简 |
namedFlavor("black") | 纯黑 |
namedFlavor("grayscale") | 灰度 |
中文标签: 设置
lang: "zh"后,城市名称、道路名称等会使用中文渲染(如果 OSM 数据中有中文名)。
4. React 集成模板
import * as React from "react";
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { Protocol } from "pmtiles";
import { layers, namedFlavor } from "@protomaps/basemaps";
export const MapViewProtomaps = () => {
const mapRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const protocol = new Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
const map = new maplibregl.Map({
container: mapRef.current!,
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",
attribution: '© <a href="https://protomaps.com">Protomaps</a>',
},
},
layers: layers("protomaps", namedFlavor("light"), { lang: "zh" }),
},
center: [121.47, 31.23],
zoom: 11,
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
return () => { map.remove(); }; // 防内存泄漏
}, []);
return <div ref={mapRef} style={{ width: "100%", height: "500px" }} />;
};
React 要点: Map 必须在
useEffect内创建,cleanup 中map.remove()。
5. 底图切换(可选)
const switchToSatellite = () => {
const mapStyle = map.getStyle();
mapStyle.sources = {
satellite: {
type: "raster",
tiles: ["https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"],
tileSize: 256,
},
};
mapStyle.layers = [{ id: "satellite", type: "raster", source: "satellite" }];
map.setStyle(mapStyle);
};
setStyle()会重置地图状态(控件、图层),需要重新添加。
6. 性能清单
-
map.remove()在组件卸载时调用 - 事件监听在 useEffect cleanup 中移除
-
pmtiles://协议全局只注册一次 - 大量点数据用 Layer + GeoJSON source(而非 Marker)
- 生产环境将 PMTiles 放到 CDN / R2(免流量费)
7. 参考链接
| 资源 | 链接 |
|---|---|
| MapLibre GL JS 文档 | https://maplibre.org/maplibre-gl-js/docs/ |
| MapLibre PMTiles 集成指南 | https://docs.protomaps.com/pmtiles/maplibre |
| @protomaps/basemaps API | https://docs.protomaps.com/basemaps/maplibre |
| MapLibre Demo Tiles | https://demotiles.maplibre.org/ |
| 底图下载与预览 | https://maps.protomaps.com/ |
| 实战封装(原型) | protomaps-map-view — 把本页集成方式封装成通用 MapView 组件(React + Vite,可嵌入纯 HTML / 发 S3 分发) |
→ 上一站:上海地区底图项目
Backlinks (2)
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["Infrastructure"]
n20["Prototypes"]
n21["Research"]
n22["Better Auth 源码阅读指南"]
n23["DuckDB 环境与基本使用"]
n24["DuckDB 实战研究"]
n25["DuckDB 模拟数据"]
n26["PostgreSQL 数据用 DuckDB 加速查询"]
n27["HK 角色阅读"]
n28["HK 台词阅读"]
n29["Hollow Knight 英语主题"]
n30["HK 物品阅读"]
n31["HK 地点阅读"]
n32["HK 世界观 / Lore 阅读"]
n33["HK 英语学习素材清单"]
n34["英语学习 Dashboard"]
n35["English Scraps Archive"]
n36["English Scraps 使用指南"]
n37["Jellyfin 源码阅读指南"]
n38["Lux 资料整理"]
n39["Nest Commander 学习资料"]
n40["NestJS 源码阅读指南"]
n41["Protomaps 自建底图研究"]
n42["自制 PMTiles 地图(最简单例子)"]
n43["MapLibre 集成 Protomaps"]
n44["PMTiles 格式与工具链"]
n45["上海地区底图项目"]
n46["Redash 源码阅读指南"]
n47["Rust 学习计划"]
n48["shadcn/ui 源码阅读指南"]
n49["TRIP 项目核心原理与代码阅读指南"]
n1 --> n24
n6 --> n0
n6 --> n1
n6 --> n2
n6 --> n3
n6 --> n4
n6 --> n5
n6 --> n7
n6 --> n8
n6 --> n9
n6 --> n10
n6 --> n11
n6 --> n21
n12 --> n15
n13 --> n14
n13 --> n17
n13 --> n18
n13 --> n20
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 --> n20
n18 --> n13
n18 --> n14
n18 --> n17
n18 --> n20
n19 --> n12
n20 --> n13
n20 --> n16
n20 --> n17
n20 --> n18
n20 --> n41
n21 --> n22
n21 --> n24
n21 --> n34
n21 --> n37
n21 --> n38
n21 --> n39
n21 --> n40
n21 --> n41
n21 --> n46
n21 --> n47
n21 --> n48
n21 --> n49
n23 --> n25
n24 --> n1
n24 --> n21
n24 --> n23
n24 --> n25
n24 --> n26
n25 --> n23
n25 --> n26
n26 --> n24
n26 --> n25
n29 --> n27
n29 --> n28
n29 --> n30
n29 --> n31
n29 --> n32
n29 --> n33
n29 --> n36
n33 --> n28
n34 --> n29
n34 --> n35
n34 --> n36
n36 --> n34
n36 --> n35
n41 --> n8
n41 --> n42
n41 --> n43
n41 --> n44
n41 --> n45
n42 --> n44
n43 --> n20
n43 --> n45
n44 --> n42
n44 --> n45
n45 --> n43
n45 --> n44
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 "../../../../knowledge/infrastructure/" "Infrastructure"
click n20 "../../../../prototypes/" "Prototypes"
click n21 "../../../" "Research"
click n22 "../../better-auth/" "Better Auth 源码阅读指南"
click n23 "../../duckdb/basic-usage/" "DuckDB 环境与基本使用"
click n24 "../../duckdb/" "DuckDB 实战研究"
click n25 "../../duckdb/mock-data/" "DuckDB 模拟数据"
click n26 "../../duckdb/postgresql-acceleration/" "PostgreSQL 数据用 DuckDB 加速查询"
click n27 "../../english/hollow-knight/characters/" "HK 角色阅读"
click n28 "../../english/hollow-knight/dialogues/" "HK 台词阅读"
click n29 "../../english/hollow-knight/" "Hollow Knight 英语主题"
click n30 "../../english/hollow-knight/items/" "HK 物品阅读"
click n31 "../../english/hollow-knight/locations/" "HK 地点阅读"
click n32 "../../english/hollow-knight/lore/" "HK 世界观 / Lore 阅读"
click n33 "../../english/hollow-knight/resources/" "HK 英语学习素材清单"
click n34 "../../english/" "英语学习 Dashboard"
click n35 "../../english/scraps/archive/" "English Scraps Archive"
click n36 "../../english/scraps/" "English Scraps 使用指南"
click n37 "../../jellyfin/" "Jellyfin 源码阅读指南"
click n38 "../../lux/" "Lux 资料整理"
click n39 "../../nest-commander/" "Nest Commander 学习资料"
click n40 "../../nestjs/" "NestJS 源码阅读指南"
click n41 "../" "Protomaps 自建底图研究"
click n42 "../make-own-map/" "自制 PMTiles 地图(最简单例子)"
click n43 "./" "MapLibre 集成 Protomaps"
click n44 "../pmtiles/" "PMTiles 格式与工具链"
click n45 "../shanghai-map/" "上海地区底图项目"
click n46 "../../redash/" "Redash 源码阅读指南"
click n47 "../../rust/" "Rust 学习计划"
click n48 "../../shadcn-ui/" "shadcn/ui 源码阅读指南"
click n49 "../../trip/" "TRIP 项目核心原理与代码阅读指南"