前言
在上一篇文章中我们学会了如何获取两个面的交集,在获取了交集河流面之后我们需要利用 kriging 的 train方法训练我们的数据模型,也就是实现值更具距离的递减。
使用到的工具:
kriging,turf
1.定义等级以及色阶
比如我们在我们的交面圈内含有很多的点位,点位数据内包含经纬度信息,以及他们的颜色等级。
未来渲染到我们的地图上实现各个等级可以有对应的颜色,所有我们需要 ui 提供一个等级颜色的色阶,比如:我们有 1~6等级的水质等级分别对应以下颜色:
'#3680A4','#1480D8','#5CAA76','#ABCA46','#DFBA36','#E12631'
但是显然如果只有这6个颜色的话,在地图上显示不同颜色区间之间的间隔会没有过度,而且我们使用 kriging.train 生成的训练值会更具距离相互影响出现各种 5.4,2.1 等等小数,所有我们和ui要了一个色阶,把每一个颜色分成10份,加上一个0(渲染的过度效果好坏直接影响因素就是色阶的数量,划分的细度)
2.处理点位数据,生成 x,y,z数据
我们的点位数据例如如下:
const point = [
{
name: '测试点',
longitude: 39.96, // 纬度
latitude: 112.35, // 经度
value: 3 // 水质等级
},
......
]
计算获取x,y,z数据,分别是经度,纬度,值数组。
const getValueTYZ = (point = []) => {
// 构造插值数据
const t = []
const x = []
const y = []
point.forEach(site => {
const { level = 0, longitude, latitude } = site
t.push(level)
x.push(parseFloat(longitude))
y.push(parseFloat(latitude))
})
return { t, x, y }
}
但是这边我们需要注意以下几点,为了验证情况我们可以先不加上规避逻辑,来看看会发生什么。
- 经度或者纬度有错误。
- 经纬度有重复,的点位。
- 数据量比较少的情况会不会出现错误。
3.模型训练设置数据
首先我们需要引入我们需要使用到的工具
import * as turf from '@turf/turf'
import kriging from 'kriging'
接下来将我们获取到的x,y,t数据放入到 kriging.train 中进行训练,参数已经配好后面的不需要动。然后对我们的 河流 geo数据zhejiangRiverJson进行运算, 使用 kriging.predict 方法对我们训练好的模型通过对我们数据的每一个features的经纬度上的实际训练出的值进行颜色渲染,并最终添加颜色到 feature.properties 上去,用于我们后面渲染图层使用。
const { t, x, y } = getValueTYZ(point)
// 训练数据集
const fitModel = kriging.train(t, x, y, 'exponential', 0, 226)
zhejiangRiverJson.features.forEach(feature => {
const point = turf.centroid(feature)
const grade = kriging.predict(point.geometry.coordinates[0], point.geometry.coordinates[1], fitModel)
const color = getColor(fitModel, grade)
feature.properties.color = color
})
/**
* 获取插值颜色
* @param variogram 插值模型
* @param value 数值
*/
export const getColor = (variogram, value) => {
if (value < 0) value = 0
const level = Math.round(value / 0.1)
return colors[level]
}
我们可以打印一下我们的x,y,z 以及最终运算出来的 grade 在各个features内的经纬度坐标上的值如图所示:
其实我们最终就是要获取到grade然后根据我们的色阶匹配对应的颜色就可以了。最后就是渲染地图图层:
MapUtil._addSourceToMap('kriging-source1', zhejiangRiverJson)
if (!window.glMap.getLayer(waterLayerDic.INTERPOLATION_LAYER_1)) {
window.glMap.addLayer(
{
id: waterLayerDic.INTERPOLATION_LAYER_1,
type: 'fill',
source: 'kriging-source1',
paint: {
'fill-color': ['get', 'color']
}
},
window.glMap.getLayer(waterPopLayerDic.WATER_SYMBOL_POP) ? waterPopLayerDic.WATER_SYMBOL_POP : ''
)
}
MapUtil._showOrHideMapLayer([waterLayerDic.INTERPOLATION_LAYER_1], 'show')
最终渲染如下:
4.错误解决以及优化
但是对于点位数据的处理也就是最终 x,y,t数据,我们还需要很多谨慎的地方,比如当我们点位数据比较少只有两个的时候:
可以发现运算出来的值都是NaN了,河流也变成了黑色😈:
那是因为我们只有两个点时
kriging.train 内部Math运算会出错,出现NaN的情况,包括点位重复,或者经纬度有错误等,都会导致,但是如果真实数据就是只有两个点位怎么办呢?
其实很简单,我们可以在加入3个距离我们当前区域很远的点,因为如果距离足够远,你的值对与我们当前的点位影响就会很小,这样一来就算我们只有一个点都可以渲染了,所以我们针对我们上面 getValueTYZ 的方法进行优化:
const getValueTYZ = (point = []) => {
// 构造插值数据
const t = []
const x = []
const y = []
const longlatArr = []
point.forEach(site => {
const { level = 0, longitude, latitude } = site
if (!latitude || !longitude) return
const longLatStr = `${parseFloat(longitude)}${parseFloat(latitude)}`
if (longlatArr.includes(longLatStr)) return
longlatArr.push(longLatStr)
t.push(level)
x.push(parseFloat(longitude))
y.push(parseFloat(latitude))
})
setValueRange(t, x, y)
return { t, x, y }
}
/**
* 设置插值静态远端数据
* @param t
* @param x
* @param y
*/
export const setValueRange = (t, x, y) => {
// min
t.push(0)
x.push(116.35)
y.push(39.96)
// mid
t.push(3)
x.push(116.37)
y.push(39.97)
// max
t.push(6)
x.push(116.36)
y.push(39.97)
}