• geohash算法原理及实现方式


    1、geohash特点

    2、geohash原理

    3、geohash的php 、python、java、C#实现代码

    4、观点讨论

     w微博:http://weibo.com/dxl0321

    geohash有以下几个特点:

    首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同时应用索引 (例如MySQL 4之前的版本,Google App Engine的数据层等),利用geohash,只需在一列上应用索引即可。

    其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。 使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。

    第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),即可查询附近的所有地点。

    Geohash比直接用经纬度的高效很多。

    Geohash的原理

    Geohash的最简单的解释就是:将一个经纬度信息,转换成一个可以排序,可以比较的字符串编码


            首先将纬度范围(-90, 90)平分成两个区间(-90,0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。

    由于39.92324属于(0, 90),所以取编码为1。

    然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。

    以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。

    纬度范围

    划分区间0

    划分区间1

    39.92324所属区间

    (-90, 90)

    (-90, 0.0)

    (0.0, 90)

    1

    (0.0, 90)

    (0.0, 45.0)

    (45.0, 90)

    0

    (0.0, 45.0)

    (0.0, 22.5)

    (22.5, 45.0)

    1

    (22.5, 45.0)

    (22.5, 33.75)

    (33.75, 45.0)

    1

    (33.75, 45.0)

    (33.75, 39.375)

    (39.375, 45.0)

    1

    (39.375, 45.0)

    (39.375, 42.1875)

    (42.1875, 45.0)

    0

    (39.375, 42.1875)

    (39.375, 40.7812)

    (40.7812, 42.1875)

    0

    (39.375, 40.7812)

    (39.375, 40.0781)

    (40.0781, 40.7812)

    0

    (39.375, 40.0781)

    (39.375, 39.7265)

    (39.7265, 40.0781)

    1

    (39.7265, 40.0781)

    (39.7265, 39.9023)

    (39.9023, 40.0781)

    1

    (39.9023, 40.0781)

    (39.9023, 39.9902)

    (39.9902, 40.0781)

    0

    (39.9023, 39.9902)

    (39.9023, 39.9462)

    (39.9462, 39.9902)

    0

    (39.9023, 39.9462)

    (39.9023, 39.9243)

    (39.9243, 39.9462)

    0

    (39.9023, 39.9243)

    (39.9023, 39.9133)

    (39.9133, 39.9243)

    1

    (39.9133, 39.9243)

    (39.9133, 39.9188)

    (39.9188, 39.9243)

    1

    (39.9188, 39.9243)

    (39.9188, 39.9215)

    (39.9215, 39.9243)

    1

    经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。

    经度范围

    划分区间0

    划分区间1

    116.3906所属区间

    (-180, 180)

    (-180, 0.0)

    (0.0, 180)

    1

    (0.0, 180)

    (0.0, 90.0)

    (90.0, 180)

    1

    (90.0, 180)

    (90.0, 135.0)

    (135.0, 180)

    0

    (90.0, 135.0)

    (90.0, 112.5)

    (112.5, 135.0)

    1

    (112.5, 135.0)

    (112.5, 123.75)

    (123.75, 135.0)

    0

    (112.5, 123.75)

    (112.5, 118.125)

    (118.125, 123.75)

    0

    (112.5, 118.125)

    (112.5, 115.312)

    (115.312, 118.125)

    1

    (115.312, 118.125)

    (115.312, 116.718)

    (116.718, 118.125)

    0

    (115.312, 116.718)

    (115.312, 116.015)

    (116.015, 116.718)

    1

    (116.015, 116.718)

    (116.015, 116.367)

    (116.367, 116.718)

    1

    (116.367, 116.718)

    (116.367, 116.542)

    (116.542, 116.718)

    0

    (116.367, 116.542)

    (116.367, 116.455)

    (116.455, 116.542)

    0

    (116.367, 116.455)

    (116.367, 116.411)

    (116.411, 116.455)

    0

    (116.367, 116.411)

    (116.367, 116.389)

    (116.389, 116.411)

    1

    (116.389, 116.411)

    (116.389, 116.400)

    (116.400, 116.411)

    0

    (116.389, 116.400)

    (116.389, 116.394)

    (116.394, 116.400)

    0

    接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。

    最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。

    十进制

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    base32

    0

    1

    2

    3

    4

    5

    6

    7

    8

    9

    b

    c

    d

    e

    f

    g

    十进制

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    base32

    h

    j

    k

    m

    n

    p

    q

    r

    s

    t

    u

    v

    w

    x

    y

    z

    解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。

    实现代码:

    php版本的实现方式:http://blog.dixo.net/downloads/geohash-php-class/  我下载了一个上传的

     php:

    geohash.class.php

    View Code
    复制代码
      1 <?php
      2 /**
      3  * Geohash generation class
      4  * http://blog.dixo.net/downloads/
      5  *
      6  * This file copyright (C) 2008 Paul Dixon (paul@elphin.com)
      7  *
      8  * This program is free software; you can redistribute it and/or
      9  * modify it under the terms of the GNU General Public License
     10  * as published by the Free Software Foundation; either version 3
     11  * of the License, or (at your option) any later version.
     12  *
     13  * This program is distributed in the hope that it will be useful,
     14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  * GNU General Public License for more details.
     17  *
     18  * You should have received a copy of the GNU General Public License
     19  * along with this program; if not, write to the Free Software
     20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     21  */
     22 
     23 
     24 
     25 /**
     26 * Encode and decode geohashes
     27 *
     28 */
     29 class Geohash
     30 {
     31     private $coding="0123456789bcdefghjkmnpqrstuvwxyz";
     32     private $codingMap=array();
     33     
     34     public function Geohash()
     35     {
     36         //build map from encoding char to 0 padded bitfield
     37         for($i=0; $i<32; $i++)
     38         {
     39             $this->codingMap[substr($this->coding,$i,1)]=str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
     40         }
     41         
     42     }
     43     
     44     /**
     45     * Decode a geohash and return an array with decimal lat,long in it
     46     */
     47     public function decode($hash)
     48     {
     49         //decode hash into binary string
     50         $binary="";
     51         $hl=strlen($hash);
     52         for($i=0; $i<$hl; $i++)
     53         {
     54             $binary.=$this->codingMap[substr($hash,$i,1)];
     55         }
     56         
     57         //split the binary into lat and log binary strings
     58         $bl=strlen($binary);
     59         $blat="";
     60         $blong="";
     61         for ($i=0; $i<$bl; $i++)
     62         {
     63             if ($i%2)
     64                 $blat=$blat.substr($binary,$i,1);
     65             else
     66                 $blong=$blong.substr($binary,$i,1);
     67             
     68         }
     69         
     70         //now concert to decimal
     71         $lat=$this->binDecode($blat,-90,90);
     72         $long=$this->binDecode($blong,-180,180);
     73         
     74         //figure out how precise the bit count makes this calculation
     75         $latErr=$this->calcError(strlen($blat),-90,90);
     76         $longErr=$this->calcError(strlen($blong),-180,180);
     77                 
     78         //how many decimal places should we use? There's a little art to
     79         //this to ensure I get the same roundings as geohash.org
     80         $latPlaces=max(1, -round(log10($latErr))) - 1;
     81         $longPlaces=max(1, -round(log10($longErr))) - 1;
     82         
     83         //round it
     84         $lat=round($lat, $latPlaces);
     85         $long=round($long, $longPlaces);
     86         
     87         return array($lat,$long);
     88     }
     89 
     90     
     91     /**
     92     * Encode a hash from given lat and long
     93     */
     94     public function encode($lat,$long)
     95     {
     96         //how many bits does latitude need?    
     97         $plat=$this->precision($lat);
     98         $latbits=1;
     99         $err=45;
    100         while($err>$plat)
    101         {
    102             $latbits++;
    103             $err/=2;
    104         }
    105         
    106         //how many bits does longitude need?
    107         $plong=$this->precision($long);
    108         $longbits=1;
    109         $err=90;
    110         while($err>$plong)
    111         {
    112             $longbits++;
    113             $err/=2;
    114         }
    115         
    116         //bit counts need to be equal
    117         $bits=max($latbits,$longbits);
    118         
    119         //as the hash create bits in groups of 5, lets not
    120         //waste any bits - lets bulk it up to a multiple of 5
    121         //and favour the longitude for any odd bits
    122         $longbits=$bits;
    123         $latbits=$bits;
    124         $addlong=1;
    125         while (($longbits+$latbits)%5 != 0)
    126         {
    127             $longbits+=$addlong;
    128             $latbits+=!$addlong;
    129             $addlong=!$addlong;
    130         }
    131         
    132         
    133         //encode each as binary string
    134         $blat=$this->binEncode($lat,-90,90, $latbits);
    135         $blong=$this->binEncode($long,-180,180,$longbits);
    136         
    137         //merge lat and long together
    138         $binary="";
    139         $uselong=1;
    140         while (strlen($blat)+strlen($blong))
    141         {
    142             if ($uselong)
    143             {
    144                 $binary=$binary.substr($blong,0,1);
    145                 $blong=substr($blong,1);
    146             }
    147             else
    148             {
    149                 $binary=$binary.substr($blat,0,1);
    150                 $blat=substr($blat,1);
    151             }
    152             $uselong=!$uselong;
    153         }
    154         
    155         //convert binary string to hash
    156         $hash="";
    157         for ($i=0; $i<strlen($binary); $i+=5)
    158         {
    159             $n=bindec(substr($binary,$i,5));
    160             $hash=$hash.$this->coding[$n];
    161         }
    162         
    163         
    164         return $hash;
    165     }
    166     
    167     /**
    168     * What's the maximum error for $bits bits covering a range $min to $max
    169     */
    170     private function calcError($bits,$min,$max)
    171     {
    172         $err=($max-$min)/2;
    173         while ($bits--)
    174             $err/=2;
    175         return $err;
    176     }
    177     
    178     /*
    179     * returns precision of number
    180     * precision of 42 is 0.5
    181     * precision of 42.4 is 0.05
    182     * precision of 42.41 is 0.005 etc
    183     */
    184     private function precision($number)
    185     {
    186         $precision=0;
    187         $pt=strpos($number,'.');
    188         if ($pt!==false)
    189         {
    190             $precision=-(strlen($number)-$pt-1);
    191         }
    192         
    193         return pow(10,$precision)/2;
    194     }
    195     
    196     
    197     /**
    198     * create binary encoding of number as detailed in http://en.wikipedia.org/wiki/Geohash#Example
    199     * removing the tail recursion is left an exercise for the reader
    200     */
    201     private function binEncode($number, $min, $max, $bitcount)
    202     {
    203         if ($bitcount==0)
    204             return "";
    205         
    206         #echo "$bitcount: $min $max<br>";
    207             
    208         //this is our mid point - we will produce a bit to say
    209         //whether $number is above or below this mid point
    210         $mid=($min+$max)/2;
    211         if ($number>$mid)
    212             return "1".$this->binEncode($number, $mid, $max,$bitcount-1);
    213         else
    214             return "0".$this->binEncode($number, $min, $mid,$bitcount-1);
    215     }
    216     
    217 
    218     /**
    219     * decodes binary encoding of number as detailed in http://en.wikipedia.org/wiki/Geohash#Example
    220     * removing the tail recursion is left an exercise for the reader
    221     */
    222     private function binDecode($binary, $min, $max)
    223     {
    224         $mid=($min+$max)/2;
    225         
    226         if (strlen($binary)==0)
    227             return $mid;
    228             
    229         $bit=substr($binary,0,1);
    230         $binary=substr($binary,1);
    231         
    232         if ($bit==1)
    233             return $this->binDecode($binary, $mid, $max);
    234         else
    235             return $this->binDecode($binary, $min, $mid);
    236     }
    237 }
    238 
    239 
    240 
    241 
    242 
    243 
    244 ?>
    复制代码

    python:

    python版本的geohash:python-geohash

    java:

    java版本的geohash,实现:http://code.google.com/p/geospatialweb/source/browse/#svn/trunk/geohash/src

    View Code

    C#:

    复制代码
     C#版本的geohash代
    复制代码
      1 using System;
      2 
      3 namespace sharonjl.utils
      4 {
      5     public static class Geohash
      6     {
      7         #region Direction enum
      8 
      9         public enum Direction
     10         {
     11             Top = 0,
     12             Right = 1,
     13             Bottom = 2,
     14             Left = 3 
     15         }
     16 
     17         #endregion
     18 
     19         private const string Base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
     20         private static readonly int[] Bits = new[] {16, 8, 4, 2, 1};
     21 
     22         private static readonly string[][] Neighbors = {
     23                                                            new[]
     24                                                                {
     25                                                                    "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Top
     26                                                                    "bc01fg45238967deuvhjyznpkmstqrwx", // Right
     27                                                                    "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Bottom
     28                                                                    "238967debc01fg45kmstqrwxuvhjyznp", // Left
     29                                                                }, new[]
     30                                                                       {
     31                                                                           "bc01fg45238967deuvhjyznpkmstqrwx", // Top
     32                                                                           "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Right
     33                                                                           "238967debc01fg45kmstqrwxuvhjyznp", // Bottom
     34                                                                           "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Left
     35                                                                       }
     36                                                        };
     37 
     38         private static readonly string[][] Borders = {
     39                                                          new[] {"prxz", "bcfguvyz", "028b", "0145hjnp"},
     40                                                          new[] {"bcfguvyz", "prxz", "0145hjnp", "028b"}
     41                                                      };
     42 
     43         public static String CalculateAdjacent(String hash, Direction direction)
     44         {
     45             hash = hash.ToLower();
     46 
     47             char lastChr = hash[hash.Length - 1];
     48             int type = hash.Length%2;
     49             var dir = (int) direction;
     50             string nHash = hash.Substring(0, hash.Length - 1);
     51 
     52             if (Borders[type][dir].IndexOf(lastChr) != -1)
     53             {
     54                 nHash = CalculateAdjacent(nHash, (Direction) dir);
     55             }
     56             return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)];
     57         }
     58 
     59         public static void RefineInterval(ref double[] interval, int cd, int mask)
     60         {
     61             if ((cd & mask) != 0)
     62             {
     63                 interval[0] = (interval[0] + interval[1])/2;
     64             }
     65             else
     66             {
     67                 interval[1] = (interval[0] + interval[1])/2;
     68             }
     69         }
     70 
     71         public static double[] Decode(String geohash)
     72         {
     73             bool even = true;
     74             double[] lat = {-90.0, 90.0};
     75             double[] lon = {-180.0, 180.0};
     76 
     77             foreach (char c in geohash)
     78             {
     79                 int cd = Base32.IndexOf(c);
     80                 for (int j = 0; j < 5; j++)
     81                 {
     82                     int mask = Bits[j];
     83                     if (even)
     84                     {
     85                         RefineInterval(ref lon, cd, mask);
     86                     }
     87                     else
     88                     {
     89                         RefineInterval(ref lat, cd, mask);
     90                     }
     91                     even = !even;
     92                 }
     93             }
     94 
     95             return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2};
     96         }
     97 
     98         public static String Encode(double latitude, double longitude, int precision = 12)
     99         {
    100             bool even = true;
    101             int bit = 0;
    102             int ch = 0;
    103             string geohash = "";
    104 
    105             double[] lat = {-90.0, 90.0};
    106             double[] lon = {-180.0, 180.0};
    107 
    108             if (precision < 1 || precision > 20) precision = 12;
    109 
    110             while (geohash.Length < precision)
    111             {
    112                 double mid;
    113 
    114                 if (even)
    115                 {
    116                     mid = (lon[0] + lon[1])/2;
    117                     if (longitude > mid)
    118                     {
    119                         ch |= Bits[bit];
    120                         lon[0] = mid;
    121                     }
    122                     else
    123                         lon[1] = mid;
    124                 }
    125                 else
    126                 {
    127                     mid = (lat[0] + lat[1])/2;
    128                     if (latitude > mid)
    129                     {
    130                         ch |= Bits[bit];
    131                         lat[0] = mid;
    132                     }
    133                     else
    134                         lat[1] = mid;
    135                 }
    136 
    137                 even = !even;
    138                 if (bit < 4)
    139                     bit++;
    140                 else
    141                 {
    142                     geohash += Base32[ch];
    143                     bit = 0;
    144                     ch = 0;
    145                 }
    146             }
    147             return geohash;
    148         }
    149     }
    150 }
    复制代码

    C#代码来自:https://github.com/sharonjl/geohash-net

    复制代码

    geohash演示:http://openlocation.org/geohash/geohash-js/

    各种版本下载:打包下载

    观点讨论

    引用阿里云以为技术专家的博客上的讨论:

    1.两个离的越近,geohash的结果相同的位数越多,对么?
    这一点是有些用户对geohash的误解,虽然geo确实尽可能的将位置相近的点hash到了一起,可是这并不是严格意义上的(实际上也并不可能,因为毕竟多一维坐标),
    例如在方格4的左下部分的点和大方格1的右下部分的点离的很近,可是它们的geohash值一定是相差的相当远,因为头一次的分块就相差太大了,很多时候我们对geohash的值进行简单的排序比较,结果貌似真的能够找出相近的点,并且似乎还是按照距离的远近排列的,可是实际上会有一些点被漏掉了。
    上述这个问题,可以通过搜索一个格子,周围八个格子的数据,统一获取后再进行过滤。这样就在编码层次解决了这个问题。
    2.既然不能做到将相近的点hash值也相近,那么geohash的意义何在呢?
    我觉得geohash还是相当有用的一个算法,毕竟这个算法通过无穷的细分,能确保将每一个小块的geohash值确保在一定的范围之内,这样就为灵活的周边查找和范围查找提供了可能。

    常见的一些应用场景

    A、如果想查询附近的点?如何操作

    查出改点的gehash值,然后到数据库里面进行前缀匹配就可以了。

    B、如果想查询附近点,特定范围内,例如一个点周围500米的点,如何搞?

    可以查询结果,在结果中进行赛选,将geohash进行解码为经纬度,然后进行比较

     *在纬度相等的情况下:

     *经度每隔0.00001度,距离相差约1米;

     *每隔0.0001度,距离相差约10米;

     *每隔0.001度,距离相差约100米;

     *每隔0.01度,距离相差约1000米;

     *每隔0.1度,距离相差约10000米。

     *在经度相等的情况下:

     *纬度每隔0.00001度,距离相差约1.1米;

     *每隔0.0001度,距离相差约11米;

     *每隔0.001度,距离相差约111米;

     *每隔0.01度,距离相差约1113米;

     *每隔0.1度,距离相差约11132米。

    Geohash,如果geohash的位数是6位数的时候,大概为附近1千米…

    参考资料:

    http://iamzhongyong.iteye.com/blog/1399333

    http://tech.idv2.com/2011/06/17/location-search/

    http://blog.sina.com.cn/s/blog_62ba0fdd0100tul4.html

  • 相关阅读:
    玩转web之javaScript(五)---js和jquery一些不可不知的方法(input篇)
    设计模式 外观模式 一键电影模式
    设计模式 适配器模式 以手机充电器为例
    高仿微信5.2.1主界面架构 包含消息通知
    Java进阶 创建和销毁对象
    sql语句中单引号嵌套问题
    Spark SQL UDF和UDAF示例
    Spark Parquet使用
    iptables只允许指定ip访问本机的指定端口
    Spark On YARN内存和CPU分配
  • 原文地址:https://www.cnblogs.com/php-rearch/p/6217235.html
Copyright © 2020-2023  润新知