前言
Redis提供了GEO地理索引的实现,其存储结构本质上来说是一个ZSET,这一点可以从Redisson中的GEO接口看出来【public interface RGeoAsync extends RScoredSortedSetAsync】
Redis 命令描述
- GEOHASH 返回一个或多个位置元素的 Geohash 表示,Redis 使用 Geohash 技术的变体来表示元素的位置
- GEOPOS 从 key 里返回所有给定位置元素的位置(经度和纬度)
- GEODIST 返回两个给定位置之间的距离
- GEORADIUS 以给定的经纬度为中心, 找出某一半径内的元素
- GEOADD 将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中
- GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定
注意:没有 GEODEL 命令,因为可以使用 ZREM 来删除元素,地理索引结构只是一个有序集合【zset】。
注意:如果数据量过亿甚至更大,可能造成redis节点卡顿,这时就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 zset 集合的大小。(注意:zset集合大小,进行合适地切分)
新增地理元素
csharp
RGeo<String> geo = client.getGeo("GEO_TEST");
geo.add(new GeoEntry(121.472641,31.231707,"上海"));
// 新增返回1,更新返回0
long 添加数量 = geo.add(new GeoEntry(116.405289,39.904987,"北京"));
System.out.println("添加数量:"+添加数量);
经纬度越界
- 有效经度从-180到180度。
- 有效纬度从 -85.05112878 到 85.05112878 度。
当用户尝试索引超出指定范围的坐标时,该命令将报告错误。
csharp
// 异常:RedisException: ERR invalid longitude,latitude pair xxx,xxx
geo.add(new GeoEntry(183.549130,22.198750,"经度越界"));
geo.add(new GeoEntry(113.549130,92.198750,"纬度越界"));
计算两个元素之间的距离
ini
Double distance = geo.dist("北京", "上海", GeoUnit.KILOMETERS);
System.out.println("北京和上海的距离:" + distance + " 千米");
输出信息
北京和上海的距离:1067.597 千米
元素的经纬度信息
ini
Map<String, GeoPosition> pos = geo.pos("北京", "上海");
System.out.println("两个元素的经纬度信息\n" + pos);
输出信息
两个元素的经纬度信息
{上海=GeoPosition [longitude=121.47264093160629, latitude=31.231707441819232], 北京=GeoPosition [longitude=116.40528827905655, latitude=39.90498588819134]}
附近的元素
ini
// 返回10个在上海附近200米的元素名称和距离
Map<String, Double> radiusWithDistance = geo.radiusWithDistance(
"上海", 500, GeoUnit.KILOMETERS, GeoOrder.ASC, 10);
System.out.println("上海附近500千米的元素名称和距离[千米]\n" + radiusWithDistance);
// 经度 120,纬度30,半径500千米,这个范围内的元素
List<String> cities = geo.radius(120, 30, 500, GeoUnit.KILOMETERS);
System.out.println("经度120,纬度30,半径500千米,这个范围内的元素\n" + cities);
// 返回10个在经度15,纬度37附近200米内的元素名称和距离,方便在列表中显示
Map<String, Double> radiusWithDistance2 = geo.radiusWithDistance(
120, 30, 500, GeoUnit.KILOMETERS, GeoOrder.ASC, 10);
System.out.println("经度120,纬度30附近500千米内的元素名称和距离[千米]\n" + radiusWithDistance2);
输出信息
上海附近500千米的元素名称和距离[千米]
{上海=0.0, 杭州=164.0861, 南京=271.5419, 合肥=403.2241}
经度120,纬度30,半径500千米,这个范围内的元素
[南昌, 合肥, 杭州, 上海, 南京, 福州]
经度120,纬度30附近500千米内的元素名称和距离[千米]
{杭州=35.2204, 上海=196.5638, 南京=255.6618, 合肥=331.7151, 南昌=424.5977, 福州=441.8072}
redisson实现
RGeo的实现类是【RedissonGeo】,其中基本都是直接调用redis的指令来完成计算,没有特别的自定义逻辑,所以不进行详细介绍。例如:
scss
public RFuture<Map<V, Double>> radiusWithDistanceAsync(double longitude, double latitude, double radius,
GeoUnit geoUnit, GeoOrder geoOrder, int count) {
return commandExecutor.readAsync(getName(), codec, GEORADIUS_RO_DISTANCE, getName(), convert(longitude),
convert(latitude), radius, geoUnit, "WITHDIST", "COUNT", count, geoOrder);
}