需要在一個新的地方租房子或者買房子的時候,一個重要的考慮因素就是交通是否便利,我在租房子的時候就很想知道從我周邊的公交車站,能夠不換乘到達哪些地方,我當時有這個需求之后上網搜,並沒有找到類似的功能,而且麻煩的是,百度地圖最基本的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').onclick=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的地方,歡迎大家指正!