You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
856 lines
31 KiB
856 lines
31 KiB
(function (root, factory) {
|
|
if (typeof exports === 'object') {
|
|
module.exports = factory();
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
define(factory);
|
|
} else {
|
|
root.BMapLib = root.BMapLib || {};
|
|
root.BMapLib.Heatmap = root.BMapLib.Heatmap || factory();
|
|
}
|
|
})(this, function() {
|
|
function inherits (subClass, superClass, className) {
|
|
var key, proto,
|
|
selfProps = subClass.prototype,
|
|
clazz = new Function();
|
|
|
|
clazz.prototype = superClass.prototype;
|
|
proto = subClass.prototype = new clazz();
|
|
for (key in selfProps) {
|
|
proto[key] = selfProps[key];
|
|
}
|
|
subClass.prototype.constructor = subClass;
|
|
subClass.superClass = superClass.prototype;
|
|
|
|
// 类名标识,兼容Class的toString,基本没用
|
|
if ("string" == typeof className) {
|
|
proto._className = className;
|
|
}
|
|
};
|
|
var heatmapFactory = (function(){
|
|
// store object constructor
|
|
// a heatmap contains a store
|
|
// the store has to know about the heatmap in order to trigger heatmap updates when datapoints get added
|
|
var store = function store(hmap){
|
|
|
|
var _ = {
|
|
// data is a two dimensional array
|
|
// a datapoint gets saved as data[point-x-value][point-y-value]
|
|
// the value at [point-x-value][point-y-value] is the occurrence of the datapoint
|
|
data: [],
|
|
// tight coupling of the heatmap object
|
|
heatmap: hmap
|
|
};
|
|
// the max occurrence - the heatmaps radial gradient alpha transition is based on it
|
|
this.max = 1;
|
|
|
|
this.get = function(key){
|
|
return _[key];
|
|
};
|
|
this.set = function(key, value){
|
|
_[key] = value;
|
|
};
|
|
}
|
|
|
|
store.prototype = {
|
|
// function for adding datapoints to the store
|
|
// datapoints are usually defined by x and y but could also contain a third parameter which represents the occurrence
|
|
addDataPoint: function(x, y){
|
|
if(x < 0 || y < 0)
|
|
return;
|
|
|
|
var me = this,
|
|
heatmap = me.get("heatmap"),
|
|
data = me.get("data");
|
|
|
|
if(!data[x])
|
|
data[x] = [];
|
|
|
|
if(!data[x][y])
|
|
data[x][y] = 0;
|
|
|
|
// if count parameter is set increment by count otherwise by 1
|
|
data[x][y]+=(arguments.length<3)?1:arguments[2];
|
|
|
|
me.set("data", data);
|
|
// do we have a new maximum?
|
|
if(me.max < data[x][y]){
|
|
// max changed, we need to redraw all existing(lower) datapoints
|
|
heatmap.get("actx").clearRect(0,0,heatmap.get("width"),heatmap.get("height"));
|
|
me.setDataSet({ max: data[x][y], data: data }, true);
|
|
return;
|
|
}
|
|
heatmap.drawAlpha(x, y, data[x][y], true);
|
|
},
|
|
setDataSet: function(obj, internal){
|
|
var me = this,
|
|
heatmap = me.get("heatmap"),
|
|
data = [],
|
|
d = obj.data,
|
|
dlen = d.length;
|
|
// clear the heatmap before the data set gets drawn
|
|
heatmap.clear();
|
|
this.max = obj.max;
|
|
// if a legend is set, update it
|
|
heatmap.get("legend") && heatmap.get("legend").update(obj.max);
|
|
|
|
if(internal != null && internal){
|
|
for(var one in d){
|
|
// jump over undefined indexes
|
|
if(one === undefined)
|
|
continue;
|
|
for(var two in d[one]){
|
|
if(two === undefined)
|
|
continue;
|
|
// if both indexes are defined, push the values into the array
|
|
heatmap.drawAlpha(one, two, d[one][two], false);
|
|
}
|
|
}
|
|
}else{
|
|
while(dlen--){
|
|
var point = d[dlen];
|
|
heatmap.drawAlpha(point.x, point.y, point.count, false);
|
|
if(!data[point.x])
|
|
data[point.x] = [];
|
|
|
|
if(!data[point.x][point.y])
|
|
data[point.x][point.y] = 0;
|
|
|
|
data[point.x][point.y] = point.count;
|
|
}
|
|
}
|
|
heatmap.colorize();
|
|
this.set("data", d);
|
|
},
|
|
exportDataSet: function(){
|
|
var me = this,
|
|
data = me.get("data"),
|
|
exportData = [];
|
|
|
|
for(var one in data){
|
|
// jump over undefined indexes
|
|
if(one === undefined)
|
|
continue;
|
|
for(var two in data[one]){
|
|
if(two === undefined)
|
|
continue;
|
|
// if both indexes are defined, push the values into the array
|
|
exportData.push({x: parseInt(one, 10), y: parseInt(two, 10), count: data[one][two]});
|
|
}
|
|
}
|
|
|
|
return { max: me.max, data: exportData };
|
|
},
|
|
generateRandomDataSet: function(points){
|
|
var heatmap = this.get("heatmap"),
|
|
w = heatmap.get("width"),
|
|
h = heatmap.get("height");
|
|
var randomset = {},
|
|
max = Math.floor(Math.random()*1000+1);
|
|
randomset.max = max;
|
|
var data = [];
|
|
while(points--){
|
|
data.push({x: Math.floor(Math.random()*w+1), y: Math.floor(Math.random()*h+1), count: Math.floor(Math.random()*max+1)});
|
|
}
|
|
randomset.data = data;
|
|
this.setDataSet(randomset);
|
|
}
|
|
};
|
|
|
|
var legend = function legend(config){
|
|
this.config = config;
|
|
|
|
var _ = {
|
|
element: null,
|
|
labelsEl: null,
|
|
gradientCfg: null,
|
|
ctx: null
|
|
};
|
|
this.get = function(key){
|
|
return _[key];
|
|
};
|
|
this.set = function(key, value){
|
|
_[key] = value;
|
|
};
|
|
this.init();
|
|
};
|
|
legend.prototype = {
|
|
init: function(){
|
|
var me = this,
|
|
config = me.config,
|
|
title = config.title || "Legend",
|
|
position = config.position,
|
|
offset = config.offset || 10,
|
|
gconfig = config.gradient,
|
|
labelsEl = document.createElement("ul"),
|
|
labelsHtml = "",
|
|
grad, element, gradient, positionCss = "";
|
|
|
|
me.processGradientObject();
|
|
|
|
// Positioning
|
|
|
|
// top or bottom
|
|
if(position.indexOf('t') > -1){
|
|
positionCss += 'top:'+offset+'px;';
|
|
}else{
|
|
positionCss += 'bottom:'+offset+'px;';
|
|
}
|
|
|
|
// left or right
|
|
if(position.indexOf('l') > -1){
|
|
positionCss += 'left:'+offset+'px;';
|
|
}else{
|
|
positionCss += 'right:'+offset+'px;';
|
|
}
|
|
|
|
element = document.createElement("div");
|
|
element.style.cssText = "border-radius:5px;position:absolute;"+positionCss+"font-family:Helvetica; width:256px;z-index:10000000000; background:rgba(255,255,255,1);padding:10px;border:1px solid black;margin:0;";
|
|
element.innerHTML = "<h3 style='padding:0;margin:0;text-align:center;font-size:16px;'>"+title+"</h3>";
|
|
// create gradient in canvas
|
|
labelsEl.style.cssText = "position:relative;font-size:12px;display:block;list-style:none;list-style-type:none;margin:0;height:15px;";
|
|
|
|
|
|
// create gradient element
|
|
gradient = document.createElement("div");
|
|
gradient.style.cssText = ["position:relative;display:block;width:256px;height:15px;border-bottom:1px solid black; background-image:url(",me.createGradientImage(),");"].join("");
|
|
|
|
element.appendChild(labelsEl);
|
|
element.appendChild(gradient);
|
|
|
|
me.set("element", element);
|
|
me.set("labelsEl", labelsEl);
|
|
|
|
me.update(1);
|
|
},
|
|
processGradientObject: function(){
|
|
// create array and sort it
|
|
var me = this,
|
|
gradientConfig = this.config.gradient,
|
|
gradientArr = [];
|
|
|
|
for(var key in gradientConfig){
|
|
if(gradientConfig.hasOwnProperty(key)){
|
|
gradientArr.push({ stop: key, value: gradientConfig[key] });
|
|
}
|
|
}
|
|
gradientArr.sort(function(a, b){
|
|
return (a.stop - b.stop);
|
|
});
|
|
gradientArr.unshift({ stop: 0, value: 'rgba(0,0,0,0)' });
|
|
|
|
me.set("gradientArr", gradientArr);
|
|
},
|
|
createGradientImage: function(){
|
|
var me = this,
|
|
gradArr = me.get("gradientArr"),
|
|
length = gradArr.length,
|
|
canvas = document.createElement("canvas"),
|
|
ctx = canvas.getContext("2d"),
|
|
grad;
|
|
// the gradient in the legend including the ticks will be 256x15px
|
|
canvas.width = "256";
|
|
canvas.height = "15";
|
|
|
|
grad = ctx.createLinearGradient(0,5,256,10);
|
|
|
|
for(var i = 0; i < length; i++){
|
|
grad.addColorStop(1/(length-1) * i, gradArr[i].value);
|
|
}
|
|
|
|
ctx.fillStyle = grad;
|
|
ctx.fillRect(0,5,256,10);
|
|
ctx.strokeStyle = "black";
|
|
ctx.beginPath();
|
|
|
|
for(var i = 0; i < length; i++){
|
|
ctx.moveTo(((1/(length-1)*i*256) >> 0)+.5, 0);
|
|
ctx.lineTo(((1/(length-1)*i*256) >> 0)+.5, (i==0)?15:5);
|
|
}
|
|
ctx.moveTo(255.5, 0);
|
|
ctx.lineTo(255.5, 15);
|
|
ctx.moveTo(255.5, 4.5);
|
|
ctx.lineTo(0, 4.5);
|
|
|
|
ctx.stroke();
|
|
|
|
// we re-use the context for measuring the legends label widths
|
|
me.set("ctx", ctx);
|
|
|
|
return canvas.toDataURL();
|
|
},
|
|
getElement: function(){
|
|
return this.get("element");
|
|
},
|
|
update: function(max){
|
|
var me = this,
|
|
gradient = me.get("gradientArr"),
|
|
ctx = me.get("ctx"),
|
|
labels = me.get("labelsEl"),
|
|
labelText, labelsHtml = "", offset;
|
|
|
|
for(var i = 0; i < gradient.length; i++){
|
|
|
|
labelText = max*gradient[i].stop >> 0;
|
|
offset = (ctx.measureText(labelText).width/2) >> 0;
|
|
|
|
if(i == 0){
|
|
offset = 0;
|
|
}
|
|
if(i == gradient.length-1){
|
|
offset *= 2;
|
|
}
|
|
labelsHtml += '<li style="position:absolute;left:'+(((((1/(gradient.length-1)*i*256) || 0)) >> 0)-offset+.5)+'px">'+labelText+'</li>';
|
|
}
|
|
labels.innerHTML = labelsHtml;
|
|
}
|
|
};
|
|
|
|
// heatmap object constructor
|
|
var heatmap = function heatmap(config){
|
|
// private variables
|
|
var _ = {
|
|
radius : 40,
|
|
element : {},
|
|
canvas : {},
|
|
acanvas: {},
|
|
ctx : {},
|
|
actx : {},
|
|
legend: null,
|
|
visible : true,
|
|
width : 0,
|
|
height : 0,
|
|
max : false,
|
|
gradient : false,
|
|
opacity: 180,
|
|
premultiplyAlpha: false,
|
|
bounds: {
|
|
l: 1000,
|
|
r: 0,
|
|
t: 1000,
|
|
b: 0
|
|
},
|
|
debug: false
|
|
};
|
|
// heatmap store containing the datapoints and information about the maximum
|
|
// accessible via instance.store
|
|
this.store = new store(this);
|
|
|
|
this.get = function(key){
|
|
return _[key];
|
|
};
|
|
this.set = function(key, value){
|
|
_[key] = value;
|
|
};
|
|
// configure the heatmap when an instance gets created
|
|
this.configure(config);
|
|
// and initialize it
|
|
this.init();
|
|
};
|
|
|
|
// public functions
|
|
heatmap.prototype = {
|
|
configure: function(config){
|
|
var me = this,
|
|
rout, rin;
|
|
|
|
me.set("radius", config["radius"] || 40);
|
|
me.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element));
|
|
me.set("visible", (config.visible != null)?config.visible:true);
|
|
me.set("max", config.max || false);
|
|
me.set("gradient", config.gradient || { 0.45: "rgb(0,0,255)", 0.55: "rgb(0,255,255)", 0.65: "rgb(0,255,0)", 0.95: "yellow", 1.0: "rgb(255,0,0)"}); // default is the common blue to red gradient
|
|
me.set("opacity", parseInt(255/(100/config.opacity), 10) || 180);
|
|
me.set("width", config.width || 0);
|
|
me.set("height", config.height || 0);
|
|
me.set("debug", config.debug);
|
|
|
|
if(config.legend){
|
|
var legendCfg = config.legend;
|
|
legendCfg.gradient = me.get("gradient");
|
|
me.set("legend", new legend(legendCfg));
|
|
}
|
|
|
|
},
|
|
resize: function () {
|
|
var me = this,
|
|
element = me.get("element"),
|
|
canvas = me.get("canvas"),
|
|
acanvas = me.get("acanvas");
|
|
canvas.width = acanvas.width = me.get("width") || element.style.width.replace(/px/, "") || me.getWidth(element);
|
|
this.set("width", canvas.width);
|
|
canvas.height = acanvas.height = me.get("height") || element.style.height.replace(/px/, "") || me.getHeight(element);
|
|
this.set("height", canvas.height);
|
|
},
|
|
|
|
init: function(){
|
|
var me = this,
|
|
canvas = document.createElement("canvas"),
|
|
acanvas = document.createElement("canvas"),
|
|
ctx = canvas.getContext("2d"),
|
|
actx = acanvas.getContext("2d"),
|
|
element = me.get("element");
|
|
|
|
|
|
me.initColorPalette();
|
|
|
|
me.set("canvas", canvas);
|
|
me.set("ctx", ctx);
|
|
me.set("acanvas", acanvas);
|
|
me.set("actx", actx);
|
|
|
|
me.resize();
|
|
canvas.style.cssText = acanvas.style.cssText = "position:absolute;top:0;left:0;z-index:10000000;";
|
|
|
|
if(!me.get("visible"))
|
|
canvas.style.display = "none";
|
|
|
|
element.appendChild(canvas);
|
|
if(me.get("legend")){
|
|
element.appendChild(me.get("legend").getElement());
|
|
}
|
|
|
|
// debugging purposes only
|
|
if(me.get("debug"))
|
|
document.body.appendChild(acanvas);
|
|
|
|
actx.shadowOffsetX = 15000;
|
|
actx.shadowOffsetY = 15000;
|
|
actx.shadowBlur = 15;
|
|
},
|
|
initColorPalette: function(){
|
|
|
|
var me = this,
|
|
canvas = document.createElement("canvas"),
|
|
gradient = me.get("gradient"),
|
|
ctx, grad, testData;
|
|
|
|
canvas.width = "1";
|
|
canvas.height = "256";
|
|
ctx = canvas.getContext("2d");
|
|
grad = ctx.createLinearGradient(0,0,1,256);
|
|
|
|
// Test how the browser renders alpha by setting a partially transparent pixel
|
|
// and reading the result. A good browser will return a value reasonably close
|
|
// to what was set. Some browsers (e.g. on Android) will return a ridiculously wrong value.
|
|
testData = ctx.getImageData(0,0,1,1);
|
|
testData.data[0] = testData.data[3] = 64; // 25% red & alpha
|
|
testData.data[1] = testData.data[2] = 0; // 0% blue & green
|
|
ctx.putImageData(testData, 0, 0);
|
|
testData = ctx.getImageData(0,0,1,1);
|
|
me.set("premultiplyAlpha", (testData.data[0] < 60 || testData.data[0] > 70));
|
|
|
|
for(var x in gradient){
|
|
grad.addColorStop(x, gradient[x]);
|
|
}
|
|
|
|
ctx.fillStyle = grad;
|
|
ctx.fillRect(0,0,1,256);
|
|
|
|
me.set("gradient", ctx.getImageData(0,0,1,256).data);
|
|
},
|
|
getWidth: function(element){
|
|
var width = element.offsetWidth;
|
|
if(element.style.paddingLeft){
|
|
width+=element.style.paddingLeft;
|
|
}
|
|
if(element.style.paddingRight){
|
|
width+=element.style.paddingRight;
|
|
}
|
|
|
|
return width;
|
|
},
|
|
getHeight: function(element){
|
|
var height = element.offsetHeight;
|
|
if(element.style.paddingTop){
|
|
height+=element.style.paddingTop;
|
|
}
|
|
if(element.style.paddingBottom){
|
|
height+=element.style.paddingBottom;
|
|
}
|
|
|
|
return height;
|
|
},
|
|
colorize: function(x, y){
|
|
// get the private variables
|
|
var me = this,
|
|
width = me.get("width"),
|
|
radius = me.get("radius"),
|
|
height = me.get("height"),
|
|
actx = me.get("actx"),
|
|
ctx = me.get("ctx"),
|
|
x2 = radius * 3,
|
|
premultiplyAlpha = me.get("premultiplyAlpha"),
|
|
palette = me.get("gradient"),
|
|
opacity = me.get("opacity"),
|
|
bounds = me.get("bounds"),
|
|
left, top, bottom, right,
|
|
image, length, alpha, offset, finalAlpha;
|
|
|
|
if(x != null && y != null){
|
|
if(x+x2>width){
|
|
x=width-x2;
|
|
}
|
|
if(x<0){
|
|
x=0;
|
|
}
|
|
if(y<0){
|
|
y=0;
|
|
}
|
|
if(y+x2>height){
|
|
y=height-x2;
|
|
}
|
|
left = x;
|
|
top = y;
|
|
right = x + x2;
|
|
bottom = y + x2;
|
|
|
|
}else{
|
|
if(bounds['l'] < 0){
|
|
left = 0;
|
|
}else{
|
|
left = bounds['l'];
|
|
}
|
|
if(bounds['r'] > width){
|
|
right = width;
|
|
}else{
|
|
right = bounds['r'];
|
|
}
|
|
if(bounds['t'] < 0){
|
|
top = 0;
|
|
}else{
|
|
top = bounds['t'];
|
|
}
|
|
if(bounds['b'] > height){
|
|
bottom = height;
|
|
}else{
|
|
bottom = bounds['b'];
|
|
}
|
|
}
|
|
|
|
image = actx.getImageData(left, top, right-left, bottom-top);
|
|
length = image.data.length;
|
|
// loop thru the area
|
|
for(var i=3; i < length; i+=4){
|
|
|
|
// [0] -> r, [1] -> g, [2] -> b, [3] -> alpha
|
|
alpha = image.data[i],
|
|
offset = alpha*4;
|
|
|
|
if(!offset)
|
|
continue;
|
|
|
|
// we ve started with i=3
|
|
// set the new r, g and b values
|
|
finalAlpha = (alpha < opacity)?alpha:opacity;
|
|
image.data[i-3]=palette[offset];
|
|
image.data[i-2]=palette[offset+1];
|
|
image.data[i-1]=palette[offset+2];
|
|
|
|
if (premultiplyAlpha) {
|
|
// To fix browsers that premultiply incorrectly, we'll pass in a value scaled
|
|
// appropriately so when the multiplication happens the correct value will result.
|
|
image.data[i-3] /= 255/finalAlpha;
|
|
image.data[i-2] /= 255/finalAlpha;
|
|
image.data[i-1] /= 255/finalAlpha;
|
|
}
|
|
|
|
// we want the heatmap to have a gradient from transparent to the colors
|
|
// as long as alpha is lower than the defined opacity (maximum), we'll use the alpha value
|
|
image.data[i] = finalAlpha;
|
|
}
|
|
// the rgb data manipulation didn't affect the ImageData object(defined on the top)
|
|
// after the manipulation process we have to set the manipulated data to the ImageData object
|
|
// image.data = imageData;
|
|
ctx.putImageData(image, left, top);
|
|
},
|
|
drawAlpha: function(x, y, count, colorize){
|
|
// storing the variables because they will be often used
|
|
var me = this,
|
|
radius = me.get("radius"),
|
|
ctx = me.get("actx"),
|
|
max = me.get("max"),
|
|
bounds = me.get("bounds"),
|
|
xb = x - (1.5 * radius) >> 0, yb = y - (1.5 * radius) >> 0,
|
|
xc = x + (1.5 * radius) >> 0, yc = y + (1.5 * radius) >> 0;
|
|
|
|
ctx.shadowColor = ('rgba(0,0,0,'+((count)?(count/me.store.max):'0.1')+')');
|
|
|
|
ctx.shadowOffsetX = 15000;
|
|
ctx.shadowOffsetY = 15000;
|
|
ctx.shadowBlur = 15;
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(x - 15000, y - 15000, radius, 0, Math.PI * 2, true);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
if(colorize){
|
|
// finally colorize the area
|
|
me.colorize(xb,yb);
|
|
}else{
|
|
// or update the boundaries for the area that then should be colorized
|
|
if(xb < bounds["l"]){
|
|
bounds["l"] = xb;
|
|
}
|
|
if(yb < bounds["t"]){
|
|
bounds["t"] = yb;
|
|
}
|
|
if(xc > bounds['r']){
|
|
bounds['r'] = xc;
|
|
}
|
|
if(yc > bounds['b']){
|
|
bounds['b'] = yc;
|
|
}
|
|
}
|
|
},
|
|
toggleDisplay: function(){
|
|
var me = this,
|
|
visible = me.get("visible"),
|
|
canvas = me.get("canvas");
|
|
|
|
if(!visible)
|
|
canvas.style.display = "block";
|
|
else
|
|
canvas.style.display = "none";
|
|
|
|
me.set("visible", !visible);
|
|
},
|
|
// dataURL export
|
|
getImageData: function(){
|
|
return this.get("canvas").toDataURL();
|
|
},
|
|
clear: function(){
|
|
var me = this,
|
|
w = me.get("width"),
|
|
h = me.get("height");
|
|
|
|
me.store.set("data",[]);
|
|
// @TODO: reset stores max to 1
|
|
//me.store.max = 1;
|
|
me.get("ctx").clearRect(0,0,w,h);
|
|
me.get("actx").clearRect(0,0,w,h);
|
|
},
|
|
cleanup: function(){
|
|
var me = this;
|
|
me.get("element").removeChild(me.get("canvas"));
|
|
}
|
|
};
|
|
|
|
return {
|
|
create: function(config){
|
|
return new heatmap(config);
|
|
},
|
|
util: {
|
|
mousePosition: function(ev){
|
|
// this doesn't work right
|
|
// rather use
|
|
/*
|
|
// this = element to observe
|
|
var x = ev.pageX - this.offsetLeft;
|
|
var y = ev.pageY - this.offsetTop;
|
|
|
|
*/
|
|
var x, y;
|
|
|
|
if (ev.layerX) { // Firefox
|
|
x = ev.layerX;
|
|
y = ev.layerY;
|
|
} else if (ev.offsetX) { // Opera
|
|
x = ev.offsetX;
|
|
y = ev.offsetY;
|
|
}
|
|
if(typeof(x)=='undefined')
|
|
return;
|
|
|
|
return [x,y];
|
|
}
|
|
}
|
|
};
|
|
})();
|
|
|
|
var HeatmapOverlay = function(opts) {
|
|
try {
|
|
BMap;
|
|
} catch (e) {
|
|
throw Error('Baidu Map JS API is not ready yet!');
|
|
}
|
|
if (!HeatmapOverlay._isExtended) {
|
|
HeatmapOverlay._isExtended = true;
|
|
inherits(HeatmapOverlay, BMap.Overlay, "HeatmapOverlay");
|
|
var newHeatmap = new HeatmapOverlay(opts);
|
|
this.__proto__ = newHeatmap.__proto__;
|
|
}
|
|
// HeatmapOverlay.prototype = new BMap.Overlay();
|
|
this.conf = opts;
|
|
this.heatmap = null;
|
|
this.latlngs = [];
|
|
this.bounds = null;
|
|
this._moveendHandler = this._moveendHandler.bind(this);
|
|
}
|
|
|
|
HeatmapOverlay.prototype.initialize = function(map) {
|
|
this._map = map;
|
|
var el = document.createElement("div");
|
|
el.style.position = "absolute";
|
|
el.style.top = 0;
|
|
el.style.left = 0;
|
|
el.style.border = 0;
|
|
el.style.width = this._map.getSize().width + "px";
|
|
el.style.height = this._map.getSize().height + "px";
|
|
this.conf.element = el;
|
|
map.getPanes().mapPane.appendChild(el);
|
|
this.heatmap = heatmapFactory.create(this.conf);
|
|
this._div = el;
|
|
return el;
|
|
}
|
|
|
|
HeatmapOverlay.prototype.draw = function() {
|
|
var currentBounds = this._map.getBounds();
|
|
|
|
if (currentBounds.equals(this.bounds)) {
|
|
return;
|
|
}
|
|
this.bounds = currentBounds;
|
|
|
|
var ne = this._map.pointToOverlayPixel(currentBounds.getNorthEast()),
|
|
sw = this._map.pointToOverlayPixel(currentBounds.getSouthWest());
|
|
if (!ne || !sw) {
|
|
return
|
|
}
|
|
var topY = ne.y,
|
|
leftX = sw.x,
|
|
h = sw.y - ne.y,
|
|
w = ne.x - sw.x;
|
|
|
|
this.conf.element.style.left = leftX + 'px';
|
|
this.conf.element.style.top = topY + 'px';
|
|
this.conf.element.style.width = w + 'px';
|
|
this.conf.element.style.height = h + 'px';
|
|
this.heatmap.store.get("heatmap").resize();
|
|
|
|
if (this.latlngs.length > 0) {
|
|
this.heatmap.clear();
|
|
|
|
var len = this.latlngs.length;
|
|
var d = {
|
|
max: this.heatmap.store.max,
|
|
data: []
|
|
};
|
|
|
|
while (len--) {
|
|
var latlng = this.latlngs[len].latlng;
|
|
|
|
if (!currentBounds.containsPoint(latlng)) {
|
|
continue;
|
|
}
|
|
|
|
var divPixel = this._map.pointToOverlayPixel(latlng),
|
|
screenPixel = new BMap.Pixel(divPixel.x - leftX, divPixel.y - topY);
|
|
var roundedPoint = this.pixelTransform(screenPixel);
|
|
d.data.push({
|
|
x: roundedPoint.x,
|
|
y: roundedPoint.y,
|
|
count: this.latlngs[len].c
|
|
});
|
|
}
|
|
this.heatmap.store.setDataSet(d);
|
|
}
|
|
}
|
|
|
|
HeatmapOverlay.prototype.pixelTransform = function(p) {
|
|
var w = this.heatmap.get("width"),
|
|
h = this.heatmap.get("height");
|
|
|
|
while (p.x < 0) {
|
|
p.x += w;
|
|
}
|
|
|
|
while (p.x > w) {
|
|
p.x -= w;
|
|
}
|
|
|
|
while (p.y < 0) {
|
|
p.y += h;
|
|
}
|
|
|
|
while (p.y > h) {
|
|
p.y -= h;
|
|
}
|
|
|
|
p.x = (p.x >> 0);
|
|
p.y = (p.y >> 0);
|
|
|
|
return p;
|
|
}
|
|
|
|
HeatmapOverlay.prototype._moveendHandler = function (e) {
|
|
this.setDataSet(this._data);
|
|
delete this._data;
|
|
this._map.removeEventListener('moveend', this._moveendHandler);
|
|
}
|
|
|
|
HeatmapOverlay.prototype.setDataSet = function(data) {
|
|
if (!this._map) {
|
|
return;
|
|
}
|
|
var currentBounds = this._map.getBounds();
|
|
|
|
var ne = this._map.pointToOverlayPixel(currentBounds.getNorthEast()),
|
|
sw = this._map.pointToOverlayPixel(currentBounds.getSouthWest());
|
|
if (!ne || !sw) {
|
|
this._data = data
|
|
this._map.addEventListener('moveend', this._moveendHandler);
|
|
}
|
|
|
|
var mapdata = {
|
|
max: data.max,
|
|
data: []
|
|
};
|
|
var d = data.data,
|
|
dlen = d.length;
|
|
|
|
|
|
this.latlngs = [];
|
|
|
|
while (dlen--) {
|
|
var latlng = new BMap.Point(d[dlen].lng, d[dlen].lat);
|
|
this.latlngs.push({
|
|
latlng: latlng,
|
|
c: d[dlen].count
|
|
});
|
|
|
|
if (!currentBounds.containsPoint(latlng)) {
|
|
continue;
|
|
}
|
|
|
|
var divPixel = this._map.pointToOverlayPixel(latlng),
|
|
leftX = this._map.pointToOverlayPixel(currentBounds.getSouthWest()).x,
|
|
topY = this._map.pointToOverlayPixel(currentBounds.getNorthEast()).y,
|
|
screenPixel = new BMap.Pixel(divPixel.x - leftX, divPixel.y - topY);
|
|
var point = this.pixelTransform(screenPixel);
|
|
|
|
mapdata.data.push({
|
|
x: point.x,
|
|
y: point.y,
|
|
count: d[dlen].count
|
|
});
|
|
}
|
|
this.heatmap.clear();
|
|
this.heatmap.store.setDataSet(mapdata);
|
|
}
|
|
|
|
HeatmapOverlay.prototype.addDataPoint = function(lng, lat, count) {
|
|
var latlng = new BMap.Point(lng, lat),
|
|
point = this.pixelTransform(this._map.pointToOverlayPixel(latlng));
|
|
|
|
this.heatmap.store.addDataPoint(point.x, point.y, count);
|
|
this.latlngs.push({
|
|
latlng: latlng,
|
|
c: count
|
|
});
|
|
}
|
|
|
|
HeatmapOverlay.prototype.toggle = function() {
|
|
this.heatmap.toggleDisplay();
|
|
}
|
|
|
|
return HeatmapOverlay;
|
|
});
|
|
|