渲染格点数据
本示例的最终完成效果
示例说明
以下代码以二维讲解,三维几乎一致,可以参考最后的完整代码。
将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);
效果如下:
数据按照我们设定的范围显示了出来,但是我们只是简单的使用了红色进行填充,接下来我们要根据数值的大小来设置不同的颜色。
更新为数据驱动的样式
在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();
这时候,数据已经能够根据大小进行填色了,而且还自动进行了实时的插值,因此显示出来的效果并不是一个个小的方块,通过以下设置,我们可以实现每个网格根据数值进行颜色填充而不进行插值:
const style={
// fillColor:"red",
colorScale:colorScale,
fillMode:0
}
完整代码
二维
三维
总结
到此,我们已经成功的显示了格点数据,虽然这个数据本身没有实际意义,但是我们完整的体验了数据获取、解码、样式设置、地图显示的过程,如果悟性够高,应该已经可以显示自己的有意义的格点数据了!
No Comments