高端网校通,安徽网站优化怎么做,上海网站建设公司推荐,上海网站制作建设前言 #x1f4eb; 大家好#xff0c;我是南木元元#xff0c;热衷分享有趣实用的文章#xff0c;希望大家多多支持#xff0c;一起进步#xff01; #x1f345; 个人主页#xff1a;南木元元 目录
背景
前置知识
风场数据
绘制风场
准备工作
生成二维网格
获取…前言 大家好我是南木元元热衷分享有趣实用的文章希望大家多多支持一起进步 个人主页南木元元 目录
背景
前置知识
风场数据
绘制风场
准备工作
生成二维网格
获取格点风矢位置
风力等级
计算风矢坐标位置
旋转角度
绘制格点风矢
结语 背景
项目里遇到个需求要求绘制出风场的空间分布图一开始的想法是这有什么难的直接用echarts不就可以了。但当我看完设计图后不得不感叹一句好家伙这还真有点复杂。最终要实现的效果如下图所示 由于自定义的程度比较高echarts肯定是不行的思来想去于是决定用canvas来从0到1自己实现同时也可以顺带把canvas的知识巩固一下温馨提示全文可能有点长。
前置知识
首先解释一下什么是风场空间分布图。 风场空间分布图一种用于展示区域内风速和风向随空间位置变化的图表这种图表通常以箭头或风矢的形式来表示风的方向和强度。这使我们可以直观地看到风速、风向的变化规律它常常在气象学、风能工程等领域中被广泛使用。 本文采用风矢的形式来进行风场的可视化。在气象学中风矢是用于表示风向和风速的符号图标。风矢由2部分组成分别为风向杆与风羽。
风向杆表示风的方向风羽分别用长划线和短划线或者与风三角组合的方式表示风速的大小。 了解了上面的概念后我们下面就将使用Canvas来展示如何绘制风场的空间分布图。
风场数据
数据来源于用户自建的气象观测产品库原始数据一般是netcdf或grib2的格式需要后端将其解析成json格式的数据解析后的数据格式大致如下
{yaxis: [10, 20, ...],xaxis: [[39.4, 107.16], [37.286667, 107.72223], ...]elementDataList: [{name: windS,subData: [{level: 10,data: [8.9,10.3,...]},{level: 20,data: [4.6,8.1,...]},...},{name: windD,subData: [{level: 10,data: [59.8,65.0,...]},{level: 20,data: [60.1,58.5,...]},...]}]
}
纵轴yaxis代表不同的高度层横轴xaxis代表不同的经纬度坐标要素列表elementDataList中目前只有一个风场要素还有其它的气象要素如温度、降水量等这里不展开由于风场是矢量要素同时具有大小和方向所以这里将风的数据拆分成了windS风速列表和windD风向列表列表中的值分别为每个高度层所对应的数据。
绘制风场
准备工作
定义一个绘制的类做一些初始化的操作属性设置获取canvas的2d渲染上下文。
class drawWind {constructor(data){ //网格属性this.property {OFFSET_X: 42, //x轴间隔OFFSET_Y: 20, //y轴间隔};//获取2d渲染上下文this.canvas2d document.getElementById(canvas);this.ctx2d this.canvas2d.getContext(2d);//后端返回数据this.data data;this.xaxis data.xaxis;this.yaxis data.yaxis;//处理后的数据this.wind10S [];this.wind10D [];}//初始化数据init() {//处理一下返回的风速和风向数据这里不详细展开最终处理成网格点数据即可this.wind10S this.handleData(wind10S);this.wind10D this.handleData(wind10D);}}
}
还需要处理一下后端返回的数据变成二维网格点数据如下
风速数据 风向数据 最终需要的数据就是网格点数据即每个网格点都对应其风速和风向数据。
生成二维网格
生成风场需要构造二维网格canvas绘制二维网格的思路很简单先使用strokeRect设置一个矩形的边框然后分别遍历横坐标和纵坐标列表进行虚线的绘制。
draw2dMesh() {//生成矩形边框this.ctx2d.strokeRect(0, 0, this.canvas2d.width, this.canvas2d.height);//设置虚线样式this.ctx2d.lineWidth 0.6;this.ctx2d.strokeStyle rgb(192, 192, 192);this.ctx2d.beginPath();//遍历绘制纵向虚线for (let i 1; i this.xaxis.length; i) {this.ctx2d.setLineDash([5, 3]);this.ctx2d.moveTo(this.property.OFFSET_X * i, 0);this.ctx2d.lineTo(this.property.OFFSET_X * i, this.canvas2d.height);}//遍历绘制横向虚线for (let i 1; i this.yaxis.length; i) {this.ctx2d.setLineDash([5, 3]);this.ctx2d.moveTo(0, this.property.OFFSET_Y * i);this.ctx2d.lineTo(this.canvas2d.width, this.property.OFFSET_Y * i);}this.ctx2d.stroke();
}
绘制的网格如下 获取格点风矢位置
每个网格点上的风矢形状是下面这样的。 所以在正式绘制前我们还需要先计算每个风矢中的风杆和风羽数得到每个点的位置。
风力等级
风力等级的计算公式 可以参考这两篇文章风力的级别换算和风力、等级、风速对照表和计算公式。
这里我们采用的是32个等级可以预先定义好每个等级对应的风杆、长短划线以及风三角的数量。
this.Level {TRIANGLE: 20,LONG: 4,SHORT: 2,
},
this.Count {TRIANGLE: 10,LONG: 2,SHORT: 1,
},
//32个风力等级每个数组中的四个值依次代表风杆数量、短划线数量、长划线数量、风三角数量
this.windLevel [[0, 1, 0, 0],[1, 1, 0, 0],[1, 0, 1, 0],[1, 1, 1, 0],[1, 0, 2, 0],[1, 1, 2, 0],[1, 0, 3, 0],[1, 1, 3, 0],[1, 0, 4, 0],[1, 1, 4, 0],[1, 0, 0, 1],[1, 1, 0, 1],[1, 0, 1, 1],[1, 1, 1, 1],[1, 0, 2, 1],[1, 1, 2, 1],[1, 0, 3, 1],[1, 1, 3, 1],[1, 0, 4, 1],[1, 1, 4, 1],[1, 0, 0, 2],[1, 1, 0, 2],[1, 0, 1, 2],[1, 1, 1, 2],[1, 0, 2, 2],[1, 1, 2, 2],[1, 0, 3, 2],[1, 1, 3, 2],[1, 0, 4, 2],[1, 1, 4, 2],
],
//风矢属性风杆长长划线长短划线长划线间隔风三角边长
this.featherProperty {poleLength: 10,longLine: 10,shortLine: 5,lineSpace: 1,triangle: 2,
};
定义计算风力等级的方法。
// 根据风速计算风力等级公式v 0.836 * b^(3/2) v:风速 b:风级
calWindLevel(speed) {let triangle Math.floor(speed / this.Level.TRIANGLE);let long Math.floor((speed - this.Level.TRIANGLE * triangle) / this.Level.LONG);let short Math.floor((speed - this.Level.TRIANGLE * triangle - this.Level.LONG * long) / this.Level.SHORT);let idx triangle * this.Count.TRIANGLE long * this.Count.LONG short * this.Count.SHORT;if (idx 30) {idx 30;}return idx;
}
计算风矢坐标位置
接下来需要计算得到每个网格点上的风矢中每个点的位置这部分是整个流程中最为复杂的。 来说说我的思路定义一个数组用于存放当前格点的风矢位置然后获取计算得到的风杆、长短划线等数量从风杆顶部开始依次放入风杆、风三角、长划线、短划线的位置。
//用于存放所有网格点风矢的位置
let position [];
// 计算坐标位置Num为当前网格点对应的风力等级包含各种数量
getPointPosition(Num) {//用于存放当前格点风矢的位置let position []; let pole Num[0]; //风杆数量let short Num[1]; //短划线数量let long Num[2]; //长划线数量let triangle Num[3]; //风三角数量//当前顶点纵坐标位置从风杆顶部开始这里为负是由于canvas坐标系y轴向下为正let yOffset -this.featherProperty.poleLength;if (pole 0) { //风杆数为0position.push(0, 0,this.featherProperty.shortLine, 0,this.featherProperty.shortLine, 0 //为了和风三角的三个一组一致多加了一个点);//把当前格点的风羽位置放入数组position.push(position);return;}//放入风杆位置position.push(0, 0,0, -this.featherProperty.poleLength, //向上为负0, -this.featherProperty.poleLength);//判断风三角是否为0不为0向其中添加顶点if (triangle ! 0) {for (let i 0; i triangle; i) {position.push(0, yOffset,0, yOffset this.featherProperty.triangle, //triangle为三角形边长this.featherProperty.longLine, yOffset (this.featherProperty.triangle / 2));//每画完一个三角形当前y坐标就要下移由于canvas向下为正所以即为加上三角形边长再加划线和三角形的间距yOffset yOffset this.featherProperty.triangle this.featherProperty.lineSpace;}}//判断长划线是否为0不为0向其中添加顶点if (long ! 0) {for (let i 0; i long; i) {position.push(0, yOffset,this.featherProperty.longLine, yOffset,this.featherProperty.longLine, yOffset);yOffset yOffset this.featherProperty.lineSpace;}}//判断短划线是否为0不为0向其中添加顶点if (short ! 0) {for (let i 0; i short; i) {position.push(0, yOffset,this.featherProperty.shortLine, yOffset,this.featherProperty.shortLine, yOffset);yOffset yOffset this.featherProperty.lineSpace;}}//把当前格点的风羽位置放入数组position.push(position);
}
得到的风矢各个点的坐标数组大致如下 旋转角度
风向决定了每个风矢在格点的旋转角度由于旋转的时候以每个格点坐标为中心所以记录一下每个格点的坐标位置。
// 获取旋转角度
getRotateData() {// 保存旋转中心点即网格点坐标let center [];// 保存风向let angle [];for (let y 0; y this.yaxis.length; y) {for (let x 0; x this.xaxis.length; x) {// 获取风向let angle_point this.angle[x y * this.xaxis.length];// 计算网格点坐标let center [(x 1) * this.offsetX, (y 1) * this.offsetY];center.push(center); angle.push([angle_point]);}}return {angle: angle,center: center,};
}
绘制格点风矢
做完上述操作后终于可以开始绘制啦。绘制的思路由于之前在计算位置的时候就统一3个坐标为一组即画线只需两个坐标点但我们也多加了一个重复的点为了和画三角形统一所以现在只需遍历顶点数组来绘制每个格点的风矢就可以了。
// 绘制
drawFeather(data, color, size) {// 设置样式this.ctx.lineWidth size; this.ctx.strokeStyle color; this.ctx.fillStyle color;// 让虚线变成实线条this.ctx.setLineDash([]);let position data.position;let center data.center;let angle data.angle;// 遍历顶点数组绘制每个格点的风矢for(let i 0; i center.length; i) {for(let j 0; j position[i].length; j 6) {// 保存画布 (canvas) 的所有状态this.ctx.save(); // 移动canvas原点到此处使得当前格点为坐标为原点0,0this.ctx.translate(center[i][0],center[i][1]); this.ctx.rotate(angle[i][0] * Math.PI/180);this.ctx.beginPath();// 之前处理后的数据都是三个为一组包括线条直接画线即可this.ctx.moveTo(position[i][j], position[i][j1]);this.ctx.lineTo(position[i][j2], position[i][j3]);this.ctx.lineTo(position[i][j4], position[i][j5]);this.ctx.fill(); this.ctx.stroke(); // 恢复 canvas 状态this.ctx.restore(); }}
}
注意在绘制每个格点风矢的时候都需要save保存一下将当前canvas的状态入栈绘制完后restore弹出恢复状态为的是绘制下一个格点的风矢时都可以重新从canvas的坐标原点0,0开始平移到网格中心点然后进行旋转操作。
最终的效果 现在主要的部分我们都已经完成了剩下的其实就是绘制横坐标和纵坐标由于这部分比较简单其实就是利用canvas绘制文字这里就不再详细展开了。
结语
本文主要记录了一次自己使用canvas从0到1绘制风场空间分布图的经历整个过程还是蛮复杂的不过也刚好巩固了一下自己的canvas知识将其运用到了实践中同时也发现自己对知识的理解其实还存在许多的不足需要继续努力
如果此文对你有帮助的话欢迎关注、点赞、⭐收藏、✍️评论支持一下博主~