Geohash算法原理及实现

这篇具有很好参考价值的文章主要介绍了Geohash算法原理及实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近需要实现一个功能,查找车辆附近的加油站,如果车和加油站距离在200米以内,则查找成功。

加油站数量肯定不小,能否缩小查找范围,否则以遍历形式,效率肯定高不了。

Geohash算法就是将经纬度编码,将二维变一维,给地址位置分区的一种算法。

基本原理

GeoHash是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串

我们知道,经度范围是东经180到西经180,纬度范围是南纬90到北纬90,我们设定西经为负,南纬为负,所以地球上的经度范围就是[-180, 180],纬度范围就是[-90,90]。如果以本初子午线、赤道为界,地球可以分成4个部分。

如果纬度范围[-90°, 0°)用二进制0代表,(0°, 90°]用二进制1代表,经度范围[-180°, 0°)用二进制0代表,(0°, 180°]用二进制1代表,那么地球可以分成如下4个部分
Geohash算法原理及实现

如果在小块范围内递归对半划分呢?
Geohash算法原理及实现

可以看到,划分的区域更多了,也更精确了。geohash算法就是基于这种思想,划分的次数更多,区域更多,区域面积更小了。通过将经纬度编码,给地理位置分区

Geohash算法

Geohash算法一共有三步。

  1. 首先将经纬度变成二进制。

比如这样一个点(39.923201, 116.390705)
纬度的范围是(-90,90),其中间值为0。对于纬度39.923201,在区间(0,90)中,因此得到一个1;(0,90)区间的中间值为45度,纬度39.923201小于45,因此得到一个0,依次计算下去,即可得到纬度的二进制表示,如下表:
Geohash算法原理及实现

最后得到纬度的二进制表示为:

10111000110001111001

同理可以得到经度116.390705的二进制表示为:

11010010110001000100
  1. 第2步,就是将经纬度合并。

经度占偶数位,纬度占奇数位,注意,0也是偶数位。

  11100 11101 00100 01111 00000 01101 01011 00001
  1. 第3步,按照Base32进行编码

Base32编码表的其中一种如下,是用0-9、b-z(去掉a, i, l, o)这32个字母进行编码。具体操作是先将上一步得到的合并后二进制转换为10进制数据,然后对应生成Base32码。需要注意的是,将5个二进制位转换成一个base32码。上例最终得到的值为

  wx4g0ec1

Geohash比直接用经纬度的高效很多,而且使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。

  • GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引(某些情况下无法在两列上同时应用索引)
  • GeoHash表示的并不是一个点,而是一个矩形区域
  • GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。这个特性可以用于附近地点搜索

编码越长,表示的范围越小,位置也越精确。因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离。
Geohash算法原理及实现

问题

geohash算法有两个问题。首先是边缘问题。

如图,如果车在红点位置,区域内还有一个黄点。相邻区域内的绿点明显离红点更近。但因为黄点的编码和红点一样,最终找到的将是黄点。这就有问题了。

要解决这个问题,很简单,只要再查找周边8个区域内的点,看哪个离自己更近即可。

另外就是曲线突变问题。

本文第2张图片比较好地解释了这个问题。其中0111和1000两个编码非常相近,但它们的实际距离确很远。所以编码相近的两个单位,并不一定真实距离很近,这需要实际计算两个点的距离才行。

代码实现

geohash原理清楚后,代码实现就比较简单了。不过仍然有一个问题需要解决,就是如何计算周边的8个区域key值呢

假设我们计算的key值是6位,那么二进制位数就是 6*5 = 30位,所以经纬度分别是15位。我们以纬度为例,纬度会均分15次。这样我们很容易能够算出15次后,划分的最小单位是多少

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

得到了最小单位,那么周边区域的经纬度也可以计算得到了。比如说左边区域的经度肯定是自身经度减去最小经度单位。纬度也可以通过加减,得到上下的纬度值,最终周围8个单位也可以计算得到。

可以到 http://geohash.co/ 进行geohash编码,以确定自己代码是否写错

整体代码如下所示:文章来源地址https://www.toymoban.com/news/detail-466731.html

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 = 3 * 5; //经纬度单独编码长度

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);
    geohash.getArroundGeoHash(40.222012, 116.248283);
//        double[] geo = geohash.decode(s);
//        System.out.println(geo[0]+" "+geo[1]);
}
}

到了这里,关于Geohash算法原理及实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 孩子都能学会的FPGA:第二十八课——用FPGA实现最近最少使用(LRU)算法

    (原创声明:该文是 作者的原创 ,面向对象是 FPGA入门者 ,后续会有进阶的高级教程。宗旨是 让每个想做FPGA的人轻松入门 , 作者不光让大家知其然,还要让大家知其所以然 !每个工程作者都搭建了全自动化的仿真环境,只需要双击 top_tb.bat 文件就可以完成整个的仿真(前

    2024年02月19日
    浏览(30)
  • 最近项目上需要发送短信整理了一篇文章 SpringBoot整合阿里云发送短信

    阿里云短信服务网址:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 第一步:申请签名(一般申请时长在1-2小时之间)特别注意:场景说明不要乱填以免申请不通过  第二步:申请短信模板(一般申请时长在1-2小时之间)特别注意:场景说明不要乱填以免申请不

    2024年02月06日
    浏览(35)
  • Stable Diffusion你需要知道的算法原理;ChatGPT新增函数调用;Adobe Illustrator引入AI工具Firefly;

    🦉 AI新闻 🚀 OpenAI更新ChatGPT,新增函数调用、4倍上下文长度、更低的嵌入成本 摘要 :OpenAI在官网发布了ChatGPT的更新细节,包括新增函数调用、GPT-4和GPT-3.5-Turbo的升级、降低成本等。其中,新增函数调用能力使得模型能够直接支持Agent或Plugin,并且能够更可靠地从模型中获

    2024年02月16日
    浏览(23)
  • 设计一个LRU(最近最少使用)缓存

    约束和假设 我们正在缓存什么? 我们正在缓存Web Query的结果 我们可以假设输入是有效的,还是需要对其验证? 假设输入是有效的 我们可以假设它适应内存吗? 对 编码实现

    2024年01月24日
    浏览(43)
  • 【协议设计与实现】Linux环境下,如何从0开始设计并实现一个网络协议之一——需要考虑的因素

    🐚作者简介:花神庙码农(专注于Linux、WLAN、TCP/IP、Python等技术方向) 🐳博客主页:花神庙码农 ,地址:https://blog.csdn.net/qxhgd 🌐系列专栏:TCP/IP协议 📰如觉得博主文章写的不错或对你有所帮助的话,还望大家三连支持一下呀!!! 👉关注✨、点赞👍、收藏📂、评论。

    2024年01月17日
    浏览(26)
  • 2023-05-25 最近的一个客户POC的反思

    最近在遇到一个客户的POC的问题,其中经历诸多有意思的事情, 有必要记录一下,以作为后续创业所要避免的地方。 查询SQL中, 存在给查询到的列属性赋值的情况 给属性的赋值的数据类型,和列属性的数据类型,不匹配,比如给整形的属性赋值字符串 在所做的项目中,数据库

    2024年02月07日
    浏览(52)
  • AutoGPT最近火出圈,却透露一个给我们的重要机会

    在AutoGPT和其他类似的人工智能技术越来越普遍的时代,许多人可能会担心潜在的失业和各种任务的自动化趋势。然而,虽然人工智能确实可以替代许多繁琐和重复性的工作,但重要的是要记住,AI无法真正复制的一个关键方面是人类的创造力。 创造力是人类经验的重要组成部

    2023年04月17日
    浏览(32)
  • TCP实现原理和为什么需要三次握手?两次握手不可以?四次握手不可以?

    TCP实现原理和为什么需要三次握手?两次握手不可以?四次握手不可以? 1. 什么是TCP协议? TCP:Transmission Control Protocol翻译过来就是传输控制协议,TCP协议是一个面向连接的、可靠的、基于字节流的传输层协议 RFC 793对TCP连接的定义 Connections: The reliability and flow control mechanisms descri

    2024年02月16日
    浏览(34)
  • 无涯教程-KNN算法 - 寻找最近邻居

    K最近邻(KNN)算法是一种监督的ML算法,可用于分类以及回归预测问题,但是,它主要用于行业中的分类预测问题。以下两个属性将很好地定义KNN- 惰性学习算法    -  KNN是一种惰性学习算法,因为它没有专门的训练阶段,并且在分类时将所有数据用于训练。 非参数学习算法

    2024年02月11日
    浏览(24)
  • 【数据结构和算法】最近的请求次数

    Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 队列问题一般解法 2.2 方法一:队列 三、代码 3.1 方法一:队列 四、复杂度分析 4.1 方法一:队列 这是力扣的 933 题,难度为简单,解

    2024年02月01日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包