先上效果图(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: