mapboxgl 中插值表達式的應用場景
一、前言
interpolate是mapboxgl地圖樣式中用于插值的表達式,能對顏色和數(shù)字進行插值。
它的應用場景有兩類:
- 對地圖數(shù)據(jù)進行顏色拉伸渲染。常見的應用場景有:熱力圖、軌跡圖、模型網(wǎng)格渲染等。
- 在地圖縮放時對圖形屬性進行插值。具體為,隨著地圖的縮放,在改變圖標大小、建筑物高度、圖形顏色等屬性時,對屬性進行插值,從而實現(xiàn)平滑的過渡效果。
這篇文章就把 mapboxgl 中interpolate插值工具的常見應用場景介紹一下。
二、語法
先看一下interpolate插值工具的語法。
interpolate表達式要求至少有5個參數(shù),分別是表達式名稱、插值類型、輸入值、判斷值、輸出值。
["interpolate", //表達式名稱
interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ], //插值類型
input: number, //輸入值
stop_input_1: number, stop_output_1: OutputType, //一組判斷值和輸出值
stop_input_n: number, stop_output_n: OutputType, ... //一組判斷值和輸出值
]: OutputType (number, array<number>, or Color) //返回插值完的結果
其中插值類型會在后面詳細介紹,這里先不多說。
判斷值、輸出值是“一組”的關系,它們必須兩兩出現(xiàn)。
還有一點需要注意,就是判斷值必須遵循升序規(guī)則。
下面我們結合實際場景理解起來會更容易一些,先說第一類應用場景:對地圖數(shù)據(jù)進行顏色拉伸渲染。
三、對地圖顏色進行拉伸渲染
這個和ArcGIS中對柵格數(shù)據(jù)進行顏色拉伸渲染是一個意思。
地圖顏色拉伸渲染的本質(zhì),是根據(jù)網(wǎng)格的屬性值為網(wǎng)格設置顏色,當網(wǎng)格足夠小、足夠密時,就容易產(chǎn)生顏色平滑過渡的效果。
前面說到,常見的應用場景有:熱力圖、軌跡圖、模型網(wǎng)格渲染等。
在mapboxgl中,熱力圖和軌跡圖它們雖然看上去不像是由網(wǎng)格組成的,但在計算機圖形學的框架下,任何在屏幕上顯示的內(nèi)容,都是由像素來呈現(xiàn)的,而像素是規(guī)律排列的網(wǎng)格,所以可以把熱力圖和軌跡也看成是由網(wǎng)格組成的。
這一點在WebGL開發(fā)時尤為明顯,因為需要自己寫片元著色器定義每個像素的顏色。
mapboxgl提供了熱力圖和軌跡圖的像素屬性值計算工具:
-
熱力圖中為
heatmap-density表達式,用來計算熱力圖上每個像素的熱力值。 -
軌跡線中為
line-progress表達式,用來計算在當前線段上每個像素的行進百分比。
模型網(wǎng)格渲染時,網(wǎng)格需要自己生成,網(wǎng)格中的屬性值也需要自己計算,通常在項目上這些是由模型完成的,如:EFDC水動力模型、高斯煙羽大氣污染擴散模型等。
模型輸出的結果就是帶屬性值的網(wǎng)格,interpolate表達式的任務仍然是根據(jù)網(wǎng)格的屬性值為網(wǎng)格設置顏色。
1. 熱力圖
實現(xiàn)效果:

數(shù)據(jù)使用的是北京市公園綠地無障礙設施數(shù)量。
代碼為:
//添加圖層
map.addLayer({
"id": "park",
"type": "heatmap",
"minzoom": 0,
"maxzoom": 24,
"source": "park",
"paint": {
"heatmap-weight": 1,
"heatmap-intensity": 1,
'heatmap-opacity':0.4,
'heatmap-color': [//熱力圖顏色
'interpolate',
['linear'],
['heatmap-density'],
0,'rgba(255,255,255,0)',
0.2,'rgb(0,0,255)',
0.4, 'rgb(117,211,248)',
0.6, 'rgb(0, 255, 0)',
0.8, 'rgb(255, 234, 0)',
1, 'rgb(255,0,0)',
]
}
});
上述代碼中,使用interpolate表達式進行線性插值,輸入值是heatmap-density熱力圖密度,熱力圖密度的值在0-1之間,輸出值是熱力圖中各個像素的顏色。
'heatmap-color': [
'interpolate',
['linear'],
['heatmap-density'],
0,'rgba(255,255,255,0)',
0.2,'rgb(0,0,255)',
0.4, 'rgb(117,211,248)',
0.6, 'rgb(0, 255, 0)',
0.8, 'rgb(255, 234, 0)',
1, 'rgb(255,0,0)',
]
表達式詳解:
- 密度為
0或小于0,輸出顏色'rgba(255,255,255,0)' - 密度為
0-0.2,輸出顏色在'rgba(255,255,255,0)'和'rgb(0,0,255)'之間 - 密度為
0.2,輸出顏色'rgb(0,0,255)' - 密度為
0.2-0.4,輸出顏色在'rgb(0,0,255)'和'rgb(117,211,248)'之間 - 密度為
0.4,輸出顏色'rgb(117,211,248)' - 密度為
0.4-0.6,輸出顏色在'rgb(117,211,248)'和'rgb(0, 255, 0)'之間 - 密度為
0.6,輸出顏色'rgb(0, 255, 0)' - 密度為
0.6-0.8,輸出顏色在'rgb(0, 255, 0)'和'rgb(255,0,0)'之間 - 密度為
0.8,輸出顏色'rgb(255, 234, 0)' - 密度為
0.8-1,輸出顏色在'rgb(255, 234, 0)'和'rgb(255,0,0)'之間 - 密度為
1或大于1,輸出顏色'rgb(255,0,0)'
在線示例:http://gisarmory.xyz/blog/index.html?demo=mapboxglStyleInterpolate1
和顏色拉伸渲染對應的另一種渲染方式,是使用
step表達式對數(shù)據(jù)進行顏色分類渲染。顏色分類渲染的實現(xiàn)方式在上面示例的代碼中就有,只是被注釋了,可以把代碼下載下來自行嘗試。
實現(xiàn)效果如下:
2. 軌跡圖
mapboxgl官網(wǎng)上提供了一個示例,是用顏色來表達軌跡行進的進度,效果圖如下:

它是用線的line-gradient屬性來實現(xiàn)的,其中用到了插值表達式interpolate和線進度表達式line-progress,interpolate表達式在這里的作用依舊是對屬性值進行顏色拉伸渲染,代碼如下:
map.addLayer({
type: 'line',
source: 'line',
id: 'line',
paint: {
'line-color': 'red',
'line-width': 14,
// 'line-gradient' 必須使用 'line-progress' 表達式實現(xiàn)
'line-gradient': [ //
'interpolate',
['linear'],
['line-progress'],
0, "blue",
0.1, "royalblue",
0.3, "cyan",
0.5, "lime",
0.7, "yellow",
1, "red"
]
},
layout: {
'line-cap': 'round',
'line-join': 'round'
}
});
在實際項目中,這種用顏色表達軌跡進度的場景相對少見,更多時候我們需要用顏色來表示軌跡的速度。
用顏色表示軌跡速度:
我們準備了一條騎行軌跡數(shù)據(jù),軌跡由多個線段組成,每個線段上包含開始速度、結束速度和平均速度屬性,相鄰的兩條線段,前一條線段的結束點和下一條線段的開始點,它們的經(jīng)緯度和速度相同。
//line數(shù)據(jù)中的單個線段示例
{
"type": "Feature",
"properties": {
"startSpeed": 8.301424026489258, //開始速度
"endSpeed": 9.440339088439941, //結束速度
"speed": 8.8708815574646 //平均速度
},
"geometry": {
"coordinates": [
[
116.29458653185719,
40.08948061960585
],
[
116.29486002031423,
40.08911413450488
]
],
"type": "LineString"
}
}
最簡單的實現(xiàn)方式就是,根據(jù)線段的平均速度,給每條線段設置一個顏色。
實現(xiàn)方式仍然是使用interpolate表達式,用它來根據(jù)軌跡中線段的速度對顏色進行插值。
核心代碼如下:
//添加圖層
map.addLayer({
type: 'line',
source: 'line',
id: 'line',
paint: {
'line-color': [
'interpolate',//表達式名稱
["linear"],//表達式類型,此處是線性插值
["get", "speed"],//輸入值,此處是屬性值speed
0,'red',//兩兩出現(xiàn)的判斷值和輸出值
8,'yellow',
10,'lime'
],
'line-width': 6,
'line-blur': 0.5
},
layout: {
'line-cap': 'round'
}
});
上面代碼中,interpolate表達式的意思是:
0km/h及以下(含0km/h)輸出紅色0-8km/h輸出紅到黃之間的顏色8km/h輸出黃色8-10km/h輸出黃到綠之間的顏色10km/h及以上(含10km/h)輸出綠色
實現(xiàn)效果如下:

示例在線地址:http://gisarmory.xyz/blog/index.html?demo=mapboxglStyleInterpolate2
整體看上去還不錯,但放大地圖時會發(fā)現(xiàn),顏色是一段一段的,過渡不夠平滑,如下圖:

如何能讓局部的顏色也平滑起來呢?
要是能讓兩個線段間的顏色平滑過渡就好了。
想到這里,我們又想起了前面那個用顏色表示軌跡進度的官方示例,如果把兩種方式結合一下或許能實現(xiàn)想要的效果。
實現(xiàn)思路:
每條線段的屬性中有開始速度和結束速度,根據(jù)顏色和速度的對應關系,可以插值出每條線段的開始顏色和結束顏色,前一條線段的開始顏色和后一條線段的結束顏色為同一個顏色,每條線段中間的顏色通過使用line-gradient實現(xiàn)從開始顏色到結束顏色的漸變。
這樣就能實現(xiàn)兩個線段間顏色的平滑過渡了。
實現(xiàn)方法:
按照這個思路需要進行兩次插值,第一次插值是插值出每個線段的開始顏色和結束顏色,第二次是插值出每個線段上每個像素的顏色
本來是想在mapboxgl中,通過多個表達式的嵌套來實現(xiàn)此功能,這樣代碼會比較簡潔,但多次嘗試發(fā)現(xiàn)行不通,原因是,因為mapboxgl對
line-gradient和line-progress在的使用上的一些限制,所以第一次插值的邏輯需要自己動手實現(xiàn)。
第一步,自己動手寫個顏色插值函數(shù),插值出每個線段的開始顏色和結束顏色,實現(xiàn)方式注釋里面已經(jīng)寫的比較清楚了。
//通過canvas獲取開始顏色和結束顏色:
//原理是利用canvas創(chuàng)建一個線性漸變色對象,再通過計算顏色所在的位置去用getImageData獲取顏色,最后返回這個顏色
//1.創(chuàng)建canvas
var canvas = document.createElement("canvas");
canvas.width = 101;
canvas.height = 1;
var ctx = canvas.getContext('2d');
//2.創(chuàng)建線性漸變的函數(shù),該函數(shù)會返回一個線性漸變對象,參數(shù)0,1,101,1分別指:漸變的起始點x,y和漸變的終止點x,y
var grd = ctx.createLinearGradient(0,1,101,1)
//3.給線性漸變對象添加顏色點的函數(shù),參數(shù)分別是停止點、顏色
grd.addColorStop(0,'red');
grd.addColorStop(0.8,'yellow');
grd.addColorStop(1,'lime');
//4.給canvas填充漸變色
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 101, 1);
//5.返回漸變色的函數(shù)
function getInterpolateColor(r) {
//6.這里是漸變色的精細度,我將canvas分成101份來取值,每一份都會有自己對應的顏色
//聲明的變量x就是我們需要的顏色在漸變對象上的位置
let x = parseInt(r * 100);
x>100?x=100:x=x
//7.傳入插值顏色所在的位置x,通過canvas的getImageData方法獲取顏色
var colorData = ctx.getImageData(x, 0, 1, 1).data;
//8.返回這個顏色
return `rgba(${colorData[0]},${colorData[1]},${colorData[2]},${colorData[3]})`
}
第二步,每個線段設置為一個圖層,每個圖層調(diào)用第一步的方法獲取線段的開始顏色和結束顏色,然后使用line-gradient屬性設置線段中間的顏色。
//allFeatures是line數(shù)據(jù)中單個線段組成的集合
allFeatures.map((item,index)=>{
//通過上面的漸變色函數(shù)獲取開始顏色和結束顏色
let startColor = getInterpolateColor(item.properties.startSpeed/10)
let endColor = getInterpolateColor(item.properties.endSpeed/10)
//循環(huán)添加圖層
map.addLayer({
type: 'line',
source: 'line',
id: 'line'+index,
paint: {
'line-width': 6,
'line-blur': 0.5,
'line-gradient': [
'interpolate',
['linear'],
['line-progress'],
0, startColor,
1, endColor
]
},
layout: {
'line-cap': 'round',
},
'filter': ['==', "$id", index]
});
})
每個線段設置為一個圖層,最后可能會有上千個圖層,這樣不容易管理。
這里提供另一種思路,可以將所有線段合并為一條折線,然后計算出折線上每個節(jié)點的速度、顏色和占整個軌跡的百分比,占整個軌跡的百分比通過節(jié)點距離起點和終點的長度來計算。
將所有節(jié)點的百分比和顏色兩兩對應作為
line-gradient的判斷參數(shù),這樣就能產(chǎn)生和多個圖層同樣的效果,同時只需要創(chuàng)建一個圖層。這種方式的缺點是需要處理數(shù)據(jù),具體適合用哪種可以根據(jù)實際情況來定。
最終實現(xiàn)效果如下:

示例在線地址:http://gisarmory.xyz/blog/index.html?demo=mapboxglStyleInterpolate3
2. 模型網(wǎng)格渲染
這種模式下,網(wǎng)格數(shù)據(jù)主要來自模型輸出結果,在輸出結果的基礎上,只需要用interpolate插值工具,根據(jù)網(wǎng)格屬性值插值出網(wǎng)格顏色就ok。
下面的代碼和效果圖,是用EFDC模型的輸出結果做的示例,這個網(wǎng)格相對比較大一些,但中間部分的過渡還算自然。
代碼:
//圖層
{
"id": "waterTN",
"type": "fill",
"source": "efdc",
"paint": {
"fill-color": [
"interpolate",
["linear"],
["get", "TN"],//輸入值是屬性TN
0, "#36D1DC",
15, "#6666ff",
20, "#4444FF"
]
}
}
效果圖:

四、隨著地圖縮放對圖形屬性進行插值
mapboxgl官網(wǎng)給出了兩個相關示例:
一個是按照縮放級別改變建筑顏色,里面同時對建筑物的顏色和透明度進行了插值。
相關代碼:
//對顏色插值
map.setPaintProperty('building', 'fill-color', [
"interpolate",
["exponential", 0.5],
["zoom"],
15,
"#e2714b",
22,
"#eee695"
]);
//對透明度插值
map.setPaintProperty('building', 'fill-opacity', [
"interpolate",
["exponential", 0.5],
["zoom"],
15,
0,
22,
1
]);
效果圖:

另一個是按照地圖縮放級別去改變建筑物顯示高度,里面對建筑物的高度和建筑物距離地圖的高度進行了插值。
相關代碼:
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': '#aaa',
'fill-extrusion-height': [
"interpolate", ["linear"],
["zoom"],
15, 0,
15.05, ["get", "height"]
],
'fill-extrusion-base': [
"interpolate", ["linear"],
["zoom"],
15, 0,
15.05, ["get", "min_height"]
],
'fill-extrusion-opacity': .6
}
}, labelLayerId);
效果圖:

同理,我們還可以對地圖圖標的大小進行插值,比如縮放級別越大圖標越大,縮放級別越小圖標越小等。
五、interpolate的高階用法
前面介紹插值工具interpolate的語法時,暫時沒有介紹插值類型這個選項,這一節(jié)我們好好說說它。
前面的多數(shù)示例中,插值類型選項我們都是使用的['linear']這個類型,意思是線性插值。
除了線性插值外,插值類型還支持["exponential",base]指數(shù)插值和["cubic-bezier", x1, y1, x2, y2]三次貝賽爾曲線插值。
它們的語法為:
["linear"]線性插值,沒有其它參數(shù)。["exponential",base]指數(shù)插值,base參數(shù)為指數(shù)值。["cubic-bezier",x1,y1,x2,y2]三次貝塞爾曲線插值,x1、y1、x2、y24個參數(shù)用于控制貝塞爾曲線的形態(tài)。
聽上去可能有點抽象,我們舉個例子:
下面這段的代碼是根據(jù)地圖縮放級別改變建筑物的透明度:
map.setPaintProperty('building', 'fill-opacity', [
"interpolate",
["linear"],
["zoom"],
15,0,
22,1
]);
意思為:
-
當縮放級別小于15時,透明度為0。
-
當縮放級別大于等于22時,透明度為1。
-
當縮放級別在15到22之間時,使用線性插值方式自動計算透明度的值,介于0到1之間。
線性插值:
如果把縮放級別設置為x,透明度為y,限定x的值在15到22之間,則線性插值的方程式為:
y=(x-15)/(22-15)
從下面的函數(shù)圖像上可以直觀的看出,它就是一條直線,這意味著地圖放大時,從15級開始到22級,建筑物不透明度會勻速的增加,直到完全顯示。

指數(shù)插值:
指數(shù)插值的方程式在線性插值方程式的基礎上增加了指數(shù)值,這個值我們用z來表示,方程式為:
y=((x-15)/(22-15))^z
通過z值來我們可以調(diào)整函數(shù)圖像的形態(tài),如:分別取z值為0.1、0.5、1、2、10這5個值,畫成圖如下:

以上圖中指數(shù)為10次方的紫色線為例,當?shù)貓D從15級放大到19級時,會一直都看不到建筑物,因為建筑物的透明度一直為0。
繼續(xù)放大,從19級放大到22級時,建筑物會快速的顯現(xiàn)直到完全顯示。
這就是指數(shù)插值和線性插值的區(qū)別,它提供給了我們一個可以控制插值輸出快慢的方式。
三次貝塞爾曲線插值:
三次貝塞爾曲線插值和上面的指數(shù)插值是一個意思,都是為了能夠更靈活的控制插值輸出的快慢。
還是通過函數(shù)圖像來幫助理解,指數(shù)插值的圖像只能向一個方向彎曲,指數(shù)在0-1之間時曲線向上彎曲,大于1時曲線向下彎曲。
而三次貝塞爾曲線插值則可以讓曲線雙向彎曲。
mapboxgl官網(wǎng)提供了一個海洋深度的示例,里面有用到三次貝塞爾曲線插值。
示例中使用三次貝塞爾曲線對表示海洋深度的顏色進行插值,效果如下圖:

相關代碼如下:
{
'id': '10m-bathymetry-81bsvj',
'type': 'fill',
'source': '10m-bathymetry-81bsvj',
'source-layer': '10m-bathymetry-81bsvj',
'layout': {},
'paint': {
'fill-outline-color': 'hsla(337, 82%, 62%, 0)',
'fill-color': [
'interpolate',
['cubic-bezier', 0, 0.5, 1, 0.5],
['get', 'DEPTH'],
200,'#78bced',
9000,'#15659f'
]
}
},
上面代碼中,三次貝塞爾曲線插值的4個參數(shù)x1、y1、x2、y2的值分別為:0、 0.5、 1、 0.5。
它的函數(shù)圖像為:

通過上圖可以看出,函數(shù)輸出的速度是 先快 再慢 最后又快,結合海洋深度的示例,當深度在200米和9000米附近時,顏色變化較快,深度在中間時,顏色變化比較平緩。下面兩張圖是線性插值和三次貝塞爾曲線插值的對比:

上圖使用["linear"]線性插值,顏色勻速輸出,能看出深淺變化,但是‘塊狀感’明顯
下圖使用 ['cubic-bezier', 0, 0.5, 1, 0.5]三次貝塞爾曲線插值,顏色輸出先快再慢最后又快,既能看出深淺變化,又能實現(xiàn)自然過渡的平滑效果,會讓人感覺更柔和。

推薦文章一篇通俗易懂的三次貝塞爾曲線講解可以了解三次貝塞爾曲線是怎么畫出來的,還有一個工具網(wǎng)站可以自己畫點幫助理解。
這三種插值方法所代表的函數(shù)都可以在坐標軸中畫出來,無論畫出來是直線還是各種曲線,我們都不需要去糾結這個線條是如何畫的,因為這一步我們可以借助工具來完成,需要關心的是這條線它輸出速度的快慢,這才和我們
"interpolate"表達式的意義平滑過渡相關。
六、總結
interpolate是mapboxgl地圖樣式中用于插值的表達式,能對顏色和數(shù)字進行插值,可以讓地圖實現(xiàn)平滑的過渡效果。- 它的應用場景有兩類,一類是對地圖數(shù)據(jù)進行顏色拉伸渲染,如:熱力圖、軌跡圖、模型網(wǎng)格渲染等。
- 另一類是在地圖縮放時對圖形屬性進行插值,如:隨著地圖的縮放實現(xiàn)建筑物高度的緩慢變化、圖形顏色的平滑切換等效果。
interpolate插值工具提供了三種插值方式,線性插值、指數(shù)插值、三次貝塞爾曲線插值,它們的區(qū)別在于控制插值輸出快慢的不同。
* * *
原文地址:http://gisarmory.xyz/blog/index.html?blog=mapboxglStyleInterpolate
歡迎關注《GIS兵器庫》

本文章采用 知識共享署名-非商業(yè)性使用-相同方式共享 4.0 國際許可協(xié)議 進行許可。歡迎轉(zhuǎn)載、使用、重新發(fā)布,但務必保留文章署名《GIS兵器庫》(包含鏈接: http://gisarmory.xyz/blog/),不得用于商業(yè)目的,基于本文修改后的作品務必以相同的許可發(fā)布。


浙公網(wǎng)安備 33010602011771號