类别 | 用途 |
|---|
数学运算 | 算术、比较、逻辑 | 字符串运算 | 拼接、格式化 | 数据运算 | get、has、id、geometry-type、properties、feature-state
| 相机运算 | zoom,以及隐含的 pitch / bearing
| 断言与类型转换 | number、string、boolean 等
|
数据表达式数据表达式访问要素自身的数据,是数据驱动样式(DDS)的基础。 {
"circle-color": [
"rgb",
["get", "temperature"], // 温度越高红色越强
0, // 绿色恒为 0
["-", 100, ["get", "temperature"]] // 温度越低蓝色越强
]
}
数据表达式可以用于 filter 属性,以及绝大多数 paint 和 layout 属性。但不是全部——规范里专门标注了哪些属性支持数据表达式(data-driven styling 一栏),要用的时候查一下。 常见的例外:line-dasharray、text-line-height、line-cap 目前只支持随 zoom 变化,不支持数据表达式。
相机表达式:随缩放变化用 zoom 让外观随缩放级别演化: {
"circle-radius": [
"interpolate", ["linear"], ["zoom"],
5, 1, // zoom ≤ 5 时半径 1px
10, 5 // zoom ≥ 10 时半径 5px,中间线性插值
]
}
这里有一个非常容易踩的坑,官方文档特别强调过: 当相机表达式作为 paint 或 layout 属性的值时,它必须严格是以下四种形式之一: ["interpolate", interpolation, ["zoom"], ...]
["step", ["zoom"], ...]
["let", ...变量绑定..., ["interpolate", interpolation, ["zoom"], ...]]
["let", ...变量绑定..., ["step", ["zoom"], ...]]
也就是说,["zoom"] 只能作为最外层 interpolate 或 step 的输入,不能嵌套在别的表达式中间。 layout 与 paint 的求值时机差异这是一个实际会影响表现的区别: 因为 layout 影响几何布局(文字位置、面的大小),频繁重算代价高;paint 只影响着色,重算便宜。 实践建议:想让点随缩放平滑变大 → 用 circle-radius(paint);想让文字标签只在整数级别出现 → 用 text-size(layout)。 三个核心表达式match:等值匹配用于按属性值分支,功能上类似 switch: ["match", ["get", "type"],
"highway", "#ff0000",
"secondary", "#00ff00",
"#0000ff" // 默认值
]
当输入是枚举值(类型、分类、等级)时,优先用 match 而不是 case——match 的求值效率更高,因为它是哈希匹配而非顺序求值。 case:多条件分支条件判断,按顺序求值,第一个为真的条件决定结果: ["case",
["==", ["get", "type"], "forest"], "green",
["any",
["==", ["get", "type"], "industrial"],
["==", ["get", "type"], "residential"]], "orange",
[">=", ["get", "population"], 100000], "blue",
"pink" // 默认值
]
读法:森林是绿色;工业或居住区是橙色;其余中人口 ≥10 万的蓝色;其余粉色。 注意顺序的重要性:居住区人口即使超过 10 万,仍然是橙色——因为居住区的判断在前。 step:分段取值["step", ["zoom"],
2, // 默认值
8, 4, // zoom ≥ 8 时为 4
12, 8 // zoom ≥ 12 时为 8
]
step 和 interpolate 的选择:
需求 | 用哪个 |
|---|
数值连续变化(半径、宽度、不透明度) | interpolate
| 离散跳变(图标切换、字体大小分级) | step
|
一个典型场景:低缩放显示一个简单小点,高缩放无闪烁地切换成完整图标——用 step,因为中间状态没有意义。 interpolate:连续插值["interpolate", ["linear"], ["get", "value"],
0, "#ffffff",
0.5, "#ffffcc",
1, "#ff0000"
]
也支持指数/三次贝塞尔插值: ["interpolate", ["exponential", 1.5], ...]
["interpolate", ["cubic-bezier", 0.4, 0, 0.6, 1], ...]
在数据可视化里做热力色带、坡度分级、密度分级,interpolate 是主力工具。 let:绑定变量避免重复表达式嵌套深了会很难读,let 可以绑定中间值: ["let",
"temp", ["get", "temperature"],
"pop", ["get", "population"],
["case",
[">", ["temp", 30], "#ff0000",
[">", ["var", "pop"], 100000], "#ffaa00",
"#0000ff"
]
]
["var", "name"] 引用绑定变量。规范还要求:相机表达式若通过 let 使用,zoom 仍必须是最外层 interpolate/step 的输入,即 ["let", ..., ["interpolate", ..., ["zoom"], ...]] 这个形式。
类型断言与转换这两个操作符容易混淆,区别很重要: 断言(assertion)—— 类型不符则报错["string", ["get", "feature_property"]]
如果属性值不是字符串,求值时抛错。它不转换类型,只是声明"我期望这里是字符串"。 转换(coercion)—— 类型不符则用默认值["to-number", ["get", "feature_property"], 0]
尝试转成数字,失败则返回 0。 命名约定:形如 to-xxx 的是转换,形如 xxx(不加 to-)的是断言。 实际开发中的建议:处理可能缺失或类型不一致的外部数据时,用 to-number 带默认值,而不是用断言。让地图因为一个脏数据崩掉是不可接受的。 综合运用:数据 + 缩放 + 变量一个真实的样式片段——道路按等级分色,同时线宽随缩放变化: {
"id": "roads",
"type": "line",
"source": "osm",
"source-layer": "transportation",
"paint": {
"line-color": [
"match", ["get", "class"],
"motorway", "#e74c3c",
"trunk", "#e67e22",
"primary", "#f1c40f",
"secondary", "#95a5a6",
"#7f8c8d"
],
"line-width": [
"interpolate", ["linear"], ["zoom"],
8, ["match", ["get", "class"], "motorway", 2, 1],
16, ["match", ["get", "class"], "motorway", 8, 4]
],
"line-opacity": [
"interpolate", ["linear"], ["zoom"],
10, 0.6,
14, 1
]
}
}
数据驱动颜色、缩放驱动宽度,两者完全解耦。想调配色?改 line-color。想调线宽?改 line-width。互不影响,也不需要碰 JavaScript。 这就是样式表达式的真正价值。 filter 也可以是表达式别忘了过滤器本身支持完整表达式能力: {
"id": "big-roads-only",
"type": "line",
"source": "osm",
"filter": [
"all",
["==", ["get", "class"], "motorway"],
[">", ["get", "lanes"], 4]
]
}
all / any / none 用于组合条件,in 用于集合判断:
["in", ["get", "type"], ["literal", ["industrial", "residential"]]]
literal 是必需的——没有它,数组会被解释为表达式而不是字面量数组。这是新手最常见的报错来源。
常见错误速查现象 | 原因 |
|---|
样式不生效,无报错 | 用了不支持数据表达式的属性(如 line-dasharray) | 表达式解析失败 | 数组字面量漏了 ["literal", ...] | 类型不匹配报错 | 用了断言(string)而非转换(to-number 带默认值) | 缩放时行为怪异 | ["zoom"] 没有作为最外层 interpolate/step 的输入
| | |
|