從拿到班車手冊.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.pswp.cn/news/451691.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/451691.shtml
英文地址,請注明出處:http://en.pswp.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…