maplibre-cartography

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MapLibre Cartography

MapLibre 制图指南

MapLibre renders exactly what you describe in your style JSON. This skill covers how to describe it well: choosing label colors for readability on any basemap, building a coherent visual hierarchy, sourcing and self-hosting fonts and icons, and ordering layers correctly.
MapLibre 会严格按照您在样式 JSON 中定义的内容进行渲染。本指南涵盖如何合理定义样式:选择在任意底图上都具备可读性的标签颜色、构建连贯的视觉层级、获取并自托管字体与图标,以及正确排序图层。

When to Use This Skill

适用场景

  • Choosing label
    text-color
    and
    text-halo-color
    for a new or migrated style
  • Map labels are hard to read against a background (imagery, dark basemap, complex vector)
  • Setting up
    glyphs
    and
    sprite
    for a custom or self-hosted style
  • Injecting your own data layers into an existing basemap without covering labels
  • Making point symbols, markers, or custom icons readable on satellite/aerial imagery
  • Restyling roads from a light-basemap vector palette so they sit in (not on top of) imagery
  • Route shields render as bare numbers or missing badges
  • Auditing a style for contrast accessibility
  • 为新样式或迁移后的样式选择标签
    text-color
    text-halo-color
  • 地图标签在背景(影像、深色底图、复杂矢量图)上难以辨认
  • 为自定义或自托管样式配置
    glyphs
    sprite
  • 将自有数据图层注入现有底图时不遮挡标签
  • 确保点符号、标记或自定义图标在卫星/航空影像上可读
  • 重新设计浅色矢量底图的道路配色,使其适配(而非突兀于)影像背景
  • 道路标识牌仅显示数字或缺失标识框
  • 审核样式的对比度无障碍性

Basemap Type Determines Label Colors

底图类型决定标签颜色

MapLibre places labels dynamically, so you cannot mask the background behind each label as you would on a static map. Instead, choose a
text-halo-color
that separates the label from every background it might land on, and a
text-color
that reads against the halo:
Basemap typeBackgroundRecommended text colorRecommended halo
Light vector (streets, OpenFreeMap positron)Pale/whiteDark (
#333
or similar)
Light semi-transparent (
rgba(255,255,255,0.8)
)
Dark vector (dark-matter, navigation night)DarkWhite or near-white (
#ffffff
)
Dark semi-transparent (
rgba(0,0,0,0.75)
)
Satellite or aerial imagery (NAIP, Sentinel-2)Unpredictable — bright crops, dark forests, urban rooftopsWhite (
#ffffff
)
Dark semi-transparent (
rgba(0,0,0,0.75)
)
The most common mistake is a white halo with no transparency: unless the background is pure white, it disrupts the spatial connection between the label and the feature it labels — add transparency. The second is reusing a light-vector palette over imagery, where it fails on dark terrain, forests, and water. On imagery, always use white text and a dark semi-transparent halo (
"text-color": "#ffffff"
,
"text-halo-color": "rgba(0,0,0,0.75)"
,
"text-halo-width": 1.2
).
For tinted labels (parks, water, POIs), use a light tint of the semantic color (
#c8f5cc
parks,
#a8d8ff
water) rather than the dark saturated version: tints read against dark halos while keeping semantic meaning, where full-saturation colors contrast poorly at small sizes.
MapLibre 会动态放置标签,因此无法像静态地图那样为每个标签添加背景遮罩。取而代之的是,选择能让标签与所有可能出现的背景区分开的
text-halo-color
,以及与光晕搭配可读的
text-color
底图类型背景特征推荐文本颜色推荐光晕样式
浅色矢量底图(街道图、OpenFreeMap positron)浅色调/白色深色(
#333
及类似色)
浅色半透明(
rgba(255,255,255,0.8)
深色矢量底图(dark-matter、夜间导航图)深色调白色或近白色(
#ffffff
深色半透明(
rgba(0,0,0,0.75)
卫星或航空影像(NAIP、Sentinel-2)不可预测——明亮的农田、深色的森林、城市屋顶白色(
#ffffff
深色半透明(
rgba(0,0,0,0.75)
最常见的错误是使用无透明度的白色光晕:除非背景是纯白色,否则会破坏标签与其标注要素之间的空间关联——务必添加透明度。第二个错误是在影像底图上复用浅色矢量底图的配色方案,这会在深色地形、森林和水域上失效。在影像底图上,务必使用白色文本搭配深色半透明光晕
"text-color": "#ffffff"
"text-halo-color": "rgba(0,0,0,0.75)"
"text-halo-width": 1.2
)。
对于有色调的标签(公园、水域、兴趣点),请使用语义颜色的浅色调(公园用
#c8f5cc
,水域用
#a8d8ff
)而非深饱和版本:浅色调在深色光晕下仍具可读性,同时保留语义,而高饱和颜色在小尺寸下对比度极差。

Halo width

光晕宽度

Wider halos increase legibility but add visual weight. Typical values:
Context
text-halo-width
Body labels (city, town, village)0.8–1.5
Country / continent (large text)1.5–2.0
Small POI or peak labels0.8–1.2
Water / park labels with colored text1.0–1.5
text-halo-width
is in pixels relative to the text. The halo must not bleed into adjacent labels: keep it tight at small text sizes and add transparency.
更宽的光晕可提升可读性,但会增加视觉权重。典型取值:
使用场景
text-halo-width
取值
主体标签(城市、城镇、村庄)0.8–1.5
国家/大洲(大尺寸文本)1.5–2.0
小型兴趣点或山峰标签0.8–1.2
带颜色文本的水域/公园标签1.0–1.5
text-halo-width
是相对于文本的像素值。光晕不得渗入相邻标签:小尺寸文本时保持紧凑,并添加透明度。

Point Symbols and Icons on Imagery

影像底图上的点符号与图标

Markers face the same figure-ground problem as labels, but with different tools. A colored icon on aerial imagery competes with an unpredictable, busy, desaturated photographic background.
  • You cannot separate a symbol from a background that owns its hue. A green icon over green parkland, a brown icon over bare soil: both camouflage. Most aerial imagery is low-saturation, so the axis the background is weakest on is chroma. A saturated fill (amber, terracotta) separates while still reading as a natural, earthy color. Shifting hue alone, toward a different earth tone, does not help if that hue is also in the scene.
  • Carve the symbol out with a casing, exactly as you would halo a label. A thin light casing reads against dark canopy and water; a darker edge holds against bright soil and rooftops. Keep it thin: a fat ring reads as a sticker. Terminology: a halo contrasts the background to lift the symbol off it; a knockout matches the background to mask busy texture immediately around the symbol. Both buy separation.
  • Flat fills read as stickers on a photo. Give landform or 3D symbols dimensional cues. A gradient (lighter on the lit slope, darker on the shaded slope) models form. A contact shadow, a blurred flattened ellipse pooled under the base, anchors the symbol to the ground far better than an offset drop-shadow, which makes it look like it floats. Match the symbol's lighting and shadow direction to the basemap's
    hillshade-illumination-direction
    (commonly NW, 315°) so the symbol sits in the same light as the terrain.
SVG icons via
addImage
:
when loading an SVG into a sprite image at runtime (fetch the SVG, decode it as an
Image
, then
map.addImage
), the SVG rasterizes at decode time, so
linearGradient
and
feDropShadow
filters bake in correctly.[1] Two gotchas: pad the
viewBox
so halos and shadows are not clipped at the icon edge, and keep
width
/
height
proportional to the
viewBox
or the glyph distorts. Use
"icon-allow-overlap": true
for dense point data.
标记面临与标签相同的图底问题,但解决工具不同。航空影像上的彩色图标会与不可预测、杂乱且低饱和度的照片背景产生竞争。
  • 无法将符号与拥有相同色调的背景区分开。绿色图标放在绿色公园、棕色图标放在裸露土壤上都会融入背景。大多数航空影像饱和度较低,因此背景最弱的维度是色度。饱和填充色(琥珀色、赤陶色)既能与背景区分开,又能保持自然的土色调。仅调整色调(转向其他土色调)并无帮助,如果该色调也存在于场景中。
  • 用轮廓勾勒符号,就像给标签添加光晕一样。细浅色轮廓可在深色树冠和水域上清晰显示;深色边缘则能在明亮土壤和屋顶上保持可见。轮廓要细:粗环会让符号看起来像贴纸。术语说明:光晕 通过与背景对比让符号突出;挖空轮廓 匹配背景颜色,以掩盖符号周围杂乱的纹理。两者都能实现分离效果。
  • 纯色填充在照片上会像贴纸。为地貌或3D符号添加维度提示。渐变(受光面较亮,阴影面较暗)可模拟形态。接触阴影——符号底部的模糊扁平椭圆——比偏移投影阴影更能将符号固定在地面上,偏移投影会让符号看起来漂浮着。将符号的光照和阴影方向与底图的
    hillshade-illumination-direction
    (通常为西北方向,315°)匹配,使符号与地形处于同一光照环境中。
通过
addImage
添加 SVG 图标
:在运行时将 SVG 加载到精灵图中(获取 SVG,解码为
Image
,然后调用
map.addImage
),SVG 会在解码时光栅化,因此
linearGradient
feDropShadow
滤镜会正确固化。[1] 两个注意事项:调整
viewBox
以避免光晕和阴影在图标边缘被裁剪,保持
width
/
height
viewBox
成比例,否则字形会扭曲。对于密集点数据,使用
"icon-allow-overlap": true

Visual Hierarchy

视觉层级

A well-ordered label hierarchy means the most important features dominate at the appropriate zoom level. MapLibre controls hierarchy through text size, font weight, letter spacing, and zoom-range visibility.
有序的标签层级意味着最重要的要素在合适的缩放级别下占据主导地位。MapLibre 通过文本大小、字体粗细、字间距和缩放范围可见性来控制层级。

Text size by feature class

按要素类别设置文本大小

Text size should decrease as feature importance decreases. These stops are a starting point; adjust for your tile schema and zoom range:
Label typeBase zoomMax zoomSize range (px)
Continent1414–20
Country2711–17
City71114–24
Town101411–16
Village / hamlet111610–14
Airport / POI101612–14
Peak / summit81310–11
Points of interest (POI) labels should be visually lighter (smaller, thinner weight) than settlement labels at the same zoom. On an imagery map showing gentle terrain like rolling hills, keep peak labels smaller than airport labels — these are elevation markers, not dominant landmarks.
文本大小应随要素重要性降低而减小。以下是起始参考值;可根据瓦片方案和缩放范围调整:
标签类型基础缩放级别最大缩放级别尺寸范围(像素)
大洲1414–20
国家2711–17
城市71114–24
城镇101411–16
村庄/村落111610–14
机场/兴趣点101612–14
山峰/山顶81310–11
兴趣点(POI)标签在相同缩放级别下应比定居点标签视觉上更轻盈(更小、更细的字体)。在展示平缓地形(如丘陵)的影像地图上,山峰标签应比机场标签小——这些是海拔标记,而非主导地标。

Font weight

字体粗细

Use font weight to reinforce hierarchy via
text-font
(e.g.
["Noto Sans Bold"]
): Bold for countries and capital cities, Regular for towns, cities, and most labels, Italic for water bodies, parks, and regions (a cartographic convention no longer always observed).
通过
text-font
使用字体粗细来强化层级(例如
["Noto Sans Bold"]
):粗体用于国家和首都,常规体用于城镇、城市和大多数标签,_斜体_用于水体、公园和区域(这是制图惯例,但如今并非始终遵循)。

Multi-line labels

多行标签

For compact two-line labels (e.g. a symbol character above a name), reduce
text-line-height
below 1.0 to avoid excessive spacing:
json
{
  "text-field": "△\n{name:latin}",
  "text-line-height": 0.9,
  "text-max-width": 8
}
Values around 0.9 produce tight, readable two-line labels at small sizes. Do not go below ~0.8 or lines will overlap at standard font sizes.
对于紧凑的两行标签(例如符号字符在名称上方),将
text-line-height
设为1.0以下以避免间距过大:
json
{
  "text-field": "△\n{name:latin}",
  "text-line-height": 0.9,
  "text-max-width": 8
}
0.9左右的值可在小尺寸下生成紧凑且可读的两行标签。不要低于约0.8,否则标准字体大小下会出现行重叠。

Text transform and spacing

文本转换与间距

  • Use
    "text-transform": "uppercase"
    for country and continent labels — a conventional cartographic practice
  • Use
    "text-letter-spacing": 0.05–0.1
    for region labels to spread them across a territory
  • 对国家和大洲标签使用
    "text-transform": "uppercase"
    ——这是传统制图惯例
  • 对区域标签使用
    "text-letter-spacing": 0.05–0.1
    ,使其在区域内均匀分布

Styling Vector Roads Over Imagery

影像底图上的矢量道路样式设计

Vector road palettes from light-basemap styles (OSM Bright, OSM Liberty) are tuned to pop on pale paper. Dropped on imagery they dominate: high saturation against a desaturated photo, warm hues advance toward the eye, full opacity. Invert the priority. The imagery is the subject; roads are a reference overlay.
  • Desaturate hard. Move fills and casings toward neutral greys or muted tones. The bright orange/yellow road hierarchy (
    #f90
    ,
    #fd4
    ,
    #b06010
    ) is the most common offender; replace fills with light greys and casings with a darker grey or a deep same-hue color.
  • Keep hierarchy in width and value, not hue. The width ramps already encode motorway > residential; you do not need loud color to say it.
  • Opaque, not transparent. Semi-transparent roads let imagery texture bleed through and flatten the whole map. Prefer opaque fills with a value-contained casing for crisp, layered roads.
  • The casing contains the road. A casing darker than the fill draws the median line that keeps dual carriageways from merging into one blob. A knockout casing, a deeper shade of the fill's own hue rather than a foreign black, defines the edge without a harsh cartoon outline.
  • Control brightness by zoom. Roads tuned at high zoom often read too heavy at the opening (low) zoom, where only thin major roads show and the casing dominates. Interpolate color by zoom: casing dark at low zoom lightening as you zoom in, fills the lightest element brightening as the network fills in.
json
{
  "line-color": ["interpolate", ["linear"], ["zoom"], 10, "#454545", 12, "#5a5a5a", 14, "#6e6e6e"]
}
浅色底图样式(OSM Bright、OSM Liberty)的矢量道路配色是为在浅色纸质背景上突出显示而设计的。将其放在影像底图上会过于醒目:高饱和度与低饱和度照片形成对比,暖色调会向视觉前方突出,完全不透明。应颠倒优先级:影像是主体,道路是参考叠加层。
  • 大幅降低饱和度。将填充色和轮廓色调整为中性灰色或柔和色调。亮橙/黄色道路层级(
    #f90
    #fd4
    #b06010
    )是最常见的问题;用浅灰色替换填充色,用深灰色或同色调深色替换轮廓色。
  • 通过宽度和明度而非色调保持层级。宽度渐变已体现了高速公路>居民区道路的层级;无需用鲜艳颜色强调。
  • 使用不透明而非透明。半透明道路会让影像纹理透过来,使整个地图显得扁平。优先选择不透明填充色搭配明度匹配的轮廓色,以实现清晰的分层道路效果。
  • 轮廓色包裹道路。比填充色深的轮廓色会勾勒出中线,防止双向车道合并成一个色块。挖空轮廓色——填充色自身的更深色调而非黑色——可定义边缘,避免生硬的卡通式轮廓。
  • 按缩放级别控制亮度。在高缩放级别调整的道路在初始(低)缩放级别下通常显得过于厚重,此时仅显示细的主干道,轮廓色占主导地位。按缩放级别插值颜色:低缩放级别轮廓色较深,缩放放大时变浅;填充色作为最亮元素,随着道路网络密集化而变亮。
json
{
  "line-color": ["interpolate", ["linear"], ["zoom"], 10, "#454545", 12, "#5a5a5a", 14, "#6e6e6e"]
}

Typography: Glyphs and Font Stacks

排版:字形与字体栈

MapLibre renders text using SDF (signed-distance field) glyphs — precomputed font files that scale cleanly at any zoom or screen density. Glyphs are served from a URL matching the pattern in the style's
glyphs
field. In MapLibre GL JS ≥ 5.11.0 (PR #4564), a missing glyph PBF is no longer fatal — MapLibre renders the glyph locally via TinySDF instead, treating
text-font
as a cascading list of local/web font names (example). That fallback is environment-dependent (it needs a matching font available to the browser or OS) and GL JS onlyMapLibre Native still requires glyphs served from a URL matching this field. Production styles should still serve glyphs explicitly rather than relying on the fallback.
MapLibre 使用 SDF(有符号距离场)字形 渲染文本——预计算的字体文件可在任意缩放级别或屏幕密度下清晰缩放。字形从与样式
glyphs
字段模式匹配的 URL 提供。在 MapLibre GL JS ≥ 5.11.0PR #4564)中,缺失的字形 PBF 文件不再是致命问题——MapLibre 会通过 TinySDF 在本地渲染字形,将
text-font
视为本地/网络字体名称的级联列表(示例)。该回退机制依赖环境(需要浏览器或系统中有匹配的字体),且仅适用于 GL JS——MapLibre Native 仍需要从匹配该字段的 URL 提供字形。生产环境样式仍应明确提供字形,而非依赖回退机制。

Setting the glyphs URL

设置字形 URL

The style's
glyphs
field is a URL template ending in
/{fontstack}/{range}.pbf
(e.g.
https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf
), where
{fontstack}
is the comma-joined
text-font
list and
{range}
a Unicode range — full mechanics: style spec — glyphs.
text-font
is itself a fallback list — see Noto for global maps below.
样式的
glyphs
字段是一个以
/{fontstack}/{range}.pbf
结尾的 URL 模板(例如
https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf
),其中
{fontstack}
是逗号分隔的
text-font
列表,
{range}
是 Unicode 范围——完整机制:样式规范——字形
text-font
本身是一个回退列表——见下文 面向全球地图的 Noto 字体

Font options

字体选项

SourceFonts availableNotes
demotiles.maplibre.org/font
Noto Sans (Latin, Arabic, CJK, etc.), Noto Sans Bold, ItalicFree, publicly hosted; good for prototyping
OpenMapTiles
fonts.openmaptiles.org
Klokantech Noto Sans familyMatched to OMT schema styles
Self-hosted, existing fontReuse prebuilt PBFs (openmaptiles/fonts, UNDP-Data/fonts, or your current server's tree)Full control; no generation needed for standard fonts
Self-hosted, custom fontGenerate PBFs from your own TTF/OTFOnly needed when no prebuilt PBF set exists for the font
For standard fonts (Noto Sans, Open Sans, Roboto, and similar), you do not need to generate anything. The simplest no-generation path is to copy the
{fontstack}/{range}.pbf
tree a glyph server already serves (e.g. the one your style currently points at) onto your own origin. Projects such as openmaptiles/fonts and UNDP-Data/fonts package the common standard fonts as glyph PBFs you can build or pull — note both also run hosted endpoints, which are themselves third-party servers to avoid if self-hosting is the point. Point the style's
glyphs
field at your own URL template; the font names in your
text-font
arrays must exactly match the served font-stack folder names.
Generating glyphs from a TTF/OTF is a separate, heavier task — only needed for a custom or brand font with no existing PBF set. Use Font Maker or fontnik to produce the
.pbf
files, then serve and reference them the same way as above.
来源可用字体说明
demotiles.maplibre.org/font
Noto Sans(拉丁语、阿拉伯语、中日韩等)、Noto Sans Bold、Italic免费、公开托管;适合原型开发
OpenMapTiles
fonts.openmaptiles.org
Klokantech Noto Sans 系列与 OMT 样式架构匹配
自托管现有字体复用预构建的 PBF 文件(openmaptiles/fonts、UNDP-Data/fonts 或当前服务器的目录)完全可控;标准字体无需生成
自托管自定义字体从自有 TTF/OTF 生成 PBF 文件仅当字体无现有 PBF 集合时需要
对于标准字体(Noto Sans、Open Sans、Roboto 等),无需生成任何内容。最简单的无生成路径是将字形服务器已提供的
{fontstack}/{range}.pbf
目录树(例如您当前样式指向的服务器)复制到自有源。openmaptiles/fontsUNDP-Data/fonts 等项目将常见标准字体打包为可构建或拉取的字形 PBF 文件——注意两者也提供托管端点,但如果目标是自托管,应避免依赖这些第三方服务器。将样式的
glyphs
字段指向自有 URL 模板;
text-font
数组中的字体名称必须与提供的字体栈文件夹名称完全匹配。
从 TTF/OTF 生成字形是一项独立且繁琐的任务——仅当自定义或品牌字体无现有 PBF 集合时才需要。使用 Font Makerfontnik 生成
.pbf
文件,然后按上述方式提供和引用。

Noto for global maps

面向全球地图的 Noto 字体

Noto ("no tofu") is Google's open-source family built for near-universal Unicode coverage: Noto Sans covers Latin/Greek/Cyrillic, and script-specific fonts (Noto Sans Arabic, Noto Sans Devanagari, Noto Sans Thai, the region-specific Noto Sans CJK SC/TC/JP/KR) extend it. How you handle non-Latin text depends on the script, and CJK is the case people most often get wrong.
CJK (Chinese, Japanese, Korean) — rendered locally by default; do not serve CJK glyph PBFs. MapLibre GL JS's
localIdeographFontFamily
map option defaults to
'sans-serif'
, so CJK characters are generated on-device (TinySDF) and the style's
text-font
is ignored for them (except the weight keyword). This exists because CJK text has poor locality across Unicode ranges — a single tile can otherwise trigger dozens of large glyph requests.[3] Leave it on; optionally point it at a nicer on-device CJK font. Setting
localIdeographFontFamily: false
restores served glyphs for CJK, which is much slower — only do it if you specifically need the served font's shapes.
javascript
const map = new maplibregl.Map({
  // ...
  localIdeographFontFamily: '"Noto Sans CJK SC", sans-serif' // optional; default is 'sans-serif'
});
Other non-Latin scripts (Arabic, Hebrew, Thai, …) — need real glyphs.
localIdeographFontFamily
does not apply here. Add the relevant Noto script font to the layer's
text-font
fallback list and serve its glyph PBFs (or rely on the GL JS ≥ 5.11.0 local fallback, which is environment-dependent — see the top of this section). Font names must match those the glyph server knows.
Devanagari, Khmer, and other scripts requiring ligatures/reordering — glyphs alone will not fix this. MapLibre maps each Unicode codepoint to one glyph with no shaping engine (no HarfBuzz/Raqm), so it cannot form the conjuncts and reordering these scripts require — serving the correct font's PBFs will not produce correct-looking text. There is currently no configuration fix; this is a known architectural limitation.[6]
json
{ "text-font": ["Noto Sans Regular", "Noto Sans Devanagari Regular"] }
Arabic and Hebrew additionally need the RTL text plugin for correct right-to-left shaping and ordering — glyph coverage alone is not enough. MapLibre GL JS does not handle RTL by default[2]:
javascript
import { setRTLTextPlugin } from 'maplibre-gl';
setRTLTextPlugin('https://unpkg.com/maplibre-gl/dist/maplibre-gl-rtl-text.js', null, true);
Call this before initializing the map.
Noto(意为“无豆腐块”)是谷歌的开源字体系列,旨在实现近乎通用的 Unicode 覆盖:Noto Sans 支持拉丁语/希腊语/西里尔语,特定脚本字体(Noto Sans Arabic、Noto Sans Devanagari、Noto Sans Thai、针对区域的 Noto Sans CJK SC/TC/JP/KR)扩展了其覆盖范围。非拉丁语文本的处理方式取决于脚本,中日韩(CJK)文本是最常出错的情况。
中日韩(CJK)——默认本地渲染;请勿提供 CJK 字形 PBF 文件。MapLibre GL JS 的
localIdeographFontFamily
地图选项默认为
'sans-serif'
,因此 CJK 字符会在设备上生成(TinySDF),样式的
text-font
对其无效(除了字体粗细关键字)。这是因为 CJK 文本在 Unicode 范围内的局部性较差——单个瓦片可能触发数十个大型字形请求。[3] 保持该选项开启;可选择性地将其指向更优质的设备本地 CJK 字体。设置
localIdeographFontFamily: false
会恢复 CJK 字形的托管提供,这会慢得多——仅在特别需要托管字体的字形时才这么做。
javascript
const map = new maplibregl.Map({
  // ...
  localIdeographFontFamily: '"Noto Sans CJK SC", sans-serif' // 可选;默认值为 'sans-serif'
});
其他非拉丁语脚本(阿拉伯语、希伯来语、泰语等)——需要真实字形
localIdeographFontFamily
不适用于这些脚本。将相关的 Noto 脚本字体添加到图层的
text-font
回退列表,并提供其字形 PBF 文件(或依赖 GL JS ≥ 5.11.0 的本地回退,这依赖环境——见本节开头)。字体名称必须与字形服务器识别的名称匹配。
梵文、高棉语等需要连字/重排的脚本——仅靠字形无法解决问题。MapLibre 将每个 Unicode 码点映射到一个字形,无排版引擎(无 HarfBuzz/Raqm),因此无法形成这些脚本所需的连字和重排——提供正确字体的 PBF 文件也无法生成正确的文本外观。目前无配置修复方案,这是已知的架构限制。[6]
json
{ "text-font": ["Noto Sans Regular", "Noto Sans Devanagari Regular"] }
阿拉伯语和希伯来语还需要 RTL 文本插件才能实现正确的从右到左排版和排序——仅靠字形覆盖是不够的。MapLibre GL JS 默认不处理 RTL[2]:
javascript
import { setRTLTextPlugin } from 'maplibre-gl';
setRTLTextPlugin('https://unpkg.com/maplibre-gl/dist/maplibre-gl-rtl-text.js', null, true);
在初始化地图前调用此方法。

Sprites: Icons and Markers

精灵图:图标与标记

The style JSON's
sprite
value is a base URL with no file extension (e.g.
https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite
, for testing purposes only, do not use in production); MapLibre appends
.json
,
.png
, and
@2x
variants itself. Symbol layers reference sprite images by ID with
icon-image
; the value must exactly match an ID in the sprite JSON index or the icon is silently not rendered.
样式 JSON 的
sprite
值是不带文件扩展名的基础 URL(例如
https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite
,仅用于测试,请勿在生产环境使用);MapLibre 会自动追加
.json
.png
@2x
变体。符号图层通过
icon-image
按 ID 引用精灵图中的图像;该值必须与精灵图 JSON 索引中的 ID 完全匹配,否则图标会静默不渲染。

Self-hosted sprites

自托管精灵图

To avoid third-party dependencies, copy an existing sprite directory (PNG + JSON, plus any @2x files) from a style or tileset provider and host it under your own domain, pointing the style's
sprite
property at its base URL. Always check the provider's license before republishing and add attribution if required.
Host sprite assets on a static host you control (GitHub Pages, Netlify, Vercel, S3, same origin as the style). Do not point production styles at
raw.githubusercontent.com
Raw is for serving repository blobs, not production assets: anonymous requests are aggressively rate-limited so real users see intermittent HTTP 429s [4], caching is fixed at five minutes with no control, there is no SLA, and private-repo URLs return 404 to everyone but authenticated collaborators (it works for you while logged in, then fails for every other user) [5].
为避免依赖第三方,从样式或瓦片提供商处复制现有精灵图目录(PNG + JSON,以及所有 @2x 文件),并托管在自有域名下,将样式的
sprite
属性指向其基础 URL。重新发布前务必检查提供商的许可证,并按需添加署名。
将精灵图资产托管在您可控的静态主机上(GitHub Pages、Netlify、Vercel、S3、与样式同源)。请勿将生产环境样式指向
raw.githubusercontent.com
——Raw 用于提供仓库 blob,而非生产资产:匿名请求会被严格限流,导致真实用户间歇性遇到 HTTP 429 错误 [4],缓存固定为5分钟且无法控制,无服务级别协议(SLA),私有仓库 URL 对除已认证协作者外的所有人返回404(您登录时可用,但其他用户访问时会失败)[5]。

Building a sprite from SVGs

从 SVG 构建精灵图

Generate sprite assets from a directory of SVGs with tools such as spritezero, spreet, or Martin.
Useful icon sources include Maki and Temaki. These are common source repositories for map-style SVG icons, but check each repository's license before republishing derived sprite assets.
使用 spritezerospreetMartin 等工具从 SVG 目录生成精灵图资产。
实用的图标源包括 MakiTemaki。这些是地图样式 SVG 图标常用的源仓库,但重新发布衍生精灵图资产前请检查每个仓库的许可证。

Creating your own icons

创建自有图标

For a small number of custom icons,
map.loadImage()
and
addImage()
can work without a full sprite pipeline. For larger reusable icon sets, generating a sprite remains the standard and more maintainable approach. [10]
对于少量自定义图标,
map.loadImage()
addImage()
无需完整的精灵图流水线即可工作。对于更大的可复用图标集,生成精灵图仍是标准且更易维护的方法。[10]

Broken route shields

道路标识牌失效

Broken-looking route shields (bare floating numbers, missing badges) are almost always a missing sprite image. The shield number is text (font) and usually renders fine; the badge behind it is an
icon-image
from the sprite. Diagnose in this order:
  1. Confirm glyphs load. Probe the
    glyphs
    server for the exact
    text-font
    names and expect HTTP 200. If they 200, the font is not the problem.
  2. Confirm the sprite carries the shield images. OpenMapTiles and OSM Liberty shield layers use
    icon-image: "{network}_{ref_length}"
    for known networks (e.g.
    us-interstate_2
    ,
    us-highway_3
    ,
    us-state_2
    ) and
    road_{ref_length}
    for generic refs. A missing icon is silently omitted, so grep the sprite JSON for those keys.
Not every sprite carries shields localized for the US, so grep the sprite JSON for the
{network}_{ref_length}
keys before assuming they exist. Both the
demotiles.maplibre.org/styles/osm-bright-gl-style/sprite
and
openmaptiles.github.io/osm-bright-gl-style/sprite
sheets currently include
us-interstate_*
,
us-highway_*
, and
us-state_*
(alongside the generic
road_1
road_6
), but a minimal or custom sprite may ship only the generic
road_*
. If yours lacks the shield images and your tiles populate
network
,
ref
, and
ref_length
(the OSM US OpenMapTiles tiles do), point
sprite
at one that has them — the
{network}_{ref_length}
layers then resolve with no layer edits.
道路标识牌显示异常(仅显示浮动数字、缺失标识框)几乎总是因为缺失精灵图图像。标识牌数字是文本(字体),通常可正常渲染;其背后的标识框是来自精灵图的
icon-image
。按以下顺序排查:
  1. 确认字形已加载。针对确切的
    text-font
    名称探测字形服务器,预期返回 HTTP 200。如果返回200,则字体无问题。
  2. 确认精灵图包含标识牌图像。OpenMapTiles 和 OSM Liberty 标识牌图层对已知网络使用
    icon-image: "{network}_{ref_length}"
    (例如
    us-interstate_2
    us-highway_3
    us-state_2
    ),对通用标识使用
    road_{ref_length}
    。缺失图标会被静默忽略,因此在精灵图 JSON 中搜索这些键。
并非所有精灵图都包含针对美国本地化的标识牌,因此在假设其存在前,请在精灵图 JSON 中搜索
{network}_{ref_length}
键。
demotiles.maplibre.org/styles/osm-bright-gl-style/sprite
openmaptiles.github.io/osm-bright-gl-style/sprite
目前均包含
us-interstate_*
us-highway_*
us-state_*
(以及通用的
road_1
road_6
),但极简或自定义精灵图可能仅包含通用的
road_*
。如果您的精灵图缺少标识牌图像,且瓦片包含
network
ref
ref_length
(OSM US OpenMapTiles 瓦片包含这些字段),请将
sprite
指向包含这些图像的精灵图——
{network}_{ref_length}
图层无需编辑即可解析。

Layer Ordering

图层排序

MapLibre renders layers in the order they appear in the style
layers
array — first item is drawn first (bottom), last is drawn last (top). Getting this wrong is the most common cause of data layers obscuring basemap labels.
MapLibre 按样式
layers
数组中的顺序渲染图层——第一个元素最先绘制(最底层),最后一个元素最后绘制(最顶层)。排序错误是数据图层遮挡底图标签的最常见原因。

The injection pattern

注入模式

When adding your own data to an existing basemap style at runtime, insert your layers before the first symbol layer (find it with
map.getStyle().layers.find((l) => l.type === 'symbol')?.id
and pass it as the second argument of
addLayer
) so your geometry renders under labels. Without that argument the layer goes above everything, including labels.
在运行时向现有底图样式添加自有数据时,将图层插入第一个符号图层之前(通过
map.getStyle().layers.find((l) => l.type === 'symbol')?.id
找到该图层,并将其作为
addLayer
的第二个参数),使您的几何图形渲染在标签下方。如果不传入该参数,图层会添加到所有内容之上,包括标签。

Canonical layer order for custom styles

自定义样式的标准图层顺序

When building a style from scratch, follow this ordering bottom to top:
  1. background
  2. Raster imagery (if using satellite/aerial source)
  3. Hillshade layers (if any — see maplibre-terrain-patterns for configuration)
  4. Terrain fill (water, land, parks — polygon layers)
  5. Line layers (roads, boundaries, rivers)
  6. Your data polygon and line layers
  7. Symbol layers from the basemap (place labels, road labels)
  8. Your data symbol/label layers (if any)
Hillshade sits directly above raster imagery and below all vector layers, with sufficient transparency to allow the imagery to show through. If you add transparency to the imagery and layer it over the hillshade, the imagery will appear faded or washed out. Hillshade applied over vector layers will make line and fill colors look blotchy, blurry or muted.
从头构建样式时,请遵循以下从下到上的顺序:
  1. background
    (背景)
  2. 栅格影像(如果使用卫星/航空源)
  3. 晕渲图层(如有——配置见 maplibre-terrain-patterns
  4. 地形填充(水域、陆地、公园——多边形图层)
  5. 线图层(道路、边界、河流)
  6. 自有数据多边形和线图层
  7. 底图符号图层(地点标签、道路标签)
  8. 自有数据符号/标签图层(如有)
晕渲图层直接位于栅格影像之上、所有矢量图层之下,需设置足够的透明度以显示影像。如果为影像添加透明度并将其置于晕渲图层之上,影像会显得褪色或泛白。晕渲图层应用于矢量图层之上会使线和填充颜色显得斑驳、模糊或暗淡。

Accessibility

无障碍设计

MapLibre styles are rendered in the browser as a WebGL canvas. Accessibility considerations:
  • Text contrast: WCAG 2.1 AA requires 4.5:1 for normal text, 3:1 for large.[9] White text on a
    rgba(0,0,0,0.75)
    halo satisfies this for most backgrounds — check the combined text+halo color, not the text alone, with a tool like the WebAIM Contrast Checker.
  • Do not rely on color alone: use shape, size, or pattern in addition to hue.
  • Minimum label size: prefer size stops that start at 10px even at low zoom.
  • Screen readers and the WebGL canvas: MapLibre's canvas is not inherently accessible to screen readers. For accessible map experiences, provide an accessible alternative such as a data table or a text description of the map contents, and use maplibre-gl-accessibility for keyboard navigation and ARIA roles.
MapLibre 样式在浏览器中渲染为 WebGL 画布。无障碍设计注意事项:
  • 文本对比度:WCAG 2.1 AA 标准要求普通文本对比度为4.5:1,大文本为3:1。[9] 白色文本搭配
    rgba(0,0,0,0.75)
    光晕在大多数背景下都能满足要求——使用 WebAIM 对比度检查器 检查文本+光晕的组合颜色,而非仅文本颜色。
  • 不要仅依赖颜色:除色调外,还使用形状、大小或图案。
  • 最小标签尺寸:优先选择起始尺寸为10px的缩放停止点,即使在低缩放级别也是如此。
  • 屏幕阅读器与 WebGL 画布:MapLibre 的画布本身对屏幕阅读器不友好。如需无障碍地图体验,请提供替代方案(如数据表或地图内容的文本描述),并使用 maplibre-gl-accessibility 实现键盘导航和 ARIA 角色。

Related Skills

相关指南

  • maplibre-tile-sources — Setting up glyphs, sprites, and source configuration.
  • maplibre-pmtiles-patterns — Serving imagery (raster) and terrain sources from PMTiles files.
  • maplibre-terrain-patterns — Hillshade configuration, multi-pass techniques, 3D terrain, DEM sources.
  • maplibre-tile-sources — 配置字形、精灵图和源。
  • maplibre-pmtiles-patterns — 从 PMTiles 文件提供影像(栅格)和地形源。
  • maplibre-terrain-patterns — 晕渲配置、多通道技术、3D地形、DEM源。

References

参考资料

  1. Map.addImage()
    (MapLibre GL JS API)
  2. setRTLTextPlugin
    (MapLibre GL JS API)
    — required for correct Arabic/Hebrew shaping
  3. Use locally generated ideographs (MapLibre GL JS example)
    localIdeographFontFamily
    default and CJK rendering behavior
  4. Unauthenticated rate limits on
    raw.githubusercontent.com
    (GitHub Community Discussion)
    — anonymous requests are rate-limited; production traffic sees intermittent HTTP 429
  5. raw.githubusercontent.com
    and private repositories (GitHub Community Discussion)
    — private-repo raw URLs return 404/403 to anonymous requests
  6. "About Text Rendering in MapLibre" — SDF glyph architecture, codepoint-to-glyph mapping, and why shaping-dependent scripts (Devanagari, Khmer) don't render correctly
  1. Map.addImage()
    (MapLibre GL JS API)
  2. setRTLTextPlugin
    (MapLibre GL JS API)
    — 阿拉伯语/希伯来语正确排版所需
  3. 使用本地生成的表意文字 (MapLibre GL JS 示例)
    localIdeographFontFamily
    默认值和 CJK 渲染行为
  4. raw.githubusercontent.com
    匿名请求限流 (GitHub 社区讨论)
    — 匿名请求会被限流;生产流量会间歇性遇到 HTTP 429
  5. raw.githubusercontent.com
    与私有仓库 (GitHub 社区讨论)
    — 私有仓库 Raw URL 对匿名请求返回404/403
  6. "MapLibre 文本渲染说明" — SDF 字形架构、码点到字形的映射,以及依赖排版的脚本(梵文、高棉语)无法正确渲染的原因