mapbox渲染圆切交集图层并绘制插值图层2-绘制河流插值

261 阅读3分钟

前言

在上一篇文章中我们学会了如何获取两个面的交集,在获取了交集河流面之后我们需要利用 krigingtrain方法训练我们的数据模型,也就是实现值更具距离的递减。
使用到的工具:

kriging, turf

1.定义等级以及色阶

比如我们在我们的交面圈内含有很多的点位,点位数据内包含经纬度信息,以及他们的颜色等级。 未来渲染到我们的地图上实现各个等级可以有对应的颜色,所有我们需要 ui 提供一个等级颜色的色阶,比如:我们有 1~6等级的水质等级分别对应以下颜色:

'#3680A4','#1480D8','#5CAA76','#ABCA46','#DFBA36','#E12631'

但是显然如果只有这6个颜色的话,在地图上显示不同颜色区间之间的间隔会没有过度,而且我们使用 kriging.train 生成的训练值会更具距离相互影响出现各种 5.4,2.1 等等小数,所有我们和ui要了一个色阶,把每一个颜色分成10份,加上一个0(渲染的过度效果好坏直接影响因素就是色阶的数量,划分的细度

color.png

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 }
}

但是这边我们需要注意以下几点,为了验证情况我们可以先不加上规避逻辑,来看看会发生什么。

  1. 经度或者纬度有错误。
  2. 经纬度有重复,的点位。
  3. 数据量比较少的情况会不会出现错误。

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内的经纬度坐标上的值如图所示:

image.png

image.png

其实我们最终就是要获取到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')

最终渲染如下:

image.png

4.错误解决以及优化

但是对于点位数据的处理也就是最终 x,y,t数据,我们还需要很多谨慎的地方,比如当我们点位数据比较少只有两个的时候:

image.png 可以发现运算出来的值都是NaN了,河流也变成了黑色😈:

image.png 那是因为我们只有两个点时 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)
}