Redis中的地理信息处理(redistemplate)

发表于2020-06-19,长度2733, 691个单词, 7分钟读完
Flag Counter

在之前的文章《elasticsearch 地理信息处理》中我们通介绍了es提供的地理信息计算能力。实际上提供地信存储和计算能力的存储中间件很多,很多数据库都支持,hbase也支持;redis从3.2开始也提供了功能受限的地理信息处理能力。但是提供的能力太有限,所以这里我们搭配Java原生代码来实现一些基本功能。

MySQL 不支持地信,postgresql支持

redis geo 功能

redis 提供的geo能力有限,现在是定义在org.springframework.data.redis.core.DefaultGeoOperations中的geo api:

org.springframework.data.redis.core.RedisTemplate提供了获取GeoOperations的入口:

	// cache singleton objects (where possible)
	private ValueOperations<K, V> valueOps;
	private ListOperations<K, V> listOps;
	private SetOperations<K, V> setOps;
	private ZSetOperations<K, V> zSetOps;
	private GeoOperations<K, V> geoOps;
	private HyperLogLogOperations<K, V> hllOps;

可以看到,对地理点redis只支持存储、查询、删除和点间直线距离计算(将地球按照标准球形处理)。操作起来也简单,这里就不贴代码了。

与Java搭配

因为redis没有提供比如点是否在多边形区域内的api,我们获取到点集后需要自行计算。比如通过geoRadius或geoRadiusByMember方法拿到一堆点集,然后我们想判断某个给定点是否在查出来的那些点构成的多边形内部,可以使用下面这个方法:

public static boolean isPointInPolygon(double ALon, double ALat, Point[] ps) {
    double dLon1 = 0.0D;
    double dLon2 = 0.0D;
    double dLat1 = 0.0D;
    double dLat2 = 0.0D;
    if (ps.length < 3) {
        return false;
    } else {
        int iSum = 0;
        int iCount = ps.length;

        for(int iIndex = 0; iIndex < iCount; ++iIndex) {
            if (iIndex == iCount - 1) {
                dLon1 = ps[iIndex].getX();
                dLat1 = ps[iIndex].getY();
                dLon2 = ps[0].getX();
                dLat2 = ps[0].getY();
            } else {
                dLon1 = ps[iIndex].getX();
                dLat1 = ps[iIndex].getY();
                dLon2 = ps[iIndex + 1].getX();
                dLat2 = ps[iIndex + 1].getY();
            }

            if ((ALat >= dLat1 && ALat < dLat2 || ALat >= dLat2 && ALat < dLat1) && Math.abs(dLat1 - dLat2) > 0.0D) {
                double dLon = dLon1 - (dLon1 - dLon2) * (dLat1 - ALat) / (dLat1 - dLat2);
                if (dLon < ALon) {
                    ++iSum;
                }
            }
        }

        if (iSum % 2 != 0) {
            return true;
        } else {
            return false;
        }
    }
}

还有一种方法是使用awt包提供的多边形计算api:

private static boolean checkWithJdkGeneralPath(Point2D.Double point, List<Point2D.Double> polygon) {  
    java.awt.geom.GeneralPath p = new java.awt.geom.GeneralPath();  

    Point2D.Double first = polygon.get(0);  
    p.moveTo(first.x, first.y);  
    polygon.remove(0);  
    for (Point2D.Double d : polygon) {  
        p.lineTo(d.x, d.y);  
    }  

    p.lineTo(first.x, first.y);  

    p.closePath();  

    return p.contains(point);  
} 

不过有一点要特别注意:从上面的方法实现可以看到,无论哪一种方法,传入的数组集合是有序的,从第一个点开始向后连线,最后一个点练到第一个点上,这样构成一个多边形。所以在调用这个方法以前要先保证这一点。

好在redis将点存在了zset中,所以直接拿出来就能用


名言:欲达高峰,必忍其痛;欲予动容,必入其中;欲安思命,必避其凶;欲情难纵,必舍其空;欲心若怡,必展其宏;欲想成功,必有其梦;欲戴王冠,必承其重!

Written on June 19, 2020
分类: dev, 标签: java redis
如果你喜欢,请赞赏! davelet