关于 maplibre-sprites-icons
MapLibre Sprites and Icons 是面向 MapLibre GL JS 地图开发者的技能,帮助选择和配置地图图标的呈现方式。内容涵盖 sprite、运行时 addImage、Marker 以及图标缺失和路线盾牌的排查。
主要功能
- 01比较 Marker、sprite 与 addImage
- 02配置单个或多个 sprite 图集
- 03从 SVG 构建 sprite 资源
- 04注册运行时图像
- 05诊断图标与路线盾牌问题
适用场景
- 01为点数据选择地图符号图层图标方案
- 02在底图样式中添加自定义图标集
- 03排查符号图层图标未显示的问题
- 04检查路线盾牌徽标缺失原因
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ maplibre/ maplibre-agent-skills --skill maplibre-sprites-icons安装后在 Claude Code 中输入/maplibre-sprites-icons确认已加载。
详细介绍
MapLibre Sprites 和图标
MapLibre GL JS 地图上的图标,要么属于地图:由符号图层从 sprite(通过样式的 sprite URL 提供的 PNG 图集和 JSON 索引)绘制,或使用运行时通过 addImage 注册的图像绘制;要么位于地图上方,作为样式之外的 Marker。关于图标颜色、光晕以及图像底图上的图底关系,请参阅 maplibre-cartography;关于文本和字形设置,请参阅 maplibre-fonts-glyphs。
何时使用此技能
- 为地图上的图标选择
Marker、sprite 还是addImage - 符号图层的图标无法渲染
- 为
sprite不受你控制的样式添加自己的图标图集 - 设置、自托管或从 SVG 构建
sprite(关于glyphs,请参阅 maplibre-fonts-glyphs) - 为应用在运行时添加的图层注册图标,包括 SVG
- 路线盾牌只显示数字,或缺少徽章
- 编写读取或生成符号图层的工具,并且需要识别盾牌
选择 Marker、sprite 还是 addImage
依次回答两个问题;下文各节也按此顺序展开。
- 图标属于地图,还是位于地图上方? 属于地图,意味着它是符号图层的
icon-image:与标签一同放置,发生碰撞时会隐藏,并按照图层顺序绘制,适用于任意规模的点数据集。少量位于地图上方的注记,如果需要让用户拖动或需要独立 HTML,则使用Marker。 - 对于符号图层,图像来自哪里?
Sprites
样式的 sprite 值是一个不带文件扩展名的基础 URL;MapLibre 会自行追加 .json、.png 和 @2x 变体。[1] 符号图层会在 icon-image 中指定 sprite 图像(其他图层则在各自的 *-pattern 属性中指定),名称必须与 sprite JSON 索引中的 ID 完全一致,否则图标不会绘制。
{
"sprite": "https://example.com/sprites/poi",
"layers": [
{
"id": "cafes",
"type": "symbol",
"source": "places",
"source-layer": "poi",
"layout": { "icon-image": "cafe" }
}
]
}
https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite 可用于测试;请勿在生产环境中使用。
同一地图样式中使用多个精灵图集
sprite 不限于单个字符串。从 MapLibre GL JS 3.0 起([#1805](https://github.com/maplibre/maplibre-gl-js/pull/1805),它也接受一个由 {id, url} 对象组成的数组,[1] 因此地图样式可以在加载底图提供商的图集时,同时加载自己的图标。3.0 之前,地图样式只能指定一个精灵图集,因此要添加自己的图标,就必须将它们合并到提供商的图集中;使用数组形式后,每个图集都可以保留在原本的托管位置。
{
"sprite": [
{ "id": "default", "url": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm" },
{ "id": "my-icons", "url": "https://example.com/sprites/poi" }
]
}
根据这种形式,需要遵循以下规则:
- 通过
id:image-name引用图像——"icon-image": "my-icons:cafe"。未加前缀的"cafe"只会在default图集中查找。 id为default是唯一的例外:其中的图像无需前缀。这样,将现有地图样式的字符串sprite转换为数组形式时,其icon-image值仍然有效。将底图的图集设为id: "default",样式中已有的盾牌和兴趣点图层就能继续正常解析。- **所有
id和 URL 都必须唯一。**重复项会导致验证错误(all the sprites' ids must be unique, but <id> is duplicated),而不是采用后者覆盖前者的合并方式。 - 每个 URL 仍然不带扩展名——MapLibre 会为每个条目添加
.json、.png和@2x变体,与字符串形式完全相同。 - 从代码中调用
map.addSprite('my-icons', 'https://example.com/sprites/poi')可在地图样式加载后添加图集;字符串形式的sprite会成为数组中的default条目。[3] - ❌
{ "id": "my-icons", "url": "https://example.com/sprites/poi.json" }→ 请求poi.json.json - ❌ 在自己的工具中将
sprite设为string类型:数组值会被转换为[object Object],加载器会以Invalid sprite URL "[object Object]", must be absolute拒绝它。读取地图样式的sprite时,请同时处理这两种形式。
自托管精灵图
为避免依赖第三方,请从地图样式或瓦片集提供商处复制现有的精灵图目录(PNG + JSON,以及所有 @2x 文件),并将其托管在自己的域名下,然后将地图样式的 sprite 属性指向该目录的基础 URL。重新发布前,请务必检查提供商的许可证,并在要求时注明出处。
将精灵图资源托管在自己控制的静态托管服务上(与地图样式同源)。**生产环境中的地图样式切勿指向 raw.githubusercontent.com。**Raw 用于提供仓库文件,并非生产环境资源:匿名请求会受到严格的速率限制,导致实际用户间歇性遇到 HTTP 429 错误 [5];缓存时间固定为五分钟,无法控制;没有 SLA;私有仓库的 URL 对除已认证协作者外的所有人都会返回 404(你登录时能正常使用,但其他用户都会遇到故障)[6]。
从 SVG 构建精灵图
可以使用 spritezero、spreet 或 Martin 等工具,从 SVG 目录生成精灵图资源。
常用的图标来源包括 Maki 和 Temaki。这些是地图样式 SVG 图标的常见源代码仓库,但在重新发布衍生的精灵图资源前,请检查各仓库的许可证。
使用 addImage 加载运行时图像
对于应用在运行时添加的图层,请在样式加载后为每张图像注册一个 ID,然后在图层的 icon-image 中指定该 ID。请先用 hasImage() 检查 ID:若样式中已存在该 ID,对其调用 addImage 会触发错误(An image named "<id>" already exists.),而不是替换图像。[3] [8]
map.on('load', async () => {
// PNG, WebP, or JPEG: loadImage() resolves to a response whose data is the image
const { data } = await map.loadImage('https://example.com/icons/cafe.png');
if (!map.hasImage('cafe')) map.addImage('cafe', data);
// SVG: an Image element, rasterized at its width and height
const park = new Image(24, 24);
park.crossOrigin = 'anonymous'; // or addImage throws on a cross-origin SVG
await new Promise((resolve, reject) => {
park.onload = resolve;
park.onerror = reject;
park.src = 'https://example.com/icons/park.svg';
});
if (!map.hasImage('park')) map.addImage('park', park);
map.addSource('venues', { type: 'geojson', data: 'https://example.com/venues.geojson' });
map.addLayer({
id: 'venues',
type: 'symbol',
source: 'venues',
layout: { 'icon-image': ['get', 'category'] } // 'cafe' or 'park'
});
});
- ❌
map.loadImage(url, callback)。GL JS 4.0.0 已移除回调形式,[4] 因此回调永远不会运行;请对 Promise 使用await。[3] - ❌ 通过
map.loadImage()加载 SVG,该方法只接受 PNG、WebP 或 JPEG。[3] 如需按需加载 SVG,GL JS 6 的setMissingStyleImageResolver使用与 MapLibre 显示远程 SVG 符号示例相同的Image路径。 - ❌ 对普通 SVG 设置
sdf: true。标记为sdf的图像会将其 alpha 作为距离场读取,并将每个不透明像素绘制为icon-color(默认为黑色),因此多色图标会变成单色剪影。不设置sdf时,icon-color和icon-halo-*不起作用:它们仅适用于 SDF 图标。[2] - 符号图层默认会隐藏发生碰撞的图标(
icon-allow-overlap为false,设置icon-overlap时则由它覆盖);symbol-sort-key决定哪些图标会保留。开启重叠(icon-allow-overlap: true或icon-overlap: "always")后,绘制每个图标时都不会检查碰撞;GL JS 大数据指南建议在要素数量较多时采用这种方式,但图标会相互堆叠。 - ❌ 使用
"icon-optional": true让发生碰撞的图标消失。它仅适用于同时包含图标和文本的符号:当图标发生碰撞而文本未发生碰撞时,允许文本在没有图标的情况下显示;对于没有text-field的图层,该设置不会产生任何效果。
addImage 的选项接受与 sprite 索引相同的单图元数据:pixelRatio、sdf、stretchX、stretchY、content、textFitWidth 和 textFitHeight。[3]
标记
Marker[7] 是位于地图画布上方、样式之外的 HTML 元素:它绘制在所有图层(包括标签)之上,且从不参与碰撞检测。
new maplibregl.Marker({ draggable: true })
.setLngLat([-122.42, 37.77])
.setPopup(new maplibregl.Popup().setText('Pickup point'))
.addTo(map);
Marker是一个 DOM 元素,会在每次摄像机移动时重新定位。 适合在地图上方显示少量注释;对于点数据集,请将点放入 GeoJSON 源中,并将其绘制为符号图层。
图标未显示时
GL JS 会报告所有无法找到的图标:地图会触发 styleimagemissing,控制台则会针对每个图像 ID 发出一次警告 Image "<id>" could not be loaded。[8] 加载 sprite URL 失败也会触发地图的 error 事件;如果没有监听该事件,则会将其发送到 console.error。[8]
load 事件触发后,map.hasImage(id) 可以帮助区分这两种原因:[3]
- 图像表从未加载:对于 sprite JSON 中确实包含的 ID,
hasImage()返回false。请检查样式是否设置了sprite、其 URL 是否不带扩展名、对应的.json和.png是否返回 200,以及 CORS 设置。 - 图像表已加载,但不包含该 ID:其他 sprite ID 返回
true。请将icon-image的值与 sprite JSON 的键进行比较;对于非默认图像表,还要检查是否包含id:前缀。map.listImages()会返回地图中的所有 ID。
路线盾牌显示异常
路线盾牌看起来异常(只显示悬浮数字、徽章缺失)几乎总是因为精灵图像缺失。盾牌编号是文本(字体),通常能正常渲染;其后方的徽章则是来自精灵图的 icon-image。请按以下顺序排查:
- 确认字形已加载。 请求
glyphs服务器,检查对应的text-font名称,并确认返回 HTTP 200。如果都返回 200,就可以推断问题出在精灵图像缺失或引用错误。 - 确认精灵图包含盾牌图像。 OSM Bright 的盾牌图层对美国路线网络使用
icon-image: "{network}_{ref_length}"(例如us-interstate_2、us-highway_3、us-state_2),对其他路线编号使用road_{ref_length};OSM Liberty 使用default_{ref_length}。缺失的图标会被省略,因此请在精灵图 JSON 中搜索这些键。
https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite 和 https://openmaptiles.github.io/osm-bright-gl-style/sprite 目前包含 road_1–_6、us-state_1–_6、us-highway_1–_3 和 us-interstate_1–_3(openmaptiles.github.io 上为 _1–_5),但精简版或自定义精灵图可能只包含通用的 road_*。如果你的精灵图缺少盾牌图像,而瓦片中有 network、ref 和 ref_length 属性(OSM US OpenMapTiles 瓦片确实有),请将 sprite 指向包含这些图像的精灵图——这样 {network}_{ref_length} 样式图层就能正常解析,无需修改图层。
盾牌是位于路线编号文本后方的徽章图像,尺寸有两种设置方式。 OSM Bright 和 OSM Liberty 会根据路线编号长度选择预先绘制的徽章,并使用 OpenMapTiles 的 ref_length 属性来选择合适的徽章。[9] OSM Bright 的州际公路盾牌图层(节选):
{
"id": "highway-shield-us-interstate",
"layout": {
"icon-image": "{network}_{ref_length}",
"symbol-placement": {
"base": 1,
"stops": [
[7, "point"],
[7, "line"],
[8, "line"]
]
},
"text-field": "{ref}"
}
}
若要让所有路线编号长度共用一张徽章图像,请将其设置为可拉伸。在徽章的精灵图 JSON 条目中,stretchX 和 stretchY 列出可拉伸的像素范围,content 则定义文本必须容纳在其中的区域。在盾牌图层上,将 icon-text-fit 设为 "both" 或 "width",并设置 icon-text-fit-padding 为文本周围留出边距。[1] [2] 这样 I-5 和 I-405 就能以不同宽度共用同一张图像。OSM Bright 和 OSM Liberty 不使用可拉伸徽章,因此能识别盾牌图层的工具必须同时支持这两种类型。只有可拉伸徽章会设置 icon-text-fit(默认值为 none);而 text-anchor 的默认值是 center,因此不设置它并不能说明图层的实际情况。
从样式中读取布局值的代码不得假设这些值都是字符串。OSM Bright 的三个盾牌图层和 OSM Liberty 的一个盾牌图层都使用旧式缩放函数设置 symbol-placement;这是一个包含 stops 的对象,会在 point 和 line 之间切换。任何符号布局值也都可能是表达式,即数组。因此,对这些图层执行 layer.layout['symbol-placement'] === 'point' 会得到 false,代码便会跳过它们。比较之前,请先检查该值是字符串、缩放函数还是表达式。
相关技能
- maplibre-cartography — 航空影像上点符号的图标颜色、
icon-halo-color/icon-halo-width和图地关系;样式图层排序。 - maplibre-fonts-glyphs —
glyphsURL、字体 PBF 和 GL JS 本地字体回退;盾牌的文本部分。 - maplibre-source-wiring —
sprite在样式其他根属性中的位置,以及可自托管资源的 CORS。 - maplibre-v6-migration — v6 中的
styleimagemissing仅用于通知;v6 地图通过setMissingStyleImageResolver提供精灵图中不存在的icon-image。
参考资料
- 样式规范:
sprite— 字符串和{id, url}数组形式、图像名称前缀、defaultID,以及 sprite 索引文件中的content、stretchX/stretchY和textFitWidth/textFitHeight字段 - 样式规范:符号图层属性 —
icon-text-fit的取值(默认值为none,以及width、height、both)和icon-text-fit-padding;icon-color和icon-halo-*仅适用于 SDF Map.addImage()(MapLibre GL JS API) — 同一页面还介绍了hasImage()、listImages()、addSprite()和loadImage():loadImage接受 PNG、WebP 或 JPEG,并解析为一个响应,其data就是图像- MapLibre GL JS 更新日志 — “在一个样式文件中支持多个
sprite声明”随 3.0.0 发布;在 4.0.0 中,map.loadImage返回一个Promise,并移除了回调(#3233、#3422) raw.githubusercontent.com上未经身份验证的速率限制(GitHub 社区讨论) — 匿名请求会受到速率限制;生产流量偶尔会遇到 HTTP 429raw.githubusercontent.com与私有仓库(GitHub 社区讨论) — 私有仓库的 raw URL 对匿名请求会返回 404/403Marker(MapLibre GL JS API) —draggable选项、setLngLat和setPopup- GL JS 6.11.2
image_manager.ts—styleimagemissing和缺失图像警告;重复 ID 错误见style.ts,如果没有监听器,error事件会在evented.ts中传递给console.error - OpenMapTiles
transportation_name图层 —ref_length:“适合用作高速公路标签背景的盾牌图标。”
此技能内容为快照。 如果主要来源与其矛盾——包括以上参考资料、MapLibre 当前文档,或运行 MapLibre 时的实际行为——请以主要来源为准。遵循该来源,然后报告此处的不一致,并注明来源和你的 MapLibre 版本:修改你安装的副本对其他人没有帮助,而且下次更新时会被覆盖。