乐趣区

关于前端:mapboxgl-中插值表达式的应用场景

一、前言

interpolate是 mapboxgl 地图款式中用于插值的表达式,能对色彩和数字进行插值。

它的利用场景有两类:

  1. 对地图数据进行色彩拉伸渲染。常见的利用场景有:热力求、轨迹图、模型网格渲染等。
  2. 在地图缩放时对图形属性进行插值。具体为,随着地图的缩放,在扭转图标大小、建筑物高度、图形色彩等属性时,对属性进行插值,从而实现平滑的过渡成果。

这篇文章就把 mapboxgl 中 interpolate 插值工具的常见利用场景介绍一下。

二、语法

先看一下 interpolate 插值工具的语法。

interpolate表达式要求至多有 5 个参数,别离是 表达式名称 插值类型 输出值 判断值 输入值

["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)        // 返回插值完的后果

其中 插值类型 会在前面具体介绍,这里先不多说。

判断值 输入值 是“一组”的关系,它们必须两两呈现。

还有一点须要留神,就是 判断值 必须遵循 升序 规定。

上面咱们结合实际场景了解起来会更容易一些,先说第一类利用场景:对地图数据进行色彩拉伸渲染。

三、对地图色彩进行拉伸渲染

这个和 ArcGIS 中对栅格数据进行色彩拉伸渲染是一个意思。

地图色彩拉伸渲染的实质,是依据网格的属性值为网格设置色彩,当网格足够小、足够密时,就容易产生色彩平滑过渡的成果。

后面说到,常见的利用场景有:热力求、轨迹图、模型网格渲染等。

在 mapboxgl 中,热力求和轨迹图它们尽管看上去不像是由网格组成的,但在计算机图形学的框架下,任何在屏幕上显示的内容,都是由像素来出现的,而像素是法则排列的网格,所以能够把热力求和轨迹也看成是由网格组成的。

这一点在 WebGL 开发时尤为显著,因为须要本人写片元着色器定义每个像素的色彩。

mapboxgl 提供了热力求和轨迹图的像素属性值计算工具:

  • 热力求中为 heatmap-density 表达式,用来计算热力求上每个像素的热力值。
  • 轨迹线中为 line-progress 表达式,用来计算在以后线段上每个像素的前进百分比。

模型网格渲染时,网格须要本人生成,网格中的属性值也须要本人计算,通常在我的项目上这些是由模型实现的,如:EFDC 水能源模型、高斯烟羽大气污染扩散模型等。

模型输入的后果就是带属性值的网格,interpolate表达式的工作依然是依据网格的属性值为网格设置色彩。

1. 热力求

实现成果:

数据应用的是北京市公园绿地无障碍设施数量。

代码为:

// 增加图层
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 表达式对数据进行色彩 分类 渲染。

色彩分类渲染的实现形式在下面示例的代码中就有,只是被正文了,能够把代码下载下来自行尝试。

实现成果如下:

2. 轨迹图

mapboxgl 官网上提供了一个示例,是用色彩来表白轨迹前进的进度,效果图如下:

它是用线的 line-gradient 属性来实现的,其中用到了插值表达式 interpolate 和线进度表达式 line-progressinterpolate 表达式在这里的作用仍旧是对属性值进行色彩拉伸渲染,代码如下:

map.addLayer({
    type: 'line',
    source: 'line',
    id: 'line',
    paint: {
        'line-color': 'red',
        'line-width': 14,
        // 'line-gradient' 必须应用 'line-progress' 表达式实现
        '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'
    }
});

在理论我的项目中,这种用色彩表白轨迹进度的场景绝对少见,更多时候咱们须要用色彩来示意轨迹的速度。

用色彩示意轨迹速度:

咱们筹备了一条骑行轨迹数据,轨迹由多个线段组成,每个线段上蕴含开始速度、完结速度和平均速度属性,相邻的两条线段,前一条线段的完结点和下一条线段的开始点,它们的经纬度和速度雷同。

//line 数据中的单个线段示例
{
    "type": "Feature",
        "properties": {
            "startSpeed": 8.301424026489258, // 开始速度
            "endSpeed": 9.440339088439941, // 完结速度
            "speed": 8.8708815574646 // 平均速度
        },
        "geometry": {
            "coordinates": [
                [
                    116.29458653185719,
                    40.08948061960585
                ],
                [
                    116.29486002031423,
                    40.08911413450488
                ]
            ],
                "type": "LineString"
        }
}

最简略的实现形式就是,依据线段的平均速度,给每条线段设置一个色彩。

实现形式依然是应用 interpolate 表达式,用它来依据轨迹中线段的速度对色彩进行插值。

外围代码如下:

// 增加图层
map.addLayer({
    type: 'line',
    source: 'line',
    id: 'line',
    paint: {
        'line-color': [
            'interpolate',// 表达式名称
            ["linear"],// 表达式类型,此处是线性插值
            ["get", "speed"],// 输出值,此处是属性值 speed
            0,'red',// 两两呈现的判断值和输入值
            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) 输入 绿色

实现成果如下:

示例在线地址:http://gisarmory.xyz/blog/index.html?demo=mapboxglStyleInterpolate2

整体看上去还不错,但放大地图时会发现,色彩是一段一段的,过渡不够平滑,如下图:

如何能让部分的色彩也平滑起来呢?

要是能让两个线段间的色彩平滑过渡就好了。

想到这里,咱们又想起了后面那个用色彩示意轨迹进度的官网示例,如果把两种形式联合一下或者能实现想要的成果。

实现思路:

每条线段的属性中有 开始速度 完结速度 ,依据色彩和速度的对应关系,能够插值出每条线段的 开始色彩 完结色彩 ,前一条线段的 开始色彩 和后一条线段的 完结色彩 为同一个色彩,每条线段两头的色彩通过应用 line-gradient 实现从 开始色彩 完结色彩 的突变。

这样就能实现两个线段间色彩的平滑过渡了。

实现办法:

依照这个思路须要进行两次插值,第一次插值是插值出每个线段的 开始色彩 完结色彩,第二次是插值出每个线段上每个像素的色彩

原本是想在 mapboxgl 中,通过多个表达式的嵌套来实现此性能,这样代码会比拟简洁,但屡次尝试发现行不通,起因是,因为 mapboxgl 对 line-gradientline-progress在的应用上的一些限度,所以第一次插值的逻辑须要本人入手实现。

第一步,本人入手写个色彩插值函数,插值出每个线段的 开始色彩 完结色彩,实现形式正文外面曾经写的比较清楚了。

// 通过 canvas 获取开始色彩和完结色彩:// 原理是利用 canvas 创立一个线性渐变色对象,再通过计算色彩所在的地位去用 getImageData 获取色彩,最初返回这个色彩
//1. 创立 canvas
var canvas = document.createElement("canvas");
canvas.width = 101;
canvas.height = 1;
var ctx = canvas.getContext('2d');
//2. 创立线性突变的函数,该函数会返回一个线性突变对象,参数 0,1,101,1 别离指:突变的起始点 x,y 和突变的终止点 x,y
var grd = ctx.createLinearGradient(0,1,101,1) 
//3. 给线性突变对象增加色彩点的函数,参数别离是进行点、色彩
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. 返回渐变色的函数
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]})`
}

第二步,每个线段设置为一个图层,每个图层调用第一步的办法获取线段的 开始色彩 完结色彩 ,而后应用line-gradient 属性设置线段两头的色彩。

//allFeatures 是 line 数据中单个线段组成的汇合
allFeatures.map((item,index)=>{
    // 通过下面的渐变色函数获取开始色彩和完结色彩
    let startColor = getInterpolateColor(item.properties.startSpeed/10)
    let endColor = getInterpolateColor(item.properties.endSpeed/10)
    // 循环增加图层
    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]
    });
})

每个线段设置为一个图层,最初可能会有上千个图层,这样不容易治理。

这里提供另一种思路,能够将所有线段合并为一条折线,而后计算出折线上每个节点的速度、色彩和占整个轨迹的百分比,占整个轨迹的百分比通过节点间隔终点和起点的长度来计算。

将所有节点的百分比和色彩两两对应作为 line-gradient 的判断参数,这样就能产生和多个图层同样的成果,同时只须要创立一个图层。

这种形式的毛病是须要解决数据,具体适宜用哪种能够依据理论状况来定。

最终实现成果如下:

示例在线地址:http://gisarmory.xyz/blog/index.html?demo=mapboxglStyleInterpolate3

2. 模型网格渲染

这种模式下,网格数据次要来自模型输入后果,在输入后果的根底上,只须要用 interpolate 插值工具,依据网格属性值插值出网格色彩就 ok。

上面的代码和效果图,是用 EFDC 模型的输入后果做的示例,这个网格绝对比拟大一些,但两头局部的过渡还算天然。

代码:

// 图层
{
    "id": "waterTN",
    "type": "fill",
    "source": "efdc",
    "paint": {
        "fill-color": [
            "interpolate",
            ["linear"],
            ["get", "TN"],// 输出值是属性 TN
            0, "#36D1DC",
            15, "#6666ff",
            20, "#4444FF"
        ]
    }
}

效果图:

四、随着地图缩放对图形属性进行插值

mapboxgl 官网给出了两个相干示例:

一个是依照缩放级别扭转修建色彩,外面同时对建筑物的色彩和透明度进行了插值。

相干代码:

// 对色彩插值
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 的语法时,临时没有介绍 插值类型 这个选项,这一节咱们好好说说它。

后面的少数示例中,插值类型 选项咱们都是应用的 ['linear'] 这个类型,意思是线性插值。

除了线性插值外,插值类型 还反对 ["exponential",base] 指数插值和 ["cubic-bezier", x1, y1, x2, y2] 三次贝赛尔曲线插值。

它们的语法为:

  • ["linear"]线性插值,没有其它参数。
  • ["exponential",base]指数插值,base参数为指数值。
  • ["cubic-bezier",x1,y1,x2,y2]三次贝塞尔曲线插值,x1y1x2y2 4 个参数用于管制贝塞尔曲线的状态。

听下来可能有点形象,咱们举个例子:

上面这段的代码是依据地图缩放级别扭转建筑物的透明度:

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)

从上面的函数图像上能够直观的看出,它就是一条直线,这意味着地图放大时,从 15 级开始到 22 级,建筑物不透明度会匀速的减少,直到齐全显示。

指数插值

指数插值的方程式在线性插值方程式的根底上减少了指数值,这个值咱们用 z 来示意,方程式为:

y=((x-15)/(22-15))^z

通过 z 值来咱们能够调整函数图像的状态,如:别离取 z 值为 0.1、0.5、1、2、10 这 5 个值,画成图如下:

以上图中指数为 10 次方的紫色线为例,当地图从 15 级放大到 19 级时,会始终都看不到建筑物,因为建筑物的透明度始终为 0。

持续放大,从 19 级放大到 22 级时,建筑物会疾速的浮现直到齐全显示。

这就是指数插值和线性插值的区别,它提供给了咱们一个能够 管制插值输入快慢 的形式。

三次贝塞尔曲线插值:

三次贝塞尔曲线插值和下面的指数插值是一个意思,都是为了可能更灵便的管制插值输入的快慢。

还是通过函数图像来帮忙了解,指数插值的图像只能向一个方向蜿蜒,指数在 0 - 1 之间时曲线向上蜿蜒,大于 1 时曲线向下蜿蜒。

而三次贝塞尔曲线插值则能够让曲线双向蜿蜒。

mapboxgl 官网提供了一个陆地深度的示例,外面有用到三次贝塞尔曲线插值。

示例中应用三次贝塞尔曲线对示意陆地深度的色彩进行插值,成果如下图:

相干代码如下:

{
    '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 个参数 x1y1x2y2 的值别离为:0、0.5、1、0.5。

它的函数图像为:

通过上图能够看出,函数输入的速度是 先快 再慢 最初又快 ,联合陆地深度的示例,当深度在200 米和 9000 米左近时,色彩变动较快,深度在两头时,色彩变动比拟平缓。上面两张图是线性插值和三次贝塞尔曲线插值的比照:

上图应用 ["linear"] 线性插值,色彩匀速输入,能看出深浅变动,然而‘块状感’显著

下图应用 [‘cubic-bezier’, 0, 0.5, 1, 0.5]三次贝塞尔曲线插值,色彩输入先快再慢最初又快,既能看出深浅变动,又能实现天然过渡的平滑成果,会让人感觉更柔和。

举荐文章一篇通俗易懂的三次贝塞尔曲线解说能够理解三次贝塞尔曲线是怎么画进去的,还有一个工具网站能够本人画点帮忙了解。

这三种插值办法所代表的函数都能够在坐标轴中画进去,无论画进去是直线还是各种曲线,咱们都不须要去纠结这个线条是如何画的,因为这一步咱们能够借助工具来实现,须要关怀的是这条线它 输入速度的快慢 ,这才和咱们"interpolate" 表达式的意义 平滑过渡 相干。

六、总结

  1. interpolate是 mapboxgl 地图款式中用于插值的表达式,能对色彩和数字进行插值,能够让地图实现平滑的过渡成果。
  2. 它的利用场景有两类,一类是对地图数据进行色彩拉伸渲染,如:热力求、轨迹图、模型网格渲染等。
  3. 另一类是在地图缩放时对图形属性进行插值,如:随着地图的缩放实现建筑物高度的迟缓变动、图形色彩的平滑切换等成果。
  4. interpolate插值工具提供了三种插值形式,线性插值、指数插值、三次贝塞尔曲线插值,它们的区别在于管制插值输入快慢的不同。


原文地址:http://gisarmory.xyz/blog/index.html?blog=mapboxglStyleInterpolate

欢送关注《GIS 兵器库》

本文章采纳 常识共享署名 - 非商业性应用 - 雷同形式共享 4.0 国内许可协定 进行许可。欢送转载、应用、从新公布,但务必保留文章署名《GIS 兵器库》(蕴含链接:http://gisarmory.xyz/blog/),不得用于商业目标,基于本文批改后的作品务必以雷同的许可公布。

退出移动版