目录
一、问题由来
二、探索
三、尝试
四、修正
五、优化方案
六、kriging.js 使用感受小结
七、参考文章
八、原始例程下载 下载连接:https://download.csdn.net/download/jessezappy/16769114
一、问题由来
因业务需要,需要根据小时温度、雨量等气象数据自动生成色斑图,供网页展示。但不管是各 GIS 软件,还是 surfer ,虽然都有自动化脚本功能,但是一个是必须安装不小的软件,另一个是必须手动启动该软件,才能执行自动化脚本,无法实现系统重启后无人值守自动出图功能。因此,势必需要自己单独写一个小程序来实现无人值守自动出图才能满足实际工作环境需求。
二、探索
根据以上需求,想过自己用自己熟悉的 VBS 或 VB(surfer ActiveX) 来实现。但用 VBS 的话,因为不懂平面插值算法,故无法写出插值程序,只好作罢。
用 VB(surfer ActiveX) 的话,一个是需要安装 surfer ,另一个是从用最早的 winsurfer 开始到现在我有近18年没用过 surfer 这个软件了,从头来学实在是懒,而且以前用的也不熟,现在的任务需求时间比较紧,只好作罢。
搜来搜去,搜到了 python 的一个色斑图绘制程序 python 绘制降水量色斑图_小杨丿的博客-CSDN博客_python画色斑图 ,但是,拿来执行了一下,无果。我对 python 也不熟,只好作罢。
最后,搜到 Openlayers利用kriging.js实现纯前端插值_hpugisers的博客-CSDN博客_kriging.train ,发现了希望,这个使用克里金插值算法的 kriging.js 来实现在谷歌地图上贴上了指定数据的色斑图,修改起来方便快捷,遂准备以此为蓝本来开发,但底图不是我想要的简化线框边界图,而且,带了个累赘的 Openlayers ,又大又慢。不过,看了 Cesium+kriging.js实现雨量插值_gis_rc的博客-CSDN博客_arcengine 克里金插值 和 kriging插值在web端的应用含kriging.js下载地址_Viccy_Yao的博客-CSDN博客 对 kriging.js 的介绍,貌似有希望搞成。
三、尝试
利用那个在谷歌地图上贴 canvas 装色斑图的例子,根据另外两篇对 kriging.js 介绍的文章,我分析,应该是可以将 canvas 单独分离出来使用的,因为分离出来离开了地图后,我才有自主控制的空间。
首先去除了地图相关的代码后,发现执行无显示,然后看了下这句:
let canvas = document.createElement('canvas');
总感觉哪里好像不对,执行后页面并未生成新的 canvas ,于是,手工写一个固定的 canvas ,然后修改上面的代码为:
let canvas = document.getElementById("myCanvas");// document.createElement('canvas');
终于显示出正确的图了。至此,即实现了在自主控制的 canvas 绘制出色斑图了,而且根据找到多个资料(如: 如何使用js将canvas保存为图片文件,并且可以自定义文件名_DASEason的博客-CSDN博客_canvas保存为图片 ),用 chrome 打开页面,利用 js 可以直接实现输出自动下载 canvas 中绘制的图像。
但是问题又来了,这样绘制出来的色斑图,上和左是顶在 canvas 最边缘的,也就是相当于从 (0,0) 位置开始绘制的,而我想在色斑图的左边底边加上经纬度,上边加上文字等信息,而现在实现的是直接输出 canvas 的图像,那么和我的需求就不符了。
四、修正
根据需要,我需要在图上画出经纬度线框,地图边界线框,加上色卡,标题等信息一起出图,类似这样:
但是绘制好的色斑图是没有边界线框的,而且在绘制色斑图的 canvas 中,只能先绘制色斑图,后绘制其他线条,因为 kriging.plot 会清除 canvas 一次,同时,边界线框用 js 代码绘制的话效率未免太低了。
那么,用图像转移的方法,将绘制色斑图的 canvas 中的色斑图图像转移到已经贴好底图边界线框位图图片的 canvas 中呢?
试了下,图倒是复制进去指定位置了,但是透明度数据没了,底图被盖掉个方形框了,才想起来,贴图过去的话,色斑图 canvas 中的图像已经是普通位图损失了透明度信息了,所以不行。于是想到了获取色斑图 canvas 中数据的办法,判断颜色是否是需要设为透明度数据的颜色,如果是,那么跳过,如果不是,那么在线框地图底图 canvas 中指定位置按原颜色绘制该点,代码如下:
/*** 在本地进行文件保存* @param {String} data 要保存到本地的图片数据* @param {String} filename 文件名*/var saveFile = function (data, filename){var save_link = document.createElementNS( 'http://www.w3.org/1999/xhtml' , 'a' );save_link.href = data;save_link.download = filename;var event = document.createEvent( 'MouseEvents' );event.initMouseEvent( 'click' , true , false , window, 0, 0, 0, 0, 0, false , false , false , false , 0, null );save_link.dispatchEvent(event);};// 下载后的文件名var filename ='wendu.png'; function saveAsLocalImage () { var myCanvas = document.getElementById("kycanvas1");var image = myCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream");saveFile(image,filename); }
//---------以上为图片下载保存,以下为搬运色斑图到底图 Canvas
var canvas = document.getElementById("myCanvas");//色斑图Canvas
var context = canvas.getContext("2d");
var canvas1 = document.getElementById("kycanvas1");//底图及最后合成Canvas
var context1 = canvas1.getContext("2d");
var image = new Image();
image.src = "bgx.jpg";//指定背景位图image.onload = function (){context1.drawImage(image,0,0,canvas.width,canvas.height);var imageData = context.getImageData(0,0,canvas.width,canvas.height);//取得色斑图Canvas的图像信息 var data = imageData.data;//取得色斑图Canvas的点阵图像信息var x=0,y=0;//console.log("#" + ((1 << 24) + (255 << 16) + (128 << 8) + 127).toString(16).slice(1));context1.globalAlpha =0.5;//还可以指定透明度for(let i = 0; i < data.length; i+=4) {// 得到 RGBA 通道的值let r = data[i], g = data[i+1], b = data[i+2], a = data[i+3]x=x+1;if(x>=imageData.width){y=y+1;x=0;}if(a!=0){// 绘制context1.fillStyle="#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);//"#0000FF";转换颜色数字为字符串context1.fillRect(x-1,y-1,1,1);}}saveAsLocalImage();
}
这样基本实现了色斑图和底图叠加的效果,底图可事先做成位图,加载到 kycanvas1 ,而且绘制完毕后自动下载图片。
但是,kriging.js 绘制的色斑图边缘是锯齿的,而且边界数据过细的时候更为明显,本来是想用边界框底图盖住色斑图的,现在色斑图又在上层了,难道要将底图先放在另一个 Canvas 中,再用上面搬运图像点阵数据的方法搬运过来色斑图上?不过,我未再做尝试了,至此,自动绘制色斑图并自动下载的需求基本完成了。
只是,效果不太理想,总感觉哪里不爽,怪怪的。
五、优化方案
虽然以上方案已经可以实现自动化出图并下载了,但是总感觉有点七拼八凑的,代码啰嗦。于是在想,能不能象一些浏览器一样,直接将整个网页保存为图片呢?这样就免去了一大堆麻烦的搬图像数据的 js 代码,直接在 html 元素上调整下层级,就不用将 Canvas 中的图像搬来搬去了,思考瞎逛中,忽然发现了个网页保存的库:Html2Canvas_Alien的笔记 试了下,效果不错耶。于是将上面的方案进行修改,总结如下:
1、标题直接用html代码制作
<div id="waikuang" style="position: absolute;top: 0px;margin: auto;width: 100%;"><p><span id="ddt">2020年9月30日1时</span><span>1小时雨量</span></p></div><div id="waikuang" style="position: absolute;top: 40px;margin: auto;width: 100%;"><span>(单位:</span><span id="dec">mm</span><span>)</span></div>
2、用 BIGEMAP 下载需要地域的的边界数据及行政边界位图,边界数据加工为 geojson 格式,如:
var coord = [[
[102.2254522,24.91366818],
[102.23019,24.91451367],
[102.2353284,24.91387074],
[102.239521,24.91532629],
......
[102.2198955,24.90666487],
[102.2236062,24.90893716],
[102.2238734,24.9106202],
[102.2243019,24.91243735],
[102.2254522,24.91366818]]];
行政边界位图加工成透明线条。
3、使用 coord 边界、各数据点位信息等数据绘制色斑图到指定的 myCanvas
<div id="sbtu" style="position:absolute;top:118px ;left:122px;"><canvas id="myCanvas" width="810px" height="710px" style="">您的浏览器不支持canvas标签</canvas></div>
let letiogram = kriging.train(values, lngs, lats,params.krigingModel, params.krigingSigma2, params.krigingAlpha);
let grid = kriging.grid(coord, letiogram, (extent[2] - extent[0]) / params.gridnum);
console.log(grid);
//取得图层
let canvas = document.getElementById("myCanvas");// document.createElement('canvas');
canvas.width = params.w;
canvas.height = params.h;//canvas.style.display = 'none';
//设置canvas透明度
canvas.getContext('2d').globalAlpha = params.canvasAlpha;
//使用分层设色渲染
kriging.plot(canvas, grid,[extent[0], extent[2]], [extent[1], extent[3]], params.colors);
4、根据绘制的色斑图的边界,用图片编辑软件将边界描出指定宽度的边框,画上经纬度线框,根据需要补上边界内部次级行政边界线条,保存为透明的 png 图片。
5、将经纬度边界线框背景图片直接用 html 代码加载,并调整之前绘制的色斑图图层之间的相对位置,使之重叠正确:
<div id="yxmap" style="position:absolute;top:-20px ;left: 0;"><img src="yuxibg.png" /></div>
6、在另一个单独的 canvas 中绘制色卡,颜色,级别自行设置:
<div id="sbtu" style="position:absolute;top:890px ;margin: auto;width: 100%;"><canvas id="colortb" width="704px" height="60px" style="margin: auto;">您的浏览器不支持canvas标签。</canvas></div>
function setcolortb(){//绘制色卡let cvs = document.getElementById("colortb");//求values最大最小值let maxvl=Math.max.apply(Math, values);let minvl=Math.min.apply(Math, values);var ctx=cvs.getContext('2d');ctx.strokeStyle= "#fefefe";//设置画笔的颜色ctx.textAlign="center";ctx.font="16px Arial";ctx.strokeStyle= "#333333";for(let i=0;i<params.colors.length;i++){ctx.beginPath();ctx.fillStyle=params.colors[i];//ctx.rect((640/256)*i,0,(640/256),30);ctx.rect(64*i+32,1,64,30);ctx.stroke();ctx.fill();//----------写色卡数字ctx.fillStyle="#000000";let cvl=minvl+i*(maxvl-minvl)/params.colors.length;ctx.fillText(cvl.toFixed(1),64*i+32,50);lasti=i;}//补色卡最后一个数字lasti++;let cvl=minvl+lasti*(maxvl-minvl)/params.colors.length;console.log('cvl:'+cvl);ctx.fillText(cvl.toFixed(1),64*lasti+32,50);ctx.save();}
7、为确保输出的网页图片尺寸可固定设置,用新的页面 wendu.html 将以上做好的网页加载到指定尺寸的 iframe 中
<!DOCTYPE html>
<html><head><title>下载整页</title><meta charset="utf-8" ><script src="/js/jquery-3.3.1.min.js"></script><script type="text/javascript">$(function() {$('#loadpng').attr('src','./wendusbt.html?v='+Math.random());});</script></head><body><iframe id="loadpng" src="" width="1000" height="960" frameborder="0" scrolling="no" seamless></iframe></body>
</html>
使用时,打开这个含 iframe 的页面,内部原色斑图绘制页面自动保存时就会按照 iframe 的尺寸来保存(注意内部元素不要超出指定宽度,否则会保存出空白边)。
8、加入整页下载代码
在页面任意区域放置图片下载链接框:<a href="" download="rain.png" id="download"></a> ,剩余部分代码如下:
<!-- 页头、边界数据、点位数据、经纬度数据加载略 -->
<script src="/js/jquery-3.3.1.min.js"></script>
<script src="kriging.js"></script>
<script src="html2canvas-1.0.0.min.js"></script>
<script language="JavaScript" type="text/javascript">var shareContent;function saveCanvas() {html2canvas(shareContent).then(function(canvas) {var imgUri = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");$("#download").attr("href",imgUri);//console.log(imgUri);document.getElementById("download").click();});}var params = {krigingModel: 'exponential',//model还可选'gaussian','spherical',exponential//mapCenter: [102.27996826,24.23034668],krigingSigma2: 0,krigingAlpha: 100,//此参数可能控制钻孔扩散范围,越小范围越大,但点多了且分布均匀以后即效果不明显了canvasAlpha: 1,//canvas图层透明度gridnum:811,//网格数量,越大越细,一般最大设为需要出图的 canvas 的宽度即可,则 kriging.grid 返回点阵尺寸为每点一像素,再大无用w:810,h:710,//色斑图绘制区域尺寸,根据实际需要决定colors: ["#ffffff", "#00ff65", "#00ff02", "#acff00", "#defb6b", "#ffff82", "#f3e0d9", "#ffbfc0", "#ff5600", "#ea0100"]//色卡数据};var extent =[101.27531641,23.317184342,103.14981013,24.95515513];//绘制区域四边界数据var values = [], lngs = [], lats = [];//-----/*取温度或雨量数据到 values ,将数据对应点位的经纬度存入 lngs 、lats ,如:*///------------一组测试数据values = [17.9,14.5,18.8,16.3,10,12.6,5.8,16.9,17.1];lngs = [102.55,101.97,102.75,102.92,102.41,102.91,101.97,102.18,102.76];lats = [24.33,24.07,24.13,24.68,24.18,24.19,23.63,24.66,24.29]; //-----
$(function(){shareContent = document.body;
//绘制kriging插值图
let drawKriging =function (extent){if (values.length > 3) {let letiogram = kriging.train(values, lngs, lats,params.krigingModel, params.krigingSigma2, params.krigingAlpha);let grid = kriging.grid(coord, letiogram, (extent[2] - extent[0]) / params.gridnum);console.log(grid);//取得图层let canvas = document.getElementById("myCanvas");// document.createElement('canvas');canvas.width = params.w;canvas.height = params.h;//canvas.style.display = 'none';//设置canvas透明度canvas.getContext('2d').globalAlpha = params.canvasAlpha;//使用分层设色渲染kriging.plot(canvas, grid,[extent[0], extent[2]], [extent[1], extent[3]], params.colors); } else {//alert("有效样点个数不足,无法插值");}
}//加载,渲染差值色斑图drawKriging(extent);//------绘制色卡见前代码//setcolortb();saveCanvas();//保存整个页面
});
</script>
9、编辑打开 chrome 的快捷方式,如:
"D:\Program Files\google\chrome61\chrome.exe" --user-data-dir="User Data" http://127.0.0.1:8081/klg/wendu.html --disable-background-networking
这样,下一步用 DOS 批处理启动时就可以根据需要打开不同的页面下载指定的色斑图。
10、编辑 DOS 批处理文件,最后可加入到计划任务中执行获取指定色斑图并保存到指定位置
@echo off
start D:\klg\downwundu1.lnk
:startx
if exist C:\Users\Administrator\Downloads\rain.png (@echo c盘下 C:\Users\Administrator\Downloads\rain.png 存在 @echo 移动 rain.pngmove /y C:\Users\Administrator\Downloads\rain.png D:\klg\webroot\img\taskkill /f /im chrome.exe /t
) else (@echo c盘下不存在需要的 Downloads\rain.png 文件,重新查找ping 127.0.0.1 -n 3goto startx
)
因为色斑图按 canvas 宽度为精度来处理的话,kriging.grid 执行时间较长,例如:我有200个点的数据,网格精度为 810 ,出图时间需要十多秒,所以才将该方法放在后端出图,并用 DOS 批处理在 chrome 自动下载保存目录内循环检索需要的图片是否下载完毕,如下载完毕则将其移动到网页所需目录内,确保 chrome 自动下载保存目录内不存在重复文件以免下一次下载被重命名为 *(n) ,之后强制杀除 chrome 浏览器进程,前端浏览时直接浏览生成好的图片即可。
精度提高后生成较慢,因此,该方法不适合用于前端出图!切记!
至此,完整的后端无人值守自动出色斑图的流程完毕。剩余的实时数据更新、边界数据获取、点位经纬度数据处理等的都是小问题了,如果你能看懂上面的流程的话。
六、kriging.js 使用感受小结
kriging.js 的使用很简单,如果你有完整数据的话,那么简单来说就是分三步走:
1、let letiogram = kriging.train(values, lngs, lats,params.krigingModel, params.krigingSigma2, params.krigingAlpha);
2、let grid = kriging.grid(coord, letiogram, (extent[2] - extent[0]) / params.gridnum);
3、kriging.plot(canvas, grid,[extent[0], extent[2]], [extent[1], extent[3]], params.colors);
其中各所需数据前面已经有范例和解释,我再单独说下几个参数:
krigingModel: 'exponential',//model还可选'gaussian','spherical',一般推荐使用 exponential ,其他两个可根据需要测试看效果
krigingSigma2: 0, // (博主 gis_rc 回复:sigma2是σ²,对应高斯过程的方差参数,也就是你这组数据z的距离,方差参数σ²的似然性反映了高斯过程中的误差,并应手动设置)。一般设置为 0 ,其他数值设了可能会出空白图。
krigingAlpha: 100,//(博主 gis_rc 回复:Alpha α对应方差函数的先验值),此参数可能控制钻孔扩散范围,越小范围越大,少量点效果明显,但点多了且分布均匀以后改变该数字即基本无效果了,默认设置为100
以下为相同数据及参数,两个不同 krigingAlpha 时的出图效果,因为只有6个数据点,因此修改 krigingAlpha 后,图形变化明显。
gridnum:811,//kriging.grid返回的网格数量,越大越细处理越慢,建议最大设置为绘制 canvas 的像素宽度即可,此时可保证每个网格为一个像素,因此多了意义不大,而且数字大了,网格数量是成平方方式增长,处理过程会超级慢。小了的话,出图锯齿严重,因此,嫌慢的,可以适当减小数值。
colors: ["#ffffff", "#00ff65", "#00ff02", "#acff00", "#defb6b", "#ffff82", "#f3e0d9", "#ffbfc0", "#ff5600", "#ea0100"]//色阶数据,颜色数量越多,可绘制的颜色阶数越多,颜色过渡越平滑,可根据 values 的最大值减最小值范围来决定需要多少级颜色变化。
// 如果你需要指定颜色为固定数值对应固定颜色范围,那么,可按照颜色数量添加相同数量的虚假点, values 值从小到大,添加你需要的数值范围,比如 10 级 0~100 那么添加 0,10,20,~~,100 ,11个数值进 values ,同时添加经纬度数据位于你的边界区域外很远的地方,比如 (0.1,0.1),(0.1,0.2) :
//--------尝试设置虚拟点位数字以控制颜色级别for(let i=0;i<=params.colors.length;i++){values.push(i*10);lngs.push(i/100);lats.push(0.1);}
这样,指定数量级的数值点就会以你规定的颜色来显示。比如绘制雨量图时,这样设置就可以保证雨量为 0 的区域显示为你指定的比如白色 #ffffff ,其余区域根据你设定的颜色来对应显示相应级别的数据。
七、参考文章
1、Openlayers利用kriging.js实现纯前端插值_hpugisers的博客-CSDN博客_kriging.train
2、Cesium+kriging.js实现雨量插值_gis_rc的博客-CSDN博客_arcengine 克里金插值
3、kriging插值在web端的应用含kriging.js下载地址_Viccy_Yao的博客-CSDN博客
4、如何使用js将canvas保存为图片文件,并且可以自定义文件名_DASEason的博客-CSDN博客_canvas保存为图片
5、Html2Canvas_Alien的笔记
6、GitHub - oeo4b/kriging.js: Javascript library for geospatial prediction and mapping via ordinary kriging
八、原始例程下载
应各位小伙伴要求,希望有个演示例程,故将得自 CSDN 并经我简化整理可以出图的第一稿例程放出来,给大家参考。
注:原始例程。简化了数据输入、色卡制作、图上写值等内容,保留了图片自动下载功能(未命名,无扩展名。仅用于演示可工作于后端自动出图),未添加网页保存图片功能,目前精度为100格:gridnum:100 ,高精度请自行修改该值。
下载连接:色斑图制作及后端无人值守自动出图原始例程-互联网文档类资源-CSDN下载
此记!未尽事宜,得闲再补充,如有更多想法,请评论留言!