从拿到班车手册.xls到搜索附近班车地点

起因

七月份要去某厂报道了,异地租房的时候发现想租一个有公司班车的地方,却不知道哪里有班车。辗转流传出班车手册后发现搜索实在是太不方便了,于是有了一个主义,想做一个可以搜索房子地址,找出附近班车点(类似大众点评的定位搜索附近餐馆的功能)。现在做的差不多了,发现好像本来公司就有做这个东西。。权当学一下一些位置匹配的技术了。
最后成果是这样子的:

clipboard.png

大头针是输入的位置(福田中学),附近的蓝点就是一个一个站点。由于一个站点他会在上班下班夜班不同的线路的不同站点位置,会在不同时刻到达,因此聚合为多个同一站点的数据会聚合为一个点。点击蓝色的站点就会在下面显示出这个站点所在的所有线路。

具体实现

下面将分为几个步骤讲一下具体使用了什么方法什么技术:

1. 原始数据转换成我们需要的数据

一开始拿到的是excel手册,所以我们有的原始数据是长成这样的(忽略的从excel中导出的步骤):
['A(B门口)(07:30)→C(政府前100米天桥下)(07:45)→D(2站台前10米)→E→F(09:12)', ...路线二, ...路线三]
然后我们需要做的事情是:

  1. 从数组里把每一条线路的站点拆分成一个个独立的单元
    这一步比较简单,str.split('→')
  2. 每一个单元分离出站点和时间
    这一步要做的就多一点点了,需要用到正则匹配,而且因为站点的名字其实是有多种的,需要考虑到多种情况。因此我的方法是:

    1. 先用/(.*)(\([0-9:]*\))/分离时间和站点,因为只有时间是左右括号内只包含数字和:的。
    2. 实际上站点名称里有一些非法字符,因此还需要进行一步过滤station.replace(/([^\u4e00-\u9fa5\(\)\d])/g, '')
  3. 每一个站点获取到经纬度
    这个就没啥好说的了。。调用腾讯地图的api,不过由于调用api有每秒请求数和每日请求数的限制,用异步回调加定时器的方式模拟了休眠,然后运行脚本慢慢等结果返回就好了。

2. 怎么在一堆经纬度表示的点里找出附近的点呢(geohash)

我参考的资料
简单介绍一下geohash就是,把经纬度按照一定的规则去映射出一个hash字符串,在后续搜索的时候,只要hash字符串匹配程度足够高就可以认为这两个点是相近的。具体的内容可以阅读上面的参考资料。下面给我javascript代码的实现。

function geoHashCode (num, range) {range = [-range, range]let retCode = []for (let i = 1; i <= 20; i++) {let middle = (range[0] + range[1]) / 2let code = num < middle ? '0' : '1'if (code === '0') {range[1] = middle} else {range[0] = middle}retCode.push(code)}return retCode
}
function geoHash ({ lng, lat }) { // lng: 经度, lat: 纬度let lngCode = geoHashCode(lng, 180)let latCode = geoHashCode(lat, 90)// 偶数位放经度,奇数位放纬度,把2串编码组合生成新串let code = []for (let i = 0; i < 40; i++) {if (i % 2 === 0) { // 偶数code[i] = lngCode[i / 2]} else {code[i] = latCode[(i - 1) / 2]}}const base32 = ['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']let newCode = []const splitLen = 5for (let i = 0; i < 8; i++) {newCode.push(code.slice(i * 5, i * 5 + 5).join(''))}// base32编码newCode = newCode.map(item => base32[parseInt(item, 2)]).join('')return newCode
}

经过上述步骤,我们可以得到什么呢?
一个很大的list,每一个单元为

{station:班车名字,location:该点的经纬度,name:属于上班下班夜班中的哪一个,lineIndex:属于该班车类型的拿一条线路,stationIndex:属于该线路里的第几个站点,time:到站时间,geohash:该点经纬度映射出的的geohash
}

到这一步其实已经可以做到输入一个点,匹配出附近班车的点了,只要把输入的点通过api查询出经纬度,再转化成geohash,最后遍历这个list把匹配程度足够高的点挑出来就可以了。
但是其实我们有5000个这样的点,在页面上不断做这种遍历匹配我觉得挺蠢的,于是我想到构建一个匹配树。把一组hash映射成一个匹配森林,然后输入点的geohash不断寻找匹配节点去遍历这个森林的时候可以完全避开不匹配的项去提高匹配效率。举个例子就是:

clipboard.png

我们根据左侧的hashList映射出右侧的匹配森林,由于geohash的精度关系是会出现多个站点的geohash是一样的。因此我在叶子节点里用一个数组存放所有的对应站点信息。当我们要匹配'wsc2'时我们可以一直搜索到叶子节点,取出‘站点1,站点2’,但是有时候我们要搜索的geohash没办法匹配到叶子节点,我们就要先判断当前精度是否足够高,误差会不会太大,比如我们认为匹配了三个前缀字符的时候精度就足够高了,那么搜索'ws11'的时候由于只匹配到两个,不应返回结果。而匹配'wsc3'的时候,可以匹配到前缀字符'wsc',虽然没有到叶子节点,但是我们可以认为以'wsc'为根(大概是那个意思你们应该明白)的树的所有叶子节点都可以认为是这个geohash的附近节点,也就是返回'站点1,站点2,站点6'。至于误差范围可以看上面的参考文献。

3.构建页面需要的内容

  1. 腾讯地图或者其他地图的开放接口
  2. 获取输入地址转化为经纬度和geohash
  3. 查找树获取匹配的地址在list中的index
  4. 聚合相同经纬度的点为一个绘制点
    将经纬度作为键名构建一个map
  5. 绘制,附近的点为蓝色,输入的点为大头针,绑定附近的点的点击事件(渲染列表,生成该点的所有线路信息)

其他

这个小玩具就这么结束了,中间其实还有一些值得一提的地方。我也就一起记下来了,感觉还是挺有趣的,做一些好玩的东西。

定时器+异步模拟休眠

  • 必备知识点: sync/await(只是因为这么写看起来很爽,没有别的意思

function sleep () {return new Promise((resolve, reject) => {setTimeout(() => {resolve()}, 500)}) 
}
(async function () {let i = locationList.length // 计数器let newList = []while (i !== -1) {let item = locationList.pop() // 取出要查询的点let locationtry {if (locationMap[item.station]) { // 如果这个点请求过了就直接用缓存信息location = locationMap[item.station]} else {location = await getXY(item.station) // 调用api获取经纬度locationMap[item.tation] = location // 缓存经纬度信息await sleep() // 休眠}item.location = locationitem.geoHash = geoHash(location) // 获取geohashnewList.push(item)} catch (e) { // 请求失败了,把这个点推回去重新请求console.log(e)locationList.push(item)i++}i--console.log(i)}
})()

数据劫持

其实一开始设计的时候没有查询地点附近的班车站点功能的。而是显示上班线路下班线路的功能。不同线路之间的转换用了数据劫持的方式,也就是vue实现数据绑定的Object.defineProperty,还真的挺有意思的,建议大家也可以用这个试一下。另外还有单页应用路由里面的hashchange事件。这些都是些可以再创造的api。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/451691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2018.08.09洛谷P3959 宝藏(随机化贪心)

传送门 回想起了自己赛场上乱搜的20分。 好吧现在也就是写了一个随机化贪心就水过去了&#xff0c;不得不说随机化贪心大法好。 代码&#xff1a; #include<bits/stdc.h> using namespace std; inline int read(){int ans0;char chgetchar();while(!isdigit(ch))chget…

AWT和Swing

AWT 是Abstract Window ToolKit (抽象窗口工具包)的缩写&#xff0c;这个工具包提供了一套与本地图形界面进行交互的接口。AWT 中的图形函数与操作系统所提供的图形函数之间有着一一对应的关系&#xff0c;我们把它称为peers。 也就是说&#xff0c;当我们利用 AWT 来构件图形用…

解决 : Apache Tomcat/8.0.0-RC1 - Error report ... HTTP Status 404

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.报错&#xff1a; Apache Tomcat/8.0.0-RC1 - Error report HTTP Status 404 - /richer/getOnLineRicherCount The requested resour…

py 5.24

#面向对象 #类&#xff1a;模子。Person&#xff0c;不具体。 #实例/对象&#xff1a;依托于类产生的具体的带有属性的。alex #实例化&#xff1a;产生对象的过程。 alex Person() #类&#xff1a; #分为静态属性&#xff08;一般的变量&#xff09;。动态属性(函数&#xff0…

多线程原理实例应用详解

从单进程单线程到多进程多线程是操作系统发展的一种必然趋势&#xff0c;当年的DOS系统属于单任务操作系统&#xff0c;最优秀的程序员也只能通过驻留内存的方式实现所谓的"多任务"&#xff0c;而如今的Win32操作系统却可以一边听音乐&#xff0c;一边编程&#xff0…

git中使用fork

在git中使用fork相当于你在原项目的主分支上又建立了一个分支&#xff0c;你可以在该分支上任意修改。如果想将你的修改合并到原项目中时&#xff0c;可以pull request&#xff0c;这样原项目的作者如果认同你的修改&#xff0c;就可以将你修改的东西合并到原项目的主分支上去。…

一、【Collection、泛型】

主要内容 Collection集合迭代器增强for泛型 教学目标 能够说出集合与数组的区别 说出Collection集合的常用功能 能够使用迭代器对集合进行取元素 能够说出集合的使用细节 能够使用集合存储自定义类型 能够使用foreach循环遍历集合 能够使用泛型定义集合对象 能够理解泛型上下…

字符装换

2019独角兽企业重金招聘Python工程师标准>>> 字母大小写转换 a →A char toUpperCase( char ch){ if((ch >a) && (ch <z)){ return (char)(ch - 32); // 主要 这里(char)是必要的&#xff0c;因为char -32是返回的数值&#xff0c;必须转换成对应的字…

解决 Unable to translate SQLException with Error code ‘17059‘, will now try the fallback translator

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1.报错&#xff1a; Unable to translate SQLException with Error code 17059, will now try the fallback translator 报错如下&…

企业使用开源软件的风险

很多时候&#xff0c;我们过高地估计了开源软件面临的版权威胁&#xff0c;开源软件并非天生就比专有软件存在更多风险。虽然在企业中开源软件越来越普及&#xff0c;但开源软件始终难以摆脱知识产权带来的阴影。2007年&#xff0c;微软声称开源软件侵犯了它235项专利&#xff…

杭电多校 Harvest of Apples 莫队

问题 B: Harvest of Apples 时间限制: 1 Sec 内存限制: 128 MB 提交: 78 解决: 35 [提交] [状态] [讨论版] [命题人:admin] 题目描述 There are n apples on a tree, numbered from 1 to n. Count the number of ways to pick at most m apples. 输入 The first line of the …

linux和GNU之间的关系

Linux只是一个操作系统内核而已&#xff0c;而GNU提供了大量的自由软件来丰富在其之上各种应用程序。 因此&#xff0c;严格来讲&#xff0c;Linux这个词本身只表示Linux内核&#xff0c;但在实际上人们已经习惯了用Linux来形容整个基于Linux内核&#xff0c;并且使用GNU 工程各…

二、【List、Set、数据结构、Collections】

主要内容 数据结构List集合Set集合Collections 教学目标 能够说出List集合特点 能够说出常见的数据结构 能够说出数组结构特点 能够说出栈结构特点 能够说出队列结构特点 能够说出单向链表结构特点 能够说出Set集合的特点 能够说出哈希表的特点 使用HashSet集合存储自定义元素…

@Java | Thread synchronized - [ 线程同步锁 基本使用]

对实现了Runnable或者Callable接口类&#xff0c;可以通过多线程执行同一实例的run或call方法&#xff0c;那么对于同一实例中的局部变量&#xff08;非方法变量&#xff09;就会有多个线程进行更改或读取&#xff0c;这就会导致数据不一致&#xff0c;synchronized(关键字)可以…

解决bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: ORA-00911: 无效字符

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 报错&#xff1a; ### Cause: java.sql.SQLSyntaxErrorException: ORA-00911: 无效字符; bad SQL grammar []; nested exception is …

开源代码的使用 二次开发

开源开发&#xff0c;就我的理解&#xff0c;有三种。 1、当作底层基础&#xff0c;使用。例如大家使用mysql就算。有人会认为我说错了。但我认为&#xff0c;开发不代表就是要同一个语言&#xff0c;甚至修改代码。例如我们使用动态库&#xff0c;原先的动态库是什么写的并不重…

Java Application和Java Applet

Java Applet和Java Application 主要区别&#xff1a; &#xff08;1&#xff09;运行方式不同。Java Applet程序不能单独运行&#xff0c;它必须依附于一个用HTML语言编写的网页并嵌入其中&#xff0c;通过与Java兼容的浏览器来控制执行。 Java Application是完整的程序&a…

激活prompt

1.下载SQLPrompt 2. 断网&#xff0c; 打开注册机&#xff0c;拷贝验证码 2. 点击activate&#xff0c; 拷贝代码 转载于:https://www.cnblogs.com/zxhome/p/9459415.html

Map 四种获取 key 和 value 值的方法,以及对 map 中的元素排序

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。1. 获取map的值主要有四种方法&#xff0c;分为两类&#xff1a; 调用 map.keySet() 方法来获取 key 和 value 的值&#xff1b; 通…

三、【Map】

主要内容 Map集合 教学目标 能够说出Map集合特点 使用Map集合添加方法保存数据 使用”键找值”的方式遍历Map集合 使用”键值对”的方式遍历Map集合 能够使用HashMap存储自定义键值对的数据 能够使用HashMap编写斗地主洗牌发牌案例 第一章 Map集合 1.1 概述 现实生活中&am…