前言:
最近一直在做数据可视化方面的工作,其中平面可视化没什么难度,毕竟已经有很多成熟的可供使用的框架,比如百度的echart.js,highcharts.js等。还有就是3D可视化了,整体来说难度也还好,通过WEBGL技术一般的可视化效果还是很好实现,如果对于WEBGL光线渲染力不从心的话直接通过three.js来做也是很方便。
其实真正困扰我开发的是可视化项目中一般会存在很多基于地图方面的开发工作,只要是涉及到地图的可视化必然会跟经纬度相关联。如果是单纯获取地图上某个特定城市或者特定点的经纬度这个很好实现,通过 百度地图经纬度拾取系统 便可以很方便的获取到,但是这个系统只能是获取单个点的经纬度,并不能根据地图上的特定轨迹来拾取轨迹对应的经纬度信息。因为在实际开发过程中,地图可视化项目最多的还是在地图上绘制轨迹的需求。举个实际工作中遇到的需求,需要在某城市的地图上绘制出该城市所有地铁线路图,并且在对应站点展示高峰流量。这个需求中特定站点的绘制相对简单,可以通过上述讲的百度地图经纬度拾取系统来获取,但是对应的地铁轨迹的经纬度信息获取就蛋疼了,在网上查询了好久都没能够查询到(可以肯定的是网上肯定有,但是我就是查不到)。
针对上面所讲的困惑,我就有了想自己制作一个工具的想法,这个工具可以根据用户在地图上绘制的轨迹对应的将轨迹的经纬度信息保存并提供给用户,先不说这个想法是否能够帮助到其他人,至少能够满足我工作的需求。
工具原理说明:
1 用户在工具集成地图上绘制轨迹(点轨迹,圆轨迹,线轨迹,多边形轨迹,四边形轨迹);
2 工具内部调用集成地图API获取绘制轨迹的经纬度信息;
3 将集成地图API返回的经纬度信息展示在工具对应的位置中,每次绘制玩一个轨迹就生成一条经纬度信息;
4 用户通过工具提供的查看轨迹详情按钮可以查看经纬度信息并且可以直接复制到自己项目中使用;
5 为了满足项目中一个需求包含多个轨迹的可能性,因此增加了一个合并轨迹经纬度信息的功能。
集成地图选型:
目前用户群体最大的两个地图是百度和阿里旗下的高德地图,经过比对最终还是选择了百度地图,因为百度地图API中有提供鼠标绘制工具DrawingManager,并且使用简单,API文档也比较完善。
工具界面设计:
既然集成地图选择了百度地图,那工具界面也就同百度地图界面,界面效果图具体如下:
工具实现代码说明:
1 dom结构说明:
1.1 main-div 为整体显示框架;
2.2 search-frame 用于存放搜索内容;
2.3 travel-frame 用于存放轨迹;
2.4 setting-frame 用于存放地图皮肤和鼠标绘制工具配置项内容;
2.5 allmap 用于存放百度地图。
1 <div class="main-div"> 2 <div class="search-frame"> 3 <div class="search"> 4 <input class="search-input" placeholder="请输入搜索关键字"> 5 <button class="search-btn"><i class="fa fa-search"></i></button> 6 </div> 7 <div class="search-info" style="max-height: 122px;"></div> 8 </div> 9 <!-- 轨迹 --> 10 <div class="travel-frame"> 11 12 </div> 13 <!-- 配置项 --> 14 <div class="setting-frame"> 15 <div class="setting-content" style=""> 16 <div class="setting-list map-theme"> 17 <p>地图皮肤配置</p> 18 <div class="theme-content clearfix"> 19 20 </div> 21 </div> 22 <div class="setting-list"> 23 <p>地图绘制工具参数配置</p> 24 <div class="list-content"> 25 <label>边线颜色</label> 26 </div> 27 <div class="list-content"> 28 <label>填充颜色</label> 29 </div> 30 <div class="list-content"> 31 <label>边线宽度</label> 32 </div> 33 <div class="list-content"> 34 <label>边线类型</label> 35 </div> 36 </div> 37 <div class="setting-operate"><i class="fa fa-chevron-down"></i></div> 38 </div> 39 <div id="allmap"></div> 40 </div>
2 js代码说明:
2.1 首先在代码中引入百度地图API js文件,鼠标操作工具css文件和js文件,具体引用代码如下;
1 <link rel="stylesheet" href="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.css" /> 2 <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=您在百度申请的key值"></script> 3 <script type="text/javascript" src="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>
2.2 百度地图初始化,地图开启滚轮缩放,默认展示区域为北京。因为实现的工具不需要展示具体点的详情信息,所以需要禁止点击地图的详情弹窗,通过设置enableMapClick = false实现。
1 // 百度地图初始化,其中allmap为页面中需要展示地图的id值 2 var map = new BMap.Map("allmap", { 3 enableMapClick: false 4 }) 5 // 开启滚轮缩放 6 map.enableScrollWheelZoom(true); 7 // 初始化地图默认显示地点为北京 8 var point = new BMap.Point(120.12, 30.16); 9 map.centerAndZoom(point, 13);
2.3 地图初始化完成后,需要根据用户当前所在地对应的进行地图显示位置切换,该操作需要获取位置信息,需要用户授权。
1 // 获取当前用户所在位置信息 2 var geolocation = new BMap.Geolocation(); 3 // 获取定位,如果获取成功则定位到当前位置,定位不成功使用默认的位置 4 geolocation.getCurrentPosition(function (r) { 5 if (this.getStatus() == BMAP_STATUS_SUCCESS) { 6 map.panTo(r.point); 7 map.enableScrollWheelZoom(true); 8 } else { 9 layer.alert(‘failde‘ + this.getStatus()); 10 } 11 }, {enableHighAccuracy: true})
2.4 鼠标绘制工具参数说明及鼠标绘制工具实例化。
1 // 鼠标绘制巩固配置项及说明 2 var styleOptions = { 3 strokeColor: ‘#ff0000‘, // 边线颜色。 4 fillColor: ‘#ff0000’, // 填充颜色,当该参数为空时,圆形/多边形/正方形将没有填充效果。 5 strokeWeight: 1, // 边线的宽度,以像素为单位,默认为1。 6 strokeOpacity: 0.8, // 边线的透明度,取值范围为0~1。 7 fillOpacity: 0.6, // 填充的透明度,取值范围为0~1。 8 strokeStyle: ‘solid‘ // 边线的样式,可选solid和dashed。 9 } 10 // 鼠标绘制工具实例化 11 var drawingManger = new BMapLib.DrawingManager(map, { 12 isOpen: false, // 表示是否开启绘制模式 13 enableDrawingTool: true, // 是否显示工具栏 14 drawingToolOptions: { 15 anchor: BMAP_ANCHOR_TOP_RIGHT, // 工具栏显示在右上角 16 offset: new BMap.Size(5,5) // 工具栏偏移右上角位置 17 }, 18 circleOptions: styleOptions, // 圆的样式 19 polylineOptions: styleOptions, // 线的样式 20 polygonOptions: styleOptions, // 多边形的样式 21 rectangleOptions: styleOptions, // 矩形的样式 22 })
2.5 鼠标绘制工具样式更改和地图皮肤更换。鼠标绘制工具样式更改通过重定义 styleOptions 对应参数来实现,其中边框颜色和填充颜色需要把颜色和透明度分开,地图皮肤切换通过 map.setMapStyle 来实现。
1 // 地图皮肤更改代码 2 $(‘.theme-list‘).click(function () { 3 // 获取地图皮肤类型 4 var themeName = $(this).data(‘type‘); 5 // 将皮肤类型保存到本地 6 localStorage.setItem(‘themeType‘, themeName); 7 // 执行更改皮肤操作 8 map.setMapStyle({ 9 style: themeName 10 }) 11 }) 12 // 鼠标操作工具配置项更改方法 13 function changeOptionFun () { 14 var strokeColorArray = mapOption.borderColor.split(‘,‘); 15 var strokeColor = mapOption.borderColor.indexOf(‘rgba‘) != -1 ? ‘rgb(‘ + [strokeColorArray[0].split(‘(‘)[1],strokeColorArray[1],strokeColorArray[2]].join(‘,‘) + ‘)‘ : mapOption.borderColor; 16 var strokeOpacity = strokeColorArray[3].split(‘)‘)[0]; 17 var fillColor = mapOption.fillColor.indexOf(‘rgba‘) != -1 ? ‘rgb(‘ + [fillColorArray[0].split(‘(‘)[1],fillColorArray[1],fillColorArray[2]].join(‘,‘) + ‘)‘ : mapOption.fillColor; 18 var fillOpacity = fillColorArray[3].split(‘)‘)[0]; 19 // 参数更新 20 styleOptions.strokeColor = strokeColor; 21 styleOptions.fillColor = fillColor; 22 styleOptions.strokeWeight = Number(mapOption.borderWidth); 23 styleOptions.strokeOpacity = Number(mapOption.strokeOpacity ); 24 styleOptions.fillOpacity= Number(mapOption.fillOpacity); 25 styleOptions.strokeStyle= Number(mapOption.borderType); 26 }
2.6 鼠标绘制工具添加轨迹绘制完成方法overlaycomplete,该方法中执行轨迹绘制完成后保存绘制轨迹数据到 markerArray 中,对应的需要更新页面中轨迹弹窗中的数据,轨迹新增完成后需要先输入保存轨迹的名称,然后才能执行以后的步骤,如果不输入名称默认不保存该轨迹。
1 // 鼠标绘制工具监听事件,用于获取绘制结果并同步绘制结果到页面 2 var overlaycomplete = function () { 3 // 需要添加鼠标工具绘制完成之后的逻辑 4 var removeBool = true; 5 selectArray = []; 6 layer.prompt({title: ‘输入轨迹名称,并确认‘, formType: 3}, function(text, index){ 7 layer.close(index); 9 if (e.overlay.ia) { 10 markersArray[new Date().getTime()] = { 11 location: [{ 12 locationInfo: e.overlay.ia, 13 name: text 14 }], 15 name: text, 16 overlay: e.overlay 17 } 18 } else { 19 markersArray[new Date().getTime()] = { 20 location: [{ 21 locationInfo: [e.overlay.point], 22 name: text 23 }], 24 name:text, 25 overlay: e.overlay 26 } 27 } 28 removeBool = false; 29 // 赋值轨迹 30 $(‘.travle-general b‘).html(Object.keys(markersArray).length); 31 // 轨迹循环 32 var html = ‘‘; 33 for (var list in markersArray) { 34 var travelNum = 0; 35 markersArray[list].location.forEach(function (item, index) { 36 travelNum += item.locationInfo.length; 37 }) 38 html += ‘<div class="info-list clearfix">‘ 39 +‘<div class="info-list-l">‘ 40 +‘ <div class="checkbox-bg"></div>‘ 41 +‘ <input class="checkbox" data-key=‘ + list + ‘ type="checkbox" />‘ 42 +‘ <i></i>‘ 43 +‘</div>‘ 44 +‘<div class="info-list-r">‘ 45 +‘ <p class="title">‘ + markersArray[list].name + ‘</p>‘ 46 +‘ <div class="info-frame clearfix">‘ 47 +‘ <p class="info">共有<span>‘ + travelNum + ‘</span>个位置经纬度信息</p>‘ 48 +‘ <button class="travel-info" data-key=‘ + list + ‘>详情</button>‘ 49 +‘ <button class="travel-delete" data-key=‘ + list + ‘>删除</button>‘ 50 +‘ </div>‘ 51 +‘</div>‘ 52 +‘</div>‘; 53 } 54 if (Object.keys(markersArray).length > 1) { 55 html += ‘<div class="combine-btn"><button>合并查看详情</button></div>‘ 56 } 57 $(‘.travle-info‘).html(html);
58 }); 59 } 60 // 鼠标绘制工具添加监听事件 61 drawingManager.addEventListener(‘overlaycomplete‘, overlaycomplete);
2.7 地图搜索功能实现,通过百度地图API中的BMap.LocalSearch来搜索数据,自定义setSearchCompleteCallback方法来实现搜索后页面数据展示的逻辑,搜索数据最多显示十条,需要过滤掉名称一样的数据。
1 // 开启百度地图搜索功能 2 var localSearch = new BMap.LocalSearch(map); 3 //搜索成功后回调函数定义 4 localSearch.setSearchCompleteCallback(function (searchResult) { 5 // 用于存放过滤后的搜索数据 6 var filterSearchResult = []; 7 // 存放搜索数据title值,主要用于过滤重复数据使用 8 var filterSearchResultTitle = []; 9 // 循环处理获取到的数据,过滤title重复的数据 10 searchResult.Ar.forEach(function (item) { 11 if (filterSearchResultTitle.indexOf(item.title) == -1) { 12 filterSearchResultTitle.push(item.title); 13 filterSearchResult.push(item); 14 }
// 此处需要补充代码 15 }) 16 }) 17 // 搜索方法 18 function searchByStationName(keyword) { 19 localSearch.search(keyword); 20 }
2.8 已有轨迹删除方法,轨迹删除成功之后需要在对应的轨迹弹窗中将需要删除的轨迹移除,并且如果地图中有该轨迹也需要调用 map.removeOverlay 方法移除地图中的轨迹。
1 // 删除轨迹事件 2 $(‘.travel-delete‘).on(‘click‘, function () { 3 var element = $(this); 4 var key = element.data(‘key‘); 5 layer.confirm(‘轨迹删除不可恢复,是否删除?‘, { 6 btn: [‘确认‘,‘取消‘], 7 title: ‘删除轨迹‘ 8 }, function(index){ 9 layer.close(index);10 map.removeOverlay(markersArray[key].overlay); 11 delete markersArray[key]; 12 element.parents(‘.info-list‘).remove(); 13 // 重新赋值轨迹 14 $(‘.travle-general b‘).html(Object.keys(markersArray).length); 15 }, function(index){ 16 layer.close(index); 17 }); 18 })
2.9 查看轨迹详情和合并查看详情需要选中至少两条轨迹,点击之后会弹出轨迹详情弹窗,会将选择的需要合并的轨迹中的数据合并到一起展示,并且通过复制粘贴便可以使用。
1 // 合并查看详情事件 2 $(‘.combine-btn‘).off().click(function () { 3 if (selectArray.length < 2) { 4 layer.alert(‘轨迹合并请至少选择两条轨迹‘); 5 return false; 6 } 7 layer.open({ 8 type: 1, 9 title: ‘轨迹经纬度详情(<span style="color:#ff0000;">左边为格式化,右边为未格式化</span>),使用请直接进行复制粘贴操作‘, 10 skin: ‘layui-layer-rim‘, //加上边框 11 area: [‘100%‘, ‘100%‘], //宽高 12 content: infoHtml 13 }); 14 var _markersArray = []; 15 for (var key in markersArray) { 16 if (selectArray.indexOf(parseInt(key)) != -1) { 17 _markersArray.push(markersArray[key].location[0]); 18 } 19 }20 $(‘#code-content-str‘).html(JSON.stringify(_markersArray)); 21 aceInitFun(formatJson(_markersArray).trim()); 22 })
工具项目预览及体验地址:
基于百度地图API实现在地图上绘制轨迹并拾取轨迹对应经纬度工具
后话:
由于平时只有下班时间才能够开发,所以开发这个工具前前后后花费了有将近两周的时间,中间还有个几次通宵的情况,如果工具对您的工作有帮助,或者您对该工具有什么好的建议欢迎留言。