[譯] 用 Redis 和 Python 構建一個共享單車的 app

用 Redis 和 Python 構建一個共享單車的 app

瞭解如何使用 Redis 和 Python 構建位置感知應用程序。

google bikes on campus
google bikes on campus

圖片來源: Travis Wise. CC BY-SA 2.0前端

雖然我常常出差,但我不太喜歡開車,因此當我有空的時候,我更喜歡在城市裏散步或騎自行車。我出差去過的許多城市都有自行車租賃系統,可讓你租幾個小時的自行車。這些系統中的大多數都有一個應用程序來幫助用戶定位和租賃他們的自行車,但對於像我這樣的用戶來講,有一個單獨的地方來獲取城市中全部可供租賃的自行車的信息會更有幫助。python

爲了解決這個問題並開源向 Web 應用程序添加位置感知特性的能力,我結合了公開可用的共享單車數據、Python 編程語言和開源 Redis 內存數據結構服務器來索引並查詢地理空間數據。android

由此產生的共享單車應用程序集成了來自許多不一樣共享系統的數據,包括在紐約市的 Citi Bike 共享單車。它利用了 Citi Bike 系統提供的通用共享單車數據流,並使用其數據演示了可使用 Redis 創建的一些功能來索引地理空間數據。Citi Bike 數據是根據 Citi Bike 數據許可協議提供的。ios

通用共享單車數據流規範

通用共享單車數據流規範(GBFS)是由北美共享單車協會開發的開源數據規範,目的是讓地圖和交通類應用程序更輕易地將共享單車系統添加到它們的平臺中。目前世界上有 60 多個不一樣的共享系統在使用該規範。git

數據流由包含有關係統狀態信息的幾個簡單 JSON 數據文件組成。數據流從引用子數據流數據的 URL 的頂級 JSON 文件開始:github

{
    "data": {
        "en": {
            "feeds": [
                {
                    "name": "system_information",
                    "url": "https://gbfs.citibikenyc.com/gbfs/en/system_information.json"
                },
                {
                    "name": "station_information",
                    "url": "https://gbfs.citibikenyc.com/gbfs/en/station_information.json"
                },
                . . .
            ]
        }
    },
    "last_updated": 1506370010,
    "ttl": 10
}
複製代碼

第一步是將 system_information 和 station_information 中,數據流有關共享單車站點的信息的數據加載到 Redis 裏。redis

system_information 數據流系統 ID,這是一個可用於 Redis 密鑰建立命名空間的短代碼。GBFS 規範沒有指定系統 ID 的格式,但保證它是全局惟一的。對於系統 ID,許多共享單車數據流都是使用簡短的名稱,如 coast_bike_share、boise_greenbike 或者 topeka_metro_bikes。另外一些使用熟悉的地理縮寫,如 NYC 或 BA,其中一個使用通用惟一標識符(UUID)。共享單車應用程序使用標識符做爲前綴來構造給定系統的惟一密鑰。數據庫

Station_Information 數據流提供了組成系統的關於共享站點的靜態信息。站點由帶有多個字段的 JSON 對象表示。在站點對象中有幾個必填字段,它們提供真實站點的 ID、名稱和位置。還有幾個可選字段提供的有用信息,如最近的十字路口或所接受的付款方式。這是共享單車應用程序這部分的主要信息來源。編程

建立數據庫

我寫了一個示例應用程序 —— load_station_data.py,它模擬了從外部源加載數據的後端過程當中可能發生的狀況。json

查找共享單車站點

Github 的 GBFS 倉庫systems.csv 文件加載共享單車數據。

倉庫的 systems.csv 文件爲註冊的共享單車系統提供了一個帶有可用的 GBFS 數據流發現 URL。發現 URL 是處理共享單車信息的起點。

load_station_data 應用程序獲取系統文件中發現的每一個 URL,並使用它查詢兩個數據流 URL:系統信息和站點信息。系統信息數據流提供了一條關鍵信息:系統的惟一 ID。(注意:systems.csv 文件也提供了系統 ID,可是該文件中的一些標識符與提要中的標識符不匹配,所以我老是從反饋中獲取標識符。)系統的詳細信息,好比共享單車 URL、電話號碼和電子郵件。能夠添加到應用程序的將來版本中。 所以使用鍵 ${system_id}:system_info 將數據存儲在 Redis 散列中。

加載站點數據

站點信息提供系統中每一個站點的數據,包括系統的位置。load_station_data 應用程序迭代站點反饋中的每一個站點,使用形如 ${system_id}:station:${station_id} 的鍵將每一個站點的數據存到 Redis 散列中。使用 GEOADD 命令將每一個站點的位置添加到共享單車的地理空間索引中。

更新數據

在隨後的運行中,我不但願代碼從 Redis 中刪除全部數據流數據再將其從新加載到一個空的 Redis 數據庫中,所以我仔細考慮瞭如何處理數據的本地更新。

代碼首先載入已經被系統加載到內存中的全部共享單車站點信息的數據集。當爲站點加載信息時,從站點的內存集中刪除站點(按鍵)。一旦加載了全部站點數據,咱們就會獲得一個包含了該系統必須刪除的全部站點的數據集合。

應用程序迭代這組站點並建立一個事務來刪除站點信息,從地理空間索引中刪除站點鍵,並從系統的站點列表中刪除站點。

代碼註釋

示例代碼中有一些有趣的事情須要注意。首先,使用 GEOADD 命令將詞條添加到地理空間索引中,可是用 ZREM 命令刪除。因爲地理空間類型底層實現使用排序集,所以使用 ZREM 刪除詞條。請注意,爲了簡潔,示例代碼演示瞭如何使用單個 Redis 節點;若是在集羣環境中運行,須要對事務模塊進行重構。

若是您使用的是 Redis 4.0 (或者更高版本),則在代碼中有一些 DELETEHMSET 命令的替代方法。Redis 4.0 提供UNLINK 命令做爲 DELETE 命令的異步替代。UNLINK 將從密鑰空間中刪除密鑰,但它在單獨的線程中回收內存。HMSET 命令在 Redis 4.0 中被棄用,HSET 命令如今是可變的(也就是說,它接受數目不定的參數)。

通知客戶端

在流程結束時,將根據咱們的數據向客戶端發送通知。使用 Redis pub/sub 機制,通知經過 geobike:Station_Changed 通道發送,並帶有系統的 ID。

數據模型

在用 Redis 構造數據時,要考慮的最重要的事情是如何查詢信息,共享單車應用程序須要支持的兩個主要查詢是:

  • 找到附近站點
  • 顯示站點相關信息

Redis 提供兩種用於存儲數據的主要數據類型:散列和排序集。散列類型 很好地映射到表示站點的 JSON 對象;因爲 Redis 散列不強制執行模式,所以可使用它們存儲可變站點信息。

固然,在地理上尋找站點須要一個地理空間索引來搜索相對於某些座標的地點。Redis 提供一些命令來使用排序集數據結構構建地理控件索引。

咱們使用 ${System_id}:Station:${Station_id} 格式的散列構造密鑰,其中包含站點和密鑰的信息,使用的 ${System_id}:Station:Location 格式來查詢站點的地理空間索引。

獲取用戶位置

構建應用程序的下一步是肯定用戶的當前位置。大多數應用程序經過操做系統提供的內置服務來實現這一點。該操做系統能夠爲應用程序提供基於內置於設備中的 GPS 硬件或近似於設備的可用 WiFi 網絡的位置。

查詢位置

在找到用戶的位置後,下一步是定位附近的共享單車站點。Redis 的地理空間功能能夠在用戶當前座標的給定距離內返回站點的信息。下面是一個使用 Redis 命令行接口的示例。

Apple 美國紐約店地址
Apple 紐約店店址地圖

想象我在紐約市第五大道上的蘋果店,我想去西區 37 街的 Mood,和個人好友 Swatch 聊天。我能夠乘出租車或地鐵,但我寧願騎自行車。附近有共享站點麼?我能夠在那裏租有一輛車去麼?

Apple 專賣店位於 40.76384, -73.97297。根據地圖,兩個自行車站點 —— Grand Army Plaza 和 Central Park South 和 East 58th St. & Madison —— 位於 500 英尺的範圍內(在以上地圖是藍色的)。

我可使用 Redis GEORADIUS 命令查詢紐約系統索引,查找半徑爲 500 英尺的站點:

127.0.0.1:6379> GEORADIUS NYC:stations:location -73.97297 40.76384 500 ft
1) "NYC:station:3457"
2) "NYC:station:281"
複製代碼

Redis 使用地理空間索引中的元素做爲特定站點的元數據的鍵,返回在該半徑內找到的兩個自行車共享位置。下一步是查找這兩個站點的名稱:

127.0.0.1:6379> hget NYC:station:281 name
"Grand Army Plaza & Central Park S"
 
127.0.0.1:6379> hget NYC:station:3457 name
"E 58 St & Madison Ave"
複製代碼

這些鍵對應以上地圖肯定的站臺。若是願意,我能夠在 GEORADIUS 命令中添加更多的標誌,以獲取元素的列表、它們的座標以及它們與當前站點的距離:

127.0.0.1:6379> GEORADIUS NYC:stations:location -73.97297 40.76384 500 ft WITHDIST WITHCOORD ASC 
1) 1) "NYC:station:281"
   2) "289.1995"
   3) 1) "-73.97371262311935425"
      2) "40.76439830559216659"
2) 1) "NYC:station:3457"
   2) "383.1782"
   3) 1) "-73.97209256887435913"
      2) "40.76302702144496237"
複製代碼

查找與這些鍵相關聯的名稱會生成一個有序的站點列表,我能夠從中進行選擇。Redis 不提供方向或路由功能,所以我使用設備操做系統的路由功能來繪製從當前位置到所選自行車站點的路線。

GEORADIUS 函數能夠輕易在您喜歡的開發框架的 API 中實現,以便將位置功能添加到 app 中。

其餘查詢命令

除了 GEORADIUS 命令之外,Redis 還提供了三個用於從索引中查找數據的命令 GEOPOSGEODIST 和 GEORADIUSBYMEMBER

GEOPOS 命令能夠從地理散列中提供給定元素的座標。例如,若是我知道在 West 38th and 8th 有共享單車站點,並且它的 ID 是 523,那麼該站點的元素名稱是 NYC:station:523。使用 Redis,我能夠找到站點的經度和維度:

127.0.0.1:6379> geopos NYC:stations:location NYC:station:523
1) 1) "-73.99138301610946655"
   2) "40.75466497634030105"
複製代碼

GEODIST 命令提供兩個元素之間的距離索引。若是我想要找出 Grand Army Plaza 和 Central Park South 和在 East 58th St. & Madison 站點之間的距離, 我會發出如下命令:

127.0.0.1:6379> GEODIST NYC:stations:location NYC:station:281 NYC:station:3457 ft
"671.4900"
複製代碼

最後,GEORADIUSBYMEMBER 命令相似於 GEORADIUS 命令,但該命令沒有接受一組座標,而是取索引的另外一個成員的名稱,並返回以該成員爲中心所給定半徑內的全部成員。要找到 Grand Army Plaza 和 Central Park 南 1000 英尺範圍內的全部車站,請輸入如下內容:

127.0.0.1:6379> GEORADIUSBYMEMBER NYC:stations:location NYC:station:281 1000 ft WITHDIST
1) 1) "NYC:station:281"
   2) "0.0000"
2) 1) "NYC:station:3132"
   2) "793.4223"
3) 1) "NYC:station:2006"
   2) "911.9752"
4) 1) "NYC:station:3136"
   2) "940.3399"
5) 1) "NYC:station:3457"
   2) "671.4900"
複製代碼

雖然這個示例側重於使用 Python 和 Redis 來解析數據並構建自行車共享系統位置的索引,但它能夠很容易地推廣到定位餐館、公共交通或任何其餘類型的地方,以幫助用戶查找。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索