被客戶爸爸折騰了好久,最后終于做出基本滿意的“熱力圖”
起初做的熱力圖,但是來回反復(fù)測(cè)試百度地圖熱力圖和echars熱力圖+百度地圖,終究不滿足客戶爸爸,原因很簡(jiǎn)單某些地區(qū)放大后,為什么啥也沒有了,為什么?為什么?mmmp不知當(dāng)講不當(dāng)講,熱力圖放大數(shù)據(jù)稀疏的地方當(dāng)然不明顯,遂開始改熱力圖各種配置,漸變色,透明度,無奈怎么也不滿足。最后技術(shù)總監(jiān)說干脆用某某項(xiàng)目的聚合算了,接著就進(jìn)入了正題百度地圖--聚合折騰之路。
百度地圖的聚合在文檔里不太好找,在百度地圖開源庫里才能找到
貼一下官方的簡(jiǎn)單說明
| 類 | 描述 |
|---|---|
| BMapLib.MarkerClusterer(map, options) | MarkerClusterer |
方法
| 方法 | 返回值 | 描述 |
|---|---|---|
| addMarker(marker) | None | 添加一個(gè)聚合的標(biāo)記。 |
| addMarkers(markers) | None | 添加要聚合的標(biāo)記數(shù)組。 |
| clearMarkers() | None | 從地圖上徹底清除所有的標(biāo)記 |
| getClustersCount() | Number | 獲取聚合的總數(shù)量。 |
| getGridSize() | Number | 獲取網(wǎng)格大小 |
| getMap() | Map | 獲取聚合的Map實(shí)例。 |
| getMarkers() | Array | 獲取所有的標(biāo)記數(shù)組。 |
| getMaxZoom() | Number | 獲取聚合的最大縮放級(jí)別。 |
| getMinClusterSize() | Number | 獲取單個(gè)聚合的最小數(shù)量。 |
| getStyles() | Array | 獲取聚合的樣式風(fēng)格集合 |
| isAverageCenter() | Boolean | 獲取單個(gè)聚合的落腳點(diǎn)是否是聚合內(nèi)所有標(biāo)記的平均中心。 |
| removeMarker(marker) | Boolean | 刪除單個(gè)標(biāo)記 |
| removeMarkers(markers) | Boolean | 刪除一組標(biāo)記 |
| setGridSize(size) | None | 設(shè)置網(wǎng)格大小 |
| setMaxZoom(maxZoom) | None | 設(shè)置聚合的最大縮放級(jí)別 |
| setMinClusterSize(size) | None | 設(shè)置單個(gè)聚合的最小數(shù)量。 |
| setStyles(styles) | None | 設(shè)置聚合的樣式風(fēng)格集合 |
事實(shí)上要不是逼急一般人不會(huì)來這里看這些文檔,百度地圖的demo基本上就夠用。
由于數(shù)據(jù)量巨大,本項(xiàng)目的就有十三萬數(shù)據(jù),一股腦顯示在地圖上之后來回縮放,發(fā)現(xiàn)....卡,卡到?jīng)]朋友,還有就是這十三萬數(shù)據(jù)從服務(wù)器傳送到前端耗時(shí)十多秒趕上網(wǎng)速不好就得掛在半路500錯(cuò)誤碼。
無奈之下的繼續(xù)優(yōu)化,終于在苦苦搜索之后找到了網(wǎng)上大神的優(yōu)化代碼 (傳送門),不得不說效率確實(shí)大增,但是,優(yōu)化后依然扛不住13萬的數(shù)據(jù)。將來的數(shù)據(jù)可不止13萬。進(jìn)一步優(yōu)化。
結(jié)合網(wǎng)上的仿照鏈家等地圖找房例子(傳送門),規(guī)劃這樣的方案:
1.進(jìn)行區(qū)域劃分,省、市、區(qū)對(duì)應(yīng)不同的地圖等級(jí)顯示不同等級(jí)的數(shù)據(jù),這樣起碼就解決的全國(guó)數(shù)據(jù)時(shí)有十三萬的尷尬,這樣折合下來全國(guó)數(shù)據(jù)也就30多條,效率不言而喻的高!
2.省、市、區(qū)級(jí)別之后進(jìn)行自動(dòng)聚合,就是百度地圖真正的聚合。
3.為了避免數(shù)據(jù)多余營(yíng)銷效率,每次請(qǐng)求只請(qǐng)求可視區(qū)域內(nèi)的數(shù)據(jù)。
然后是甩鍋到本渣渣這里,開始自我猜想:
1.地圖要做到拖動(dòng)請(qǐng)求,縮放請(qǐng)求
2.每次請(qǐng)求都要知道現(xiàn)在的地圖縮放級(jí)別是多少,以便判斷當(dāng)前是省、市、區(qū)
3.每次請(qǐng)求還要有地圖可視的區(qū)域經(jīng)緯度
4.省市區(qū)要用地圖的自定義覆蓋物,Label是個(gè)不錯(cuò)的選擇
5.請(qǐng)求數(shù)據(jù)地圖級(jí)別省市區(qū)的時(shí)候,數(shù)據(jù)要包含行政區(qū)域名稱,人數(shù),經(jīng)緯度
基本上是這樣,然后開始搞
獲取地圖縮放級(jí)別
var zoom = map.getZoom(); // 直接返回?cái)?shù)字1-20吧好像
獲取可視區(qū)域的經(jīng)緯度
getBounds()
用法
var bs = map.getBounds(); // 獲取可視區(qū)域
var bssw = bs.getSouthWest(); // bssw.lng, bssw.lat
var bsne = bs.getNorthEast(); // bsne.lng, bsne.lat
添加文本標(biāo)注label
Label(content: String, opts: LabelOptions)
創(chuàng)建一個(gè)文本標(biāo)注實(shí)例。point參數(shù)指定了文本標(biāo)注所在的地理位置
|方法|返回值| 描述|
|setStyle(styles: Object)|none|設(shè)置文本標(biāo)注樣式,該樣式將作用于文本標(biāo)注的容器元素上。其中styles為JavaScript對(duì)象常量,比如: setStyle({ color : "red", fontSize : "12px" }) 注意:如果css的屬性名中包含連字符,需要將連字符去掉并將其后的字母進(jìn)行大寫處理,例如:背景色屬性要寫成:backgroundColor|
|setContent(content: String)|none|設(shè)置文本標(biāo)注的內(nèi)容。支持HTML|
|setPosition(position: Point)|none|設(shè)置文本標(biāo)注坐標(biāo)。僅當(dāng)通過Map.addOverlay()方法添加的文本標(biāo)注有效|
然后就是代碼說事兒
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
},
},
// 初始化地圖觸發(fā)事件
initMap:function(){
var _this = this;
var point = new BMap.Point(116.403981, 39.91571);
// 設(shè)置中心點(diǎn)坐標(biāo)和地圖級(jí)別 5全國(guó) 10省
map.centerAndZoom(point, 5);
// 允許滾輪縮放
map.enableScrollWheelZoom();
// 獲取地圖的當(dāng)前縮放級(jí)別
_this.data.zoom = map.getZoom();
// 地圖縮放事件,每次縮放都要重新觸發(fā)獲取視野內(nèi)新數(shù)據(jù)
map.addEventListener('zoomend', function(){
console.log('縮放事件:');
// 事件觸發(fā)優(yōu)化,防止短時(shí)間內(nèi)多次觸發(fā)事件頻繁請(qǐng)求數(shù)據(jù),造成服務(wù)器壓力,同時(shí)也為了用戶體驗(yàn)
clearTimeout(_this.data.timerMapGetdata);
_this.data.timerMapGetdata = null;
_this.data.timerMapGetdata = setTimeout(function() {
_this.getHeatData();
}, 300);
});
// 地圖拖動(dòng)事件,每次拖動(dòng)地圖,地圖的可視區(qū)域都會(huì)變化,都要重新觸發(fā)獲取視野內(nèi)新數(shù)據(jù)
map.addEventListener("moveend", function(){
console.log('拖動(dòng)事件:');
// 事件觸發(fā)優(yōu)化,防止短時(shí)間內(nèi)多次觸發(fā)事件頻繁請(qǐng)求數(shù)據(jù),造成服務(wù)器壓力,同時(shí)也為了用戶體驗(yàn)
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'],
});
//獲取可視區(qū)域
var bs = map.getBounds();
var bssw = bs.getSouthWest();
var bsne = bs.getNorthEast();
// 獲取地圖縮放級(jí)別,并且劃分請(qǐng)求級(jí)別
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.當(dāng)請(qǐng)求級(jí)別是4的時(shí)候,百度地圖自動(dòng)集合;
// 2.當(dāng)請(qǐng)求級(jí)別小于4,使用后臺(tái)做聚合查詢,前端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);
}
//最簡(jiǎn)單的用法,生成一個(gè)marker數(shù)組,然后調(diào)用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", //字號(hào)
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() { //拖動(dòng)事件
map.centerAndZoom(point, zoom+2)
});
map.addOverlay(label);
},
};
后臺(tái)用的php,這里貼上sql說明一下問題就行
當(dāng)請(qǐng)求級(jí)別是小于4的時(shí)候,sql如下:
/*
接受參數(shù):
conditioin:{
level: 1,
left: 110.329288,
right: 119.895882,
top:31.356779,
bottom: 36.253414
}
簡(jiǎn)要說明:
當(dāng)請(qǐng)求級(jí)別小于4的時(shí)候,后臺(tái)數(shù)據(jù)要做聚合處理,從user表查詢出所有人數(shù),通過‘order by city’做分組聚合處理(根據(jù)請(qǐng)求級(jí)別 省1:state,市2:city,區(qū)3:country),然后關(guān)聯(lián)address表(tf_address_region表存儲(chǔ)中國(guó)所有的省市區(qū)對(duì)應(yīng)的名字、經(jīng)緯度,具體表結(jié)構(gòu)和sql文件在最下邊展示與下載)查詢出每個(gè)地址對(duì)應(yīng)的經(jīng)緯度
*/
SELECT A.NAME,
A.LEVEL,
A.longitude,
A.latitude,
b.member_skey
FROM
tf_address_region A,
(
SELECT
city, /*省1:state,市2:city,區(qū)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,區(qū)3:country*/
) b
WHERE
( b.city = A.NAME ) /*省1:state,市2:city,區(qū)3:country*/
當(dāng)請(qǐng)求級(jí)別是4的時(shí)候,sql如下:
/*
接受參數(shù):
conditioin:{
level: 4,
left: 113.622692
right: 113.697431
top:34.739694,
bottom: 34.775282
}
簡(jiǎn)要說明:
當(dāng)請(qǐng)求級(jí)別等于4的時(shí)候,后臺(tái)數(shù)據(jù)不要做聚合處理,直接從user表查詢出所有人數(shù),聚合工作直接給百度地圖處理
*/
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表的說明:
表結(jié)構(gòu):
| 名稱 | 類型 | 備注 |
|---|---|---|
| id | bigint(11) | 表id |
| name | varchar(32) | 地區(qū)名稱 |
| level | tinyint(4) | 地區(qū)等級(jí) 分省市縣區(qū) |
| pid | bigint(10) | 父id |
| longitude | float | 百度坐標(biāo),經(jīng)度 |
| latitude | float | 百度坐標(biāo),緯度 |