被客戶爸爸折騰了好久,最后終于做出基本滿意的“熱力圖”
起初做的熱力圖,但是來回反復測試百度地圖熱力圖和echars熱力圖+百度地圖,終究不滿足客戶爸爸,原因很簡單某些地區(qū)放大后,為什么啥也沒有了,為什么?為什么?mmmp不知當講不當講,熱力圖放大數據稀疏的地方當然不明顯,遂開始改熱力圖各種配置,漸變色,透明度,無奈怎么也不滿足。最后技術總監(jiān)說干脆用某某項目的聚合算了,接著就進入了正題百度地圖--聚合折騰之路。
百度地圖的聚合在文檔里不太好找,在百度地圖開源庫里才能找到
貼一下官方的簡單說明
| 類 | 描述 |
|---|---|
| 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基本上就夠用。
由于數據量巨大,本項目的就有十三萬數據,一股腦顯示在地圖上之后來回縮放,發(fā)現....卡,卡到沒朋友,還有就是這十三萬數據從服務器傳送到前端耗時十多秒趕上網速不好就得掛在半路500錯誤碼。
無奈之下的繼續(xù)優(yōu)化,終于在苦苦搜索之后找到了網上大神的優(yōu)化代碼 (傳送門),不得不說效率確實大增,但是,優(yōu)化后依然扛不住13萬的數據。將來的數據可不止13萬。進一步優(yōu)化。
結合網上的仿照鏈家等地圖找房例子(傳送門),規(guī)劃這樣的方案:
1.進行區(qū)域劃分,省、市、區(qū)對應不同的地圖等級顯示不同等級的數據,這樣起碼就解決的全國數據時有十三萬的尷尬,這樣折合下來全國數據也就30多條,效率不言而喻的高!
2.省、市、區(qū)級別之后進行自動聚合,就是百度地圖真正的聚合。
3.為了避免數據多余營銷效率,每次請求只請求可視區(qū)域內的數據。
然后是甩鍋到本渣渣這里,開始自我猜想:
1.地圖要做到拖動請求,縮放請求
2.每次請求都要知道現在的地圖縮放級別是多少,以便判斷當前是省、市、區(qū)
3.每次請求還要有地圖可視的區(qū)域經緯度
4.省市區(qū)要用地圖的自定義覆蓋物,Label是個不錯的選擇
5.請求數據地圖級別省市區(qū)的時候,數據要包含行政區(qū)域名稱,人數,經緯度
基本上是這樣,然后開始搞
獲取地圖縮放級別
var zoom = map.getZoom(); // 直接返回數字1-20吧好像
獲取可視區(qū)域的經緯度
getBounds()
用法
var bs = map.getBounds(); // 獲取可視區(qū)域
var bssw = bs.getSouthWest(); // bssw.lng, bssw.lat
var bsne = bs.getNorthEast(); // bsne.lng, bsne.lat
添加文本標注label
Label(content: String, opts: LabelOptions)
創(chuàng)建一個文本標注實例。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
},
},
// 初始化地圖觸發(fā)事件
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();
// 地圖縮放事件,每次縮放都要重新觸發(fā)獲取視野內新數據
map.addEventListener('zoomend', function(){
console.log('縮放事件:');
// 事件觸發(fā)優(yōu)化,防止短時間內多次觸發(fā)事件頻繁請求數據,造成服務器壓力,同時也為了用戶體驗
clearTimeout(_this.data.timerMapGetdata);
_this.data.timerMapGetdata = null;
_this.data.timerMapGetdata = setTimeout(function() {
_this.getHeatData();
}, 300);
});
// 地圖拖動事件,每次拖動地圖,地圖的可視區(qū)域都會變化,都要重新觸發(fā)獲取視野內新數據
map.addEventListener("moveend", function(){
console.log('拖動事件:');
// 事件觸發(fā)優(yōu)化,防止短時間內多次觸發(fā)事件頻繁請求數據,造成服務器壓力,同時也為了用戶體驗
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();
// 獲取地圖縮放級別,并且劃分請求級別
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,區(qū)3:country),然后關聯address表(tf_address_region表存儲中國所有的省市區(qū)對應的名字、經緯度,具體表結構和sql文件在最下邊展示與下載)查詢出每個地址對應的經緯度
*/
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*/
當請求級別是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) | 地區(qū)名稱 |
| level | tinyint(4) | 地區(qū)等級 分省市縣區(qū) |
| pid | bigint(10) | 父id |
| longitude | float | 百度坐標,經度 |
| latitude | float | 百度坐標,緯度 |