回到首页

输入关键词,多个关键词用空格分隔。

← 工程原理与设计 · 系列目录

GeoHash 指南

约 3 分钟更新于 2026-11-06
本页目录

如果要查询附近的地点,逐个计算所有地点与查询点的距离显然很浪费。GeoHash 提供了一种候选筛选方式:把经纬度编码为字符串,用网格把大部分无关地点先排除,再计算精确距离。

本文整理自 2016 年的笔记,以文字推导和可运行代码替换缺失的旧截图,并修正编码、精度和相邻区域的说明。

把二维坐标交错编码

经度范围是 [−180,180][-180,180],纬度范围是 [−90,90][-90,90]。每次把一个区间平分:坐标在较低的一半记 0,在较高的一半记 1,再对选中的那一半继续二分。

标准 GeoHash 从经度开始,经纬度轮流贡献一个二进制位。每五个位组成一个数,再用下面的专用字符表转换为字符:

0123456789bcdefghjkmnpqrstuvwxyz

例如广州的一组坐标是北纬 23.1291、东经 113.2644。前五位交错编码为 11100,十进制为 28,对应字符 w。继续二分,前四个字符是 ws0e。

GeoHash 使用的是编码,不是加密。知道字符串就可以恢复对应的经纬度范围,不能靠它保护精确位置隐私。

一个可以验证的编码器

下面只实现编码,用来理解二分过程;输入采用纬度、经度、字符串长度的顺序。

def geohash_encode(latitude, longitude, precision=6):
    if not (-90 <= latitude <= 90 and -180 <= longitude < 180):
        raise ValueError('坐标超出范围')
    if not isinstance(precision, int) or not 1 <= precision <= 12:
        raise ValueError('编码长度应为 1 到 12 的整数')
    alphabet = '0123456789bcdefghjkmnpqrstuvwxyz'
    bounds = [[-180.0, 180.0], [-90.0, 90.0]]
    coordinates = [longitude, latitude]
    result = []
    value = 0
    for index in range(precision * 5):
        axis = index % 2
        low, high = bounds[axis]
        middle = (low + high) / 2
        bit = int(coordinates[axis] >= middle)
        bounds[axis][1 - bit] = middle
        value = value * 2 + bit
        if index % 5 == 4:
            result.append(alphabet[value])
            value = 0
    return ''.join(result)

assert geohash_encode(23.1291, 113.2644, 4) == 'ws0e'

一个长度为 nn 的编码包含 5n5n 个二进制位。经度分得 ⌈5n/2⌉\lceil5n/2\rceil 位,纬度分得 ⌊5n/2⌋\lfloor5n/2\rfloor 位。因此,网格的角度宽度和高度可以直接计算:

Δλ=3602⌈5n/2⌉,Δϕ=1802⌊5n/2⌋.\Delta\lambda=\frac{360}{2^{\lceil5n/2\rceil}},\qquad \Delta\phi=\frac{180}{2^{\lfloor5n/2\rfloor}}.

角度换算为地面距离还与纬度有关,不能把某个编码长度写成全球固定的“误差几米”。Elasticsearch 的 GeoHash 网格文档也按网格与精度来描述它的用途。

前缀与附近不是同一个概念

相同前缀表示落在同一个更大的网格里,但相邻的点可能被网格边界分开,拥有很不相同的前缀。只搜索查询点所在格,会漏掉边界另一侧的邻居。

查询时应先根据搜索半径确定需要覆盖的网格,再收集候选点,最后按原始经纬度计算距离。当前格加周围八格是一种常见局部覆盖方式,但是否足够取决于半径和网格大小,不能对任意半径都只取九格。

保存原始坐标,编码只作辅助

在关系数据库中,可以用前缀条件表达候选范围,例如 WHERE geohash LIKE 'ws0e%'。是否走索引要看数据库、排序规则、索引类型与执行计划;写了前缀条件不等于已经高效。

使用空间数据库时,也可以让空间索引和距离查询处理这项工作。手动保存 GeoHash 的价值主要在于明确分区、聚合或检索策略,不应为了采用某种编码而重复建设已有能力。

应用时要写清楚的约定

接口要约定坐标顺序和坐标系,避免把经纬度颠倒。编码长度决定候选区域大小,搜索半径决定覆盖范围,二者应分别传递。跨日期变更线和极区还需要专门检查边界处理。

原文参考的实现包括 mmcloughlin/geohash。无论使用哪个库,先验证已知坐标、边界两侧和目标半径内外的点,再把结果接到业务查询中。

相关文章