GeoHash的介绍
GeoHash是一种高效的地理编码系统,它通过将地球表面划分为网格并用字母数字组合的字符串来表示每个区域。
这种编码方法将二维的经纬度坐标转换为一维的字符串,使得地理位置的存储和检索变得更加简单。GeoHash的核心原理是将经纬度坐标转换为二进制,然后交替取位组合,最后转换为base32编码。这种方法的一个重要特性是,相邻区域通常会有相同的GeoHash前缀,这使得它非常适合用于快速查找附近位置。GeoHash字符串的长度决定了编码的精度,越长越精确,例如6位GeoHash可以精确到约1.2公里,而10位可以精确到2.4米。
简单来说,GeoHash就是将某个地点的地理位置转换成可以比较可以排序的唯一字符串。
功能分析
通常我们在实现发现附近xxx功能的时候,需要统计我们当前所在地点附近终端的数量或者具体的地理位置。以共享单车举例子,当我们在A地点的时候通过APP可以看到附近200米的所存在的共享单车的数量以及每辆单车的具体位置,然后就可以根据地图的定位过去寻找车辆了。这个时候我们只需要通过A地点的GeoHash字符串进行模糊匹配,找到相关区域的所有车辆然后再根据200米距离的限制就可以实现发现附近共享单车的功能了。
Java代码实现
写个小DEMO来实现这个功能
首先引入GeoHash的相关依赖
<dependency>
<groupId>ch.hsr</groupId>
<artifactId>geohash</artifactId>
<version>1.4.0</version>
</dependency>
写一个方法获取给定位置的GeoHash及其相邻区域的方位
public List<String> neighbour(float longitude, float latitude, int length) {
List<String> geoHashes = Lists.newArrayList();
if (length == 0 || (longitude == 0 && latitude == 0)) {
return geoHashes;
}
GeoHash geoHash = GeoHash.withCharacterPrecision(latitude, longitude, length);
geoHashes.add(geoHash.toBase32());
GeoHash[] neighbourArray = geoHash.getAdjacent();
for (GeoHash child : neighbourArray) {
geoHashes.add(child.toBase32());
}
return geoHashes;
}
传入参数:经度、纬度和GeoHash的精度
这里解释一下GeoHash的精度:
GeoHash使用base32编码(32个字符),每增加一个字符,精度就会提高。
- 1个字符:约 5,000km × 5,000km
- 2个字符:约 1,250km × 625km
- 3个字符:约 156km × 156km
- 4个字符:约 39.1km × 19.5km
- 5个字符:约 4.89km × 4.89km
- 6个字符:约 1.22km × 0.61km
- 7个字符:约 153m × 153m
- 8个字符:约 38.2m × 19.1m
- 9个字符:约 4.77m × 4.77m
再解释一下方位:
在GeoHash系统中,方位指的是相对于中心GeoHash的周围8个相邻区域的位置。这8个方位形成了一个3x3的网格,中心是我们关注的GeoHash
NW | N | NE
---+---+---
W | C | E
---+---+---
SW | S | SE
通过知道相邻区域的方位,我们可以快速扩展搜索范围,而不需要重新计算整个区域的GeoHash。同时GeoHash有一个特性,就是相邻的地理位置可能会有完全不同的GeoHash编码。
举个例子:
假设有两家咖啡店
- 一家在城市的东边(编码为 E999)
- 另一家在城市的西边(编码为 W001)
这两家店实际上可能只隔着一条街,非常近,但是这条街就是中轴线将城市的东西边划分开来。如果有人站在东边的咖啡店门口,想找附近的咖啡店。
他们使用一个只基于编码前缀的搜索系统(类似于简单的GeoHash搜索)。
系统可能会搜索所有以 “E99” 开头的地点。这个搜索会找到东边(E区)的许多咖啡店。但它可能会完全忽略那家就在街对面、编码完全不同(W001)的咖啡店。所以使用方位
寻找各个方向的门店,以防止出现边界问题。
通过数据库中根据每条数据的geohash进行过滤
List<String> geoHashList = GeoHashUtil.neighbour(Point.getLongitude(),
Point.getLatitude(), DistanceEunm.TWOHUNDRED.getLength());
String geoHashSql = "(";
for (int i = 0; i < geoHashList.size(); i++) {
if (i > 0) {
geoHashSql += " or ";
}
geoHashSql += " geohash LIKE '" + geoHashList.get(i) + "%' ";
}
geoHashSql += ")";
String filtersql = "ST_Distance_Sphere(POINT(bike_table.longitude, bike_table.latitude), " +
"POINT(?, ?)) < 200 AND " + geoHashSql;
// 使用参数化查询来执行 SQL
bikeservice.getBikeList(filtersql, Point.getLongitude(), Point.getLatitude());
执行的SQL
SELECT
ID,
ST_Distance_Sphere ( POINT ( bike_table.longitude, bike_table.latitude ), POINT ( longitude, latitude ) ) AS distance
FROM
bike_table
WHERE
ST_Distance_Sphere ( POINT ( bike_table.longitude, bike_table.latitude ), POINT ( longitude, latitude ) ) < 200
AND (
geohash LIKE'ws0e%'
OR geohash LIKE'ws0s%'
OR geohash LIKE'ws0u%'
OR geohash LIKE'ws0g%'
OR geohash LIKE'ws0f%'
OR geohash LIKE'ws0d%'
OR geohash LIKE'ws06%'
OR geohash LIKE'ws07%'
OR geohash LIKE'ws0k%'
)
最后就能得到相关车辆的ID以及与当前位置的距离了