需要在一个新的地方租房子或者买房子的时候,一个重要的考虑因素就是交通是否便利,我在租房子的时候就很想知道从我周边的公交车站,能够不换乘到达哪些地方,我当时有这个需求之后上网搜,并没有找到类似的功能,而且麻烦的是,百度地图最基本的API也没有同时显示多条线路的接口,所以只能借助百度地图的覆盖层来画折线,比较麻烦。于是就用百度地图的API简单实现了一个。效果图如下:
在线的演示请点击本链接(在线演示链接已失效,这里下载源码)。简单说一下功能。在左侧指定公交车站名和城市名,点击查询后出现符合搜索条件的地点列表及经过的公交线路(包括地铁)简介,点击某一项之后,右侧地图定位到该公交车站,并以随机的颜色表示经过该公交站的公交线路,点击沿途各站,在弹出窗中显示该站的名字和经过该站的公交车列表。
下面介绍一下我的实现过程,首先是HTML简单的写一下上面的布局(这是最简单的布局,我的演示链接里面用bootstrap美化了一下):
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> body, html{width: 100%;height: 100%;margin:0;font-family:"微软雅黑";} #l-map{height:500px;width:75%;float:left;} style> <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=在百度开发者中心申请的key">script> <title>公交/地铁线路查询title> head> <body> <div style="float:left;width:25%;display:inline;"> <div id="r-query"> 请输入公交站名,如:“西单”:<br/> <input type="text" id="keyword" value="西单"/><br/> 请输入城市名,如北京(仅作参考,如搜索泉城广场时,北京没有这个地址,一样会找到济南,但如果北京也有,就不会找济南了):<br/> <input type="text" id="location" value="北京"/><button id="query">查询button> div> <div id="l-result">div> div> <div id="l-map">div> body> html>
这一步最主要就是要申请一个百度开发者中心的key,申请地址在:http://developer.baidu.com/map/index.php?title=jspopular,通过左侧的“获取密钥”来申请key。
下面是js代码,首先我定义了几个全局变量:
var map = new BMap.Map("l-map"); var busStationIcon = new BMap.Icon("busstation_marker.png", new BMap.Size(10,10)); var busStationHover = new BMap.Icon("busstation_marker_hover.png", new BMap.Size(10,10)); var infoWindow = new BMap.InfoWindow(""); var currentLocation; map.centerAndZoom(new BMap.Point(116.404, 39.915), 15); map.enableScrollWheelZoom(); var activePolyline; // hover bus line var stationList = Array(); // hover bus line stations var currentPolyline; // hovered origin line
map是地图对象,当鼠标滑过某条公交线路的时候,该线路变为蓝色,并显示沿途各站,activePolyline就是这条蓝色的折线,处于active状态的线路沿途各站信息存在stationList中,沿途各站平时用busStationIcon标识,鼠标滑过的时候用busStationHover,点击该图标弹出信息窗infoWindow来显示该站的名字和经过它的公交线路。
我没有用其它的js库,就是用原生的js来写的,先来看点击查询按钮后的响应事件:
document.getElementById('query').οnclick=function(){ map.clearOverlays(); document.getElementById("l-result").innerHTML = ""; new BMap.LocalSearch(document.getElementById('location').value, { onSearchComplete: searchComplete }).search(document.getElementById('keyword').value); };
第一步清空地图上所有的覆盖物,这是考虑到再次点击查询的时候要把前一次地图上的查询结果清除掉,第二步是把显示符合条件站点的面板清空,也就是示例图中的左侧面板清空,第三步就是发起一个LocalSearch请求,第一个参数是搜索范围,也就是范例里的“北京”,第二个option参数里面,指定了结果返回后的回调函数searchComplete:
function searchComplete(result) { var resultPanel = document.getElementById("l-result"); for (var i = 0 ; i < result.getCurrentNumPois() ; i++) { var poi = result.getPoi(i); if (poi.type == BMAP_POI_TYPE_NORMAL) { continue; } var link = document.createElement('a'); link.setAttribute('href', 'javascript:void(0)'); link.poi = poi; link.onclick=function(){ map.clearOverlays(); currentLocation = this.poi.province; var marker = new BMap.Marker(this.poi.point); map.addOverlay(marker); map.panTo(this.poi.point); var busNames = this.poi.address.split(';'); for (i = 0 ; i < busNames.length ; i++) { busutil.getBusList(busNames[i]); } }; link.innerText = poi.title + " (" + poi.province + "): " + poi.tags + " : " + poi.address; resultPanel.appendChild(link); resultPanel.appendChild(document.createElement('br')); } }
遍历搜索结果,因为搜西单出来的可能不止一个站,可能还有西单东站西站南站北站,针对每一个符合公交条件的结果项,都创建一个a标签,所为符合条件,是指这个查询结果表示的是一个公交车站或地铁站,如果是普通地点BMAP_POI_TYPE_NORMAL,就不符合条件。给这个a标签一个poi属性,记录自己所表示的位置信息,当这个链接被点击的时候,清空地图的覆盖物,在该车站的位置创建marker,对于公交站或地铁站来说,address属性就是分号分隔的公交车列表,根据这个列表去查询每一条公交线路的信息,最后给这个a标签赋予显示的文字,我这里显示的是地点名称+省份+地点标签+公交信息。
这里比较重要的就是busutil查询公交线路的实现,百度地图的BusLineSearch对象有两个方法,一个叫做getBusList,它的参数是一个公交线名字,比如333路,返回匹配的公交,包括上行下行的等等,另一个方法叫做getBusLine,它的参数是一个BusListItem对象,也就是getBusList结果中的一项,下面看一下这个方法的实现:
var busutil = new BMap.BusLineSearch(map,{ onGetBusListComplete: function(buslist) { busutil.getBusLine(buslist.getBusListItem(0)); }, onGetBusLineComplete: function(busline) { var color = "#" + Math.floor(Math.random() * 65535 * 256).toString(16); var polyline = new BMap.Polyline(busline.getPath(), {strokeColor:color, strokeWeight:5, strokeOpacity:0.7}); map.addOverlay(polyline); if (isMobile()) { polyline.addEventListener("click", function(evt){ showPolyline(evt, polyline, busline); }); } else { polyline.addEventListener("mouseover", function(evt){ showPolyline(evt, polyline, busline); }); } } });
第一个是得到公交列表的回调函数,比如我搜333路,可能得到两个公交,分别是333上行线路和333下行线路,这里直接取列表的第一项去获取线路详情(getBusLine),看到这里可能会有疑问,万一还有个“333路特别线”怎么办,这里说明一下,我这个代码并不严谨,假设所有的搜索都不会有歧义。第二个函数onGetBusLineComplete就是获取到某条线路详情之后的回调方法,首先为这条线随机生成一个颜色(DEMO上代码已经作了修改,RGB三色都在0-127之间抽取,避免浅颜色的线出现),根据线路详情中的公交站点列表创建折线。这里我做了一个判断,如果是非移动设备,当鼠标滑过某一条线路的时候就把那条线路激活,激活状态的线路变蓝,并显示沿途站点。在移动设备上没有鼠标滑过事件,所以用click事件来触发,但是很奇怪,真正在移动设备上看的时候,鼠标点击无效,这个问题我还不知道是为什么,大家如果发现了问题欢迎指正!
关于把鼠标滑过的线路激活该怎么实现,我并没有去修改原来那条折线,而是在那条折线之上覆盖了一个新的:
function showPolyline(evt, polyline, busline) { if (evt.target == currentPolyline) { return; } currentPolyline = polyline; map.removeOverlay(activePolyline); removeCircles(); activePolyline = new BMap.Polyline(busline.getPath(), {strokeColor:"#3333FF", strokeWeight:5, strokeOpacity:0.9}); map.addOverlay(activePolyline); for (var i = 0 ; i < busline.getNumBusStations() ; i++) { var busStation = busline.getBusStation(i); addCircle(busline, busStation); } }
先判断如果要激活的线路已经是激活状态,那么什么也不做,否则把原来的蓝色线路删除,并根据刚刚被滑过的线路创建新的折线,并把各个站点作为marker添加到激活线路上,怎么添加marker的代码我就不粘贴了,看一下当marker被点击的时候做了什么,我们光知道这里有个站还不够,肯定要知道这是哪一路公交,最好还能知道有没有我熟悉的公交也经过这里:
marker.addEventListener("click", function(){ infoWindow.setTitle(busStation.name + " (" + busline.name + ")"); infoWindow.setContent(""); marker.openInfoWindow(infoWindow); new BMap.LocalSearch(currentLocation, { onSearchComplete: function(result) { var poi; for (var i = 0 ; i < result.getCurrentNumPois() ; i++) { poi = result.getPoi(i); if (poi.type != BMAP_POI_TYPE_NORMAL && poi.title == busStation.name) { infoWindow.setContent(poi.address); break; } } } }).search(busStation.name); });
当某一个公交站被点击的时候,先弹出信息窗,把当前激活的是哪一个公交显示出来,同时发起查询请求,查一下还有哪些公交经过这里,这里也是假设结果列表的第一项一定是准确的,也就是假设我搜索“菊园”,第一个结果项肯定是“菊园”而不是“菊园东站”,然后把路过这里的所有公交显示出来。
这样这个功能就完成了,有bug的地方,欢迎大家指正!