您现在的位置是:网站首页> 编程资料编程资料

mapboxgl 中插值表达式的应用场景分析_其它综合_

2023-05-27 247人已围观

简介 mapboxgl 中插值表达式的应用场景分析_其它综合_

一、前言

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

它的应用场景有两类:

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

这篇文章就把 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, 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的判断参数,这样就能产生和多个图层同样的效果,同时只需要创建一个图层。

这种方式的缺点是需要处理数据,具体适合用哪种可以根据实际情况来定。

最终实现效果如下:

提示: 本文由整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!

-六神源码网