欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

在平面坐标系下实现GeoHash算法 JAVA

程序员文章站 2022-04-13 22:39:12
...

在平面坐标系下实现GeoHash算法 JAVA

GeoHash算法的思想就不赘述了,网上看到的算法都是针对经纬度坐标的,而在项目中可能遇到平面坐标及独立坐标系,因此用算法思想写了实现,对平面坐标XY进行编码。

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

/**
 * GeoHash计算工具类
 *
 * @author: yx
 * @Date: 2020/3/18
 * @Description:用于在坐标非经纬度时生成GeoHash值
 */
public class GeoHashXY {
    private double MIN_Y;
    private double MAX_Y ;
    private double MIN_X ;
    private double MAX_X ;

    private double minExtentLength;//用于计算单独编码长度即Geohash的长度
    private int level;//GeoHash长度

    private int numbitY;//经纬度单独编码长度,即进行对分的次数
    private int numbitX;
    private double minY;//XY在该level下的最小差距
    private double minX;

    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++);
        }
    }


    /**
     *
     * @params
     * @return
     * @Desription GeoHash构造函数,设置该对象最大边界的范围
     */
    public GeoHashXY(double max_y,double min_y,double max_x,double min_x) {
        this.MAX_Y=max_y;
        this.MIN_Y=min_y;
        this.MAX_X=max_x;
        this.MIN_X=min_x;
        this.level=6;
        setSuitableExtent();
        setNumbitsByLevel();
        setMinXY();
    }

    /**
     *
     * @params
     * @return
     * @Desription GeoHash构造函数,设置该对象最大边界的范围并指定生成的GeoHash字符串的长度
     */
    public GeoHashXY(double max_y,double min_y,double max_x,double min_x,int level) {
        this.MAX_Y=max_y;
        this.MIN_Y=min_y;
        this.MAX_X=max_x;
        this.MIN_X=min_x;
        this.level=level;
        setSuitableExtent();
        setNumbitsByLevel();
        setMinXY();
    }

    /**
     *
     * @params
     * @return
     * @Desription GeoHash构造函数,设置该对象最大边界的范围并指定最大误差
     */
    public GeoHashXY(double max_y,double min_y,double max_x,double min_x,double minExtentLength) {
        this.MAX_Y=max_y;
        this.MIN_Y=min_y;
        this.MAX_X=max_x;
        this.MIN_X=min_x;
        this.minExtentLength=minExtentLength;
        setSuitableExtent();
        setNumbitsByMinExtentLength();
        setMinXY();
    }

    /**
     *
     * @params
     * @return
     * @Desription 返回设置的最大矩形范围,[MAX_Y,MIN_Y,MAX_X,MIN_X]
     */
    public double[] getMaxExtent(){
        double[] extent=new double[]{MAX_Y,MIN_Y,MAX_X,MIN_X};
        return  extent;
    }

    public int getLevel(){
        return this.level;
    }

    public void setLevel(int level){
        this.level=level;
        setNumbitsByLevel();
        setMinXY();
    }

    public double getMinExtentLength(){
        return this.minExtentLength;
    }

    public void setMinExtentLength(double minExtentLength){
        this.minExtentLength=minExtentLength;
        setNumbitsByMinExtentLength();
        setMinXY();
    }
    /**
     *
     * @params
     * @return
     * @Desription 先指定最大误差,再进行GeoHash编码
     */
    public String encode(double Y, double X,double minExtentLength) {
        this.minExtentLength=minExtentLength;
        setNumbitsByMinExtentLength();
        setMinXY();
        return encode(Y,X);
    }

    /**
     *
     * @params
     * @return
     * @Desription 先指定GeoHash的长度,再进行GeoHash编码
     */
    public String encode(double Y, double X,int level) {
        this.level=level;
        setNumbitsByLevel();
        setMinXY();
        return encode(Y,X);
    }

    /**
     *
     * @params
     * @return
     * @Desription 根据初始化的参数进行GeoHash编码
     */
    public String encode(double Y, double X) {
        BitSet Ybits = getBits(Y,numbitY, MIN_Y, MAX_Y);
        BitSet Xbits = getBits(X,numbitX, MIN_X, MAX_X);
        StringBuilder buffer = new StringBuilder();

        for (int i = 0; i < numbitY; i++) {
            buffer.append((Xbits.get(i)) ? '1' : '0');
            buffer.append((Ybits.get(i)) ? '1' : '0');
        }

        if(numbitX!=numbitY){
            buffer.append((Xbits.get(numbitX-1)) ? '1' : '0');
        }
        String code = base32(Long.parseLong(buffer.toString(), 2));
        return code;
    }

    /**
     *
     * @params
     * @return
     * @Desription 计算点所在矩形范围GeoHash值及周围8个矩形范围的GeoHash值
     */
    public ArrayList<String> getArroundGeoHashList(double Y, double X) {
        ArrayList<String> list = new ArrayList<>();
        double upY = Y + minY;
        double downLat = Y - minY;

        double leftlng = X - minX;
        double rightLng = X + minX;

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

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

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

        String midUp = encode(upY, X);
        list.add(midUp);

        String midMid = encode(Y, X);
        list.add(midMid);

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

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

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

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

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

    //限制maxx,minx的差与maxy,miny的差相距大小,即如果传入的最大范围如果是长矩形,则两边都用最大的长,转为正方形
    private void setSuitableExtent(){
        double differenceY = MAX_Y - MIN_Y;
        double differenceX = MAX_X - MIN_X;
        if(differenceX>differenceY){
            if(differenceX/differenceY>=5){
                double midY=(MAX_Y+MIN_Y)/2.0;
                MAX_Y=midY+differenceX/2.0;
                MIN_Y=midY-differenceX/2.0;
            }
        }else {
            if(differenceY/differenceX>=5){
                double midX=(MAX_X+MIN_X)/2.0;
                MAX_X=midX+differenceY/2.0;
                MIN_X=midX-differenceY/2.0;
            }
        }
    }

    //根据经纬度和范围,获取对应的二进制
    private BitSet getBits(double Y,int numbits, double floor, double ceiling) {
        BitSet buffer = new BitSet(numbits);
        for (int i = 0; i < numbits; i++) {
            double mid = (floor + ceiling) / 2;
            if (Y >= 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));
    }

    //根据传入的最小范围长度计算numbits
    private void setNumbitsByMinExtentLength() {
        double differenceY = MAX_Y - MIN_Y;
        int Yi=0;
        while(differenceY>=minExtentLength)
        {
            differenceY /=2.0;
            Yi++;
        }

        double differenceX = MAX_X - MIN_X;
        int Xi=0;
        while(differenceX>=minExtentLength)
        {
            differenceX /=2.0;
            Xi++;
        }

        if(Yi>=Xi) {
            if (Yi <= 2) {
                level=1;
                numbitY = 2;
                numbitX = 3;
            } else if (Yi <= 5) {
                level=2;
                numbitY = 5;
                numbitX = 5;
            } else if (Yi <= 7) {
                level=3;
                numbitY = 7;
                numbitX = 8;
            } else if (Yi <= 10) {
                level=4;
                numbitY = 10;
                numbitX = 10;
            } else if (Yi <= 12) {
                level=5;
                numbitY = 12;
                numbitX = 13;
            } else if (Yi <= 15) {
                level=6;
                numbitY = 15;
                numbitX = 15;
            } else if (Yi <= 17) {
                level=7;
                numbitY = 17;
                numbitX = 18;
            } else {
                level=8;
                numbitY = 20;
                numbitX = 20;
            }
        }else {
            if (Xi <= 3) {
                level=1;
                numbitY = 2;
                numbitX = 3;
            } else if (Xi <= 5) {
                level=2;
                numbitY = 5;
                numbitX = 5;
            } else if (Xi <= 8) {
                level=3;
                numbitY = 7;
                numbitX = 8;
            } else if (Xi <= 10) {
                level=4;
                numbitY = 10;
                numbitX = 10;
            } else if (Xi <= 13) {
                level=5;
                numbitY = 12;
                numbitX = 13;
            } else if (Xi <= 15) {
                level=6;
                numbitY = 15;
                numbitX = 15;
            } else if (Xi <= 18) {
                level=7;
                numbitY = 17;
                numbitX = 18;
            } else {
                level=8;
                numbitY = 20;
                numbitX = 20;
            }
        }

    }

    //根据传入的GeoHash长度计算numbits
    private void setNumbitsByLevel(){
        switch (level){
            case 1:
                numbitY = 2;
                numbitX = 3;
                break;
            case 2:
                numbitY = 5;
                numbitX = 5;
                break;
            case 3:
                numbitY = 7;
                numbitX = 8;
                break;
            case 4:
                numbitY = 10;
                numbitX = 10;
                break;
            case 5:
                numbitY = 12;
                numbitX = 13;
                break;
            case 6:
                numbitY = 15;
                numbitX = 15;
                break;
            case 7:
                numbitY = 17;
                numbitX = 18;
                break;
            case 8:
                numbitY = 20;
                numbitX = 20;
                break;
        }
    }

    private void setMinXY() {
        minY = MAX_Y - MIN_Y;
        for (int i = 0; i < numbitY; i++) {
            minY /= 2.0;
        }
        minX = MAX_X - MIN_X;
        for (int i = 0; i < numbitX; i++) {
            minX /= 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) {
        this.level=geohash.length();
        setNumbitsByLevel();
        setMinXY();

        StringBuilder buffer = new StringBuilder();
        for (char c : geohash.toCharArray()) {
            int i = lookup.get(c) + 32;
            buffer.append(Integer.toString(i, 2).substring(1));
        }

        BitSet Xset = new BitSet();
        BitSet Yset = new BitSet();

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

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

        double X = decode(Xset, MIN_X, MAX_X);
        double Y = decode(Yset, MIN_Y, MAX_Y);

        return new double[]{Y, X};
    }


    
}