java实现 GeoHash 算法(GeoHash位数与距离对应关系)

最近遇到一个问题,需要根据基站的经纬度给基站进行分组,两个基站距离相差10米内分到一组,最开始是通过两层循环处理的(笛卡尔积),这样的速度非常慢。最开始因为这个是偶尔还会运行一次,对效率也没要求,项目开始阶段需求紧,站也少,能凑合用,就没去优化,现在站达到40-50万个,这个就用不了了,就到处找资料,最后找到geohash算法,正好解决我这个问题,消除掉笛卡尔积问题,速度几十倍的提升,在此做下记录,以防后面忘记!

算法工具类:

package com.nokia.uip.common;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;

public class GeoHash {
public static final double MINLAT = -90;
public static final double MAXLAT = 90;
public static final double MINLNG = -180;
public static final double MAXLNG = 180;

private static int numbits = 20; //经纬度单独编码长度

private static double minLat;
private static double minLng;


private final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
        '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
        'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

//定义编码映射关系
final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
//初始化编码映射内容
static {
    int i = 0;
    for (char c : digits) {
        lookup.put(c, i++);
    }
}

public GeoHash(){
    setMinLatLng();
}

public String encode(double lat, double lon) {
    BitSet latbits = getBits(lat, -90, 90);
    BitSet lonbits = getBits(lon, -180, 180);
    StringBuilder buffer = new StringBuilder();
    for (int i = 0; i < numbits; i++) {
        buffer.append( (lonbits.get(i))?'1':'0');
        buffer.append( (latbits.get(i))?'1':'0');
    }
    String code = base32(Long.parseLong(buffer.toString(), 2));
    //Log.i("okunu", "encode  lat = " + lat + "  lng = " + lon + "  code = " + code);
    return code;
}

public ArrayList<String> getArroundGeoHash(double lat, double lon){
    //Log.i("okunu", "getArroundGeoHash  lat = " + lat + "  lng = " + lon);
    ArrayList<String> list = new ArrayList<>();
    double uplat = lat + minLat;
    double downLat = lat - minLat;

    double leftlng = lon - minLng;
    double rightLng = lon + minLng;

    String leftUp = encode(uplat, leftlng);
    list.add(leftUp);

    String leftMid = encode(lat, leftlng);
    list.add(leftMid);

    String leftDown = encode(downLat, leftlng);
    list.add(leftDown);

    String midUp = encode(uplat, lon);
    list.add(midUp);

    String midMid = encode(lat, lon);
    list.add(midMid);

    String midDown = encode(downLat, lon);
    list.add(midDown);

    String rightUp = encode(uplat, rightLng);
    list.add(rightUp);

    String rightMid = encode(lat, rightLng);
    list.add(rightMid);

    String rightDown = encode(downLat, rightLng);
    list.add(rightDown);

    //Log.i("okunu", "getArroundGeoHash list = " + list.toString());
    return list;
}

//根据经纬度和范围,获取对应的二进制
private BitSet getBits(double lat, double floor, double ceiling) {
    BitSet buffer = new BitSet(numbits);
    for (int i = 0; i < numbits; i++) {
        double mid = (floor + ceiling) / 2;
        if (lat >= mid) {
            buffer.set(i);
            floor = mid;
        } else {
            ceiling = mid;
        }
    }
    return buffer;
}

//将经纬度合并后的二进制进行指定的32位编码
private String base32(long i) {
    char[] buf = new char[65];
    int charPos = 64;
    boolean negative = (i < 0);
    if (!negative){
        i = -i;
    }
    while (i <= -32) {
        buf[charPos--] = digits[(int) (-(i % 32))];
        i /= 32;
    }
    buf[charPos] = digits[(int) (-i)];
    if (negative){
        buf[--charPos] = '-';
    }
    return new String(buf, charPos, (65 - charPos));
}

private void setMinLatLng() {
    minLat = MAXLAT - MINLAT;
    for (int i = 0; i < numbits; i++) {
        minLat /= 2.0;
    }
    minLng = MAXLNG - MINLNG;
    for (int i = 0; i < numbits; i++) {
        minLng /= 2.0;
    }
}

//根据二进制和范围解码
private double decode(BitSet bs, double floor, double ceiling) {
    double mid = 0;
    for (int i=0; i<bs.length(); i++) {
        mid = (floor + ceiling) / 2;
        if (bs.get(i)){
            floor = mid;
        }
        else {
            ceiling = mid;
        }
    }
    return mid;
}

//对编码后的字符串解码
public double[] decode(String geohash) {
    StringBuilder buffer = new StringBuilder();
    for (char c : geohash.toCharArray()) {
        int i = lookup.get(c) + 32;
        buffer.append( Integer.toString(i, 2).substring(1) );
    }

    BitSet lonset = new BitSet();
    BitSet latset = new BitSet();

    //偶数位,经度
    int j =0;
    for (int i=0; i< numbits*2;i+=2) {
        boolean isSet = false;
        if ( i < buffer.length() ) {
            isSet = buffer.charAt(i) == '1';
        }
        lonset.set(j++, isSet);
    }

    //奇数位,纬度
    j=0;
    for (int i=1; i< numbits*2;i+=2) {
        boolean isSet = false;
        if ( i < buffer.length() ) {
            isSet = buffer.charAt(i) == '1';
        }
        latset.set(j++, isSet);
    }

    double lon = decode(lonset, -180, 180);
    double lat = decode(latset, -90, 90);

    return new double[] {lat, lon};
}

public static void main(String[] args)  throws Exception{
    GeoHash geohash = new GeoHash();
        String s = geohash.encode(40.222012, 116.248283);
        System.out.println(s);
    System.out.println(geohash.encode(25.34722200,98.4827770));
    System.out.println(geohash.encode(22.44841800,99.9760520));

    //geohash.getArroundGeoHash(40.222012, 116.248283);
        double[] geo = geohash.decode(s);
        System.out.println(geo[0]+" "+geo[1]);
}
}

 

 

GeoHash位数与距离对应关系

这里通过算法encode的值是8位的时候, 距离是19米 ,刚好符合我的要求,工具代码类里这个参数是控制计算的长度的,当为20的时候符合我的要求

private static int numbits = 20; //经纬度单独编码长度

  代码留档

@Test
    public void test1(){
        List<Map<String,Object>> list = quarterCloseAreaResService.queryAreaCompute();
        GeoHash geoHash = new GeoHash();
        Map<String,List<Map<String,Object>>> concurrentHashMap = new ConcurrentHashMap<String,List<Map<String,Object>>>();
        list.parallelStream().forEach(e->{
            List<Map<String,Object>> concurrentList = null;
            String value = geoHash.encode(Double.parseDouble(e.get("compute_latitude").toString()),Double.parseDouble(e.get("compute_longitude").toString()));
            if(concurrentHashMap.containsKey(value)){
                concurrentList = concurrentHashMap.get(value);
                concurrentList.add(e);
                concurrentHashMap.put(value,concurrentList);
            }else{
                concurrentList = new ArrayList<>();
                concurrentList.add(e);
                concurrentHashMap.put(value,concurrentList);
            }
            e.put(e.get("id").toString(),value);
        }); 
        AtomicInteger smallGroup = new AtomicInteger(0);
        List<String> keyList = new ArrayList<>(concurrentHashMap.keySet()); 
        keyList.parallelStream().forEach(key->{
            int id = smallGroup.incrementAndGet();
            Map<String,Object> paramMap = new HashMap<>();
            List<Map<String, Object>> value = concurrentHashMap.get(key); 
            List<Integer> ids = new ArrayList<>();
            value.stream().forEach(e->{
                ids.add(Integer.valueOf(e.get("id").toString()));
            }); 
            paramMap.put("smallGroup",id);
            paramMap.put("list",ids); 
            quarterCloseAreaResService.updateAreaComputeGroup(paramMap); 
        });
     }

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Python中实现geohash算法,你可以使用geohash库。首先,你需要确保已经安装了geohash库。你可以使用pip命令进行安装,命令如下:pip install geohash。如果安装成功后,仍然无法导入geohash模块并提示ImportError: No module named 'geohash'的错误,你可以尝试以下方法进行修复:将Geohash文件名改为geohash,然后在geohash文件夹下的__init__.py文件中将from geohash import decode_exactly, decode, encode改为from .geohash import decode_exactly, decode, encode(在geohash前面加一个'.')。这样应该可以解决导入模块的问题。[1] 一旦你成功导入了geohash库,你就可以使用它来进行geohash算法实现。例如,你可以使用decode_exactly函数来将geohash字符串解码为经度和纬度的坐标。例如,你可以使用以下代码来解码geohash字符串"wm6nc":print(geohash.decode_exactly("wm6nc")),这将返回一个包含经度、纬度、经度精度和纬度精度的元组。(30.73974609375, 104.12841796875, 0.02197265625, 0.02197265625)[2] geohash库还提供了其他功能模块,如距离度量和几何计算。距离度量模块提供了与距离相关的函数,如distance和dimensions。几何模块提供了将多边形转换为geohash列表的函数,如polygon_to_geohashgeohash_to_polygon。这些功能可以帮助你在地理区域中进行近似地理差异的计算。你可以使用shapely库进行几何计算[3]。 综上所述,要在Python中实现geohash算法,你可以使用geohash库,并根据需要使用其提供的不同功能模块。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值