redis3.2新功能--GEO地理位置命令介紹與實戰開發

概述

redis3.2發佈rc版本已經有一段時間了,估計RedisConf 2016左右,3.2版本就能release了。3.2版本中增長的最大功能就是對GEO(地理位置)的支持。提及Redis的GEO特性,最大的貢獻仍是我們中國人。redis做者在對3.2引進新特性的博客中介紹了爲何支持GEO。GEO hashing的api是在Ardb實現的,Ardb是github用戶yinqiwen實現的基於redis協議實現的nosql系統,Ardb支持除了redis、還有LevelDB、RocksDB
、LMDB等kv引擎。其中Ardb實現了GEO hashing功能。從Ardb做者的用戶名和標識的位置在深圳能夠看出Ardb做者應該是咱中國人。Ardb是用c++寫的。redis另外一個開發者Matt Stancliff從Ardb提取GEO庫,用C語言改寫,整合進redis的一個本身的分支,並被redis做者接受,合併進了3.2版本。GEO目前提供如下6個命令。html

  • 一、geoadd:增長某個地理位置的座標。
  • 二、geopos:獲取某個地理位置的座標。
  • 三、geodist:獲取兩個地理位置的距離。
  • 四、georadius:根據給定地理位置座標獲取指定範圍內的地理位置集合。
  • 五、georadiusbymember:根據給定地理位置獲取指定範圍內的地理位置集合。
  • 六、geohash:獲取某個地理位置的geohash值。

地理位置的座標是以WGS84爲標準,WGS84,全稱World Geodetic System 1984,是爲GPS全球定位系統使用而創建的座標系統。java

GEO命令

下面來看看具體每一個命令的用法。c++

geoadd

geoadd用來增長地理位置的座標,能夠批量添加地理位置,命令格式爲:git

GEOADD key longitude latitude member [longitude latitude member ...]

key標識一個地理位置的集合。longitude latitude member標識了一個地理位置的座標。longitude是地理位置的經度,latitude是地理位置的緯度。member是該地理位置的名稱。GEOADD能夠批量給集合添加一批地理位置。github

geopos

geopos能夠獲取地理位置的座標,能夠批量獲取多個地理位置的座標,命令格式爲:redis

GEOPOS key member [member ...]

geodist

geodist用來獲取兩個地理位置的距離,命令格式爲:算法

GEODIST key member1 member2 [m|km|ft|mi]

單位能夠指定爲如下四種類型:sql

  • m:米,距離單位默認爲米,不傳遞該參數則單位爲米。
  • km:千米。
  • mi:英里。
  • ft:英尺。

georadius

georadius能夠根據給定地理位置座標獲取指定範圍內的地理位置集合。命令格式爲:apache

GEORADIUS key longitude latitude radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]

longitude latitude標識了地理位置的座標,radius表示範圍距離,距離單位能夠爲m|km|ft|mi,還有一些可選參數:api

  • WITHCOORD:傳入WITHCOORD參數,則返回結果會帶上匹配位置的經緯度。
  • WITHDIST:傳入WITHDIST參數,則返回結果會帶上匹配位置與給定地理位置的距離。
  • ASC|DESC:默認結果是未排序的,傳入ASC爲從近到遠排序,傳入DESC爲從遠到近排序。
  • WITHHASH:傳入WITHHASH參數,則返回結果會帶上匹配位置的hash值。
  • COUNT count:傳入COUNT參數,能夠返回指定數量的結果。

georadiusbymember

georadiusbymember能夠根據給定地理位置獲取指定範圍內的地理位置集合。georadius命令傳遞的是座標,georadiusbymember傳遞的是地理位置。georadius更爲靈活,能夠獲取任何座標點範圍內的地理位置。可是大多數時候,只是想獲取某個地理位置附近的其餘地理位置,使用georadiusbymember則更爲方便。georadiusbymember命令格式爲(命令可選參數與georadius含義同樣):

GEORADIUSBYMEMBER key member radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]

geohash

geohash能夠獲取某個地理位置的geohash值。geohash是將二維的經緯度轉換成字符串hash值的算法,後面會具體介紹geohash原理。能夠批量獲取多個地理位置的geohash值。命令格式爲:

GEOHASH key member [member ...]

redis GEO實現

redis GEO實現主要包含了如下兩項技術:

  • 一、使用geohash保存地理位置的座標。
  • 二、使用有序集合(zset)保存地理位置的集合。

geohash

geohash的思想是將二維的經緯度轉換成一維的字符串,geohash有如下三個特色:

  • 一、字符串越長,表示的範圍越精確。編碼長度爲8時,精度在19米左右,而當編碼長度爲9時,精度在2米左右。
  • 二、字符串類似的表示距離相近,利用字符串的前綴匹配,能夠查詢附近的地理位置。這樣就實現了快速查詢某個座標附近的地理位置。
  • 三、geohash計算的字符串,能夠反向解碼出原來的經緯度。

這三個特性讓geohash特別適合表示二維hash值。這篇文章:GeoHash核心原理解析詳細的介紹了geohash的原理,想要了解geohash實現的朋友能夠參考這篇文章。

redis GEO命令實現

知道了redis使用有序集合(zset)保存地理位置數據(想了解redis有序集合的,能夠參看這篇文章《有序集合對象》),以及geohash的特性,就很容易理解redis是如何實現redis GEO命令了。細心的讀者可能發現,redis沒有實現地理位置的刪除命令。不過因爲GEO數據保存在zset中,能夠用zrem來刪除某個地理位置。

  • geoadd命令增長地理位置的時候,會先計算地理位置座標的geohash值,而後地理位置做爲有序集合的member,geohash做爲該member的score。而後使用zadd命令插入到有序集合。
  • geopos命令則先根據地理位置獲取geohash值,而後decode獲得地理位置的座標。
  • geodist命令先根據兩個地理位置各自獲得座標,而後計算兩個座標的距離。
  • georadius和georadiusbymember使用相同的實現,georadiusbymember多了一步把地理位置轉換成對應的座標。而後查找該座標和周圍對應8個座標符合距離要求的地理位置。由於geohash獲得的值實際上是個格子,並非點,這樣經過計算周圍對應8個座標就能解決邊緣問題。因爲使用有序集合保存地理位置,在對地列位置基於範圍查詢,就至關於實現了zrange命令,內部的實現確實與zrange命令一致,只是geo有些特別的處理,好比得到的某個地理位置,還須要計算該地理位置是否符合給定的距離訪問。
  • geohash則直接返回了地理位置的geohash值。

redis關於geohash使用了Ardb的geohash庫geohash-int,redis使用的geohash編碼長度爲26位。能夠精確到0.59m的精度。

總結

經過本文,撥開GEO身後的雲霧,能夠看出redis藉助了有序集合(zset)和geohash,加上redis自己實現的命令框架,能夠很容易的實現地理位置相關的命令。

相關代碼實現:

package org.lanqiao.ssm.common.redis;

import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.alibaba.druid.pool.vendor.SybaseExceptionSorter;
import com.google.common.collect.Maps;

import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.geo.GeoRadiusParam;

/**
 * 
 * @Title: RedisTest.java
 * @Package org.lanqiao.ssm.common.redis
 * @Description: TODO(本地jedis支持的單節點操做redis)
 * @author 劉偉 15818570028@163.com
 * @date 2016年10月11日 上午9:29:35
 * @version V1.0
 */
public class RedisGeoTest {
	private static final Log log = LogFactory.getLog(RedisGeoTest.class);

	public static void main(String[] args) {
		testGeoadds();
		testGeoradius();

	}

	/**
	 * 
	 * @Description:單個添加地理位置
	 */
	public static void testGeoadd() {
		Jedis jedis = new Jedis("10.1.10.74", 6379);
		jedis.auth("123");
		jedis.geoadd("citys", 116.41667, 39.91667, "beijin");
		jedis.geoadd("citys", 121.48, 31.22, "Shanghai");
		jedis.geoadd("citys", 117.20, 39.13, "Tianjin");
		List<GeoCoordinate> geopos = jedis.geopos("citys", "beijin");
		System.out.println(geopos);
	}

	/**
	 * 
	 * @Description:批量添加地理位置
	 */
	public static void testGeoadds() {
		Jedis jedis = new Jedis("10.1.10.74", 6379);
		jedis.auth("123");
		GeoCoordinate beijin = new GeoCoordinate(116.41667, 39.91667);
		GeoCoordinate Shanghai = new GeoCoordinate(121.48, 31.22);
		GeoCoordinate Tianjin = new GeoCoordinate(117.20, 39.13);
		Map<String, GeoCoordinate> map = Maps.newHashMap();
		map.put("cs", beijin);
		map.put("hh", Shanghai);
		map.put("sy", Tianjin);
		jedis.geoadd("citys", map);
	}

	/**
	 * 
	 * @Description:georadius能夠根據給定地理位置座標獲取指定範圍內的地理位置集合
	 */
	public static void testGeoradius() {
		Jedis jedis = new Jedis("10.1.10.74", 6379);
		jedis.auth("123");
		GeoRadiusParam withCoord = GeoRadiusParam.geoRadiusParam().count(6).sortAscending().withDist().withCoord();
		List<GeoRadiusResponse> georadius = jedis.georadius("citys", 121.48, 31.22, 5000, GeoUnit.KM, withCoord);
		georadius.forEach(e ->{System.out.println(e.getMemberByString());});
	}
}
相關文章
相關標籤/搜索