前面在使用kriging.js进行克里金插值可视化时,计算分为三个步骤:
- 模型训练(kriging.train)
- 网格生成(kriging.grid)
- 绘制结果(kriging.plot)
实际在web页面中根据真实数据即时运行上面三个步骤的计算,因为模型训练涉及到的数学计算量很大,可能需要很长时间才能得到结果,前端javascript耗时统计如下:
上面三个步骤的耗时大小取决于模型选择,原始数据,以及网格分辨率等参数,查看源代码可以看到各个步骤使用的参数。
- 模型训练(kriging.train)
- 网格生成(kriging.grid)
- 绘制结果(kriging.plot)
现实需求中需要使用真实数据生成一个或多个热力图canvas,既然每次从计算到生成图片耗时很久,可不可以按照需求将每组数据计算生成的图片缓存到后台,下次使用直接从后台获取,这样页面上便能更快的看到热力图?答案是肯定的。
这里将某个时间对应的数据第一次克里金插值计算绘制生成的多个canvas的base64编码以及各自对应的经纬度范围信息保存到后台,下次再次需要绘制这个时间对应数据的热力图,直接从后台获取叠加到地图上。怎么判断是否是第一次呢,JavaScript代码逻辑首先从后台询问某个日期对应的热力图是否已经存在,如果存在就直接获取,如果不存在前端JavaScript开始计算生成,同时生成后保存到后台方便下次使用。
我们看一下前端JavaScript实现:
再来看一下后台实现:
前端代码如下:
代码语言:html复制<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html,
body,
#allmap {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
#allmap img {
height: 100%;
}
</style>
<script type="text/javascript" src="https://underscorejs.net/js/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="https://underscorejs.net/js/underscore.js"></script>
<script type="text/javascript" src="static/lib/kriging.js"></script>
<script type="text/javascript" src="static/js/china.js"></script>
<script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=wWy2A8K94nhntYTYUHS19RXW"></script>
<title>空气质量空间插值可视化</title>
</head>
<body>
<div id="allmap"></div>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap");
map.centerAndZoom(new BMap.Point(116.403765, 39.914850), 5);
map.enableScrollWheelZoom();
function loadMarkers(points) {
map.clearOverlays();
$.each(points, (i, x) => {
//console.log(x);
var position = new BMap.Point(x['经度'], x['纬度']);
var marker1 = new BMap.Marker(position);
dictMarker[x['编号']] = marker1;
map.addOverlay(marker1);
marker1.addEventListener("click", function() {
var attrs = _.omit(x, ['编号', '经度', '纬度','value2']);
var content = _.map(attrs, (x, i) => `${i}:${x}`).join('<br>');
var infoWindow = new BMap.InfoWindow(content);
marker1.openInfoWindow(infoWindow);
});
})
}
dictMarker = {};
$.get('api/stations', function(data) {
data = JSON.parse(data['result']);
window.points = data;
dictPoints = _.indexBy(points, '编号');
//loadMarkers(data);
refreshCanvas();
})
function refreshCanvas() {
var date = location.search.match(/date=([^&]*)/i);
var hour = location.search.match(/hour=([^&]*)/i);
if (!!date) {
date = date[1];
} else {
date = "20210610";
}
if (!!hour) {
hour = hour[1];
} else {
hour = "0";
}
var type = "PM2.5";
window.params={date: date,hour: hour, type: type};
$.post('krigingMap',params,function(data){console.log(data);
if(data['result']==0){
$.post('api/stations', params, function(data) {
data = JSON.parse(data['result']);
var airData = _.find(data, {
hour: parseInt(hour),
type: type
});
airData = _.omit(airData, ['date', 'hour', 'type']);
var trainData = _.compact(_.map(airData, function(item, index) {
if (!item) {
return null;
}
result = _.result(dictPoints, index, null);
if (!!result) {
result['value']=item;
result['value2'] = _.sortedIndex(standards, item);
}
return result;
}));
window.data1 = trainData;
window.canvas = getCanvas(trainData);
})
}
else{
var imgs=$.parseJSON(data['result']);
imgs.forEach(function(item){
addOverlay([item['minX'],item['minY']],[item['maxX'],item['maxY']],item['data']);
})
}
});
}
var [width, height] = [10, 5];
var [offsetX, offsetY] = [10, 5];
let canvas = null; //画布
let colors = ["green", "#82D827", "yellow", "#FFDF00", "orange", "#FF5E00",
"red", "#B71850", "purple", "#952E81","brown"];
var standards = [25, 50, 75, 100, 125, 150, 175, 200, 250, 300];
function getCanvas(data) {
var values = _.pluck(data, 'value2');
var lngs = _.pluck(data, '经度');
var lats = _.pluck(data, '纬度');
var indexes = _.unique(values);
var colors0 = colors.slice(_.min(indexes), _.max(indexes) 1);
var [minX, minY, maxX, maxY] = [_.min(lngs), _.min(lats), _.max(lngs), _.max(lats)];
var bounds = [
[
[minX, minY],
[minX, maxY],
[maxX, maxY],
[maxX, minY]
]
];
console.log(bounds);
bounds = _.flatten(china.geometry.coordinates,true);
console.time();
window.variogram = kriging.train(values, lngs, lats, 'exponential', 0, 100);
console.timeEnd();
console.time();
window.grid = kriging.grid(bounds, variogram, (maxY - minY) / 500);
console.timeEnd();
console.time();
var lngs = _.range(minX - offsetX, maxX offsetX, width);
var lats = _.range(minY - offsetY, maxY offsetY, height);
window.backupImgs=[];
_.map(lngs, function(m) {
canvas = document.createElement('canvas');
canvas.width = width * 5;
canvas.height = height * 5;
canvas.style.display = 'block';
canvas.getContext('2d').globalAlpha = 0.75;
[minX, maxX] = [m, m width]
_.map(lats, function(n) {
[minY, maxY] = [n, n height]
kriging.plot(canvas, grid, [minX, maxX], [minY, maxY], colors0);
canvasImg= canvas.toDataURL();
backupImgs.push({'minX':minX,'minY':minY,'maxX':maxX,'maxY':maxY,'data':canvasImg});
addOverlay([minX, minY], [maxX, maxY], canvasImg);
})
})
console.timeEnd();
$.post('backupImgs',$.extend({'imgs':JSON.stringify(backupImgs)},params),function(data){
if(data['result']==1){
console.info('success');
}
});
}
function addOverlay(west_south, east_north, canvasImg) {
var west_south = new BMap.Point(west_south[0], west_south[1]);
var east_north = new BMap.Point(east_north[0], east_north[1]);
var bounds = new BMap.Bounds(west_south, east_north);
var canvasOverlay = new BMap.GroundOverlay(bounds, {
imageURL: canvasImg,
opacity: 0.6
});
map.addOverlay(canvasOverlay);
}
</script>
</body>
</html>
后台代码(使用Python Web框架Tornado实现)如下:
代码语言:python代码运行次数:0复制class backupImgsHandler(tornado.web.RequestHandler):
def post(self):
imgs=self.get_argument('imgs')
date=self.get_argument('date')
hour=self.get_argument('hour')
_type=self.get_argument('type')
imgsFile=os.path.join('static/backupImgs',f'{date}-{hour}-{_type}')
with open(imgsFile,'w') as f:
f.write(imgs)
print(imgs)
self.write({'result':1})
class krigingMapHandler(tornado.web.RequestHandler):
def get(self):
self.render('krigingMap.html')
def post(self):
date=self.get_argument('date')
hour=self.get_argument('hour')
_type=self.get_argument('type')
imgsFile=os.path.join('static/backupImgs',f'{date}-{hour}-{_type}')
if os.path.exists(imgsFile):
with open(imgsFile) as f:
result=f.read()
self.write({'result':result})
else:
self.write({'result':0})