前言须知
本章讲解如何使用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依赖
1 2 3 4 5
| <dependency> <groupId>ch.hsr</groupId> <artifactId>geohash</artifactId> <version>1.4.0</version> </dependency>
|
2.计算geoHash字符串
本篇从地图上选了四个地点,以一个示例来讲计算方法及说明相关问题:

以下代码使用示例地图上的四个点的经纬度生成对应的geoHash字符串
1 2 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()); } }
}
|
输出为:
1 2 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.结语
如有疑问欢迎联系博主,谢谢!