前言须知
本章讲解如何使用Geohash查找附近的位置,以及如何解决Geohash区域边缘问题。
一个Geohash代表的是一个区域,一个Geohash字符串代表一个区域(如一个正方形),同一区域内的Geohash相同,不同区域的Geohash不同。
因相邻区域的Geohash不同,所以会有边缘问题:如范围2.4km的Geohash直接匹配则查不到相邻区域相距100米的目标。
本篇使用九宫格方法来解决边缘问题。
特别提醒:Geohash是根据经纬度计算直线距离,而大多地图导航软件会根据目标之间的路线计算路径距离,所以会有差距。
干货
1.加载依赖–使用开源项目Geohash的发行jar包
项目Github地址:https://github.com/kungfoo/geohash-java
发行jar包的Maven仓库地址:https://mvnrepository.com/artifact/ch.hsr/geohash
pom依赖
| 12
 3
 4
 5
 
 | <dependency><groupId>ch.hsr</groupId>
 <artifactId>geohash</artifactId>
 <version>1.4.0</version>
 </dependency>
 
 | 
2.计算geoHash字符串
本篇从地图上选了四个地点,以一个示例来讲计算方法及说明相关问题:

| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 | import ch.hsr.geohash.GeoHash;import org.junit.Test;
 
 public class JTest {
 
 @Test
 public void test() {
 
 int numberOfCharacters = 5;
 
 
 GeoHash geoHash = GeoHash.withCharacterPrecision(31.244966, 121.495787, numberOfCharacters);
 String geoHashBaseCode = geoHash.toBase32();
 System.out.println("和平饭店(用户所在地址)geoHash:" + geoHashBaseCode);
 
 
 GeoHash geoHash2 = GeoHash.withCharacterPrecision(31.245414, 121.506379, numberOfCharacters);
 String geoHashBaseCode2 = geoHash2.toBase32();
 System.out.println("东方明珠塔(相距和平饭店直线距离1km)geoHash:" + geoHashBaseCode2);
 
 
 GeoHash geoHash3 = GeoHash.withCharacterPrecision(31.234512, 121.488695, numberOfCharacters);
 String geoHashBaseCode3 = geoHash3.toBase32();
 System.out.println("东新大厦(相距和平饭店直线距离1.3km)geoHash:" + geoHashBaseCode3);
 
 
 GeoHash geoHash4 = GeoHash.withCharacterPrecision(31.242358, 121.477928, numberOfCharacters);
 String geoHashBaseCode4 = geoHash4.toBase32();
 System.out.println("房地大厦(相距和平饭店直线距离1.7km)geoHash:" + geoHashBaseCode4);
 
 
 GeoHash[] adjacent = geoHash.getAdjacent();
 for (GeoHash g : adjacent) {
 System.out.println("和平饭店(用户所在地址)周围八宫格geoHash:" + g.toBase32());
 }
 }
 
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | 和平饭店(用户所在地址)geoHash:wtw3s东方明珠塔(相距和平饭店直线距离1km)geoHash:wtw3u
 东新大厦(相距和平饭店直线距离1.3km)geoHash:wtw3s
 房地大厦(相距和平饭店直线距离1.7km)geoHash:wtw3s
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3u
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3v
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3t
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3m
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3k
 和平饭店(用户所在地址)周围八宫格geoHash:wtw37
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3e
 和平饭店(用户所在地址)周围八宫格geoHash:wtw3g
 
 | 
- 说明:
 相信有些读者可能注意到:
 丛示例的输出可以看出“和平饭店”与“东方明珠塔”相距1km,geoHash却不同;
 而“和平饭店”与“东新大厦”、“房地大厦”相距更远,其geoHash却相同;
 ↑ 这就是下一段落要说的边缘问题。
GeoHash可利用字符串前几位匹配的规则查找相距不超过一定距离的目标;
- 字符位数与距离对应表:
| geoHash位数 | 距离(km) |  
| 1 | ±2500 |  
| 2 | ±630 |  
| 3 | ±78 |  
| 4 | ±20 |  
| 5 | ±2.4 |  
| 6 | ±0.61 |  
| 7 | ±0.076 |  
| 8 | ±0.019 |  
 
上例中使用5位数的geoHash及锁定了±2.4km附近的位置。
即用2.4km范围内的所有地点的经纬度计算所得的5位geoHash都是一致的
因此可以根据这个特点做附近地点的高效检索:
从数据库高效检索附近地点
1.将所有地点的geoHash计算出;
2.将各地点的geoHash在数据库的表中保存为一列geoHash并建立索引
Tips:数据库里geoHash应建立索引,且应为BTree索引。因为Hash索引不支持模糊匹配;
可直接使用如下语句获取同一个geoHash区域中的附近地点
| 1
 | where geoHash like 'wtw3s%'
 | 
3.geoHash区域边缘问题
可能已经发现,既然geoHash代表的是一个区域,那必然会产生边缘性问题:
如上例中的用户所在地“和平饭店”,距离“东方明珠塔”只有1km,二者geoHash却不同。
这是因为两者处于两个geoHash区域的边缘。所以直接匹配一个字符串会出现问题。
- 笔者使用九宫格的想法:获取目标位置周围八个宫格,从数据库检索获取九个宫格中所有的目标,再在Java应用中逐个进行目标距离计算比较,根据距离约束排除在距离外的目标即可。
 上述距离计算使用经纬度可直接计算两个目标的直线距离:
 详细计算方法可参照我的另一篇分享:根据经纬度计算两者距离 (笔者尽快更新)
4.结语
如有疑问欢迎联系博主,谢谢!