话不多说,先挂最后的数据结果,如果这是你想要的,我们再接着看:
公交线路坐标数据&公交站点坐标数据
正文开始:
前期数据准备:获取城市所有公交线路名称
使用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
前端小白一个,代码写得巨辣鸡。如果能帮到你很高兴,各位有兴趣的老哥请多多批评指正,欢迎讨论!!!!