被客户爸爸折腾了好久,最后终于做出基本满意的“热力图”
起初做的热力图,但是来回反复测试百度地图热力图和echars热力图+百度地图,终究不满足客户爸爸,原因很简单某些地区放大后,为什么啥也没有了,为什么?为什么?mmmp不知当讲不当讲,热力图放大数据稀疏的地方当然不明显,遂开始改热力图各种配置,渐变色,透明度,无奈怎么也不满足。最后技术总监说干脆用某某项目的聚合算了,接着就进入了正题百度地图--聚合折腾之路。
百度地图的聚合在文档里不太好找,在百度地图开源库里才能找到
贴一下官方的简单说明
类 | 描述 |
---|---|
BMapLib.MarkerClusterer(map, options) | MarkerClusterer |
方法
方法 | 返回值 | 描述 |
---|---|---|
addMarker(marker) | None | 添加一个聚合的标记。 |
addMarkers(markers) | None | 添加要聚合的标记数组。 |
clearMarkers() | None | 从地图上彻底清除所有的标记 |
getClustersCount() | Number | 获取聚合的总数量。 |
getGridSize() | Number | 获取网格大小 |
getMap() | Map | 获取聚合的Map实例。 |
getMarkers() | Array | 获取所有的标记数组。 |
getMaxZoom() | Number | 获取聚合的最大缩放级别。 |
getMinClusterSize() | Number | 获取单个聚合的最小数量。 |
getStyles() | Array | 获取聚合的样式风格集合 |
isAverageCenter() | Boolean | 获取单个聚合的落脚点是否是聚合内所有标记的平均中心。 |
removeMarker(marker) | Boolean | 删除单个标记 |
removeMarkers(markers) | Boolean | 删除一组标记 |
setGridSize(size) | None | 设置网格大小 |
setMaxZoom(maxZoom) | None | 设置聚合的最大缩放级别 |
setMinClusterSize(size) | None | 设置单个聚合的最小数量。 |
setStyles(styles) | None | 设置聚合的样式风格集合 |
事实上要不是逼急一般人不会来这里看这些文档,百度地图的demo基本上就够用。
由于数据量巨大,本项目的就有十三万数据,一股脑显示在地图上之后来回缩放,发现....卡,卡到没朋友,还有就是这十三万数据从服务器传送到前端耗时十多秒赶上网速不好就得挂在半路500错误码。
无奈之下的继续优化,终于在苦苦搜索之后找到了网上大神的优化代码 (传送门),不得不说效率确实大增,但是,优化后依然扛不住13万的数据。将来的数据可不止13万。进一步优化。
结合网上的仿照链家等地图找房例子(传送门),规划这样的方案:
1.进行区域划分,省、市、区对应不同的地图等级显示不同等级的数据,这样起码就解决的全国数据时有十三万的尴尬,这样折合下来全国数据也就30多条,效率不言而喻的高!
2.省、市、区级别之后进行自动聚合,就是百度地图真正的聚合。
3.为了避免数据多余营销效率,每次请求只请求可视区域内的数据。
然后是甩锅到本渣渣这里,开始自我猜想:
1.地图要做到拖动请求,缩放请求
2.每次请求都要知道现在的地图缩放级别是多少,以便判断当前是省、市、区
3.每次请求还要有地图可视的区域经纬度
4.省市区要用地图的自定义覆盖物,Label是个不错的选择
5.请求数据地图级别省市区的时候,数据要包含行政区域名称,人数,经纬度
基本上是这样,然后开始搞
获取地图缩放级别
var zoom = map.getZoom(); // 直接返回数字1-20吧好像
获取可视区域的经纬度
getBounds()
用法
var bs = map.getBounds(); // 获取可视区域
var bssw = bs.getSouthWest(); // bssw.lng, bssw.lat
var bsne = bs.getNorthEast(); // bsne.lng, bsne.lat
添加文本标注label
Label(content: String, opts: LabelOptions)
创建一个文本标注实例。point参数指定了文本标注所在的地理位置
|方法|返回值| 描述|
|setStyle(styles: Object)|none|设置文本标注样式,该样式将作用于文本标注的容器元素上。其中styles为JavaScript对象常量,比如: setStyle({ color : "red", fontSize : "12px" }) 注意:如果css的属性名中包含连字符,需要将连字符去掉并将其后的字母进行大写处理,例如:背景色属性要写成:backgroundColor|
|setContent(content: String)|none|设置文本标注的内容。支持HTML|
|setPosition(position: Point)|none|设置文本标注坐标。仅当通过Map.addOverlay()方法添加的文本标注有效|
然后就是代码说事儿
var map = new BMap.Map("echars_heatmap", {minZoom:5});
var BMapLib = window.BMapLib = BMapLib || {};
var Page = {
init: function() {
this.initMap();
this.echarsHeatmap(this.data.heatmapDatas);
},
data:{
timerMapGetdata:null,
zoom: 5,
condition:{
zoom: 5,
level: 1,
},
markerClusterer: null,
heatmapDatas: {
centerPoint: null,
points: null,
lengthMax: 0
},
},
// 初始化地图触发事件
initMap:function(){
var _this = this;
var point = new BMap.Point(116.403981, 39.91571);
// 设置中心点坐标和地图级别 5全国 10省
map.centerAndZoom(point, 5);
// 允许滚轮缩放
map.enableScrollWheelZoom();
// 获取地图的当前缩放级别
_this.data.zoom = map.getZoom();
// 地图缩放事件,每次缩放都要重新触发获取视野内新数据
map.addEventListener('zoomend', function(){
console.log('缩放事件:');
// 事件触发优化,防止短时间内多次触发事件频繁请求数据,造成服务器压力,同时也为了用户体验
clearTimeout(_this.data.timerMapGetdata);
_this.data.timerMapGetdata = null;
_this.data.timerMapGetdata = setTimeout(function() {
_this.getHeatData();
}, 300);
});
// 地图拖动事件,每次拖动地图,地图的可视区域都会变化,都要重新触发获取视野内新数据
map.addEventListener("moveend", function(){
console.log('拖动事件:');
// 事件触发优化,防止短时间内多次触发事件频繁请求数据,造成服务器压力,同时也为了用户体验
clearTimeout(_this.data.timerMapGetdata);
_this.data.timerMapGetdata = null;
_this.data.timerMapGetdata = setTimeout(function() {
_this.getHeatData();
}, 300);
});
},
getHeatData: function() {
var _this = this;
// layer弹窗插件,加载弹窗
layer.load(1, {
shade: [0.1, '#fff'],
});
//获取可视区域
var bs = map.getBounds();
var bssw = bs.getSouthWest();
var bsne = bs.getNorthEast();
// 获取地图缩放级别,并且划分请求级别
var zoom = map.getZoom();
var level = (zoom < 8) ? 1 : ((zoom >= 8 && zoom <= 11) ? 2 : ((zoom > 11 && zoom < 15) ? 3 : 4));
_this.data.level = level;
_this.data.condition['level'] = level;
_this.data.condition['left'] = bssw.lng;
_this.data.condition['top'] = bssw.lat;
_this.data.condition['right'] = bsne.lng;
_this.data.condition['bottom'] = bsne.lat;
$.ajax({
url: '',
data: {
condition: _this.data.condition
},
type: 'POST',
success: function(res) {
layer.closeAll();
// 重新渲染地图
_this.data.heatmapDatas.points = res.data;
_this.data.heatmapDatas.lengthMax = res.max;
_this.echarsHeatmap(_this.data.heatmapDatas);
}
});
},
// 热力图
echarsHeatmap: function(obj) {
var _this = this;
var points = obj.points ? obj.points : [];
_this.data.zoom = map.getZoom();
var zoom = Number(_this.data.zoom);
var level = _this.data.level;
// 清除覆盖物
map.clearOverlays();
if(_this.markerClusterer) {
_this.markerClusterer.clearMarkers();
}
// 1.当请求级别是4的时候,百度地图自动集合;
// 2.当请求级别小于4,使用后台做聚合查询,前端label展示。
if (level != 4) {
for (var i = 0; i < points.length; i++) {
_this.addLabel(points[i]);
}
} else {
var markers = [];
var pt = null;
for (var i = 0; i < points.length; i++) {
pt = new BMap.Point(points[i].lng, points[i].lat);
var maker = new BMap.Marker(pt);
maker.setTitle(points[i].macs);
markers.push(maker);
}
//最简单的用法,生成一个marker数组,然后调用markerClusterer类即可。
var tt1 = new Date().getTime();
_this.markerClusterer = new BMapLib.MarkerClusterer(map, {markers:markers});
var tt2 = new Date().getTime();
}
},
// 添加覆盖物
addLabel: function(pointObj) {
var zoom = Number(this.data.zoom);
var point = new BMap.Point(pointObj.longitude, pointObj.latitude);
var label = new BMap.Label();
label.setStyle({
maxWidth: 'none',
color:"#fff", //颜色
fontSize:"12px", //字号
border:"0", //边
borderRadius: '50%',
height:"60px", //高度
width:"60px", //宽
textAlign:"center", //文字水平居中显示
background: "#F99D32",
cursor:"pointer",
position: "absolute",
left: "-60px",
top: "-60px"
});
var content = '<p style="text-align:center;margin-top:16px;">'+ pointObj.name +'</p>';
content += '<p style="text-align:center;">'+ pointObj.macs +'</p>';
label.setContent(content);
label.setPosition(point);
label.addEventListener("click", function() { //拖动事件
map.centerAndZoom(point, zoom+2)
});
map.addOverlay(label);
},
};
后台用的php,这里贴上sql说明一下问题就行
当请求级别是小于4的时候,sql如下:
/*
接受参数:
conditioin:{
level: 1,
left: 110.329288,
right: 119.895882,
top:31.356779,
bottom: 36.253414
}
简要说明:
当请求级别小于4的时候,后台数据要做聚合处理,从user表查询出所有人数,通过‘order by city’做分组聚合处理(根据请求级别 省1:state,市2:city,区3:country),然后关联address表(tf_address_region表存储中国所有的省市区对应的名字、经纬度,具体表结构和sql文件在最下边展示与下载)查询出每个地址对应的经纬度
*/
SELECT A.NAME,
A.LEVEL,
A.longitude,
A.latitude,
b.member_skey
FROM
tf_address_region A,
(
SELECT
city, /*省1:state,市2:city,区3:country*/
COUNT ( 1 ) member_skey
FROM
td_user
WHERE
(
AND ( latitude <> '' AND longitude IS NOT NULL )
AND ( latitude BETWEEN 31.356779 AND 36.253414 AND longitude BETWEEN 110.329288 AND 119.895882 )
)
GROUP BY
city /*省1:state,市2:city,区3:country*/
) b
WHERE
( b.city = A.NAME ) /*省1:state,市2:city,区3:country*/
当请求级别是4的时候,sql如下:
/*
接受参数:
conditioin:{
level: 4,
left: 113.622692
right: 113.697431
top:34.739694,
bottom: 34.775282
}
简要说明:
当请求级别等于4的时候,后台数据不要做聚合处理,直接从user表查询出所有人数,聚合工作直接给百度地图处理
*/
SELECT
longitude lng,
latitude lat,
COUNT ( 1 ) member_skey
FROM
td_user
WHERE
(
AND ( latitude <> '' AND longitude IS NOT NULL )
AND ( latitude BETWEEN 34.739694 AND 34.775282 AND longitude BETWEEN 113.622692 AND 113.697431 )
)
GROUP BY
latitude,
longitude
效果图
附加address表的说明:
表结构:
名称 | 类型 | 备注 |
---|---|---|
id | bigint(11) | 表id |
name | varchar(32) | 地区名称 |
level | tinyint(4) | 地区等级 分省市县区 |
pid | bigint(10) | 父id |
longitude | float | 百度坐标,经度 |
latitude | float | 百度坐标,纬度 |