经纬志
首页 / MapLibre / 教程

表达式与属性驱动样式

先看结论

样式里的颜色、宽度、半径都可以写成表达式,让同一个图层按要素属性或缩放级别自动变化。 表达式是 JSON 数组,第一个元素是操作符,后面是参数,嵌套起来就是一棵求值树。

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

两种输入:属性与缩放

按要素属性变化叫 data-driven,按缩放变化叫 camera-driven。两者的写法不同, 能用在哪些属性上也不同:

输入代表写法常见属性
要素属性['get', 'level']、['match', ...]fill-color、circle-radius、text-field
缩放级别['interpolate', ['linear'], ['zoom'], ...]line-width、circle-radius、fill-opacity
几何类型['geometry-type']过滤条件、text-field
要素状态['feature-state', 'hover']悬停、选中高亮
热力密度['heatmap-density']只在 heatmap 图层的 color 属性里可用

不能把两种输入混在同一个数值位置。例如用 interpolate 按缩放插值、 插值结果又写成 ['get', 'x'],规范层会拒绝,控制台报 Expected number but found ...,该属性回退到默认值。 确实需要两者同时参与时,拆成两个图层,用 minzoom / maxzoom 分工。

分类着色:match 与 case

match 适合枚举值到样式的映射,比一串嵌套 case 更好读, 也更快。最后一个参数是兜底值,漏写会得到默认样式。

map.addLayer({
  id: 'landuse-fill',
  type: 'fill',
  source: 'base',
  'source-layer': 'landuse',
  paint: {
    'fill-color': ['match', ['get', 'class'],
      'forest',  '#2f6b45',
      'farmland','#c9c07a',
      'urban',   '#c8c2ba',
      '#e8eef4'                       // 兜底
    ],
    'fill-opacity': ['case',
      ['==', ['get', 'class'], 'urban'], 0.55,
      0.35
    ]
  }
});

case 是「条件, 值, 条件, 值, 兜底」的平铺结构。 条件必须是布尔表达式,例如 ['==', ...]、['has', ...]、 ['>', ['get', 'flow'], 1000]。

连续插值:interpolate 与 step

interpolate 在断点之间做线性插值,适合半径、宽度、透明度; step 是阶梯式跳变,适合「级别到了就换一档」的离散样式。

map.addLayer({
  id: 'sites-circle',
  type: 'circle',
  source: 'sites',
  paint: {
    'circle-radius': ['interpolate', ['linear'], ['zoom'],
      8,  3,
      12, 6,
      16, 14
    ],
    'circle-color': ['step', ['get', 'level'],
      '#9ecae1',           // level 小于 2
      2, '#4292c6',
      4, '#08519c'
    ],
    'circle-stroke-width': 1,
    'circle-stroke-color': '#ffffff'
  }
});

step 的读法是:先给兜底值,然后「阈值, 结果」成对出现。 上例中 level 为 2 时进入第二档,为 4 时进入第三档。

补默认值:coalesce

数据里的字段可能缺失或为 null。直接 ['get', 'name'] 会让 text-field 变成空字符串,注记整片消失。 coalesce 返回第一个非空值。

map.addLayer({
  id: 'sites-label',
  type: 'symbol',
  source: 'sites',
  layout: {
    'text-field': ['coalesce', ['get', 'name'], ['get', 'code'], '未命名'],
    'text-size': ['interpolate', ['linear'], ['zoom'], 10, 11, 16, 15],
    'text-offset': [0, 1.2],
    'text-anchor': 'top'
  },
  paint: {
    'text-color': '#12202c',
    'text-halo-color': '#ffffff',
    'text-halo-width': 1.5
  }
});

feature-state:悬停与选中

feature-state 不写进样式,而是运行时通过 map.setFeatureState() 设置。用它做高亮,不必为每个要素重建图层。

var hoveredId = null;

map.on('mousemove', 'sites-circle', function (e) {
  if (e.features.length === 0) return;
  if (hoveredId !== null) {
    map.setFeatureState({ source: 'sites', id: hoveredId }, { hover: false });
  }
  hoveredId = e.features[0].id;
  map.setFeatureState({ source: 'sites', id: hoveredId }, { hover: true });
});

map.addLayer({
  id: 'sites-hover',
  type: 'circle',
  source: 'sites',
  paint: {
    'circle-radius': ['case', ['boolean', ['feature-state', 'hover'], false], 10, 0],
    'circle-color': '#c2410c'
  }
});

e.features[0].id 只有在源里存在要素 id 时才有值。 GeoJSON 源加 generateId: true,或者用 promoteId 把某个属性提升为 id。

注意事项

颜色必须能被解析

['get', 'color'] 直接当颜色用,遇到 "red" 以外的非法值会触发校验错误。数值转颜色用 ['to-color'] 包裹,或改用 match 映射。

layout 里很多属性不支持数据驱动

text-field、icon-image 可以按属性变化, line-cap、text-anchor 这类只能用常量。 写错时报 data expressions not supported,图层不显示。

表达式里的类型要显式

['==', ['get', 'level'], 2] 在 level 是字符串 "2" 时结果为 false。用 ['to-number'] 或 ['to-string'] 统一类型,比依赖隐式转换可靠。

引用本文

经纬志(2026)。《表达式与属性驱动样式》。https://www.jingweizhi.com/maplibre/tutorial/expressions.html(访问日期 2026-11-03)。