Geohash算法原理及实现

news2024/11/14 19:59:33

最近需要实现一个功能,查找车辆附近的加油站,如果车和加油站距离在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算法一共有三步。

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

比如这样一个点(39.923201, 116.390705)
纬度的范围是(-90,90),其中间值为0。对于纬度39.923201,在区间(0,90)中,因此得到一个1;(0,90)区间的中间值为45度,纬度39.923201小于45,因此得到一个0,依次计算下去,即可得到纬度的二进制表示,如下表:
在这里插入图片描述

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

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算法有两个问题。首先是边缘问题。

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

要解决这个问题,很简单,只要再查找周边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编码,以确定自己代码是否写错

整体代码如下所示:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/593790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

又名管道和无名管道

一、进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09; 概念&#xff1a;就是进程和进程之间交换信息。 常用通信方式 无名管道&#xff08;pipe&#xff09; 有名管道 &#xff08;fifo&#xff09; 信号&#xff08;signal&#xff09; 共…

数字化时代,低代码+进销存管理系统让你省时省力

进销存系统是一种用于管理企业物资流动和库存的软件系统&#xff0c;可以帮助企业优化物资管理过程&#xff0c;提高效率&#xff0c;减少成本&#xff0c;从而提升企业的盈利能力。本文将详细介绍进销存系统的定义、功能、好处以及如何选择适合自己企业的进销存系统&#xff0…

【嵌入式烧录/刷写文件】-2.6-剪切/保留Intel Hex文件中指定地址范围内的数据

案例背景&#xff1a; 有如下一段HEX文件&#xff0c;保留地址范围0x9140-0x91BF内的数据&#xff0c;删除地址范围0x9140-0x91BF外的数据。 :2091000058595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576775F :2091200078797A7B7C7D7E7F808182838485868788898A…

「ACL 2023」发榜!火山语音推出业内首个借助视频信息的端到端语音翻译模型

日前 ACL 2023的论文录用结果公布&#xff0c;火山语音团队多篇论文成功入选&#xff0c;内容涵盖音频合成、歌声合成以及语音翻译等多个前沿技术领域的创新突破。ACL&#xff08;Annual Meeting of the Association for Computational Linguistics&#xff09;每年由国际计算语…

Nginx服务器及其配置与应用

目录 一、Nginx的特点 1.高并发 2.低消耗 3.低消耗 4.高可用 5.高扩展 6.Nginx与Apache的差异 7.Nginx与Apache的区别 二、Linux中的I/O 1.I/O介绍 2.同步/异步 3.阻塞/非阻塞:关注调用者在等待结果返回之前所处的状态 三、编译安装Nginx服务 1.关闭防火墙&#…

SOLIDWORKS安装使用说明网络版

安装准备 系统要求&#xff1a;参考https://www.solidworks.com/sw/support/SystemRequirements.htmlSolidWorks 2017 是最蕞后一个支持win server 2008 R2 sp1的软件。 SolidWorks 2018支持win server 2012及以上的系统&#xff0c;但不支持win server 2019 SolidWorks 2019…

HNU-计算机系统-CSAPP作业答案

计算机系统CSAPP课后作业答案 计科210X wolf 202108010XXX 第2章 2.61 解: (!~x) || (!x) || (!~(x|0x00ffffff)) || (!(x&0x000000ff)) 或者: (!~x) || (!x) || (!~(x>>24)) || (!(x<<24)) 2.71 A. 实现的是逻辑位移,扩展后前面全是0,不符合符号扩…

Linux常见命令学习

目录 1.ls2.pwd3.cd (change directory)4.touch&&cat&&echo5.mkdir&&rm6.cp&&mv7.man8.less&&vim 1.ls 列出当前目录中包含的文件和目录~ 类似于在windows上双击某个目录&#xff0c;把他打开&#xff0c;看看目录里有啥~ ls -> l…

企业内容管理丨如何解决企业客户签收回执慢,缩短回款周期?

方案应用领域及行业 本方案适用于快消品行业的供应链管理和财务管理 方案应用背景 由于动产物权的政策变化&#xff0c;物权转移从交付时才开始发生效力。也就是说&#xff0c;新政之前&#xff0c;企业发出商品&#xff0c;开出销售发票&#xff0c;即可申请货物回款&#…

C# webapi接口传输byte[]数据,报错:415 Unsupported Media Type

最近需要做上传文件操作。 由于历史原因&#xff0c;以前的接口使用了这样的入参&#xff1a; 代码如下&#xff1a; /// <summary> /// 上传文件 /// </summary> [HttpPost] public Result<UploadImageResult> UploadFile(byte[] bytes, string extName, s…

html框架-----标签(上)

目录 前言&#xff1a; 标签简介 1. HTML的基本结构 (1)html标签 (2)head标签 &#xff08;3&#xff09;body标签 2. 标题标签 3. 段落标签 4. 文本格式化标签 前言&#xff1a; 现在学前端工程师的都很难找工作&#xff0c;懂的都懂了&#xff0c;因为学前端一般去做那…

console.log是异步还是同步?为什么console.log有时候不准

console.log是异步还是同步 在前端开发中&#xff0c;控制台console.log通常是同步的。这意味着&#xff0c;当代码执行到console.log语句时&#xff0c;它会立即写入到控制台中&#xff0c;并且JavaScript代码执行会在console.log完成后继续进行。 但是&#xff0c;在某些情况…

# croc用法实践(设备间文件或文件夹传输)

croc用法实践&#xff08;设备间文件或文件夹传输&#xff09; 文章目录 croc用法实践&#xff08;设备间文件或文件夹传输&#xff09;1 安装2 使用示例2.1 发送文件2.2 发送文件夹2.3 发送文本字符串2.4 发送时指定code&#xff0c;接收时自动【Y】&#xff0c;并保存到指定目…

《MySQL(一):基础篇-MySQL概述》

文章目录 1. MySQL概述1.1 数据库相关概念1.2 MySQL数据库1.2.1 版本1.2.2 下载1.2.3 安装1.2.4 启动停止1.2.5 客户端连接1.2.6 数据模型1. MySQL概述 1.1 数据库相关概念 先来了解三个概念:数据库、数据库管理系统、SQL。 而目前主流的关系型数据库管理系统的市场占有率…

ic验证的主要工作流程和验证工具是什么?

验证其实是一个“证伪”的过程&#xff0c;从流程到工具&#xff0c;验证工程师的终极目的都只有一个&#xff1a; 发现所有BUG&#xff0c;或者证明没有BUG&#xff0c;以保证芯片功能性能的正确性和可靠性。 验证环节对于一颗芯片的重要性也是不言而喻的&#xff1a; 从项…

VMware16安装Linux CentOS7完整教程(附下载地址)

一、准备工作 1.安装VMware Workstation Pro 16 &#xff08;1&#xff09;百度网盘下载地址 链接:https://pan.baidu.com/s/1jv0kQ26TLMt9MzofImRzgA?pwds0m1 提取码&#xff1a;s0m1 &#xff08;2&#xff09;VMware官网 https://www.vmware.com/cn &#xff08;2&#x…

广通优云徐育毅:筑基数字化,打造中国IT运维新范式

2023年2月&#xff0c;中共中央、国务院印发《数字中国建设整体布局规划》&#xff0c;数字技术与经济、政治、文化、社会等各领域融合愈发紧密&#xff0c;一册百花齐放、生机勃勃的数字化图卷正徐徐展开。 随着数字中国战略深入推进&#xff0c;IT运维作为核心领域受到关注。…

傻瓜式git管理。全平台,全编译器通用。github desktop+vscode

文章目录 前言Github desktop资源地址推荐理由操作介绍打开界面介绍Github账号登录Github仓库导入 从零开始新建Gitee仓库登录或注册gitee仓库Gitee新建仓库输入仓库名&#xff0c;添加gitignore,Readme什么是.gitignore什么是README 新建仓库效果github desktop使用新建本地仓…

Android播放器拖动进度条的小图预览

Android播放器拖动进度条的小图预览 背景效果图关键代码1. 获取指定位置的视频帧2. 预览图的显示和隐藏 完整代码1. xml布局文件activity_video.xml2. Activity文件VideoActivity.java 背景 我们在使用一些播放器时&#xff0c;拖动进度条会有一个预览框&#xff0c;上一篇博客…

海康、大华、tplink监控摄像头和硬盘录像机接入GB28181平台配置细节

海康、大华、tplink等各种型号监控摄像头或硬盘录像机(NVR/HVR)接入GB28181平台&#xff0c;配置过程都非常简单明了&#xff0c;但有些细节需要注意&#xff0c;避免走弯路踩泥坑。 首先要说明一点的是&#xff0c;只要监控设备和GB28181平台的网络是连通的&#xff0c;都可以…