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 数据