407 lines
12 KiB
JavaScript
407 lines
12 KiB
JavaScript
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||
typeof define === 'function' && define.amd ? define(factory) :
|
||
(global.Temperature = factory());
|
||
}(this, (function () { 'use strict';
|
||
|
||
var tool = {
|
||
merge: function merge(settings, defaults) {
|
||
Object.keys(settings).forEach(function (key) {
|
||
defaults[key] = settings[key];
|
||
});
|
||
},
|
||
//计算两点间距离
|
||
getDistance: function getDistance(p1, p2) {
|
||
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
|
||
},
|
||
//判断点是否在线段上
|
||
containStroke: function containStroke(x0, y0, x1, y1, lineWidth, x, y) {
|
||
if (lineWidth === 0) {
|
||
return false;
|
||
}
|
||
var _l = lineWidth;
|
||
var _a = 0;
|
||
var _b = x0;
|
||
// Quick reject
|
||
if (y > y0 + _l && y > y1 + _l || y < y0 - _l && y < y1 - _l || x > x0 + _l && x > x1 + _l || x < x0 - _l && x < x1 - _l) {
|
||
return false;
|
||
}
|
||
|
||
if (x0 !== x1) {
|
||
_a = (y0 - y1) / (x0 - x1);
|
||
_b = (x0 * y1 - x1 * y0) / (x0 - x1);
|
||
} else {
|
||
return Math.abs(x - x0) <= _l / 2;
|
||
}
|
||
var tmp = _a * x - y + _b;
|
||
var _s = tmp * tmp / (_a * _a + 1);
|
||
return _s <= _l / 2 * _l / 2;
|
||
},
|
||
|
||
//是否在矩形内
|
||
isPointInRect: function isPointInRect(point, bound) {
|
||
var wn = bound.wn; //西北
|
||
var es = bound.es; //东南
|
||
return point.x >= wn.x && point.x <= es.x && point.y >= wn.y && point.y <= es.y;
|
||
},
|
||
|
||
//是否在圆内
|
||
isPointInCircle: function isPointInCircle(point, center, radius) {
|
||
var dis = this.getDistanceNew(point, center);
|
||
return dis <= radius;
|
||
},
|
||
|
||
//两点间距离
|
||
getDistanceNew: function getDistanceNew(point1, point2) {
|
||
return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
|
||
}
|
||
};
|
||
|
||
var resolutionScale = function (context) {
|
||
var devicePixelRatio = window.devicePixelRatio || 1;
|
||
context.canvas.width = context.canvas.width * devicePixelRatio;
|
||
context.canvas.height = context.canvas.height * devicePixelRatio;
|
||
context.canvas.style.width = context.canvas.width / devicePixelRatio + 'px';
|
||
context.canvas.style.height = context.canvas.height / devicePixelRatio + 'px';
|
||
context.scale(devicePixelRatio, devicePixelRatio);
|
||
};
|
||
|
||
/**
|
||
* @author https://github.com/chengquan223
|
||
* @Date 2019-3-29
|
||
* 创建一个canvas,width,height为地图的大小,填充颜色,并获取到imageData
|
||
* for循环width、height,i+=2;
|
||
* 判断point(x,y)是否在地图范围内,矩形或边界地图都适用
|
||
* fasle,不在地图范围内,将color设置为透明色rgba(0,0,0,0),且point(x,y) = point(x+1,y) = point(x,y+1) = point(x+1,y+1)
|
||
* true,在地图范围内,将point(x,y)转换成经纬度坐标point(lon,lat)
|
||
* 由point(lon,lat)与grid网格关系换算得出point(lon,lat)相对于grid的网格点g(x,y)
|
||
* g00 g10
|
||
* g(x,y)
|
||
* g10 g11
|
||
* 双线性插值计算出点g(x,y)的value
|
||
* 将value作为参数传入颜色带,根据比例换算得出rgb
|
||
* 最终将rba回填至canvas图层的imageData
|
||
*/
|
||
var Temperature = function Temperature(map, userOptions) {
|
||
this.map = map;
|
||
this.lines = [];
|
||
this.pixelList = [];
|
||
|
||
//默认参数
|
||
var options = {
|
||
//线条宽度
|
||
lineWidth: 1
|
||
};
|
||
|
||
this.init(userOptions, options);
|
||
|
||
//全局变量
|
||
this.baseCtx = this.options.baseCanvas.getContext("2d");
|
||
this.width = map.width;
|
||
this.height = map.height;
|
||
|
||
this.clickEvent = this.clickEvent.bind(this);
|
||
|
||
this.bindEvent();
|
||
};
|
||
|
||
Temperature.prototype.init = function (settings, defaults) {
|
||
//合并参数
|
||
tool.merge(settings, defaults);
|
||
this.options = defaults;
|
||
|
||
this.legend = new Legend();
|
||
};
|
||
|
||
Temperature.prototype.createMask = function () {
|
||
var canvas = document.createElement('canvas'),
|
||
context = canvas.getContext('2d'),
|
||
width = this.width,
|
||
height = this.height;
|
||
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
context.fillStyle = "rgba(255, 0, 0, 1)";
|
||
context.fillRect(0, 0, width, height);
|
||
|
||
var imageData = context.getImageData(0, 0, width, height);
|
||
var data = imageData.data;
|
||
var mask = this.mask = {
|
||
imageData: imageData,
|
||
isVisible: function isVisible(x, y) {
|
||
var i = (y * width + x) * 4;
|
||
return data[i + 3] > 0;
|
||
},
|
||
set: function set(x, y, rgba) {
|
||
var i = (y * width + x) * 4;
|
||
data[i] = rgba[0];
|
||
data[i + 1] = rgba[1];
|
||
data[i + 2] = rgba[2];
|
||
data[i + 3] = rgba[3];
|
||
return this;
|
||
}
|
||
};
|
||
return mask;
|
||
};
|
||
|
||
Temperature.prototype.createView = function () {
|
||
var options = this.options,
|
||
extent = options.extent,
|
||
Point = options.Point,
|
||
start = map.toScreen(new Point({
|
||
x: extent[0],
|
||
y: extent[1],
|
||
spatialReference: {
|
||
wkid: 4326
|
||
}
|
||
})),
|
||
end = map.toScreen(new Point({
|
||
x: extent[2],
|
||
y: extent[3],
|
||
spatialReference: {
|
||
wkid: 4326
|
||
}
|
||
}));
|
||
|
||
return {
|
||
x: start.x,
|
||
y: start.y,
|
||
xMax: end.x,
|
||
yMax: end.y
|
||
};
|
||
};
|
||
|
||
Temperature.prototype.interpolateField = function () {
|
||
var self = this,
|
||
map = this.map,
|
||
xMax = this.width,
|
||
yMax = this.height,
|
||
opts = this.options,
|
||
view = this.createView(),
|
||
extent = opts.extent,
|
||
grid = opts.data,
|
||
legend = this.legend,
|
||
x = 0,
|
||
ScreenPoint = opts.ScreenPoint,
|
||
webMercatorUtils = opts.webMercatorUtils;
|
||
var mask = this.mask || this.createMask();
|
||
|
||
function interpolateColumn(x) {
|
||
for (var y = 0; y < yMax; y += 2) {
|
||
var color = [0, 0, 0, 0];
|
||
if (mask.isVisible(x, y)) {
|
||
//判断该点是否在矩形区域范围内,true则插值计算出value得到rgba并更新imageData,false则设置透明
|
||
if (x >= view.x && x <= view.xMax && y >= view.y && y <= view.yMax) {
|
||
var coord = map.toMap(new ScreenPoint(x, y)),
|
||
lonlat = webMercatorUtils.xyToLngLat(coord.x, coord.y),
|
||
value = interpolate(lonlat[0], lonlat[1]),
|
||
rgb = legend.getColor(value).rgb;
|
||
color = [rgb[0], rgb[1], rgb[2], 150];
|
||
}
|
||
mask.set(x, y, color).set(x + 1, y, color).set(x, y + 1, color).set(x + 1, y + 1, color);
|
||
} else {
|
||
console.error(x, y);
|
||
}
|
||
}
|
||
}
|
||
|
||
function interpolate(lon, lat) {
|
||
var i = floorMod(Math.abs(lon - extent[0]), 360) / opts.dx;
|
||
var j = (extent[1] - lat) / opts.dy;
|
||
|
||
var fi = Math.floor(i),
|
||
ci = fi + 1,
|
||
fj = Math.floor(j),
|
||
cj = fj + 1;
|
||
|
||
var row;
|
||
if (row = grid[fj]) {
|
||
var g00 = row[fi];
|
||
var g10 = row[ci];
|
||
if (isValue(g00) && isValue(g10) && (row = grid[cj])) {
|
||
var g01 = row[fi];
|
||
var g11 = row[ci];
|
||
if (isValue(g01) && isValue(g11)) {
|
||
return bilinearInterpolateScalar(i - fi, j - fj, g00, g10, g01, g11);
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function floorMod(a, n) {
|
||
var f = a - n * Math.floor(a / n);
|
||
return f === n ? 0 : f;
|
||
}
|
||
|
||
function isValue(x) {
|
||
return x !== null && x !== undefined;
|
||
}
|
||
|
||
function bilinearInterpolateScalar(x, y, g00, g10, g01, g11) {
|
||
var rx = 1 - x;
|
||
var ry = 1 - y;
|
||
return g00 * rx * ry + g10 * x * ry + g01 * rx * y + g11 * x * y;
|
||
}
|
||
|
||
(function batchInterpolate() {
|
||
var start = Date.now();
|
||
while (x < xMax) {
|
||
interpolateColumn(x);
|
||
x += 2;
|
||
if (Date.now() - start > 100) {
|
||
setTimeout(batchInterpolate, 25);
|
||
return;
|
||
}
|
||
}
|
||
self.renderBaselayer(mask);
|
||
})();
|
||
};
|
||
|
||
Temperature.prototype.renderBaselayer = function (mask) {
|
||
var context = this.baseCtx;
|
||
context.putImageData(mask.imageData, 0, 0);
|
||
};
|
||
|
||
Temperature.prototype.start = function () {
|
||
var self = this;
|
||
self.adjustSize();
|
||
|
||
//插值
|
||
self.interpolateField();
|
||
|
||
// self.renderBaselayer(); //底层canvas渲染
|
||
|
||
// (function drawFrame() {
|
||
// self.timer = setTimeout(function () {
|
||
// self.animationId = requestAnimationFrame(drawFrame);
|
||
// self.renderAnimatelayer(); //动画层canvas渲染
|
||
// }, 1000 / 10);
|
||
// }());
|
||
};
|
||
|
||
Temperature.prototype.adjustSize = function () {
|
||
var width = this.width;
|
||
var height = this.height;
|
||
this.baseCtx.canvas.width = width;
|
||
this.baseCtx.canvas.height = height;
|
||
this.baseCtx.clearRect(0, 0, width, height);
|
||
resolutionScale(this.baseCtx);
|
||
};
|
||
|
||
Temperature.prototype.bindEvent = function (e) {
|
||
var map = this.map;
|
||
if (this.options.methods) {
|
||
if (this.options.methods.mousemove) {
|
||
map.on('mouse-move', this.clickEvent);
|
||
}
|
||
}
|
||
};
|
||
|
||
Temperature.prototype.clickEvent = function (e) {
|
||
var self = this,
|
||
flag = false,
|
||
lines = self.pixelList;
|
||
|
||
if (lines.length > 0) {
|
||
lines.forEach(function (line, i) {
|
||
for (var j = 0; j < line.data.length; j++) {
|
||
var beginPt = line.data[j].pixel;
|
||
if (line.data[j + 1] == undefined) return;
|
||
var curPt = e;
|
||
var inCircle = tool.isPointInCircle(curPt, beginPt, self.options.lineWidth);
|
||
if (inCircle) {
|
||
self.options.methods.mousemove(e, line.data[j]);
|
||
flag = true;
|
||
return;
|
||
}
|
||
}
|
||
});
|
||
if (!flag) {
|
||
document.getElementById('tooltips').style.visibility = 'hidden';
|
||
}
|
||
}
|
||
};
|
||
|
||
function Legend() {
|
||
this.options = {
|
||
width: 400,
|
||
height: 15,
|
||
range: [0, 220],
|
||
gradient: {
|
||
0.1: '#96f3ff',
|
||
0.2: '#00c3ff',
|
||
0.3: '#00e68c',
|
||
0.4: '#00e600',
|
||
0.5: '#fffa00',
|
||
0.6: '#ffbe00',
|
||
0.7: '#ff7300',
|
||
0.8: '#fa1400',
|
||
0.9: '#c80091',
|
||
1.0: '#8200a0'
|
||
// gradient: {
|
||
// 0.1: '#38a702',
|
||
// 0.4: '#b0e000',
|
||
// 0.7: '#ffaa01',
|
||
// 1.0: '#fe0000'
|
||
// }
|
||
} };
|
||
this.init();
|
||
}
|
||
|
||
Legend.prototype.init = function () {
|
||
var options = this.options,
|
||
canvas = this.canvas = document.createElement('canvas'),
|
||
width = canvas.width = options.width,
|
||
height = canvas.height = options.height,
|
||
context = canvas.getContext('2d'),
|
||
grad = context.createLinearGradient(0, 0, width, height);
|
||
for (var gradient in options.gradient) {
|
||
grad.addColorStop(gradient, options.gradient[gradient]);
|
||
}
|
||
context.fillStyle = grad;
|
||
context.fillRect(0, 0, width, height);
|
||
this.imageData = context.getImageData(0, 0, width, height);
|
||
};
|
||
|
||
Legend.prototype.d2Hex = function (d) {
|
||
var hex = Number(d).toString(16);
|
||
while (hex.length < 2) {
|
||
hex = "0" + hex;
|
||
}
|
||
return hex.toUpperCase();
|
||
};
|
||
|
||
Legend.prototype.getRgbColor = function (point) {
|
||
var imageData = this.imageData;
|
||
var data = imageData.data;
|
||
var i = (point.y * this.canvas.width + point.x) * 4;
|
||
var rgb = [],
|
||
color = '#',
|
||
objRgbColor = {
|
||
"rgb": rgb,
|
||
"color": color
|
||
};
|
||
for (var j = 0; j < 3; j++) {
|
||
rgb.push(data[i + j]);
|
||
color += this.d2Hex(data[i + j]);
|
||
}
|
||
objRgbColor.color = color;
|
||
return objRgbColor;
|
||
};
|
||
|
||
Legend.prototype.getColor = function (value) {
|
||
var options = this.options,
|
||
range = options.range,
|
||
colorValue = value - range[0],
|
||
point = {
|
||
x: Math.round(colorValue * this.canvas.width / (range[range.length - 1] - range[0])),
|
||
y: 1
|
||
};
|
||
return this.getRgbColor(point);
|
||
};
|
||
|
||
return Temperature;
|
||
|
||
})));
|