先上效果圖(PS:數據爲mock數據):
1.一級地圖
2.二級地圖
3.三級地圖
、
4.四級地圖
概述
效果圖是不是有點多呀,不過能看到這裏的估計這些效果圖就是你們想要的效果啦,好,下面就來介紹一下
1. 功能概述
1.展示所有省份門店彙總信息,且門店數越大,標記越深,標記越大(熱力效果)
2.懸停標記,展示標記對應的省市區縣的詳細信息,並顯示該省市區縣的行政區劃
3.點擊省份標記進入市級地圖,顯示該省的所有市級熱力標記信息,並顯示該省的行政區劃,懸停某個市,顯示該市的詳細信息,並顯示該市的行政區劃
4.點擊市級標記進入區縣級地圖,顯示該市的所有區縣級熱力標記信息,並顯示該區縣的行政區劃,懸停某個區縣,顯示該區縣的詳細信息,並顯示該區縣的行政區劃
5.點擊區縣級標記進入街道鄉鎮地圖,顯示該區縣的所有門店詳細信息,包括地理位置
6.鼠標滾動縮放也能達到同樣的效果
7.在市級及區縣級地圖上拖動地圖,可以動態跨省跨市顯示數據
2. 地圖選擇
百度地圖 JavaScript API
百度地圖開發者平臺:http://lbsyun.baidu.com/index.php?title=jspopular
3. 涉及到地圖API類
Map:地圖核心類
Point:點基礎類
Marker:標記類
Label:標籤類
Polygon:自定義覆蓋物類
Convertor:用於將其他座標系的座標轉換爲百度座標
Size:以像素表示一個矩形區域的大小
Icon:標註覆蓋物所使用的圖標
InfoWindow:地圖上包含信息的窗口
Geocoder:獲取用戶的地址解析
Boundary:一個行政區域的邊界
具體類及方法參考:http://lbsyun.baidu.com/cms/jsapi/class/jsapi_reference.html#a0b0
具體demo參考:http://lbsyun.baidu.com/jsdemo.htm?a#a1_2
其他的開源庫參考:http://lbsyun.baidu.com/index.php?title=open/library
4. 技術方案
初始進入門店區域分佈頁面,初始化地圖,中心定到北京,縮放zoom=5,設置zoom屬於(5,17),初始化地圖縮放監聽事件(此方法監聽地圖縮放級數變化),初始化map拖動動態加載數據監聽事件,js從後臺抓取所有以省份爲整體的門店信息數據,然後把標記(帶上門店數量)和標籤渲染到地圖的相關省上
當縮放zoom時:
1 .當zoom∈(5,8),地圖標記不變化。
2. 當zoom = 8 時,清除所有省份標記,js調用map. getCenter()來獲取地圖中心座標,在通過Geocoder逆向獲取中心地址省份名稱,js從後臺拿到該省的以市級爲整體的門店數量數據,然後把標記和標籤渲染到地圖的相關市上。
3. 當zoom∈(8,11),地圖標記不變化。
4. 當zoom = 11 時,清除所有市級標記,js調用map. getCenter()來獲取地圖中心座標,在通過Geocoder逆向獲取中心地址市級名稱,js從後臺拿到該市的所有區縣門店座標信息,然後把標記和標籤渲染到地圖的相關區縣上。
5. 當zoom = 12 時,清除所有區縣級標記,js調用map. getCenter()來獲取地圖中心座標,在通過Geocoder逆向獲取中心地址區縣級名稱,js從後臺拿到該區縣級的所有鄉鎮街道門店座標信息,渲染到地圖上。
當zoom∈(14,18),地圖標記不變化。
當點擊標記時:
1. 當省份標記被點擊時,清除所有省份標記,js從後臺拿到該省的以市級爲整體的門店數量數據,然後把標記(帶上門店數量)渲染到地圖的相關市上,zoom調整到8。
2. 當市級標記被點擊時,清除所有市級標記,js從後臺拿到該市的所有區縣門店座標信息,創建位置標記對象,然後把標記渲染到地圖上,zoom調整到11。
3. 當區縣級標記被點擊時,清除所有區縣級標記,js從後臺拿到該區縣的所有鄉鎮街道門店座標信息,創建位置標記對象,然後把標記渲染到地圖上,zoom調整到12。
當拖動地圖時:
當zoom∈(8,11),js獲取拖動結束後的鼠標的座標,在通過Geocoder逆向獲取中心地址市級名稱,js從後臺拿到該省的以市級爲整體的門店數量數據,然後把標記和標籤渲染到地圖的相關市上。
當zoom = 11時,js獲取拖動結束後的鼠標的座標,在通過Geocoder逆向獲取中心地址市級名稱,js從後臺拿到該市的所有區縣門店座標信息,然後把標記和標籤渲染到地圖的相關區縣上。
5. 需要後臺提供的接口方法
1.getProvincesList 獲取所有省份信息
2.getCitiesByProvince 根據某個省份獲取該省所有市級信息
3.getDistrictsByCity 根據某市獲取該市所有區縣級信息
4.getStreetsByDistrict 根據區縣獲取該區縣所有鄉鎮街道信息
HTML代碼詳解
1.dom結構
so easy
<div id="container"></div>
2.js引入
這裏用到了loading插件,詳見:https://github.com/imingyu/jquery.mloading
<script charset="utf-8" src="jquery-mloading/jquery.mloading.js"></script>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=your key"></script>
JS代碼詳解
1. 常量定義
//三級(市,區縣,街道)直轄市,除此之外的省都是四級(省,市,區縣,街道)
const municipality = ["北京市","天津市","上海市","重慶市","香港特別行政區","澳門特別行政區"];
//最小級zoom,省級zoom
const ZOOM_PROVINCE_LEVEL = 5;
//市級zoom
const ZOOM_CITY_LEVEL = 8;
//區縣級zoom
const ZOOM_DISTRICT_LEVEL = 11;
//街道級zoom
const ZOOM_STREET_LEVEL = 13;
//縮放最大級別 zoom
const ZOOM_MAX_LEVEL = 17;
//當前zoom級別
let current_zoom = ZOOM_PROVINCE_LEVEL;
//省市區縣級別
const LEVEL_PROVINCE = 1;
const LEVEL_CITY = 2;
const LEVEL_DISTRICT = 3;
const LEVEL_STREET = 4;
//事件控制變量
let marker_mouseover_flag = true;
2. 清除覆蓋物與設置所有覆蓋物爲可清除
因爲行政區劃在市級區縣級時,外框行政區劃不清除,而內部行政區劃鼠標懸停才顯示,而且行政區劃與marker,label同屬於覆蓋物,所以需要按需求清除需要清除的覆蓋物
//清除覆蓋物
function removeOverlay(map) {
map.clearOverlays();
}
//設置所有覆蓋物爲可清除
function setAllOverLayClear(map){
let allOverlay = map.getOverlays();
for(let overlay of allOverlay){
overlay.enableMassClear();
}
}
3.添加行政區邊框
方法用於根據省市區縣名稱獲取行政區劃並且設置行政區邊框的樣式
/**
* 添加行政區邊框
* @param map
* @param args 行政區域名稱數組,以百度地圖標準行政區域名稱爲主
* @param isAlwaysShow 是否持續顯示,默認爲false
* @param strokeColor 邊框線條顏色,填入顏色編碼,默認爲#ff9a39
* @param fillColor 覆蓋物背景色,填入顏色編碼,默認爲無色透明
*/
function getBoundaryAndColor(map, arg,isAlwaysShow,fillColor,strokeWeight,strokeColor) {
strokeColor = strokeColor || "#ff4700";
if(fillColor == null){
fillColor = "#61dcff";
}
isAlwaysShow = isAlwaysShow || false;
strokeWeight = strokeWeight || 2;
//通過行政區域名稱獲取行政區劃
let bdary = new BMap.Boundary();
bdary.get(arg, function (rs) {
let count = rs.boundaries.length;
if (count === 0) {
return;
}
let ply = new BMap.Polygon(rs.boundaries[0],
{ strokeColor: strokeColor,fillColor: fillColor,strokeWeight:strokeWeight});
if(isAlwaysShow){
ply.disableMassClear();
}
map.addOverlay(ply);
$("#container").mLoading("hide");
marker_mouseover_flag = true;
});
}
4.渲染marker與label
這是重點方法,主要渲染marker與label,方法裏有一些爲業務代碼,大家可根據自己的實際需求刪減代碼
省市區縣的marker爲自定義marker,marker的icon爲m0-m9.png的十套藍色漸進圖標,附件會提供下載
/**
* 渲染標記和標籤(重點方法)
* @param map map地圖對象
* @param level 地圖級別 1:省級 2:市級 3:區縣級
* @param data json數據
* @param markerClickCallback 標記點擊事件方法回調函數
* @param center 中心座標
*/
function renderMarkersAndLabels(map, level, data, markerClickCallback, center, isClick) {
//設置所有覆蓋物均可清除
setAllOverLayClear(map);
//清除所有覆蓋物
removeOverlay(map);
//點擊時需要設置zoom級別與定位中心,並將current_zoom置爲當前zoom
if(level != LEVEL_PROVINCE && center){
if(level == LEVEL_CITY && isClick){
map.setZoom(ZOOM_CITY_LEVEL);
current_zoom = ZOOM_CITY_LEVEL;
map.panTo(center);
}
else if(level == LEVEL_DISTRICT && isClick){
map.setZoom(ZOOM_DISTRICT_LEVEL);
current_zoom = ZOOM_DISTRICT_LEVEL;
map.panTo(center);
}
else if(level == LEVEL_STREET && isClick){
map.setZoom(ZOOM_STREET_LEVEL);
current_zoom = ZOOM_STREET_LEVEL;
map.panTo(center);
}
}
//1,2,3級地圖省略道路,4級地圖展示道路
if(level == LEVEL_STREET){
map.setMapStyle({});
}
else{
map.setMapStyle({
styleJson:[
{
"featureType": "road",
"elementType": "all",
"stylers": {
"color": "#ffffff",
"visibility": "off"
}
}
]
});
}
for (let i = 0; i < data.length; i++) {
let d = data[i];
if (d.position) {
let x = d.position.split(",")[0];
let y = d.position.split(",")[1];
let TXPointArr = [new BMap.Point(y,x)];
//騰訊座標系 轉化爲 百度座標系
new BMap.Convertor().translate(TXPointArr, 3, 5, function (data) {
//標記顯示文本
let labelContent;
//懸停信息框展示文本
let windowInfoContent;
//獲取圖片的序號 對應m0-m9.png
let img_num;
//圖片的偏移位置大小
let size;
if (level == LEVEL_PROVINCE) {
windowInfoContent = "省份:" + d.provinceName + "<br>排名:" + d.rank + "<br>省銷售額:" + d.sale + "<br>省門店數量:" + d.shopNum;
labelContent = d.shopNum;
//3.6保證35個省能分佈到0-9的漸進圖片裏,每張圖有兩個級別,偏移位置根據圖片大小而定的
img_num = Math.floor(d.rank/3.6);
size = new BMap.Size(-10 - ((10 - img_num) * 0.6),-4 + (10 - img_num) * 0.2);
}
else if (level == LEVEL_CITY) {
windowInfoContent = "城市:" + d.cityName + "<br>排名:" + d.rank + "<br>市銷售額:" + d.sale + "<br>市門店數量:" + d.shopNum;
labelContent = d.shopNum;
//2.2保證15個市能分佈到0-7的漸進圖片裏,每張圖有兩個級別,偏移位置根據圖片大小而定的
img_num = Math.floor(d.rank/2.2);
size = new BMap.Size(-8- ((10 - img_num) * 0.4),-4 + (10 - img_num) * 0.2);
}
else if (level == LEVEL_DISTRICT) {
windowInfoContent = "區縣:" + d.districtName + "<br>排名:" + d.rank + "<br>區縣銷售額:" + d.sale + "<br>區縣門店數量:" + d.shopNum;
labelContent = d.shopNum;
//2.2保證15個區縣能分佈到0-7的漸進圖片裏,每張圖有兩個級別,偏移位置根據圖片大小而定的
img_num = Math.floor(d.rank/2.2);
size = new BMap.Size(-6- ((10 - img_num) * 0.4),-5 + (10 - img_num) * 0.2);
}
else if (level == LEVEL_STREET) {
windowInfoContent = "門店名稱" + d.shopName + "<br>銷售額:" + d.shopSale;
labelContent = d.shopName;
}
let marker;
let label;
//1,2,3級地圖選用自定義marker,4級地圖選用默認地圖,且label也不一樣
if(level != LEVEL_STREET){
//創建自定義icon以及自定義icon大小
let myIcon = new BMap.Icon("../static/slice/mendianfenbu/m"+ img_num + ".png", new BMap.Size(50+(10-img_num)*2,50+(10-img_num)*2));
marker = new BMap.Marker(data.points[0],{icon:myIcon});
let opts = {
position: data.points[0], // 指定文本標註所在的地理位置
offset:size
};
label = new BMap.Label(labelContent,opts);
label.setStyle({
color: "white",
fontSize: "4px",
height: "auto",
lineHeight: "6px",
fontFamily: "微軟雅黑",
backgroundColor: 'none',
maxWidth: 'none',
border: 'none',
'font-weight':'bold'
});
}else{
marker = new BMap.Marker(data.points[0]);
let opts = {
position: data.points[0] // 指定文本標註所在的地理位置
};
label = new BMap.Label(labelContent,opts);
label.setStyle({
color: "black",
fontSize: "12px",
height: "auto",
lineHeight: "15px",
fontFamily: "微軟雅黑",
backgroundColor: 'white',
maxWidth: 'none'
});
}
//禁止覆蓋物在map.clearOverlays方法中被清除,與行政區劃覆蓋物區別
marker.disableMassClear();
label.disableMassClear();
//註冊標記鼠標懸停事件
marker.addEventListener('mouseover',function (e) {
if(marker_mouseover_flag){
marker_mouseover_flag = false;
removeOverlay(map);
setTimeout(function(){
label.setContent(labelContent);
let opts = {
width : 50, // 信息窗口寬度
height: 100, // 信息窗口高度
offset : new BMap.Size(20,-30), //信息窗口偏移
};
// 創建信息窗口對象
let infoWindow = new BMap.InfoWindow(windowInfoContent, opts);
//開啓信息窗口
map.openInfoWindow(infoWindow,e.target.point);
if(level == LEVEL_PROVINCE){
getBoundaryAndColor(map,d.provinceName);
}
else if(level == LEVEL_CITY){
getBoundaryAndColor(map,d.cityName);
}
else if(level == LEVEL_DISTRICT){
getBoundaryAndColor(map,d.districtName);
}
marker_mouseover_flag = true;
},300);
}
});
marker.addEventListener('mouseout',function () {
if(marker_mouseover_flag){
removeOverlay(map);
}
});
//初始化標記點擊事件
if (markerClickCallback) {
markerClickCallback(map, marker,level);
}
map.addOverlay(marker);
map.addOverlay(label);
});
}
}
}
5.設置標記點擊事件
/**
* 設置區縣級標記點擊事件
* @param marker 標記對象
* @param markerLevel 標記所在的地圖級別
*/
function bindMarkersEvent(map, marker, markerLevel) {
marker.addEventListener('click', function () {
let _this = $(this);
let center = new BMap.Point(_this[0].IA.lng, _this[0].IA.lat);
//顯示loading組件
$("#container").mLoading("show");
if(markerLevel == LEVEL_PROVINCE){
renderLevelTwoMap(map,center,true);
}
else if(markerLevel == LEVEL_CITY){
renderLevelThreeMap(map,center,true);
}
else if(markerLevel == LEVEL_DISTRICT){
renderLevelFourMap(map,center,true);
}
});
}
6.四級地圖渲染方法
下方的permissionService的方法爲後端接口方法,即序言裏提到的後端需提供的方法
/**
* 獲得所有省份信息並渲染省級級(一級)地圖
* @param map map地圖對象
*/
function renderLevelOneMap(map) {
permissionService.getProvincesList(params).then((data) => {
renderMarkersAndLabels(map , LEVEL_PROVINCE , data , bindMarkersEvent);
});
}
//逆向解析省級座標並渲染該省的市級(二級)地圖
function renderLevelTwoMap(map,center,isClick){
center = center || map.getCenter();
isClick = isClick || false;
let geoc = new BMap.Geocoder();
geoc.getLocation(center, function (rs) {
//獲得省份
params.province = rs.addressComponents.province;
//判斷是否屬於直轄市
if($.inArray(params.province , municipality) == -1){
//調用後臺接口獲取城市數據cityData
permissionService.getCitiesByProvince(params).then((cityData) => {
renderMarkersAndLabels(map, LEVEL_CITY, cityData, bindMarkersEvent,center,isClick);
getBoundaryAndColor(map,params.province,true,"",4);
});
}
else {
params.city = rs.addressComponents.city;
//調用後臺接口獲取區縣數據DistrictData
permissionService.getDistrictsByCity(params).then((DistrictData) => {
renderMarkersAndLabels(map, LEVEL_DISTRICT, DistrictData, bindMarkersEvent,center,isClick);
getBoundaryAndColor(map,params.city,true,"",4);
});
}
});
}
//逆向解析市級座標並渲染該市的區縣級(三級)地圖
function renderLevelThreeMap(map,center,isClick){
center = center || map.getCenter();
isClick = isClick || false;
let geoc = new BMap.Geocoder();
geoc.getLocation(center, function (rs) {
//獲得市級
params.city = rs.addressComponents.city;
//調用後臺接口獲取區縣數據DistrictData
permissionService.getDistrictsByCity(params).then((DistrictData) => {
renderMarkersAndLabels(map, LEVEL_DISTRICT, DistrictData, bindMarkersEvent,center,isClick);
getBoundaryAndColor(map,params.city,true,"",4);
});
});
}
//逆向解析區縣級座標並渲染該區縣的鄉鎮街道級(四級)地圖
function renderLevelFourMap(map,center,isClick){
center = center || map.getCenter();
isClick = isClick || false;
let geoc = new BMap.Geocoder();
geoc.getLocation(center, function (rs) {
//獲得區縣
params.district = rs.addressComponents.district;
//調用後臺接口獲取鄉鎮街道數據streetData
permissionService.getStreetsByDistrict(params).then((streetData) => {
renderMarkersAndLabels(map, LEVEL_STREET, streetData, null,center,isClick);
getBoundaryAndColor(map,params.district,true,"",4);
});
});
}
7.zoom監聽事件
坑爹的百度沒有提供地圖縮放監聽事件,所以只能自定義滾輪事件,而且chrome與firefox兩大瀏覽器的滾輪事件定義不一樣,需要單獨設置,這裏在zoom監聽時,是禁止掉了地圖滾輪,監聽滾輪向上向下滾,向上滾動,讓zoom+1,向下滾動,讓zoom-1,如果不禁止地圖滾動,那麼大幅滾動時,地圖可能會一下同時縮放好幾級,這樣會造成四級顯示問題。
/**
* zoom切換監聽事件
* @param map map地圖對象
*/
function bindZoomSwithListener(map) {
let flag = true;
const scrollFunc = (e)=>{
if(flag){
flag = false;
setTimeout(function () {
//是否放大
let isUp = false;
//IE/Opera/Chrome 的滾輪判斷爲wheelDelta = +- 120 ,firefox的滾輪判斷爲detail = +- 3
//+120爲放大,-120爲縮小 -3爲放大,+3爲縮小
if(e.wheelDelta){
if(e.wheelDelta == 120){
isUp = true;
map.zoomIn();
}
else{
map.zoomOut();
}
}else if(e.detail){//Firefox
if(e.detail == -3){
isUp = true;
map.zoomIn();
}
else{
map.zoomOut();
}
}
//從一級跳二級
if(isUp && current_zoom == ZOOM_CITY_LEVEL - 1){
renderLevelTwoMap(map);
}
//從二級跳一級
else if(!isUp && current_zoom == ZOOM_CITY_LEVEL){
renderLevelOneMap(map);
}
//從二級跳三級
else if(isUp && current_zoom == ZOOM_DISTRICT_LEVEL - 1){
renderLevelThreeMap(map);
}
//從三級跳二級
else if(!isUp && current_zoom == ZOOM_DISTRICT_LEVEL){
renderLevelTwoMap(map);
}
//從三級跳四級
else if(isUp && current_zoom == ZOOM_STREET_LEVEL - 1){
renderLevelFourMap(map);
}
//從四級跳三級
else if(!isUp && current_zoom == ZOOM_STREET_LEVEL){
renderLevelThreeMap(map);
}
else{
map.removeOverlay();
}
if(isUp){
current_zoom = map.getZoom();
}else{
current_zoom = map.getZoom();
}
flag = true;
},300);
}
};
/*註冊事件*/
let userAgent = navigator.userAgent;
let isFF = userAgent.indexOf("Firefox") > -1; //判斷是否Firefox瀏覽器
if(isFF){
document.addEventListener('DOMMouseScroll',scrollFunc,false);
}else{
window.onmousewheel=document.onmousewheel=scrollFunc;//IE/Opera/Chrome
}
}
8.鼠標拖動地圖事件
這裏增加了市級,區縣級的鼠標拖動事件,爲了實現跨省,跨市,動態獲取數據
/**
* 鼠標拖動地圖觸發事件
* @param map
*/
function bindProvinceAndCitySwitchListener(map) {
let mouseFlag = true;
map.addEventListener('dragend',function () {
if(mouseFlag){
mouseFlag = false;
setTimeout(function () {
let zoom = map.getZoom();
//市級
if(zoom >= ZOOM_CITY_LEVEL && zoom < ZOOM_DISTRICT_LEVEL){
$("#container").mLoading("show");
renderLevelTwoMap(map);
}
//區縣級
else if(zoom >= ZOOM_DISTRICT_LEVEL && zoom < ZOOM_STREET_LEVEL){
$("#container").mLoading("show");
renderLevelThreeMap(map);
}
mouseFlag = true;
},200);
}
});
}
9.地圖初始化
哈哈,地圖最開始的初始化,放在最後說
/**
* 初始化地圖
*/
//顯示loading組件
$("#container").mLoading("show");
let point = new BMap.Point(116.404, 39.915);
//初始化地圖
let map = new BMap.Map("container");
map.centerAndZoom(point , current_zoom); //創建中心點與zoom縮放級別
map.setMinZoom(ZOOM_PROVINCE_LEVEL); //最小zoom縮放級別
map.setMaxZoom(ZOOM_MAX_LEVEL); //最大zoom縮放級別
map.disableScrollWheelZoom(); //關閉鼠標滾輪縮放,自定義滾輪事件,調用map.zoomIn()與map.zoomOut()來控制縮放,避免直接使用而導致一次放大或縮小几級導致數據加載問題
bindZoomSwithListener(map); //初始化zoom縮放監聽事件
bindProvinceAndCitySwitchListener(map); //初始化map拖動監聽事件
renderLevelOneMap(map);
附件:m0-m9的png圖片:
m0.png:
m1.png:
m2.png:
m3.png:
m4.png:
m5.png:
m6.png:
m7.png:
m8.png:
m9.png: