当前位置: 首页 > 后端技术 > Node.js

从获取班车manual.xls到搜索附近的班车位置

时间:2023-04-03 23:28:02 Node.js

原因是我7月份要去某厂报到。异地租房时,发现想租有公司班车的地方,但不知道哪里有班车。发完shuttlebus手册,发现找起来太不方便,于是灵机一动,想搜索一下房子的地址,找到附近的shuttlebuspoint(类似于大众点评的位置搜索)附近餐厅的功能)。现在差不多完成了,好像公司已经在做这个了。.是时候学习一些位置匹配技术了。最后的结果是这样的:pin是输入位置(福田中学),附近的蓝点是各个站点。由于一个站点会在不同的上班和夜班路线上到达不同的位置,并且会在不同的时间到达,所以聚合到多个相同站点的数据将聚合到一个点。点击蓝色车站会在下方显示该车站的所有线路。具体实现下面会分几个步骤说说具体使用的方法和技术:1.将原始数据转换成我们需要的数据。一开始我们拿到的是excel手册,所以我们原来的数据长这样(excel导出的忽略步骤):['A(B门)(07:30)→C(前方100米天桥下)政府)(07:45)→D(2号站台前10米)→E→F(09:12)',...2号线,...3号线]那么我们需要做的是:将阵列中每条线路的站点拆分为单独的单元。这一步比较简单,str.split('→')每个单元分隔站点和时间。这一步需要更多的工作。需要正则匹配,因为站点名称其实有很多种,需要考虑多种情??况。所以我的方法是:先用/(.*)(\([0-9:]*\))/来分隔时间和站点,因为只有时间只包含数字和左右括号中的:。实际上站名中有一些非法字符,所以需要进一步过滤station.replace(/([^\u4e00-\u9fa5\(\)\d])/g,'')得到每个站点的纬度和经度。没什么可说的。.调用腾讯地图的api,但是因为调用api时有每秒请求数和每日请求数的限制,所以采用异步回调和timer的方式模拟休眠,然后运行脚本等待结果回来。2.如何在经纬度表示的一堆点中找到附近的点(geohash)我参考资料简单介绍一下geohash,就是把经纬度按照一定的规则映射成一个hash串,并在后续搜索,只要hash串的匹配度足够高,就可以认为两点很接近。有关详细信息,请阅读上面的参考资料。下面是我的javascript代码的实现。函数geoHashCode(num,range){range=[-range,range]letretCode=[]for(leti=1;i<=20;i++){letmiddle=(range[0]+range[1])/2让代码=num<中间?'0':'1'if(code==='0'){range[1]=middle}else{range[0]=middle}retCode.push(code)}returnretCode}functiongeoHash({lng,lat}){//lng:longitude,lat:latitudeletlngCode=geoHashCode(lng,180)letlatCode=geoHashCode(lat,90)//偶数位的经度,Latitude的奇数位,合并2串代码生成一个新字符串letcode=[]for(leti=0;i<40;i++){if(i%2===0){//evencode[i]=lngCode[i/2]}else{code[i]=latCode[(i-1)/2]}}constbase32=['0','1','2','3','4','5','6','7','8','9','b','c','d','e','f','g','h','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z']letnewCode=[]constsplitLen=5for(le我=0;我<8;i++){newCode.push(code.slice(i*5,i*5+5).join(''))}//base32编码newCode=newCode.map(item=>base32[parseInt(item,2)]).join('')returnnewCode}经过上面的步骤,我们能得到什么呢?一个非常大的列表,每个单位是{station:公交车名称,location:点的经纬度,name:哪个夜班属于上班,lineIndex:乘坐属于该类型公交车的线路,stationIndex:属于这条线路的第一站时间:到达时间,geohash:该点的经纬度映射出来的geohash}此时,其实可以输入一个点,匹配附近公交车的点,只要输入的点通过api查询经纬度,然后转成geohash,最后遍历list,挑出匹配度足够高的点。但是实际上我们有5000个这样的点,我觉得一直在页面上做这种遍历匹配是很愚蠢的,所以我想到了建立一棵匹配树。将一组hash映射成一个匹配森林,然后输入点的geohash不断寻找匹配节点遍历森林,可以完全避免不匹配项,提高匹配效率。比如:我们根据左边的hashList映射出右边的匹配森林。由于geohash的精度关系,多个站点的geohash会相同。所以我在叶子节点中使用一个数组来存储所有对应的站点信息。当我们要匹配'wsc2'时,可以一直搜索到叶子节点,取出'site1,site2',但是有时候我们要搜索的geohash是匹配不到叶子节点的,所以需要判断当前准确率是否足够高,误差不会太大,比如我们认为匹配三个前缀字符时准确率足够高,那么在搜索'ws11'时,因为只找到两个匹配,不应返回结果。匹配'wsc3'时,可以匹配前缀字符'wsc'。虽然没有叶子节点,但是我们可以认为以'wsc'为根的树的所有叶子节点(大概意思你应该明白)都可以认为是这个geohash的附近节点,即return'站点1、站点2、站点6'。至于误差范围,请看上面的参考资料。3.构建页面需要的内容腾讯地图或其他地图开放接口获取输入地址转化为经纬度和geohash搜索树获取匹配地址。列表中的索引聚合了与绘制点相同经纬度的点,并以经纬度为键名构造了一张地图绘制出来,附近的点为蓝色,输入点为pin,绑定附近点的点击事件(渲染列表,生成该点的所有线信息)。其他小玩具到此结束,中间其实还有一些值得一提的地方。也一起写下来了,觉得做一些好玩的东西还是挺有意思的。定时器+异步模拟休眠必备知识点:sync/await(只是因为这样写很酷,没有别的意思functionsleep(){returnnewPromise((resolve,reject)=>{setTimeout(()=>{resolve()},500)})}(asyncfunction(){leti=locationList.length//计数器letnewList=[]while(i!==-1){letitem=locationList.pop()//取出要查询的点getXY(item.station)//调用api获取经纬度locationMap[item.tation]=location//缓存经纬度信息awaitsleep()//sleep}item.location=locationitem.geoHash=geoHash(location)//getgeohashnewList.push(item)}catch(e){//请求失败,将此点推回并重新请求console.log(e)locationList.push(item)i++}i--console.罗g(i)}})()数据劫持其实在设计之初并没有查询位置附近的班车站点的功能,而是显示通勤路线和通勤路线的功能。不同线路之间的转换使用了数据劫持的方式,即vue实现数据绑定的Object.defineProperty。这真的很有趣。我建议你也可以用这个试试。另外,单页应用的路由中有一个hashchange事件。这些是可以重新创建的API。