唯杰地图 logo唯杰地图
VJMAP
EN

sprite-glyphs

vjmap/references/map-style/sprite-glyphs.md 6.7 KB160 行
原文

本文回答: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):

{
  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。

二、动态加载单张图片

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(单色模板图标,可改色):

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()。
await map.loadImageEx("car", location.pathname + "images/car.png");
await map.loadImageEx("pin", "data:image/svg+xml;base64,PHN2ZyB4...");

四、移除图片

map.removeImage("my-icon");
map.hasImage("my-icon");     // true/false
map.listImages();            // 所有已加载图标名

五、icon-image 的动态表达式

// 按属性选图标
"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。

{
  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 使用

{
  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 一致):

// 仅在 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 强制刷新。