Skip to main content

渲染格点数据

本示例的最终完成效果

示例说明

以下代码以二维讲解,三维几乎一致,可以参考最后的完整代码。

将script标签引入,并创建基础地图

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>QuickEarth-气象数据渲染引擎</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
    <script src="https://qecloud.91weather.com/public/lib/qe/quickearth.umd.min.js"></script>
</head>
<body>
    <div id="map" style="position:absolute;top:0px;left:0px;width:100%;height:100%;overflow:hidden"></div>
    <script>
        const map = new QE.LMap("map", {crs: L.CRS.EPSG3857,fadeAnimation: false}).setView([35, 110], 5);
        QE.createTileLayer(QE.predefinedImageTiles.windy,{pane:QE.consts.customPanes.topmap.name}).addTo(map);
    </script>
</body>
</html>

这里我们看到,与之前示例中创建基础地图的代码有些区别,在这里我们使用了windy的切片(在QE中内置了大量的预设切片地址,在实际应用中请使用天地图切片,其他的仅供学习研究),并且设置了切片的层级为顶层地图(可以让色斑图上面还能显示信息),在QE中除了Leaflet自带的pane,我们还创建了一些自定义的pane,以适用于矢量和栅格数据的渲染。

创建一个函数生成格点数据

//创建格点数据
const xSize=100,ySize=100;
const dataArr=[];
for(let i=0-50;i<ySize-50;i++){
    const y=i/50.;
    for(let j=0-50;j<xSize-50;j++){
        const x=j/50.; 
        const inner=1 - x*x - (y-Math.abs(x))*(y-Math.abs(x));
        const val=5 + (-1*Math.sqrt(inner)) * Math.cos(30*((inner)));
        dataArr.push(val);
    }
}

创建格点数据解析器

方式一: 构建格点对象,结合格点地理信息属性,实时创建数据解析器

由于我们是在地图上进行表达,因此二维网格数据需要具备地理信息属性,具体的就是网格的四个顶点坐标以及网格的分辨率(间隔),这类信息在QE中使用以下方式进行表达:

export interface IGridDataOptions {
    xStart?: number;
    yStart?: number;
    xDelta?: number;
    yDelta?: number;
    xSize?: number;
    ySize?: number;
    xEnd?: number;
    yEnd?: number;
    zValues?: number[];
    tCount?: number
}

以上定义在纯JS环境就是一个具有这些字段的Object。

const options={
    xStart:70,
    xEnd:140,
    yStart:15,
    yEnd:55,
    xSize,
    ySize
};

QE中的格点数据在内部最原始的表达是一个由ArrayBuffer驱动的TypedArray,经过简单封装后形成 GridData类,该类提供了二维网格和一维网格互转,格点数据读取和更新的功能。

//创建GridData
const grid=new QE.GridData(QE.GridDataType.Float32,xSize,ySize,dataArr);   

一旦有了以上信息,我们就可以创建基于内存的数据解析器:

//构建provider
const provider=new QE.MemoryGridDataProvider([[grid]],{gridOptions:options}); 
方式二:将以上过程进行封装,创建自定义的数据解析器

有些时候数据格式较为复杂,为了更好的复用以及整体代码的简洁清晰,我们会将数据解析(在这里为创建)的过程封装一个provider,直接传入原始数据,在内部解析出符合要求的对象,实现指定的方法。

目前QE已经内置了最常用的基于数组的格点数据解析器 Array2DGridDataProvider,以上构建GridData和provider代码也可以改为:

const provider=new QE.Array2DGridDataProvider(dataArr,{gridOptions:options});

创建样式

图层=数据+样式

有了数据源,接下来我们还要创建样式,在本例中,我们计算创建的时格点填色的图层,格点填色样式的定义可以参考IPixelLayerStyleOptions,我们这里使用最简单的样式先来测试一下效果

const style={
	fillColor:"red"
};

显示到地图

在本例中,我们使用LPixelLayer,这是一个可以生成格点填色和色斑图的图层

创建图层
const layer=new QE.LPixelLayer();
设置图层数据源和样式
const layer=new QE.LPixelLayer();
layer.setDataSource(provider)
layer.setDrawOptions(style);
加入地图
layer.addTo(map);

效果如下:

chrome_7YwP3QHAgh.png

数据按照我们设定的范围显示了出来,但是我们只是简单的使用了红色进行填充,接下来我们要根据数值的大小来设置不同的颜色。

更新为数据驱动的样式

在QE中,设置色斑图层的颜色有两种方式:

  • 一种是使用colorScale,也就是一个颜色条,指定颜色条的最小值和最大值,然后将数据线性的对应到色条的颜色
  • 另一种是通过分级规则来进行填色,可以更方便的设置非线性的填色规则(第一种通过指定颜色对应的百分位也可以设置为非线性)

在本例中,我们选择第一种方式,并且直接使用QE内置的colorScale(QE内置了数百种在数据科学中常用的配色方案)。

//先获取数据的最大最小值
const maxMin=provider.getGrid().maxMin;
//调用内置函数,获取内置的色标
const colorScale=await QE.getPredefinedBitmapScale(QE.predefinedLegendNames["3gauss"],maxMin.min,maxMin.max);
const style={
    //fillColor:"red",
    colorScale:colorScale
}

从上面我们可以看到,在创建colorScale的时候,我们使用了await关键字,说明内置的方法是一个异步方法,这要求我们的代码本身处于异步方法中,所以我们有必要对整体代码进行简单的调整,创建一个async loadGridLayer方法,用于加载本示例中格点相关的代码,然后在外部进行调用:

QE.init("f17d186a2e692c9f6f2e79e48b270012");
const map = new QE.LMap("map", {crs: L.CRS.EPSG3857,fadeAnimation: false}).setView([35, 110], 5);
QE.createTileLayer(QE.predefinedImageTiles.windy,{pane:QE.consts.customPanes.topmap.name}).addTo(map);
async function loadGridLayer(){
    //创建随机格点数据
//创建格点数据
//创建格点数据
const xSize=100,ySize=100;
const cX=xSize/2;
const cY=ySize/2;
const maxLen=Math.sqrt(cX*cX+cY*cY);
const dataArr=[
];
for(let i=0;i<ySize;i++){
    for(let j=0;j<xSize;j++){
        dataArr.push(1-(Math.sqrt((cX-j)*(cX-j)+(cY-i)*(cY-i)))/maxLen);
    }
}
/* for(let i=0-50;i<ySize-50;i++){
    const y=i/50.;
    for(let j=0-50;j<xSize-50;j++){
        const x=j/50.; 
        const inner=1 - x*x - (y-Math.abs(x))*(y-Math.abs(x));
        const val=5 + (-1*Math.sqrt(inner)) * Math.cos(30*(inner));
        dataArr.push(val);
    }
} */
    const options={
        xStart:70,
        xEnd:140,
        yStart:15,
        yEnd:55,
        xSize,
        ySize
    };
    //创建GridData
    /* const grid=new QE.GridData(QE.GridDataType.Float32,xSize,ySize,dataArr);
    //设置地理属性
    //构建provider
    const provider=new QE.MemoryGridDataProvider([[grid]],{gridOptions:options}); */
    //还可以直接创建
    const provider=new QE.Array2DGridDataProvider(dataArr,{gridOptions:options});
    //设置样式
    const maxMin=provider.getGrid().maxMin;
    const colorScale=await QE.getPredefinedBitmapScale(QE.predefinedLegendNames.MPL_RdYlBu,maxMin.min,maxMin.max);
    //const colorScale=QE.BitmapColorScaleGL.create(maxMin.min,maxMin.max,256,true,["rgba(255,0,0,0)","rgba(255,0,0,1)"]);

    const style={
        // fillColor:"red",
        colorScale:colorScale
    }
    const layer=new QE.LPixelLayer();
    layer.setDataSource(provider)
    layer.setDrawOptions(style);
    layer.addTo(map);
}
loadGridLayer();

chrome_ArDmLBhkdv(1).jpg

这时候,数据已经能够根据大小进行填色了,而且还自动进行了实时的插值,因此显示出来的效果并不是一个个小的方块,通过以下设置,我们可以实现每个网格根据数值进行颜色填充而不进行插值:

const style={
    // fillColor:"red",
    colorScale:colorScale,
    fillMode:0
}

完整代码

二维
三维

总结

到此,我们已经成功的显示了格点数据,虽然这个数据本身没有实际意义,但是我们完整的体验了数据获取、解码、样式设置、地图显示的过程,如果悟性够高,应该已经可以显示自己的有意义的格点数据了!