mcpField
M
maplibre-sprites-icons

maplibre-sprites-icons

github.com/maplibre开发15545 次安装今天更新
代码仓库

关于 maplibre-sprites-icons

MapLibre Sprites and Icons 是面向 MapLibre GL JS 地图开发者的技能,帮助选择和配置地图图标的呈现方式。内容涵盖 sprite、运行时 addImage、Marker 以及图标缺失和路线盾牌的排查。

主要功能

  1. 01比较 Marker、sprite 与 addImage
  2. 02配置单个或多个 sprite 图集
  3. 03从 SVG 构建 sprite 资源
  4. 04注册运行时图像
  5. 05诊断图标与路线盾牌问题

适用场景

  1. 01为点数据选择地图符号图层图标方案
  2. 02在底图样式中添加自定义图标集
  3. 03排查符号图层图标未显示的问题
  4. 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

依次回答两个问题;下文各节也按此顺序展开。

  1. 图标属于地图,还是位于地图上方? 属于地图,意味着它是符号图层的 icon-image:与标签一同放置,发生碰撞时会隐藏,并按照图层顺序绘制,适用于任意规模的点数据集。少量位于地图上方的注记,如果需要让用户拖动或需要独立 HTML,则使用 Marker。
  2. 对于符号图层,图像来自哪里?
    • 样式。 POI、城镇地点、盾牌、图案纹理,以及所有纳入样式的其他图标或纹理,它们会参与视觉层次和碰撞处理:使用 sprite。如果底图的 sprite 不受你控制,可以使用自己的图集作为第二个图集,它仍然是 sprite。
    • 你的应用,在运行时提供。 对于应用添加的图层(例如 GeoJSON 数据源),如果没有图集中包含所需图标,则使用 addImage。请有意选择这种方式。每张图像都需要单独的请求和一次 addImage 调用;而 sprite 只需一个 PNG 和一个 JSON,其图标会像 sprite 图标一样与样式标签及彼此发生碰撞。

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。请按以下顺序排查:

  1. 确认字形已加载。 请求 glyphs 服务器,检查对应的 text-font 名称,并确认返回 HTTP 200。如果都返回 200,就可以推断问题出在精灵图像缺失或引用错误。
  2. 确认精灵图包含盾牌图像。 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 — glyphs URL、字体 PBF 和 GL JS 本地字体回退;盾牌的文本部分。
  • maplibre-source-wiring — sprite 在样式其他根属性中的位置,以及可自托管资源的 CORS。
  • maplibre-v6-migration — v6 中的 styleimagemissing 仅用于通知;v6 地图通过 setMissingStyleImageResolver 提供精灵图中不存在的 icon-image。

参考资料

  1. 样式规范:sprite — 字符串和 {id, url} 数组形式、图像名称前缀、default ID,以及 sprite 索引文件中的 content、stretchX/stretchY 和 textFitWidth/textFitHeight 字段
  2. 样式规范:符号图层属性 — icon-text-fit 的取值(默认值为 none,以及 width、height、both)和 icon-text-fit-padding;icon-color 和 icon-halo-* 仅适用于 SDF
  3. Map.addImage()(MapLibre GL JS API) — 同一页面还介绍了 hasImage()、listImages()、addSprite() 和 loadImage():loadImage 接受 PNG、WebP 或 JPEG,并解析为一个响应,其 data 就是图像
  4. MapLibre GL JS 更新日志 — “在一个样式文件中支持多个 sprite 声明”随 3.0.0 发布;在 4.0.0 中,map.loadImage 返回一个 Promise,并移除了回调(#3233、#3422)
  5. raw.githubusercontent.com 上未经身份验证的速率限制(GitHub 社区讨论) — 匿名请求会受到速率限制;生产流量偶尔会遇到 HTTP 429
  6. raw.githubusercontent.com 与私有仓库(GitHub 社区讨论) — 私有仓库的 raw URL 对匿名请求会返回 404/403
  7. Marker(MapLibre GL JS API) — draggable 选项、setLngLat 和 setPopup
  8. GL JS 6.11.2 image_manager.ts — styleimagemissing 和缺失图像警告;重复 ID 错误见 style.ts,如果没有监听器,error 事件会在 evented.ts 中传递给 console.error
  9. OpenMapTiles transportation_name 图层 — ref_length:“适合用作高速公路标签背景的盾牌图标。”

此技能内容为快照。 如果主要来源与其矛盾——包括以上参考资料、MapLibre 当前文档,或运行 MapLibre 时的实际行为——请以主要来源为准。遵循该来源,然后报告此处的不一致,并注明来源和你的 MapLibre 版本:修改你安装的副本对其他人没有帮助,而且下次更新时会被覆盖。

评论

支持 Markdown