詳解Redis中地理位置功能Geospatial的應(yīng)用
Geospatial Indexes 是 Redis 提供的一種數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)和查詢地理位置信息。它可以將地理位置的經(jīng)度和緯度編碼為二維平面上的點(diǎn),并支持根據(jù)距離或矩形區(qū)域查詢附近的地理位置點(diǎn),這使得它在很多場(chǎng)景下被廣泛應(yīng)用,比如 LBS(Location Based Service)、智能推薦、出行規(guī)劃等。
Redis 中 Geospatial Indexes 是通過(guò)有序集合實(shí)現(xiàn)的,其內(nèi)部使用 zset 數(shù)據(jù)類型來(lái)存儲(chǔ)地理位置點(diǎn)的經(jīng)緯度和成員信息。其中,經(jīng)緯度以浮點(diǎn)數(shù)表示,成員信息則可以是用戶 ID、商鋪名稱等。
在使用 Geospatial Indexes 時(shí),我們需要注意以下幾個(gè)方面:
- 地理位置點(diǎn)的經(jīng)緯度采用 WGS-84 坐標(biāo)系表示;
- 距離單位默認(rèn)為米,可以通過(guò)參數(shù)設(shè)置為其他單位;
- 矩形區(qū)域查詢默認(rèn)采用左閉右開(kāi)的方式,即包括左邊界,不包括右邊界。
Geospatial Indexes 的數(shù)據(jù)結(jié)構(gòu)
Geospatial Indexes 在 Redis 中是通過(guò) zset 實(shí)現(xiàn)的,其中地理位置點(diǎn)的經(jīng)緯度被編碼為 zset 中每個(gè) member 的分?jǐn)?shù)。具體來(lái)說(shuō),Redis 在將經(jīng)緯度編碼為分?jǐn)?shù)時(shí),使用了 zset 的有序性質(zhì),將其轉(zhuǎn)化為一個(gè)唯一的、不可重復(fù)的浮點(diǎn)數(shù)。
例如,如果我們要存儲(chǔ)以下三個(gè)地理位置點(diǎn):
39.9042° N, 116.4074° E
上海 31.2304° N, 121.4737° E
廣州 23.1291° N, 113.2644° E
則可以將經(jīng)緯度編碼為分?jǐn)?shù),存入 zset 中,如下圖所示:
ZADD city_geo_location 116.4074 39.9042 "北京"
ZADD city_geo_location 121.4737 31.2304 "上海"
ZADD city_geo_location 113.2644 23.1291 "廣州"
此時(shí),zset 中的每個(gè) member 都代表了一個(gè)地理位置點(diǎn),其分?jǐn)?shù)則代表了該點(diǎn)的唯一標(biāo)識(shí),在進(jìn)行距離或矩形區(qū)域查詢時(shí)將會(huì)用到。
常用命令
Redis 中的 Geospatial Indexes 提供了一組命令來(lái)管理和查詢地理位置數(shù)據(jù),包括以下命令:
1.GEOADD:向有序集合中添加一個(gè)或多個(gè)地理位置元素。
語(yǔ)法:GEOADD key longitude latitude member [longitude latitude member ...]
參數(shù):
key
:必需,要添加元素的有序集合的鍵名。longitude
:必需,要添加元素的經(jīng)度值,范圍為 -180 到 180 度。latitude
:必需,要添加元素的緯度值,范圍為 -90 到 90 度。member
:必需,要添加元素的成員名,必須為字符串類型。
示例:GEOADD restaurants 139.7329 35.6634 "Sushi Dai" 139.7712 35.7100 "Afuri Ramen" 139.7198 35.7101 "Komoro Soba"
2.GEOPOS:獲取指定成員在有序集合中的經(jīng)緯度坐標(biāo)。
語(yǔ)法:GEOPOS key member [member ...]
參數(shù):
key
:必需,要獲取經(jīng)緯度坐標(biāo)的有序集合的鍵名。member
:必需,要獲取經(jīng)緯度坐標(biāo)的元素的成員名,可以指定多個(gè)成員名。
返回值:一個(gè)二維數(shù)組,每個(gè)子數(shù)組表示一個(gè)成員的經(jīng)緯度坐標(biāo),每個(gè)子數(shù)組包含兩個(gè)元素,分別表示經(jīng)度和緯度。
示例:GEOPOS restaurants "Sushi Dai" "Komoro Soba"
,返回值為 [["139.7329","35.6634"],["139.7198","35.7101"]]
3.GEODIST:獲取有序集合中兩個(gè)成員之間的距離。
語(yǔ)法:GEODIST key member1 member2 [unit]
參數(shù):
key
:必需,要計(jì)算距離的有序集合的鍵名。member1
:必需,第一個(gè)成員的名字。member2
:必需,第二個(gè)成員的名字。unit
:可選,默認(rèn)為米,表示要返回的距離單位,可以是以下四種單位之一:"m"(米)、"km"(千米)、"mi"(英里)、"ft"(英尺)。
返回值:兩個(gè)成員之間的距離值,以指定的單位表示。
示例:GEODIST restaurants "Sushi Dai" "Komoro Soba" km
,返回值為 "2.0499"
4.GEORADIUS:按照給定的經(jīng)緯度坐標(biāo)和半徑范圍查找有序集合中符合條件的元素。
語(yǔ)法:GEORADIUS key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
參數(shù):
key
:必需,要查詢的有序集合的鍵名。longitude
:必需,中心點(diǎn)的經(jīng)度值,范圍為 -180 到 180 度。latitude
:必需,中心點(diǎn)的緯度值,范圍為 -90 到 90 度。radius
:必需,半徑范圍,可以是以下格式之一:m(米)、km(千米)、mi(英里)或 ft(英尺)。m|km|mi|ft
:必需,半徑范圍的單位,可以是以下四種單位之一:"m"(米)、"km"(千米)、"mi"(英里)、"ft"(英尺)。WITHCOORD
:可選,指示返回結(jié)果是否包含元素的經(jīng)緯度坐標(biāo)。如果指定了該參數(shù),則結(jié)果將包含經(jīng)緯度坐標(biāo),否則不包含經(jīng)緯度坐標(biāo)。WITHDIST
:可選,指示返回結(jié)果是否包含元素與中心點(diǎn)之間的距離值。如果指定了該參數(shù),則結(jié)果將包含距離值,否則不包含距離值。ASC|DESC
:可選,指示返回結(jié)果是否按照距離值(從小到大)排序。如果指定了該參數(shù),則結(jié)果將按距離值排序,否則默認(rèn)按有序集合中的順序返回結(jié)果。COUNT count
:可選,指示返回結(jié)果的數(shù)量限制。如果指定了該參數(shù),則結(jié)果將最多包含count
個(gè)元素,否則返回所有符合條件的元素。
返回值:若干個(gè)符合條件的元素(根據(jù)查詢參數(shù)而定),每個(gè)元素由成員名、經(jīng)度坐標(biāo)和緯度坐標(biāo)組成。如果指定了 WITHDIST 參數(shù),則每個(gè)元素還包含距離值。如果指定了 WITHCOORD 參數(shù),則每個(gè)元素還包含經(jīng)緯度坐標(biāo)。
示例:GEORADIUS restaurants 139.7329 35.6634 5 km WITHCOORD WITHDIST
,表示查找以經(jīng)緯度 (139.7329, 35.6634) 為中心,半徑為 5 公里范圍內(nèi)的所有元素,并返回它們與中心點(diǎn)之間的距離值和經(jīng)緯度坐標(biāo)。
5.GEORADIUSBYMEMBER:按照給定的成員名和半徑范圍查找有序集合中符合條件的元素。
語(yǔ)法:GEORADIUSBYMEMBER key member radius m|km|mi|ft [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
參數(shù):
key
:必需,要查詢的有序集合的鍵名。member
:必需,中心點(diǎn)的成員名。radius
:必需,半徑范圍,可以是以下格式之一:m(米)、km(千米)、mi(英里)或 ft(英尺)。m|km|mi|ft
:必需,半徑范圍的單位,可以是以下四種單位之一:"m"(米)、"km"(千米)、"mi"(英里)、"ft"(英尺)。WITHCOORD
:可選,指示返回結(jié)果是否包含元素的經(jīng)緯度坐標(biāo)。如果指定了該參數(shù),則結(jié)果將包含經(jīng)緯度坐標(biāo),否則不包含經(jīng)緯度坐標(biāo)。WITHDIST
:可選,指示返回結(jié)果是否包含元素與中心點(diǎn)之間的距離值。如果指定了該參數(shù),則結(jié)果將包含距離值,否則不包含距離值。ASC|DESC
:可選,指示返回結(jié)果是否按照距離值(從小到大)排序。如果指定了該參數(shù),則結(jié)果將按距離值排序,否則默認(rèn)按有序集合中的順序返回結(jié)果。COUNT count
:可選,指示返回結(jié)果的數(shù)量限制。如果指定了該參數(shù),則結(jié)果將最多包含count
個(gè)元素,否則返回所有符合條件的元素。
返回值:若干個(gè)符合條件的元素(根據(jù)查詢參數(shù)而定),每個(gè)元素由成員名、經(jīng)度坐標(biāo)和緯度坐標(biāo)組成。如果指定了 WITHDIST 參數(shù),則每個(gè)元素還包含距離值。如果指定了 WITHCOORD 參數(shù),則每個(gè)元素還包含經(jīng)緯度坐標(biāo)。
示例:GEORADIUSBYMEMBER restaurants "Sushi Dai" 5 km WITHCOORD WITHDIST
,表示查找以成員名 "Sushi Dai" 對(duì)應(yīng)的經(jīng)緯度為中心,半徑為 5 公里范圍內(nèi)的所有元素,并返回它們與中心點(diǎn)之間的距離值和經(jīng)緯度坐標(biāo)。
6.GEOHASH:獲取指定成員在有序集合中的 Geohash 值。
語(yǔ)法:GEOHASH key member [member ...]
參數(shù):
key
:必需,要獲取 Geohash 值的有序集合的鍵名。member
:必需,要獲取 Geohash 值的元素的成員名,可以指定多個(gè)成員名。
返回值:一個(gè)數(shù)組,每個(gè)元素表示一個(gè)成員的 Geohash 值。
示例:GEOHASH restaurants "Sushi Dai" "Komoro Soba"
,返回值為 ["xn7743","xn773w"]
7.GEOINTERSECTS:檢查指定的兩個(gè)成員之間是否存在任何交集。
語(yǔ)法:GEOINTERSECTS key member1 member2
參數(shù):
key
:必需,要檢查交集的有序集合的鍵名。member1
:必需,第一個(gè)成員的名字。member2
:必需,第二個(gè)成員的名字。
返回值:一個(gè)整數(shù)值,如果兩個(gè)成員之間存在交集,則返回 1,否則返回 0。
示例:GEOINTERSECTS restaurants "Sushi Dai" "Komoro Soba"
,如果 "Sushi Dai" 和 "Komoro Soba" 代表的位置之間存在任何交集,則返回 1,否則返回 0。
實(shí)用場(chǎng)景示例
1. 找出某一經(jīng)緯度周?chē)牟宛^
import redis.clients.jedis.GeoRadiusResponse; import redis.clients.jedis.GeoUnit; import redis.clients.jedis.Jedis; import java.util.List; public class RestaurantFinder { private final static String HOST = "localhost"; private final static int PORT = 6379; private final static int TIMEOUT = 5000; private final static String PASSWORD = ""; private static final String KEY_RESTAURANT_LOCATION = "restaurant_location"; private static Jedis jedis = null; static { try { jedis = new Jedis(HOST, PORT, TIMEOUT); if (!PASSWORD.isEmpty()) { jedis.auth(PASSWORD); } } catch (Exception e) { e.printStackTrace(); } } /** * 添加餐館的經(jīng)緯度信息 * * @param longitude 經(jīng)度 * @param latitude 緯度 * @param name 餐館名稱 * @return */ public Long addRestaurant(double longitude, double latitude, String name) { return jedis.geoadd(KEY_RESTAURANT_LOCATION, longitude, latitude, name); } /** * 根據(jù)給定的坐標(biāo)和半徑查找周?chē)牟宛^ * * @param longitude 經(jīng)度 * @param latitude 緯度 * @param radius 半徑,單位為米 * @return */ public List<GeoRadiusResponse> findNearbyRestaurants(double longitude, double latitude, double radius) { return jedis.georadius(KEY_RESTAURANT_LOCATION, longitude, latitude, radius, GeoUnit.M); } /** * 測(cè)試 * * @param args */ public static void main(String[] args) { RestaurantFinder finder = new RestaurantFinder(); finder.addRestaurant(121.451087, 31.228591, "麥當(dāng)勞"); finder.addRestaurant(121.454987, 31.227568, "星巴克"); finder.addRestaurant(121.455831, 31.225719, "肯德基"); List<GeoRadiusResponse> restaurants = finder.findNearbyRestaurants(121.453289, 31.228032, 500); for (GeoRadiusResponse restaurant : restaurants) { System.out.println(restaurant.getMemberByString() + " " + restaurant.getDistance()); } } }
2. 按照距離排序查詢景點(diǎn)
import redis.clients.jedis.GeoRadiusParam; import redis.clients.jedis.GeoRadiusResponse; import redis.clients.jedis.GeoUnit; import redis.clients.jedis.Jedis; import java.util.List; public class ScenicSpotFinder { private final static String HOST = "localhost"; private final static int PORT = 6379; private final static int TIMEOUT = 5000; private final static String PASSWORD = ""; private static final String KEY_SPOT_LOCATION = "spot_location"; private static Jedis jedis = null; static { try { jedis = new Jedis(HOST, PORT, TIMEOUT); if (!PASSWORD.isEmpty()) { jedis.auth(PASSWORD); } } catch (Exception e) { e.printStackTrace(); } } /** * 添加景點(diǎn)的經(jīng)緯度信息 * * @param longitude 經(jīng)度 * @param latitude 緯度 * @param name 景點(diǎn)名稱 * @return */ public Long addScenicSpot(double longitude, double latitude, String name) { return jedis.geoadd(KEY_SPOT_LOCATION, longitude, latitude, name); } /** * 根據(jù)給定的坐標(biāo)和半徑查找周?chē)木包c(diǎn),并按照距離排序 * * @param longitude 經(jīng)度 * @param latitude 緯度 * @param radius 半徑,單位為米 * @return */ public List<GeoRadiusResponse> findNearbyScenicSpots(double longitude, double latitude, double radius) { GeoRadiusParam geoRadiusParam = GeoRadiusParam.geoRadiusParam().sortAscending(); return jedis.georadius(KEY_SPOT_LOCATION, longitude, latitude, radius, GeoUnit.M, geoRadiusParam); } /** * 測(cè)試 * * @param args */ public static void main(String[] args) { ScenicSpotFinder finder = new ScenicSpotFinder(); finder.addScenicSpot(121.451087, 31.228591, "東方明珠"); finder.addScenicSpot(121.454987, 31.227568, "外灘"); finder.addScenicSpot(121.455831, 31.225719, "人民廣場(chǎng)"); List<GeoRadiusResponse> spots = finder.findNearbyScenicSpots(121.453289, 31.228032, 500); for (GeoRadiusResponse spot : spots) { System.out.println(spot.getMemberByString() + " " + spot.getDistance()); } } }
3. 根據(jù)經(jīng)緯度計(jì)算兩點(diǎn)距離
import redis.clients.jedis.GeoUnit; import redis.clients.jedis.Jedis; public class DistanceCalculator { private final static String HOST = "localhost"; private final static int PORT = 6379; private final static int TIMEOUT = 5000; private final static String PASSWORD = ""; private static Jedis jedis = null; static { try { jedis = new Jedis(HOST, PORT, TIMEOUT); if (!PASSWORD.isEmpty()) { jedis.auth(PASSWORD); } } catch (Exception e) { e.printStackTrace(); } } /** * 計(jì)算兩個(gè)經(jīng)緯度之間的距離 * * @param longitude1 經(jīng)度1 * @param latitude1 緯度1 * @param longitude2 經(jīng)度2 * @param latitude2 緯度2 * @return */ public Double calculateDistance(double longitude1, double latitude1, double longitude2, double latitude2) { return jedis.geodist("distance", longitude1, latitude1, longitude2, latitude2, GeoUnit.M); } /** * 測(cè)試 * * @param args */ public static void main(String[] args) { DistanceCalculator calculator = new DistanceCalculator(); double distance = calculator.calculateDistance(121.453289, 31.228032, 121.451087, 31.228591); System.out.println(distance); } }
在實(shí)際開(kāi)發(fā)中,需要注意以下幾點(diǎn):
- 每個(gè)位置都需要一個(gè)唯一的標(biāo)識(shí)符。
- 經(jīng)度和緯度的值需要使用正確的格式。
- 半徑的單位為米。
- Redis 地理位置功能支持多種查詢方式,例如矩形查詢、關(guān)鍵字查詢等,需要根據(jù)實(shí)際需求進(jìn)行選擇。
以上就是詳解Redis中地理位置功能Geospatial的應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于Redis Geospatial的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用Redis實(shí)現(xiàn)防止接口重復(fù)提交功能
大家好,本篇文章主要講的是利用Redis實(shí)現(xiàn)防止接口重復(fù)提交功能,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12深入解析Redis的LRU與LFU算法實(shí)現(xiàn)
這篇文章主要重點(diǎn)介紹了Redis的LRU與LFU算法實(shí)現(xiàn),并分析總結(jié)了兩種算法的實(shí)現(xiàn)效果以及存在的問(wèn)題,并闡述其優(yōu)劣特性,感興趣的小伙伴跟著小編一起來(lái)看看吧2023-07-07利用控制臺(tái)如何對(duì)Redis執(zhí)行增刪改查命令
這篇文章主要給大家介紹了關(guān)于利用控制臺(tái)如何對(duì)Redis執(zhí)行增刪改查命令的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08Redis持久化方式之RDB和AOF的原理及優(yōu)缺點(diǎn)
在Redis中,數(shù)據(jù)可以分為兩類,即內(nèi)存數(shù)據(jù)和磁盤(pán)數(shù)據(jù),Redis?提供了兩種不同的持久化方式,其中?RDB?是快照備份機(jī)制,AOF?則是追加寫(xiě)操作機(jī)制,本文將詳細(xì)給大家介紹Redis?持久化方式RDB和AOF的原理及優(yōu)缺點(diǎn),感興趣的同學(xué)可以跟著小編一起來(lái)學(xué)習(xí)2023-06-06解析高可用Redis服務(wù)架構(gòu)分析與搭建方案
我們按照由簡(jiǎn)至繁的步驟,搭建一個(gè)最小型的高可用的Redis服務(wù)。 本文通過(guò)四種方案給大家介紹包含每種方案的優(yōu)缺點(diǎn)及詳細(xì)解說(shuō),具體內(nèi)容詳情跟隨小編一起看看吧2021-06-06Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù)的教程
RediSearch提供了一種簡(jiǎn)單快速的方法對(duì) hash 或者 json 類型數(shù)據(jù)的任何字段建立二級(jí)索引,然后就可以對(duì)被索引的 hash 或者 json 類型數(shù)據(jù)字段進(jìn)行搜索和聚合操作,這篇文章主要介紹了Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù),需要的朋友可以參考下2023-12-12