经纬志
首页 / MapLibre / 教程

样式与图层

先看结论

MapLibre 里没有「往地图上画一个图形」这种操作,只有数据源和图层。 样式 JSON 描述的就是这两者的组合,运行时改样式本质上也是改这份 JSON。

最后验证 2026-11-03 · MapLibre GL JS 4.x · 更新 2026-10-27 · 引用本文

样式 JSON 的四个部分

{
  "version": 8,
  "sources": {
    "base": {
      "type": "vector",
      "url": "https://example.com/tiles.json"
    }
  },
  "layers": [
    { "id": "bg", "type": "background", "paint": { "background-color": "#eef3f7" } },
    {
      "id": "land",
      "type": "fill",
      "source": "base",
      "source-layer": "countries",
      "paint": { "fill-color": "#f7f4ec" }
    }
  ],
  "glyphs": "https://example.com/font/{fontstack}/{range}.pbf"
}
字段作用
version样式规范版本,固定为 8
sources数据源定义:vector、raster、geojson、raster-dem
layers绘制顺序,数组越靠后越在上层
glyphs字体切片地址,文本图层必需
sprite图标雪碧图地址,符号图层用到图标时必需

source-layer 只对矢量瓦片有效,值必须等于瓦片里实际存在的图层名。 写错不会报错,图层就是空的。用 https://demotiles.maplibre.org/tiles/tiles.json 可以查看 demo 数据里只有 countries、geolines、 centroids 三个图层。

paint 与 layout 的分工

paint 控制外观:颜色、透明度、线宽、半径,可以随缩放插值。 layout 控制几何排布与显示:是否可见、线端样式、文字大小与位置。

map.addLayer({
  id: 'route-line',
  type: 'line',
  source: 'route',
  layout: {
    'line-cap': 'round',
    'line-join': 'round'
  },
  paint: {
    'line-color': '#1f5fd0',
    'line-width': ['interpolate', ['linear'], ['zoom'], 3, 1.5, 8, 5],
    'line-dasharray': [2, 2]
  }
});

line-width 用 interpolate 表达式按缩放变化, 这是矢量地图的常见写法:低缩放细线,放大后加粗。 直接写固定值也不会报错,只是放大后显得太细。

图层顺序与 beforeId

addLayer 默认把新图层放到最上层。需要压在某层下面时, 第二个参数传目标图层的 id:

map.addLayer(pointLayer, 'route-line');   // 放在路线之下

用样式里已有的图层 id 做锚点最可靠。锚点 id 拼错时, 新图层会直接加到最上层,不抛错,于是覆盖关系与预期相反。

运行时改样式

操作方法
改单个绘制属性setPaintProperty(layerId, prop, value)
改单个布局属性setLayoutProperty(layerId, prop, value)
显示 / 隐藏setLayoutProperty(id, 'visibility', 'none')
删除图层removeLayer(id)
替换整个样式setStyle(urlOrObject)
读取当前样式map.getStyle()
map.setPaintProperty('route-line', 'line-color', '#c2410c');
map.setLayoutProperty('route-line', 'visibility', 'none');
map.setLayoutProperty('route-line', 'visibility', 'visible');

切换样式的代价

setStyle 会丢弃当前的源与图层,换成新样式的内容。 底图切换必须这么做,但自定义图层要重新添加。 需要保留要素状态时,在 style.load 里重建:

function addOverlays() {
  map.addSource('zones', { type: 'geojson', data: zones });
  map.addLayer({ id: 'zones', type: 'fill', source: 'zones',
                 paint: { 'fill-color': '#17694a', 'fill-opacity': 0.25 } });
}

map.on('style.load', addOverlays);
map.setStyle('https://demotiles.maplibre.org/style.json');

v4 的 setStyle 支持 diff 参数,新样式与原样式共享的部分会被复用。 对同一份样式只改少量图层时,diff 能避免整图重绘与闪烁。

过滤表达式

filter 写在图层上,按属性筛选要素。 矢量瓦片的属性名来自瓦片数据,GeoJSON 的属性名来自你自己的 properties。

map.addLayer({
  id: 'capitals',
  type: 'circle',
  source: 'base',
  'source-layer': 'centroids',
  filter: ['==', ['get', 'NAME'], 'Beijing'],
  paint: { 'circle-radius': 5, 'circle-color': '#c2410c' }
});

控件与图层是两套东西。导航按钮、比例尺属于控件, 不写在样式里,用 addControl 添加。 样式只描述数据怎么画。

常见错误

用 Mapbox 的图层 id 改 MapLibre 的样式

不同底图的图层 id 完全不同。把 water、 road-primary 这类 id 从 Mapbox 样式照搬到另一份样式上, 改属性时提示图层不存在。先用 map.getStyle().layers 查真实 id。

在 load 之前 addLayer

样式还没解析完时,源可能不存在,添加图层会失败。 统一放进 load 回调,能消掉这一整类问题。

把 visibility 写成 paint 属性

visibility 属于 layout。写进 paint 会被忽略, 图层依然可见,看起来像「隐藏不生效」。

引用本文

经纬志(2026)。《样式与图层》。https://www.jingweizhi.com/maplibre/tutorial/style-layers.html(访问日期 2026-11-03)。