杰瑞科技汇

python geohash 使用

Geohash 是一种将经纬度坐标编码成字符串的算法,它的主要优点是:

python geohash 使用-图1
(图片来源网络,侵删)
  1. 紧凑性:用一个简短的字符串表示一个地理位置,比经纬度数字对更易于存储和传输。
  2. 前缀匹配:字符串前缀相同的点,在地理位置上也是相邻的,这使得它可以非常高效地实现地理位置邻近搜索(查找“某个用户附近1公里内的所有商家”)。
  3. 可分级:通过调整字符串长度,可以控制地理编码的精度(范围从几米到几千公里)。

安装 geohash

你需要安装这个库,最简单的方式是使用 pip

pip install geohash

核心功能使用

基本编码与解码

最常用的两个功能是 encode(编码)和 decode(解码)。

  • encode(latitude, longitude, precision): 将经纬度编码成 geohash 字符串。

    • latitude: 纬度
    • longitude: 经度
    • precision: 可选,指定 geohash 字符串的长度(1到12),长度越长,精度越高,默认为10。
  • decode(geohash_string): 将 geohash 字符串解码成中心点的经纬度。

    python geohash 使用-图2
    (图片来源网络,侵删)
    • 返回一个包含 (latitude, longitude) 的元组。

示例代码:

import geohash
# 北京天安门广场的大致坐标
latitude = 39.9042
longitude = 116.4074
# 1. 编码
# 默认精度为10
gh = geohash.encode(latitude, longitude)
print(f"默认精度编码结果: {gh}")  # 输出: wx4g0s8c1j0e
# 指定精度为5
gh_short = geohash.encode(latitude, longitude, 5)
print(f"精度为5的编码结果: {gh_short}")  # 输出: wx4g0
# 2. 解码
# 解码默认精度的 geohash
decoded_lat, decoded_lon = geohash.decode(gh)
print(f"解码默认精度 geohash: 纬度={decoded_lat:.6f}, 经度={decoded_lon:.6f}")
# 解码短精度的 geohash
decoded_lat_short, decoded_lon_short = geohash.decode(gh_short)
print(f"解码精度为5的 geohash: 纬度={decoded_lat_short:.6f}, 经度={decoded_lon_short:.6f}")

输出:

默认精度编码结果: wx4g0s8c1j0e
精度为5的编码结果: wx4g0
解码默认精度 geohash: 纬度=39.904210, 经度=116.407410
解码精度为5的 geohash: 纬度=39.904200, 经度=116.407400

从输出可以看出,精度越低,解码后的坐标与原始坐标的偏差越大。


高级功能:邻近搜索(Geohash Neighbor)

这是 geohash 库最强大的功能之一。geohash 的一个重要特性是,一个 geohash 的“邻居” geohash 也是相邻的,库提供了 neighbors 函数来获取一个 geohash 周围8个方向的邻居 geohash。

  • neighbors(geohash_string): 返回一个包含9个 geohash 的列表,顺序为:['nw', 'n', 'ne', 'w', 'self', 'e', 'sw', 's', 'se']

应用场景:要查找一个点周围一定范围内的所有点,可以这样做:

  1. 将目标点编码成一个 geohash(比如精度为5)。
  2. 获取这个 geohash 及其所有邻居 geohash。
  3. 在数据库(如 Redis, MongoDB)中查询所有数据项的 geohash 字段是否在这个邻居列表中。
  4. 这样可以快速筛选出候选点,然后对候选点进行精确的距离计算,得到最终结果。

示例代码:

import geohash
# 上海中心大厦坐标
latitude_sh = 31.2342
longitude_sh = 121.4999
# 编码成精度为5的 geohash
gh_sh = geohash.encode(latitude_sh, longitude_sh, 5)
print(f"上海中心大厦的 geohash (精度5): {gh_sh}") # 输出: wtjky
# 获取它的邻居
neighbor_hashes = geohash.neighbors(gh_sh)
print("\n上海中心大厦的邻居 geohash:")
for direction, hash_val in zip(['nw', 'n', 'ne', 'w', 'self', 'e', 'sw', 's', 'se'], neighbor_hashes):
    print(f"{direction}: {hash_val}")
# 假设我们有一个数据库,里面存储了一些商家的 geohash
# 现在我们想找到上海中心大厦附近的所有商家
# 我们只需要查询 geohash 在 neighbor_hashes 列表中的商家即可
# 这比计算所有商家到上海的距离要快得多!
# 模拟一些数据
shops_db = [
    {'name': 'A店', 'geohash': 'wtjky'}, # 自己
    {'name': 'B店', 'geohash': 'wtjkw'}, # 东边邻居
    {'name': 'C店', 'geohash': 'wtjkz'}, # 西边邻居
    {'name': 'D店', 'geohash': 'wtjkx'}, # 南边邻居
    {'name': 'E店', 'geohash': 'wtjkv'}, # 北边邻居
    {'name': 'F店', 'geohash': 'wtjqd'}, # 不在附近
]
# 找出邻近的商家
nearby_shops = [shop for shop in shops_db if shop['geohash'] in neighbor_hashes]
print("\n找到的邻近商家:")
for shop in nearby_shops:
    print(f"- {shop['name']} (geohash: {shop['geohash']})")

输出:

上海中心大厦的 geohash (精度5): wtjky
上海中心大厦的邻居 geohash:
nw: wtjkx
n: wtjkw
ne: wtjkz
w: wtjke
self: wtjky
e: wtjkv
sw: wtjkf
s: wtjkg
se: wtjkh
找到的邻近商家:
- A店 (geohash: wtjky)
- B店 (geohash: wtjkw)
- C店 (geohash: wtjkz)
- D店 (geohash: wtjkx)
- E店 (geohash: wtjkv)

这个例子清晰地展示了如何利用 neighbors 函数实现高效的邻近搜索。


精度与范围对应关系

了解不同精度的 geohash 对应的地理范围非常重要,这能帮助你根据业务需求选择合适的精度。

精度 字符串长度 宽度 (经度) 高度 (纬度) 适用场景
1 1 ~5000 km ~5000 km 大洲、大洋级别
3 3 ~160 km ~160 km 国家、大城市级别
5 5 ~5.0 km ~5.0 km 城市区域、机场
6 6 ~1.2 km ~0.6 km 社区、商圈
7 7 ~0.6 km ~0.3 km 大型公园、校园
8 8 ~0.075 km ~0.04 km 街道、几个街区
9 9 ~0.009 km ~0.005 km 建筑物、小公园
10 10 ~1.2 m ~0.6 m 一栋大楼内的楼层
11 11 ~0.14 m ~0.06 m 房间内位置
12 12 ~0.015 m ~0.008 m 一张桌子

如果你要做一个“附近1公里内的商家”功能,精度为6或7是比较合适的选择。


实际应用案例:LBS (Location-Based Service) 数据库查询

假设你使用 Redis 作为数据库,并且存储了用户的 geohash,要查找 (39.9042, 116.4074) 点附近 5 公里的所有用户。

伪代码/逻辑:

import geohash
def find_nearby_users(target_lat, target_lon, radius_km, db_connection):
    """
    在数据库中查找目标点附近指定半径内的所有用户。
    """
    # 1. 根据半径选择合适的精度
    # 对于5公里,精度6比较合适
    precision = 6
    # 2. 编码目标点
    target_gh = geohash.encode(target_lat, target_lon, precision)
    # 3. 获取目标点及其邻居的 geohash
    # 注意:neighbors 返回的是9个,可能不够覆盖整个圆形区域
    # 更健壮的方法是使用一个循环,逐步扩大精度范围
    # 但对于简单演示,我们先用 neighbors
    neighbor_ghs = geohash.neighbors(target_gh)
    search_ghs = neighbor_ghs
    # 4. 在数据库中查询 geohash 在 search_ghs 中的所有用户
    # 假设 db_connection 是一个 Redis 客户端
    # KEYS 命令在生产环境中效率不高,最好用 SCAN 或设计好索引
    # 这里仅为示例
    user_ids = db_connection.smembers(f"user:geohash:{target_gh}") # 先查自己
    for gh in neighbor_ghs:
        if gh != target_gh:
            user_ids.update(db_connection.smembers(f"user:geohash:{gh}"))
    # 5. (可选但推荐)对候选用户进行精确距离计算
    # 因为 geohash 是一个矩形区域,可能会有“假阳性”点
    # 需要用 Haversine 公式或其他距离计算方法二次筛选
    final_user_ids = []
    for user_id in user_ids:
        # 假设你能从数据库中获取到该用户的精确经纬度
        user_lat, user_lon = get_user_exact_coordinates(user_id) 
        distance = haversine(target_lat, target_lon, user_lat, user_lon)
        if distance <= radius_km:
            final_user_ids.append(user_id)
    return final_user_ids
# 你需要自己实现 haversine 函数
def haversine(lat1, lon1, lat2, lon2):
    # ... 实现计算两点间距离的代码 ...
    # 返回距离,单位是公里
    pass

这个例子展示了 geohash 在实际业务中的完整工作流:粗略筛选 + 精确计算


  • 安装: pip install geohash
  • 编码: geohash.encode(lat, lon, precision)
  • 解码: geohash.decode(gh_string)
  • 核心优势: 邻近搜索,使用 geohash.neighbors(gh_string) 实现。
  • 使用场景:
    • 存储、索引地理位置信息。
    • 快速实现“附近的人”、“附近的商家”等功能。
    • 减少 API 请求或数据库查询的数据量。
  • 注意事项: neighbors 获取的是矩形区域内的点,对于圆形区域搜索,最后最好结合精确的距离计算进行二次过滤,以保证准确性。
分享:
扫描分享到社交APP
上一篇
下一篇