話不多說,先掛最后的數據結果,如果這是你想要的,我們再接着看:
公交線路坐標數據&公交站點坐標數據
正文開始:
前期數據准備:獲取城市所有公交線路名稱
使用python爬取,結果如下,代碼參考:https://www.cnblogs.com/Qiuzhiyu/p/12183140.html
需要准備的js包:
<!--用於坐標系轉換的js包 詳見github:https://github.com/hujiulong/gcoord --> (非必須) <script src="https://unpkg.com/gcoord/dist/gcoord.js"></script> <!-- jquery --> <script src='jquery-1.8.3.js'></script>
<!-- 百度地圖API -->
<script type="text/javascript" src="http://api.map.baidu.com/api?v=1.2"></script> <!-- js-xlsx包 使用以及獲取方法見:https://www.cnblogs.com/liuxianan/p/js-excel.html --> (非必須,如果不需要導出數據到本地)
<script src ='xlsx.full.min.js'></script> <script src ='xlsx.core.min.js'></script> <script>
對爬取的數據進行處理:
/*將爬取的廣州公交信息進行轉換*/ var line_data_list = [] var line_name_list = [] //獲取公交線路名稱 $.ajax( { url:encodeURI('guangzhou.txt'), //提前爬取的數據 async: false, success: function (guangzhou) { row_split = guangzhou.split('\n') for(var i in row_split){ line_data_list.push(JSON.parse(row_split[i])) var len = JSON.parse(row_split[i]).線路名稱[0].length line_data_list[i].線路名稱 = JSON.parse(row_split[i]).線路名稱[0].substring(2,len-5) //’廣州10路公交車路線‘->’10路‘ } for(var i in line_data_list){ line_name_list.push(line_data_list[i].線路名稱) } } } )
處理結果:
line_name_list =["10路", "11路", "12路", "14路", "15路", "19路", "101路", "102路",……]
接下來非常簡單,只需要進行公交列表查詢,這一步執行會激發后續一系列操作,獲取我們想要的數據。
// 數據導入數組開始 //slice for (var i in line_name_list) { busline.getBusList(line_name_list[i]); console.log(i) } console.log("done")
但要搞懂發生了什么,還需要理解接下來的其他部分:
首先,實例化百度地圖,用於頁面上的單個線路的展示,當然如果不想展示的話,這一塊可以省略:
/*獲取百度地圖實例*/ var map = new BMap.Map("container"); map.centerAndZoom(new BMap.Point(113.315224, 23.181452), 12); //廣州市
其次,創建百度地圖公交信息獲取類:
在這一部分,我們設置了公交列表查詢后的回調函數,公交列表查詢后的回調函數中又進行了公交線路的查詢,而公交線路的回調函數中進行了對公交線路數據的組織,這一系列操作最終使我們得到存儲了公交線路信息的對象數組。
// 公交信息獲取類 var busline = new BMap.BusLineSearch("廣州",{ // 展示獲取的公交線路,自動生成面板到id=results的element上,不必須使用 renderOptions:{map:map,panel:"results"}, // 設置公交列表查詢后的回調函數,注意與公交線路查詢區分:
//公交列表存儲多個具體的公交對象,公交對象可理解為公交線路。 onGetBusListComplete: function(result){ if(result) {
/*獲取查詢出的公交列表中的對象 0代表上行 1代表下行,實際上還可能存在getBusListItem(2)、getBusListItem(3)等等, 代表的是模糊查詢的結果,例如查詢 10路 時,getBusListItem(0)、getBusListItem(1)返回的是10路的上下行的公交線路, 而getBusListItem(2)、getBusListItem(3)返回的是b10路,具體可參考代碼塊后的貼圖*/
let up_Line = result.getBusListItem(0); //獲取查詢出的公交列表中的第一個對象,即上行線路 let down_Line = result.getBusListItem(1); //獲取查詢出的公交列表中的第一個對象,即下行線路 if(typeof up_Line === "object"){ //判斷查詢結果是否存在 busline.getBusLine(up_Line); //執行公交線路查詢 }else{ console.log("查無此公交:"+result.keyword) //在控制台輸出無法查詢的線路名稱,result.keyword 即在查詢時輸入的線路名 } if(typeof down_Line === "object"){ busline.getBusLine(down_Line); }else{ console.log("此公交無下行或不存在:"+result.keyword) //部分公交線路不存在下行線路,輸出觀察 } } }, //設置公交線路查詢后的回調函數,即執行 busline.getBusLine(up_Line) 后執行的函數 onGetBusLineComplete : function (ret) { let line_name = ret.name; let line_point = ret.getPath(); //獲取公交路線坐標 let i = ret.getNumBusStations(); let sta_info = []; for(j=0;j <= i;j++){ sta_info.push([ret.getBusStation(j)]); } get_bus_line_data(line_name,line_point,sta_info); //對數據進行組織 } });
路線與公交線路列表展示:
up_line數據結構示例,即公交列表的第一個對象:
線路查詢后的回調函數的參數數據結構:
上一代碼塊最后一行代碼的函數,會對線路查詢后的回調函數的參數(即上圖公交線路信息)存入目標數組:
function get_bus_line_data(line_name,line_point,sta_info) { var sta_info_,sta_name,sta_position; //更改數據結構 [{}] --> {} && 去除最后一個空數組 sta_info_ = sta_info.slice(0,sta_info.length - 1).map(function (re) { return re[0] }); //獲取每一站點名稱 sta_name = sta_info_.map(function (re) { return re.name.toString() }); // 獲取站點坐標 sta_position = sta_info_.map(function (re) { return fromBd09ToWgs84([re.position.lng,re.position.lat]) // return [re.position.lng,re.position.lat] 若不需要轉換坐標系,使用此行代碼 }); line_data_ass.push( { name: line_name, line_point: line_point.map(function (re) { return fromBd09ToWgs84([re.lng, re.lat]) // return fromBd09ToWgs84([re.lng, re.lat]) 若不需要轉換坐標系,使用此行代碼 }), sta_name : sta_name, sta_point : sta_position, }); console.log(line_name); //在控制台輸出成功獲取的公交線路信息的名稱 }
輸出的line_data_ass格式形式如圖:
這是一個對象數組,每個對象存儲了一條路線的所有信息,包括線路坐標點,站點名稱,站點坐標,已經包含了我們需要的所有信息。可以看到,上下行線路在名稱上是有區別的。
如果只需要這一對象數組,上述代碼足矣,完整代碼整理如下:
<!DOCTYPE html> <html> <head> <title>獲取公交信息</title> </head> <body> <p><img src="http://map.baidu.com/img/logo-map.gif" /><span style="display:inline-block;width:200px;"> </span><input type="text" value="331" id="busId" />路公交 <input type="button" value="查詢" onclick="busSearch();" /></p> <div style="float:left;width:600px;height:500px;border:1px solid gray" id="container"></div> <div id="results" style="float:left;width:300px;height:500px;font-size:13px;"></div> <!--獲取坐標系轉換js包 詳見github:https://github.com/hujiulong/gcoord --> <script src="https://unpkg.com/gcoord/dist/gcoord.js"></script> <!--本地加載jquery--> <script src='jquery-1.8.3.js'></script> <script type="text/javascript" src="http://api.map.baidu.com/api?v=1.2"></script> <!--本地加載js-xlsx包 使用以及獲取方法見:https://www.cnblogs.com/liuxianan/p/js-excel.html--> <script src ='xlsx.full.min.js'></script> <script src ='xlsx.core.min.js'></script> <script> /*將爬取的廣州公交信息進行轉換*/ var line_data_list = [] var line_name_list = [] //獲取公交線路名稱 $.ajax( { url:encodeURI('guangzhou.txt'), async: false, success: function (guangzhou) { row_split = guangzhou.split('\n') for(var i in row_split){ line_data_list.push(JSON.parse(row_split[i])) var len = JSON.parse(row_split[i]).線路名稱[0].length line_data_list[i].線路名稱 = JSON.parse(row_split[i]).線路名稱[0].substring(2,len-5) //’廣州10路公交車路線‘->’10路‘ } for(var i in line_data_list){ line_name_list.push(line_data_list[i].線路名稱) } } } ) /*獲取百度地圖實例*/ var map = new BMap.Map("container"); map.centerAndZoom(new BMap.Point(113.315224, 23.181452), 12); //廣州市 var line_data_ass = [] //存儲最終數據的集合 function get_bus_line_data(line_name,line_point,sta_info) { var sta_info_,sta_name,sta_position; //更改數據結構 [{}] --> {} && 去除最后一個空數組 sta_info_ = sta_info.slice(0,sta_info.length - 1).map(function (re) { return re[0] }); //獲取每一站點名稱 sta_name = sta_info_.map(function (re) { return re.name.toString() }); // 獲取站點坐標 sta_position = sta_info_.map(function (re) { return fromBd09ToWgs84([re.position.lng,re.position.lat]) // return [re.position.lng,re.position.lat] 若不需要轉換坐標系,使用此行代碼 }); line_data_ass.push( { name: line_name, line_point: line_point.map(function (re) { return fromBd09ToWgs84([re.lng, re.lat]) // return fromBd09ToWgs84([re.lng, re.lat]) 若不需要轉換坐標系,使用此行代碼 }), sta_name : sta_name, sta_point : sta_position, }); console.log(line_name); //在控制台輸出成功獲取的公交線路信息的名稱 } // 公交信息獲取類 var busline = new BMap.BusLineSearch("廣州",{ // 展示獲取的公交線路,自動生成面板到id=results的element上,不必須使用 renderOptions:{map:map,panel:"results"}, // 設置公交列表查詢后的回調函數,注意與公交線路查詢區分,公交列表存儲多個具體的公交對象,公交對象可理解為公交線路。 onGetBusListComplete: function(result){ if(result) { /*獲取查詢出的公交列表中的對象 0代表上行 1代表下行, 實際上還可能存在getBusListItem(2)、getBusListItem(3)等等,代表的是模糊查詢的結果, 例如查詢 10路時,getBusListItem(0)、(1)返回的是10路的上下行的公交線路, 而(2)(3)返回的是b10路,具體可參考代碼塊后的貼圖*/ //獲取查詢出的公交列表中的第一個對象,即下行線路 let up_Line = result.getBusListItem(0); //獲取查詢出的公交列表中的第一個對象,即下行線路 let down_Line = result.getBusListItem(1); if(typeof up_Line === "object"){ //判斷查詢結果是否存在 busline.getBusLine(up_Line); //執行公交線路查詢 }else{ //在控制台輸出無法查詢的線路名稱,result.keyword 即在查詢時輸入的線路名 console.log("查無此公交:"+result.keyword) } if(typeof down_Line === "object"){ busline.getBusLine(down_Line); }else{ //部分公交線路不存在下行線路,輸出觀察 console.log("此公交無下行或不存在:"+result.keyword) } } }, //設置公交線路查詢后的回調函數,即執行 busline.getBusLine(up_Line) 后執行的函數 onGetBusLineComplete : function (ret) { let line_name = ret.name; let line_point = ret.getPath(); //獲取公交路線坐標 let i = ret.getNumBusStations(); let sta_info = []; for(j=0;j <= i;j++){ sta_info.push([ret.getBusStation(j)]); } get_bus_line_data(line_name,line_point,sta_info); //對數據進行組織 } }); /*------------------執行層------------------------------------*/ // 數據導入數組開始 //slice for (var i in line_name_list.slice(0,5)){ busline.getBusList(line_name_list[i]); console.log(i) } console.log("done") /*-------------------功能函數---------------------------------*/ /*頁面操作調用的函數*/ function busSearch(){ var busName = document.getElementById("busId").value; busline.getBusList(busName); } /*坐標轉換函數*/ function fromBd09ToWgs84(arr) { var result = gcoord.transform( arr, // 經緯度坐標 gcoord.BD09, // 當前坐標系 gcoord.WGS84 // 目標坐標系 ); return result; } </script> </body> </html>
如何導出?
很多情況下,如果只是把數據存儲到JavaScript的一個數組中,是遠遠不夠的。接下來將會提供將這些數據導出到本地excel的方式,最終獲取在開頭展示的數據文件。
接下來的步驟會比較惡心了,因為通過百度API獲取公交數據需要一定的時間,所以如果在頁面加載過程中就認為已獲取了所有所需的數據,直接進行導出或者解析的話,程序會報錯。因為在頁面執行到提取數據的代碼時,往往我們所需的數據還沒有完全傳送過來,特別是在一個城市有數千條公交線路的時候。
對於這一問題,解決方案是:在數據完全獲取后,即都已存入line_data_ass之后,再F12調出控制台執行導出數據的JavaScript代碼。
在控制台執行代碼之前,還需要下列准備:
/* ①為了配合js-xlsx包導出數據,對數據進行進一步的組織。 ②准備必要的導出函數。 */
// 數據組織 data_line_point=[['line_id','line_name','lng','lat','p_id']]; data_station_point = ['line_id','line_name','sta_name','lng','lat','sta_id']; function createData(){ for(var i in line_data_ass){ for(var j in line_data_ass[i].line_point){ data_line_point.push([ i, line_data_ass[i].name, line_data_ass[i].line_point[j][0], line_data_ass[i].line_point[j][1], j // , line_data_ass[i].dir ])} for(var j in line_data_ass[i].sta_point){ data_station_point.push([ i, line_data_ass[i].name, line_data_ass[i].sta_name[j], line_data_ass[i].sta_point[j][0], line_data_ass[i].sta_point[j][1], j // , line_data_ass[i].dir ]); } } } /*基於excel_js的函數*/ //代碼來源:https://www.cnblogs.com/liuxianan/p/js-excel.html function openDownloadDialog(url, saveName) { if(typeof url == 'object' && url instanceof Blob) { url = URL.createObjectURL(url); // 創建blob地址 } var aLink = document.createElement('a'); aLink.href = url; aLink.download = saveName || ''; // HTML5新增的屬性,指定保存文件名,可以不要后綴,注意,file:///模式下不會生效 var event; if(window.MouseEvent) event = new MouseEvent('click'); else { event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); } aLink.dispatchEvent(event); } function sheet2blob(sheet, sheetName) { sheetName = sheetName || 'sheet1'; var workbook = { SheetNames: [sheetName], Sheets: {} }; workbook.Sheets[sheetName] = sheet; // 生成excel的配置項 var wopts = { bookType: 'xlsx', // 要生成的文件類型 bookSST: false, // 是否生成Shared String Table,官方解釋是,如果開啟生成速度會下降,但在低版本IOS設備上有更好的兼容性 type: 'binary' }; var wbout = XLSX.write(workbook, wopts); var blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"}); // 字符串轉ArrayBuffer function s2ab(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } return blob; }
等所有數據載入到line_data_ass中后,在控制台執行以下代碼:
//執行數據組織函數 createData(); //導出excel var sheet = XLSX.utils.aoa_to_sheet(data_line_point); var sheet2 = XLSX.utils.aoa_to_sheet(data_station_point); openDownloadDialog(sheet2blob(sheet), '導出.xlsx'); openDownloadDialog(sheet2blob(sheet2), '導出1.xlsx');
執行完后,不出意外數據就可以成功導出了。
不得不提的話:
如果你按着步驟運行這一程序,同時想要提取的公交線路又很多的時候,可能會出現以下幾個問題:
1.針對以下問題,可能的原因是:可以通過百度地圖API獲取到相應的busLine對象,但卻沒有相應的數據,例如查詢廣州的"從化19路密石班車",就會出現這樣的情況。
2.單獨查找時存在的公交線路,在程序運行過程中卻報出:"查無此公交:XXX"。這一問題目前還沒有很好的解決方案,不知道是哪里出了BUG,一個解決思路是:一次性請求較少量的公交線路數據。這樣會比較少出錯。
3.查找的公交線路不存在,但百度地圖的模糊查找會找出名稱相識的路線,例如,輸入”化16路“,得到的是”16路“。這一問題嘗試過通過字符串的indexOf方法判斷,代碼如下。但這又會導致輸入”12A路”時,“12a路”的數據無法獲取。不過好像百度地圖公交線路的名稱中英文字符多是小寫,所以把輸入中的英文統一改為小寫或許可以解決這個問題。有點麻煩,而且不一定有用,另外這一錯誤最終導致的后果只是導出的數據中多了一條重復線路,無傷大雅,后期也不難處理,所以就算了。
if(typeof up_Line === "object" && up_Line.name.indexOf(result.keyword) !== -1){...}z
參考資料 :
百度js3.0 API文檔:http://lbsyun.baidu.com/cms/jsapi/reference/jsapi_reference_3_0.html
小茗同學(如何使用JavaScript實現純前端讀取和導出excel文件):https://www.cnblogs.com/liuxianan/p/js-excel.html
求知魚(爬取某城市公交錢路--xpath過濾):https://www.cnblogs.com/Qiuzhiyu/p/12183140.html
酸奶小妹(【百度地圖API】如何制作公交線路的搜索?如331路):https://www.cnblogs.com/milkmap/archive/2011/09/16/2178553.html
前端小白一個,代碼寫得巨辣雞。如果能幫到你很高興,各位有興趣的老哥請多多批評指正,歡迎討論!!!!