> **本文回答**：sprite 图标集 / glyphs 字体 —— 如何为 symbol 图层提供图标与文字。

## 一、sprite 是什么

sprite 是合并的 PNG（精灵图）+ JSON（坐标表）。让 GPU 一次性加载所有图标。

```
sprite.png   → 一张大 PNG
sprite.json  → { "marker-15": { "x":0, "y":0, "width":16, "height":16 }, ... }
```

样式中指定根路径（会自动加 `.png` / `.json`）：

```js
{
  version: 8,
  sprite: svc.spriteUrl("mysprite"), // {服务地址}sprite/{name}
  sources: { ... },
  layers: [ ... ]
}
```

@2x 支持：

```
sprite.png / sprite.json       // 标清
sprite@2x.png / sprite@2x.json // 高清
```

vjmap 侧取 sprite 地址：`svc.spriteUrl(name)`（**必填** sprite 名，生成 `{服务地址}sprite/{name}`，引擎会自动补 `.png`/`.json`/`@2x`）；或 `svc.setSprite(name)` 设为默认 sprite（传含 `:` 的完整 URL 则原样使用）。图层不用 `icon-image` 时 style 可不写 `sprite`。

## 二、动态加载单张图片

```js
map.loadImage("https://xxx/icon.png", (err, img) => {
  if (err) throw err;
  if (!map.hasImage("my-icon")) {
    map.addImage("my-icon", img);
  }
});

// vjmap 封装（返回 Promise）
await map.loadImageEx("my-icon", "https://xxx/icon.png");

// 使用
map.addLayer({
  id: "poi", type: "symbol", source: "pts",
  layout: { "icon-image": "my-icon", "icon-size": 1 }
});
```

SDF（单色模板图标，可改色）：

```js
map.addImage("my-icon", img, { sdf: true });
// 这样 "icon-color" paint 才会生效
```

## 三、loadImageEx（vjmap 扩展）

相比原生 `loadImage`，`loadImageEx`：

- 返回 Promise；
- 自动 `addImage`（data URI / SVG 分支同名时 `updateImage`；普通 URL 分支同名会触发 error 事件）；
- 第二参是**字符串**时：图片 URL（含 `blob:` URL）、`data:image/...` base64、或以 `<svg` 开头的 SVG 标记文本（按 `options.width/height` 光栅化，默认 40×40）；
- 不支持 Canvas / SVG **元素**：非字符串实参原样转给 `addImage`（只收 `HTMLImageElement` / `ImageBitmap` / `{width, height, data}`），Canvas 请先 `ctx.getImageData(0, 0, w, h)` 或 `canvas.toDataURL()`。

```js
await map.loadImageEx("car", location.pathname + "images/car.png");
await map.loadImageEx("pin", "data:image/svg+xml;base64,PHN2ZyB4...");
```

## 四、移除图片

```js
map.removeImage("my-icon");
map.hasImage("my-icon");     // true/false
map.listImages();            // 所有已加载图标名
```

## 五、`icon-image` 的动态表达式

```js
// 按属性选图标
"icon-image": ["concat", "icon-", ["get", "type"]]

// 优先级备选（fallback）
"icon-image": [
  "coalesce",
  ["image", ["concat", "icon-", ["get", "type"]]],   // 不能写 "icon-" + [...]（那是 JS 字符串拼接，得到 "icon-get,type"）
  ["image", "icon-default"]
]
```

## 六、glyphs 是什么

glyphs 是文字字体，按 256 字符范围分片成 PBF（二进制）。symbol 图层 `text-field` 需要 glyphs。

```js
{
  glyphs: "https://vjmap.com/server/api/v1/service/fonts/{fontstack}/{range}.pbf?token=..." // 即 svc.glyphsUrl()（`{serverUrl}service/fonts/...`）
}
```

`{fontstack}` 是 `text-font` 数组用逗号拼成的**一个**字体栈名（如 `"Arial Unicode MS Regular"`），`{range}` 是 256 一档的范围（如 0-255, 256-511）。

⚠️ **中文等 CJK 字符默认不走 glyphs**：Map 选项 `localIdeographFontFamily` 默认 `'sans-serif'`，CJK 统一汉字 / 假名 / 谚文等由浏览器本地字体用 TinySDF 现画，不请求 PBF；`text-font` 对它们只影响字重（名字含 bold / medium / light），换不了字族。要让中文也用服务端字体，建图时传 `localIdeographFontFamily: false`。

vjmap 默认：`svc.glyphsUrl()`。

## 七、`text-font` 使用

```js
{
  id: "labels", type: "symbol", source: "pts",
  layout: {
    "text-field": ["get", "name"],
    "text-font": ["Arial Unicode MS Regular"],   // 官方示例用的 fontstack
    "text-size": 12
  },
  paint: {
    "text-color": "#000",
    "text-halo-color": "#fff",
    "text-halo-width": 1
  }
}
```

`text-font` 数组不会在前端逐个回退：整个数组作为一个字体栈（逗号拼接）去请求 PBF，能否回退取决于服务端是否支持拼合该组合；字体栈不存在会 404，导致该瓦片解析失败。稳妥起见用官方示例的 `["Arial Unicode MS Regular"]`。

## 八、自定义 glyphs

如果客户需要公司字体：

1. 用 `node-fontnik` 等 PBF 字体工具把 TTF 转成 PBF 目录。
2. 部署到静态文件服务（路径 `/fonts/{fontstack}/{range}.pbf`）。
3. 样式里 `glyphs: "https://xxx/fonts/{fontstack}/{range}.pbf"`。

## 九、预加载常用字符

文字图层在第一次显示时会按范围去拉 PBF，可能闪烁。默认配置下 CJK 走本地字体、不拉 PBF，预加载 CJK 段没有意义；只有关掉 `localIdeographFontFamily` 时才需要（URL 用 `svc.glyphsUrl()` 生成，字体栈与 `text-font` 一致）：

```js
// 仅在 new vjmap.Map({ localIdeographFontFamily: false, ... }) 时有用：预加载 U+4E00 到 U+9FFF
const tpl = svc.glyphsUrl().replace("{fontstack}", encodeURIComponent("Arial Unicode MS Regular"));
for (let i = 0x4E; i <= 0x9F; i++) {
  fetch(tpl.replace("{range}", `${i * 0x100}-${i * 0x100 + 0xFF}`));
}
```

## 十、常见坑

1. **icon-image 不显示** → 名字拼错；或 symbol 图层被 filter 掉了；或 `icon-allow-overlap: false` 挤不下。
2. **addImage 同名不覆盖也不抛异常**：`map.addImage` 重名时 `fire` 一个 `error` 事件（"An image with this name already exists."），**原图保留、新图不生效**——try/catch 抓不到，先 `hasImage`/`removeImage` 再注册。
2b. **addImage 只接三类实参**：`HTMLImageElement` / `ImageBitmap` / `{width, height, data:Uint8Array}`（ImageData 形态）。直接传 `Canvas` → `RangeError: mismatched image size`；`loadImageEx` 对非字符串实参也是原样转给 addImage，不做转换——Canvas 先 `getImageData` 或 `toDataURL()`，SVG 传 `<svg …>` 标记字符串。
3. **SDF 没效果** → 导入时没加 `{ sdf: true }`。
4. **文字不显示** → style 缺 `glyphs` 时带 `text-field` 的图层直接校验失败；glyphs URL 错或字体栈 404 时整个瓦片解析失败（不会出现空方块占位）。打开 Network 面板看 pbf 请求。
5. **CJK 字体不对 / 想统一字体** → 默认 CJK 由浏览器本地 `sans-serif` 绘制；要用服务端字体须 `localIdeographFontFamily: false`（随之 CJK PBF 体量大、首显慢，可预加载常用段）。
6. **sprite 版本升级后缓存**：URL 加 `?v=xxx` 强制刷新。
