GIS

GeoHash是什么

了解GeoHash的基本概念、编码原理、层级结构、精度特点以及GeoHash在GIS空间索引、附近搜索和地理位置数据中的应用。

阅读约 97 分钟

GeoHash是什么

GeoHash是一种将经纬度坐标编码成字符串的地理空间编码方法。

例如一个坐标:

116.397 39.908

经过GeoHash编码后,可以得到类似:

wx4g0ec1

因此可以简单理解为:

经纬度
    ↓
GeoHash编码
    ↓
字符串

GeoHash的核心作用是:

将二维地理位置转换成具有空间层级关系的字符串,从而方便空间索引、附近搜索和位置数据存储。

GeoHash能够做什么

GeoHash常用于:

  • 地理位置编码
  • 空间索引
  • 附近搜索
  • LBS位置服务
  • 地图数据存储
  • 地理围栏
  • 位置聚合
  • 空间数据分区
  • 数据库空间查询

例如:

查询某个位置附近的商店。

可以先:

用户位置
    ↓
GeoHash
    ↓
查询附近GeoHash区域
    ↓
获取候选商店
    ↓
计算真实距离
    ↓
最终结果

GeoHash的基本结构

GeoHash本质上是一个字符串。

例如:

wx4g0ec1

字符串长度越长,表示的空间范围通常越小。

例如:

w
wx
wx4
wx4g
wx4g0
wx4g0e
wx4g0ec
wx4g0ec1

可以理解为逐级缩小空间范围:

全球
 ↓
较大区域
 ↓
城市附近
 ↓
街区
 ↓
更小区域
 ↓
具体位置附近

因此GeoHash具有明显的:

空间层级结构。

GeoHash的核心思想

GeoHash主要通过不断划分经纬度范围来编码位置。

首先将地球经纬度范围看成:

Longitude
[-180, 180]

Latitude
[-90, 90]

然后不断进行二分。

例如经度:

[-180, 180]

第一次划分:

[-180, 0]
[0, 180]

如果目标经度位于右侧:

[0, 180]

继续划分:

[0, 90]
[90, 180]

不断重复这个过程,就可以得到一串二进制信息。

纬度也采用类似方法。

GeoHash的二分过程

例如一个坐标:

Longitude = 116.397
Latitude = 39.908

经度范围:

[-180, 180]

不断二分:

[-180, 0]
[0, 180]

116.397位于:

[0, 180]

继续:

[0, 90]
[90, 180]

116.397位于:

[90, 180]

继续划分:

[90, 135]
[135, 180]

最终会得到一串二进制判断结果。

纬度也进行同样的过程。

GeoHash的二进制编码

经过经纬度不断二分后,可以得到:

Longitude bits
+
Latitude bits

然后将这些二进制位交错组合:

Longitude
Latitude
Longitude
Latitude
Longitude
Latitude
...

形成一串二进制编码。

例如:

101101001...

然后再将二进制数据转换成Base32字符。

最终得到:

wx4g0ec1

因此可以简单理解为:

经纬度
    ↓
不断二分
    ↓
二进制编码
    ↓
经纬度位交错
    ↓
Base32
    ↓
GeoHash字符串

GeoHash使用Base32

GeoHash通常使用一种特殊的Base32字符表。

常见字符包括:

0123456789bcdefghjkmnpqrstuvwxyz

其中部分容易混淆的字符会被排除,例如:

a
i
l
o

这样可以减少人工读取和输入时的混淆。

例如:

wx4g0ec1

就是一个GeoHash字符串。

GeoHash长度与精度

GeoHash长度越长,表示的区域通常越小。

例如可以粗略理解为:

短GeoHash
    ↓
较大区域
长GeoHash
    ↓
较小区域

例如:

wx

表示一个较大的区域。

而:

wx4g0ec1

表示一个更加精细的区域。

因此:

GeoHash长度可以控制空间索引的粗细。

GeoHash不是坐标本身

需要特别注意:

GeoHash不是:

经度
+
纬度

的简单字符串拼接。

例如:

116.397,39.908

和:

wx4g0ec1

不是同一种数据。

GeoHash是一种:

空间编码结果。

它通过编码表示某个地理位置所在的空间网格。

GeoHash表示的是区域

虽然GeoHash通常由一个坐标生成,但GeoHash实际上对应的是一个空间网格区域

例如:

GeoHash = wx4g

可以理解为:

┌────────────────────┐
│                    │
│      wx4g          │
│                    │
└────────────────────┘

该字符串代表的是这个区域,而不仅仅是一个无限精确的点。

因此GeoHash可以用于:

空间分区

GeoHash的层级关系

GeoHash具有非常明显的前缀关系。

例如:

wx4
wx4g
wx4g0
wx4g0e
wx4g0ec

这些GeoHash具有共同前缀:

wx4

这意味着它们属于同一个更大的GeoHash区域。

可以理解为:

wx
└── wx4
    └── wx4g
        └── wx4g0
            └── wx4g0e

因此GeoHash非常适合做:

层级空间索引。

GeoHash与空间网格

GeoHash可以理解为一种空间网格编码。

例如:

┌────┬────┬────┬────┐
│    │    │    │    │
├────┼────┼────┼────┤
│    │    │    │    │
├────┼────┼────┼────┤
│    │    │    │    │
└────┴────┴────┴────┘

每一个网格都有自己的GeoHash。

随着GeoHash长度增加:

大网格
 ↓
小网格
 ↓
更小网格

因此GeoHash可以实现空间数据的网格化。

GeoHash附近搜索

GeoHash最常见的应用之一是附近搜索。

例如用户位置:

用户
  ●

附近商店:

●   ●

  ●

       ●

首先计算用户GeoHash:

用户坐标
    ↓
wx4g0ec1

然后查询:

wx4g0ec1

附近的GeoHash网格。

例如:

┌────┬────┬────┐
│邻居│邻居│邻居│
├────┼────┼────┤
│邻居│用户│邻居│
├────┼────┼────┤
│邻居│邻居│邻居│
└────┴────┴────┘

从而快速获取附近位置数据。

为什么需要查询邻居GeoHash

一个常见误区是:

只查询用户所在的GeoHash就可以找到附近所有对象。

实际上并不一定。

例如用户刚好位于网格边缘:

┌──────────┬──────────┐
│          │          │
│          │    ●     │
│          │          │
└──────────┴──────────┘

用户附近的对象可能位于相邻网格。

因此附近搜索通常需要查询:

当前GeoHash
+
周围邻居GeoHash

然后再进行精确距离计算。

GeoHash邻居

假设当前GeoHash:

wx4g

周围可能存在:

wx4f
wx4g
wx4h

以及其他相邻网格。

可以形成:

┌────────┬────────┬────────┐
│        │        │        │
│   N    │   N    │   N    │
├────────┼────────┼────────┤
│   N    │ 当前   │   N    │
├────────┼────────┼────────┤
│   N    │   N    │   N    │
└────────┴────────┴────────┘

实际查询时,需要根据具体GeoHash精度和位置计算邻居。

GeoHash与数据库

GeoHash非常适合存储在数据库中。

例如:

id | longitude | latitude | geohash
---|-----------|----------|--------
1  | 116.397   | 39.908   | wx4g0ec
2  | 116.405   | 39.915   | wx4g0ed
3  | 116.412   | 39.920   | wx4g0ee

可以对:

geohash

字段建立索引。

查询附近区域时:

GeoHash前缀
    ↓
数据库索引
    ↓
候选数据

可以减少全表扫描。

GeoHash与B-Tree索引

GeoHash的一个优势是:

空间位置被转换成字符串后,可以使用普通数据库的字符串索引。

例如:

geohash LIKE 'wx4g%'

可以筛选某个GeoHash区域内的数据。

因此在某些数据库环境中:

空间数据
    ↓
GeoHash字符串
    ↓
B-Tree索引

可以实现简单的空间查询。

不过这并不意味着GeoHash可以完全替代专业空间索引。

GeoHash与R-Tree

GIS数据库中常见的空间索引包括:

R-Tree
GiST
QuadTree

GeoHash则属于:

空间编码

两者思路不同。

GeoHash:

Geometry
    ↓
编码
    ↓
字符串

R-Tree:

Geometry
    ↓
空间索引结构

因此:

GeoHash是一种空间编码方法,而R-Tree是一种空间索引结构。

GeoHash与QuadTree

GeoHash和QuadTree都有空间递归划分的思想。

GeoHash:

空间
 ↓
不断二分
 ↓
编码

QuadTree:

空间
 ↓
四分
 ↓
递归划分

可以理解为:

GeoHash
    ↓
空间划分 + 字符串编码
QuadTree
    ↓
空间划分 + 树结构

两者都可以用于空间索引,但实现方式不同。

GeoHash的空间连续性问题

GeoHash有一个非常重要的特点:

GeoHash字符串前缀相同,通常表示空间上属于同一个较大区域。

但是:

空间上相邻的两个位置,不一定具有相似的GeoHash字符串。

例如:

┌────────────┬────────────┐
│            │            │
│    A       │      B     │
│            │            │
└────────────┴────────────┘

A和B空间上非常接近。

但是它们的GeoHash可能出现:

A = wx4g
B = wx4u

字符串看起来并不一定连续。

因此:

GeoHash不能简单通过字符串距离判断地理距离。

GeoHash边界问题

GeoHash最典型的问题之一是网格边界。

例如:

┌────────────┬────────────┐
│            │            │
│     A      │      B     │
│            │            │
└────────────┴────────────┘

A和B可能距离非常近:

Distance(A, B) = 10m

但是:

GeoHash(A) = wx4g
GeoHash(B) = wx4u

它们可能属于不同的GeoHash区域。

因此GeoHash附近搜索通常不能只使用:

当前GeoHash

而需要考虑邻居网格。

GeoHash与精确距离

GeoHash适合:

快速筛选候选区域

但GeoHash本身并不负责精确距离计算。

例如:

用户
 ↓
GeoHash筛选
 ↓
候选POI
 ↓
Haversine Distance
 ↓
精确距离

因此常见流程是:

GeoHash
    ↓
粗筛
    ↓
Distance
    ↓
精确排序

GeoHash与Haversine距离

Haversine公式常用于计算两个经纬度点之间的球面距离。

例如:

Point A
    ↓
Haversine
    ↓
Point B
    ↓
距离

结合GeoHash:

用户位置
    ↓
GeoHash
    ↓
附近网格
    ↓
候选POI
    ↓
Haversine
    ↓
真实距离

这种方式可以兼顾:

查询速度
+
距离精度

GeoHash与经纬度

GeoHash通常直接基于:

Longitude
Latitude

进行编码。

例如:

Longitude = 116.397
Latitude = 39.908

因此在使用GeoHash之前,应确认:

X = 经度
Y = 纬度

并且数据通常使用:

WGS 84
EPSG:4326

坐标。

如果坐标已经是:

EPSG:3857

则通常需要先转换回适合GeoHash编码的经纬度坐标。

GeoHash与CRS

GeoHash通常是针对经纬度坐标设计的。

例如:

EPSG:4326

坐标:

116.397
39.908

可以直接用于GeoHash。

但:

EPSG:3857

可能是:

12957250
4852834

这种投影坐标。

不能简单把它们直接当成:

经度
纬度

进行GeoHash编码。

正确流程通常是:

EPSG:3857
    ↓
Coordinate Transform
    ↓
EPSG:4326
    ↓
Longitude / Latitude
    ↓
GeoHash

GeoHash的应用场景

LBS附近搜索

例如:

用户
 ↓
GeoHash
 ↓
附近餐厅

适合:

  • 餐厅
  • 酒店
  • 商店
  • 加油站
  • 充电站

GeoHash的应用:车辆位置

例如车辆实时上报:

Vehicle
longitude
latitude

转换:

longitude + latitude
        ↓
     GeoHash

数据库:

vehicle_id
longitude
latitude
geohash
timestamp

可以根据GeoHash快速查询某个区域的车辆。

GeoHash的应用:物流

物流系统可以使用GeoHash进行:

车辆位置
+
订单位置
+
仓库位置

空间分区。

例如:

订单
 ↓
GeoHash
 ↓
区域
 ↓
附近配送车辆

然后结合Distance:

Distance
 ↓
最近车辆

GeoHash的应用:地理围栏

可以将区域划分成GeoHash网格:

┌────┬────┬────┐
│    │    │    │
├────┼────┼────┤
│    │    │    │
├────┼────┼────┤
│    │    │    │
└────┴────┴────┘

车辆进入某些GeoHash区域后:

Vehicle GeoHash
       ↓
Geofence GeoHash
       ↓
候选判断

不过如果需要精确的围栏边界判断,还需要进一步进行:

Point in Polygon

等Geometry空间关系计算。

GeoHash的应用:空间聚合

例如统计:

每个区域有多少用户。

可以:

用户坐标
    ↓
GeoHash
    ↓
GROUP BY geohash
    ↓
数量统计

结果:

GeoHash | 用户数量
--------|--------
wx4g    | 1200
wx4h    | 980
wx4j    | 760

这样就可以快速生成:

空间热力分布

GeoHash与热力图

GeoHash可以作为空间聚合单元。

例如:

用户
用户
用户
用户
 ↓
GeoHash
 ↓
网格统计
 ↓
热力图

不同GeoHash区域:

┌────┬────┬────┐
│ 12 │ 35 │  8 │
├────┼────┼────┤
│ 88 │120 │ 43 │
├────┼────┼────┤
│ 15 │ 62 │ 21 │
└────┴────┴────┘

可以进一步可视化成:

人口热力图
订单热力图
车辆热力图
用户活跃度热力图

GeoHash的优点

GeoHash主要有以下优点:

1. 字符串结构简单

例如:

wx4g0ec1

容易存储和传输。

2. 具有层级关系

例如:

wx
wx4
wx4g
wx4g0

可以表达不同空间尺度。

3. 方便数据库索引

可以将GeoHash存储为:

VARCHAR

并建立普通索引。

4. 适合附近搜索

通过查询当前网格和邻居网格,可以快速获取候选数据。

5. 适合空间聚合

可以按照GeoHash进行:

GROUP BY

实现空间网格统计。

GeoHash的缺点

GeoHash也存在一些局限。

1. 边界问题

相邻位置可能属于不同GeoHash。

A | B

即使:

Distance(A, B)

非常小,也可能:

GeoHash(A) ≠ GeoHash(B)

2. 精度不是完全均匀

由于经纬度范围不同:

Longitude
Latitude

对应的实际地面距离会随纬度发生变化。

因此GeoHash网格并不是全球范围内完全相同大小。

3. 不适合直接计算精确距离

GeoHash主要用于:

空间索引

而不是:

距离计算

4. 不能完全替代空间数据库

复杂GIS分析仍然需要:

PostGIS
R-Tree
GiST
Geometry
Spatial Query

等技术。

GeoHash与PostGIS

如果使用PostGIS,可以同时使用:

Geometry
+
GeoHash

例如:

Point
    ↓
ST_GeoHash
    ↓
GeoHash

然后利用GeoHash做快速候选筛选,再使用PostGIS空间函数进行精确计算。

典型流程:

GeoHash
    ↓
粗筛
    ↓
PostGIS Geometry
    ↓
ST_DWithin
    ↓
精确空间查询

GeoHash与GeoJSON

GeoHash不是GeoJSON的一部分,但可以作为GeoJSON属性保存。

例如:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [116.397, 39.908]
  },
  "properties": {
    "geohash": "wx4g0ec1"
  }
}

此时:

geometry
    ↓
真实空间位置

properties.geohash
    ↓
空间索引辅助信息

GeoHash与Feature

一个GIS Feature可以同时包含:

Feature
├── Geometry
│   └── Point
│
└── Attributes
    ├── name
    └── geohash

例如:

Feature
    ↓
Point
    ↓
116.397,39.908
    ↓
GeoHash
    ↓
wx4g0ec1

因此GeoHash通常是:

Geometry之外的一种空间编码信息。

GeoHash与WKT

WKT用于表示Geometry:

POINT (116.397 39.908)

GeoHash用于编码位置:

wx4g0ec1

两者作用不同:

WKT
 ↓
表达Geometry

GeoHash
 ↓
表达空间网格编码

可以从WKT中提取Point坐标,然后生成GeoHash:

WKT
 ↓
POINT
 ↓
Longitude / Latitude
 ↓
GeoHash

GeoHash与坐标精度

GeoHash长度决定空间编码的精细程度。

一般来说:

GeoHash长度增加
        ↓
编码区域缩小
        ↓
空间精度提高

例如:

wx

比:

wx4g0ec1

代表更大的区域。

因此实际应用中应该根据需求选择长度。

例如:

城市级分析
    ↓
较短GeoHash

附近搜索
    ↓
较长GeoHash

精细位置索引
    ↓
更长GeoHash

GeoHash编码流程

完整流程可以表示为:

Longitude
    +
Latitude
    ↓
经纬度范围二分
    ↓
生成二进制位
    ↓
交错Longitude / Latitude
    ↓
每5位一组
    ↓
Base32
    ↓
GeoHash

例如:

116.397, 39.908
        ↓
二分编码
        ↓
二进制
        ↓
Base32
        ↓
wx4g0ec1

GeoHash解码

GeoHash也可以反向解析。

例如:

wx4g0ec1

通过解码可以得到一个经纬度范围:

Longitude
[116.39, 116.40]

Latitude
[39.90, 39.91]

因此GeoHash解码通常得到的是:

一个空间范围,而不是绝对精确的原始坐标。

可以进一步取范围中心:

GeoHash
    ↓
Bounding Box
    ↓
Center
    ↓
Approximate Point

GeoHash与Bounding Box

GeoHash对应一个矩形范围:

┌──────────────────┐
│                  │
│     GeoHash      │
│                  │
└──────────────────┘

这个矩形就是:

Bounding Box

因此GeoHash解码通常可以得到:

minLongitude
maxLongitude
minLatitude
maxLatitude

即:

BBox

GeoHash完整数据关系

可以将GeoHash放到GIS数据体系中理解:

Longitude + Latitude
        ↓
      Point
        ↓
     Geometry
        ↓
     Feature
        ↓
   GeoHash Index
        ↓
 Spatial Search

GeoHash并不是Geometry本身,而是:

Geometry
    ↓
空间编码
    ↓
GeoHash

GeoHash与空间索引完整流程

实际系统中可以采用:

用户位置
    ↓
Longitude / Latitude
    ↓
GeoHash
    ↓
查询当前网格
    ↓
查询相邻网格
    ↓
获取候选对象
    ↓
Geometry / Distance精确计算
    ↓
最终结果

例如附近商店:

用户
 ↓
GeoHash
 ↓
9个邻居网格
 ↓
商店候选数据
 ↓
Haversine Distance
 ↓
按照距离排序
 ↓
最近商店

总结

GeoHash是一种将经纬度位置编码成字符串的空间编码方法。

核心流程:

Longitude
+
Latitude
    ↓
空间二分
    ↓
二进制编码
    ↓
Base32
    ↓
GeoHash

GeoHash具有:

层级性
空间分区
前缀关系
可索引性

常见用途:

附近搜索
空间索引
LBS
车辆定位
物流
地理围栏
空间聚合
热力图

可以记住:

GeoHash不是Geometry,也不是精确距离,而是一种将地理位置转换成具有空间层级关系的字符串编码方法。

最典型的应用流程是:

经纬度
    ↓
GeoHash
    ↓
空间网格
    ↓
快速筛选
    ↓
Distance / Spatial Relation
    ↓
精确空间分析
🧰

相关工具

使用 IYATools 在线工具快速处理 GIS 数据