Geohash 地理编码完全指南:经纬度如何变成一串短字符
讲清楚 geohash 把经纬度编成短字符串的原理,递归二分网格如何工作,字符越长精度越高的对应关系,以及它在附近搜索和地图索引里的真实用途。
Geohash 地理编码完全指南:经纬度如何变成一串短字符
一对经纬度,比如 57.64911, 10.40744,写出来又长又难记,也不好放进数据库索引里做范围查询。geohash 做的事情,是把这一对浮点数压成一个像 u4pruydqqvj 这样的短字符串,既能存、能比、能排序,还保留了地理上的远近关系。这篇文章把它的原理、精度规律和实际用途讲清楚。
geohash 到底是什么
geohash 是把纬度和经度合在一起,用 base32 编出来的一个短字符串。它用的字母表是 0123456789bcdefghjkmnpqrstuvwxyz,故意去掉了 a、i、l、o 这四个容易看错的字符。整个地球被递归地切成网格,字符串越长,指向的格子越小,定位越精确。
它最有用的一个性质:相近的两个地点,往往共享相同的开头。北京国贸和建国门只差一公里,它们的 geohash 前几位是一样的。这个前缀共享的特点,是后面所有索引和搜索玩法的根基。
编码原理:交错的递归二分
geohash 的编码过程,本质是一连串二分判断。经度从 -180 到 180、纬度从 -90 到 90 各自开一个区间,然后一步步对半砍:
- 看当前值落在区间的上半还是下半,上半记 1,下半记 0。
- 经度和纬度交替来,砍一刀经度,再砍一刀纬度。
- 每攒够 5 个二进制位,就映射成 base32 字母表里的一个字符。
所以 u4pru 这五个字符,背后是 25 次二分判断,按 5 位一组读出来。这种位的拆分和拼接,跟进制转换是同一套思路,如果你想直观看不同进制之间怎么换算,可以顺手玩一下 /zh/t/base-converter/。解码就是把同样的切分过程倒着回放一遍,还原出那个格子。
精度与长度:字符越长,格子越小
这是 geohash 最值得记住的一条规律:字符串每多一位,格子就小一截,定位就更精确。每个字符贡献 5 个二进制位,相当于把当前格子再细分一轮。赤道附近的大致对应关系是这样:
- 5 位:约 4.9 公里,能定位到一个城市片区。
- 6 位:约 1.2 公里,一个街区的尺度。
- 7 位:约 153 米。
- 8 位:约 38 米。
- 9 位:约 4.8 米,能钉到一家店面门口。
- 11 位:约 15 厘米,比消费级 GPS 还细。
挑长度不是越长越好,而是按需要的精确度来选。存城市级的兴趣点 6 位就够,街道地址用 8 到 9 位,测绘点位才上 11 到 12 位。你可以在 /zh/t/geohash-converter/ 里拖动精度滑块,看格子尺寸实时跟着变,选长度时心里就有数了。
一个真实的输入输出例子
拿丹麦那个常被引用的坐标举例。输入纬度 57.64911、经度 10.40744:
- 编成 5 位,得到 u4pru,这是个约 4.9 公里的大格子。
- 编成 11 位,得到 u4pruydqqvj,格子缩到约 15 厘米。
- 把 u4pruydqqvj 解码回来,落点距离原始输入不到 15 厘米。
这里有个容易踩的坑:解码返回的是格子的中心,不是你最初那个精确点,所以总带着半个格子的误差。位数高时这个误差小到可以忽略,位数低时就要心里有底。
它为什么适合做地理索引和附近搜索
我自己第一次真正用上 geohash,是给一个没有专门地理数据库的项目加附近门店查询。当时门店坐标就存在普通 SQL 表里,硬算两两距离太慢。换成 geohash 之后,思路一下子简单了:把每个门店坐标编成 geohash 存成字符串,加个普通索引,查附近就变成一句「筛出 geohash 以某个前缀开头的行」。B 树、有序键列表、Redis 有序集合都能廉价地干这件事,Redis 的 GEOADD、Elasticsearch、DynamoDB 底层都靠这个套路。
但只靠前缀匹配有个硬伤:相距一米的两个点,如果刚好跨在格子边界两侧,会落进不同的前缀,被漏掉。所以正确的半径搜索要查中心格子加它的 8 个邻居(北、东北、东、东南、南、西南、西、西北),再按真实距离过滤一遍,这样网格线上的盲缝才补得上。
顺带提一句,做这类范围估算时常要换算尺度和单位,比如把米换成公里、把面积折算成边长,配合 /zh/t/unit-converter/ 处理起来会顺手很多。
小结
geohash 用一串短字符把经纬度编码成可索引、可排序、能体现远近的 token,核心是经纬度交替的递归二分,字符越长精度越高,附近搜索时记得带上 8 个邻居。理解了这套机制,你在选精度、排查坐标、设计地理索引时,就不用再照搬教程里的魔法数字。
Made by Toolora · Updated 2026-06-13