起因

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

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

具体实现

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

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不断寻找匹配节点去遍历这个森林的时候可以完全避开不匹配的项去提高匹配效率。举个例子就是:

我们根据左侧的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。

从拿到班车手册.xls到搜索附近班车地点相关推荐

  1. 【新生指南】福建师范大学新生入学手册--【师大搜索-整理】

    --------新生入学手册----高考篇--------高三励志文章:真的猛士要面对惨淡的人生 - 新生手册 高三励志:如何制定每天的计划 - 新生手册 高中到大学,梦想和现实的距离有多远 - 新生 ...

  2. 保定华电计算机系翟学明老师,华电选课手册.xls

    一般 教十一楼A104(128) 教九楼B座401(200) 教九楼B座201(200) 教九楼B座302(230) 教九楼B座101(200) 国际合作项目班课堂 双语课堂 网络技术基础 7-18周 ...

  3. hex editor怎么搜索代码_代码审计从入门到放弃(三) phplimit

    原创: 一叶飘零 合天智汇 前言 接着前面的代码审计从入门到放弃(一) & function.代码审计从入门到放弃(二) & pcrewaf 本次是phplimit这道题,本篇文章提供 ...

  4. 包机制、阿里巴巴开发手册

    公司域名倒置作为包名 com.baidu.www 阿里巴巴开发手册可百度搜索看看

  5. manual 离线手册 韩顺平php_PHP - Manual: 手册的格式 (官方文档)

    手册的格式 <PHP 参考手册>提供了多种不同格式的版本,它们可以分为两组:在线阅读版本和可下载打包版本. Note: 有些出版商可能已经出版了本手册的一些印刷版本.不推荐印刷版,因为 P ...

  6. STM32 HAL库手册获取和查阅方法以及查看官方例程

    目录 一.概述 二.安装芯片pack包 三.查看HAL库手册 四.查看官方例程 一.概述 STM32固件使用HAL库进行开发,如何查看接口函数的定义呢?本文记录一种通过STM32CubeMX软件获取H ...

  7. 【CSS】CSS 文本样式 ② ( font 字体设置 | CSS 2.0手册使用 | font-weight 字体粗细设置 | font-style 字体斜体设置 | font 字体样式综合写法 )

    文章目录 一. CSS 2.0手册使用 1. 按照文档层次查找 2. 搜索关键字查找文档 二. font-weight 字体粗细设置 1. 语法简介 2. 代码示例 三. font-style 字体斜 ...

  8. 移动搜索关键字SEO:如何添加移动关键字!

    添加关键字 2017年SEO的主要变化是搜索重点从PC转移到了移动. 众所周知,在Bear Palm发布之后,移动变得越来越重要,而且移动索引的优先级高于PC. 看,移动端的流量也远远超过了PC端. ...

  9. 谷歌和百度常见搜索技巧

    经常使用谷歌,百度进行搜索,才发现还有很多比较基础的搜索技巧都不甚了解,抽空阅读了一下谷歌和百度的官方文档,整理总结了以下常见的搜索技巧. 谷歌搜索技巧 1.完全匹配,使用中文或者英文双引号 2.几个 ...

最新文章

  1. 消除图片在ie中缓存而无法更新的问题
  2. 太扎心!人艰不拆!16 个程序员专属笑话讲给你听
  3. Quzrtz 使用oracle集群无法正常启动问题解决
  4. bios设置_老富士通bios设置启动项方法是什么 富士通bios设置u盘启动的方法
  5. 关于IOCP完成端口的文章
  6. thinkphp与php共享session
  7. flask 配置静态文件模板文件
  8. python类和对象的定义_day15_python_类和对象
  9. 太原理工大学这两年程序设计等竞赛奖牌统计
  10. Unity Application Block 1.0系列(7): Lifetime Managers
  11. C++值传递、指针传递、引用传递的区别
  12. 安装卸载Windows服务
  13. 微信支付成功后发送短信通知
  14. 详解WSAEventSelect网络模型
  15. 电商网站商品详情架构
  16. springboot在Gradle7以上版本不识别compile解决方案
  17. matplotlib实现多个子图的盒须图
  18. 亚马逊ERP贴牌OEM代理贴牌是什么意思 起到一个什么样的作用
  19. MUX实现不同的门电路功能
  20. 为什么放大器上的输入电阻越大越好?输出电阻越小越好?

热门文章

  1. nginx指定配置文件启动_【第1717期】Nginx入门指南
  2. 五年级信息技术上册教案计算机主机探秘,第1课信息与信息技术探秘教案
  3. EL表达式的11个内置对象
  4. Android开发(3) | 权限和内容提供器的应用——调用相机和相册
  5. 动态规划-背包是否装满
  6. php生成有复杂结构的excel文档
  7. ThinkPHP redirect 页面重定向使用详解与实例
  8. Mysql索引优化实例讲解
  9. 大数据学习(09)--spark学习
  10. C++STL与泛型编程 侯捷 (1)