📌 学习目标
- 掌握使用addProtocol转换要素属性的实现方法
- 理解相关API的使用
- 能够独立完成类似功能开发
🎯 核心概念
在纯JavaScript中使用addProtocol反转国家名称。
💻 完 整 代 码
代码示例
import Protobuf from 'https://unpkg.com/pbf@4.0.1/index.js';
import {VectorTile} from 'https://esm.run/@mapbox/vector-tile@2.0.3/index.js';
import tileToProtobuf from 'https://esm.run/vt-pbf@3.1.3/index.js';
const protocol = 'reverse';
maplibregl.addProtocol(protocol, (request) => {
const url = request.url.replace(protocol + '://', '');
return fetch(url)
.then((response) => response.arrayBuffer())
.then((data) => new VectorTile(new Protobuf(data)))
.then((tile) => ({
layers: Object.entries(tile.layers).reduce((acc, [layerId, layer]) => ({
…acc,
[layerId]: {
…layer,
feature: (index) => {
const feature = layer.feature(index);
if (feature.properties && typeof feature.properties['NAME'] === 'string') {
feature.properties['NAME'] = feature.properties['NAME'].split('').reverse().join('');
}
if (feature.properties && typeof feature.properties['ABBREV'] === 'string') {
feature.properties['ABBREV'] = feature.properties['ABBREV'].split('').reverse().join('');
}
return feature;
}
}
}), {})
}))
.then((tile) => tileToProtobuf(tile).buffer)
.then((data) => ({ data }));
});
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [8, 47],
zoom: 5,
hash: 'map'
});
map.setTransformRequest((url, resourceType) => {
if (url.startsWith('https://demotiles.maplibre.org/tiles/') && resourceType === 'Tile') {
return { url: protocol + '://' + url };
}
return undefined;
});
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<title>Use addProtocol to Transform Feature Properties</title>
<meta property="og:description" content="使用纯 JavaScript 的 addProtocol 反转国家名称。" />
<meta property="og:created" content="2025-06-25" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css" />
<script src="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script type="module">
import Protobuf from 'https://unpkg.com/pbf@4.0.1/index.js';
import {VectorTile} from 'https://esm.run/@mapbox/vector-tile@2.0.3/index.js';
import tileToProtobuf from 'https://esm.run/vt-pbf@3.1.3/index.js';
const protocol = 'reverse';
maplibregl.addProtocol(protocol, (request) => {
const url = request.url.replace(protocol + '://', '');
return fetch(url)
.then((response) => response.arrayBuffer())
.then((data) => new VectorTile(new Protobuf(data)))
.then((tile) => ({
layers: Object.entries(tile.layers).reduce((acc, [layerId, layer]) => ({
…acc,
[layerId]: {
…layer,
feature: (index) => {
const feature = layer.feature(index);
if (feature.properties && typeof feature.properties['NAME'] === 'string') {
feature.properties['NAME'] = feature.properties['NAME'].split('').reverse().join('');
}
if (feature.properties && typeof feature.properties['ABBREV'] === 'string') {
feature.properties['ABBREV'] = feature.properties['ABBREV'].split('').reverse().join('');
}
return feature;
}
}
}), {})
}))
.then((tile) => tileToProtobuf(tile).buffer)
.then((data) => ({ data }));
});
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [8, 47],
zoom: 5,
hash: 'map'
});
map.setTransformRequest((url, resourceType) => {
if (url.startsWith('https://demotiles.maplibre.org/tiles/') && resourceType === 'Tile') {
return { url: protocol + '://' + url };
}
return undefined;
});
</script>
</body>
</html>
🔍 代码解析
初始化地图
使用 new maplibregl.Map() 创建地图实例,配置基本参数。本示例的核心特色是展示如何使用 maplibregl.addProtocol() 方法创建自定义协议,在瓦片加载过程中转换要素属性。
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [8, 47], // 欧洲中部
zoom: 5,
hash: 'map'
});
关键配置项
- container: 地图容器的 DOM 元素 ID
- style: 使用 MapLibre 官方样式 https://demotiles.maplibre.org/style.json
- center: 地图初始中心点 [8, 47](欧洲中部,德国附近)
- zoom: 初始缩放级别为 5,显示中等区域范围
- hash: 设置为 'map',启用 URL hash 导航,便于分享地图状态
依赖导入
import Protobuf from 'https://unpkg.com/pbf@4.0.1/index.js';
import {VectorTile} from 'https://esm.run/@mapbox/vector-tile@2.0.3/index.js';
import tileToProtobuf from 'https://esm.run/vt-pbf@3.1.3/index.js';
本示例使用三个关键库:
- pbf: 用于解析 Protocol Buffers 格式数据
- @mapbox/vector-tile: 用于解析矢量瓦片数据
- vt-pbf: 用于将数据转换回 Protocol Buffers 格式
自定义协议实现
const protocol = 'reverse';
maplibregl.addProtocol(protocol, (request) => {
// 1. 提取原始 URL(移除自定义协议前缀)
const url = request.url.replace(protocol + '://', '');
return fetch(url)
// 2. 获取瓦片数据(ArrayBuffer 格式)
.then((response) => response.arrayBuffer())
// 3. 解析为 VectorTile 对象
.then((data) => new VectorTile(new Protobuf(data)))
// 4. 转换要素属性(反转国家名称)
.then((tile) => ({
layers: Object.entries(tile.layers).reduce((acc, [layerId, layer]) => ({
…acc,
[layerId]: {
…layer,
feature: (index) => {
const feature = layer.feature(index);
// 反转 NAME 属性(国家名称)
if (feature.properties && typeof feature.properties['NAME'] === 'string') {
feature.properties['NAME'] = feature.properties['NAME'].split('').reverse().join('');
}
// 反转 ABBREV 属性(国家缩写)
if (feature.properties && typeof feature.properties['ABBREV'] === 'string') {
feature.properties['ABBREV'] = feature.properties['ABBREV'].split('').reverse().join('');
}
return feature;
}
}
}), {})
}))
// 5. 转换回 Protocol Buffers 格式
.then((tile) => tileToProtobuf(tile).buffer)
// 6. 返回处理后的数据
.then((data) => ({ data }));
});
自定义协议工作流程:
请求转换配置
map.setTransformRequest((url, resourceType) => {
// 将特定 URL 转换为自定义协议
if (url.startsWith('https://demotiles.maplibre.org/tiles/') && resourceType === 'Tile') {
return { url: protocol + '://' + url };
}
return undefined;
});
作用:将符合条件的瓦片请求重写为使用自定义协议,使得这些请求被我们注册的处理器拦截和处理。
⚙️ 参数说明
地图初始化参数
| container | string | 是 | – | 地图容器元素的 ID |
| style | string/object | 是 | – | 地图样式 URL 或内联样式对象 |
| center | [number, number] | 否 | [0, 0] | 初始中心点坐标,格式为 [经度, 纬度] |
| zoom | number | 否 | 0 | 初始缩放级别,范围 0-22 |
| hash | string/boolean | 否 | – | 启用 URL hash 导航 |
addProtocol 参数
| protocol | string | 是 | 自定义协议名称(如 ‘reverse’),用于标识协议前缀 |
| handler | function | 是 | 处理请求的回调函数,接收 request 对象并返回 Promise |
handler 函数参数
| request | object | 请求对象,包含 url、headers、method 等属性 |
| request.url | string | 请求的完整 URL(包含自定义协议前缀) |
handler 函数返回值
| data | ArrayBuffer | 处理后的瓦片数据,必须是 Protocol Buffers 格式 |
setTransformRequest 参数
| url | string | 请求的原始 URL |
| resourceType | string | 资源类型,如 ‘Tile’、‘Style’、‘Source’ 等 |
setTransformRequest 返回值
| url | string | 修改后的 URL(添加自定义协议前缀) |
🎨 效果说明

运行代码后,页面显示一个交互式地图,其中国家名称被反转显示:
- 名称反转: 地图上的国家名称(如 “France”)显示为反转后的形式(如 “ecnarF”),国家缩写(如 “Fr”)也被反转(如 “rF”)
- 协议拦截: 通过自定义协议 reverse:// 拦截矢量瓦片请求
- 属性转换: 在瓦片加载过程中实时修改要素属性,无需修改原始数据源
- 交互功能: 支持鼠标拖拽、滚轮缩放、右键旋转等标准交互
地图默认显示欧洲中部区域(中心 [8, 47]),缩放级别为 5。用户可以看到所有国家标签都以反转形式显示,直观展示了自定义协议的强大功能。
技术实现效果:
💡 常 见 问 题
Q1: 什么是自定义协议? A: 自定义协议允许开发者拦截地图的网络请求,在数据加载过程中进行处理或转换。通过 maplibregl.addProtocol() 注册协议后,可以处理特定协议前缀(如 reverse://)的 URL。
Q2: 自定义协议的工作流程是什么? A:
Q3: 自定义协议有哪些应用场景? A:
- 数据加密/解密: 在加载时解密加密的瓦片数据
- 属性转换和处理: 修改要素属性(如本示例中的名称反转)
- 数据格式转换: 转换不同的数据格式
- 请求缓存和优化: 实现自定义缓存策略
- 数据过滤和裁剪: 根据条件过滤或裁剪数据
- 请求代理: 转发请求到不同的服务器
Q4: 如何调试自定义协议? A:
Q5: 自定义协议只适用于矢量瓦片吗? A: 不是。自定义协议可以用于任何类型的地图资源请求,包括矢量瓦片、栅格瓦片、样式文件、精灵图等。
Q6: 如何移除自定义协议? A: 使用 maplibregl.removeProtocol(protocol) 方法移除已注册的自定义协议:
maplibregl.removeProtocol('reverse');
📝 练习任务
🌟 最佳实践
🔗 延伸阅读
-
Map API文档
-
MapLibre GL JS 官方文档
-
[下一课预告]:将继续学习地图图层的基础知识
本文是MapLibre GL JS实践课程系列的一部分,欢迎关注收藏






