geohash编解码
import (
"github.com/mmcloughlin/geohash"
)
func Test_Demo(t *testing.T) {
// 编码经纬度到 geohash 字符串
lat, lon := 40.7128, -74.0060 // 纽约市的位置
geoHash := geohash.Encode(lat, lon) // geohash 字符串
fmt.Println(geoHash) // dr5regw3ppyz
// 解码 geohash 字符串到经纬度坐标
lat, lon = geohash.Decode("dr5regw3ppyz")
fmt.Printf("lat: %.4f, lon: %.4f", lat, lon)
}
mmcloughlin开发的geohash
func ConvertIntToString(hash uint64, chars uint) string
将整数 geohash 转换为具有 chars 字符的等效字符串 geohash
精度:5chars
The provided integer geohash is interpreted to have 5chars bits of precision.
func ConvertStringToInt(hash string) (uint64, uint)
将字符串 geohash 转换为等效的整数 geohash
返回整数哈希值及其精度
func Decode(hash string) (lat, lng float64)
将字符串 geohash 解码为 (lat, lng) 坐标
func DecodeCenter(hash string) (lat, lng float64)
将字符串 geohash 解码到边界框的中心点
func DecodeInt(hash uint64) (lat, lng float64)
将提供的 64 位整数 geohash 解码为 (lat, lng) 坐标
func DecodeIntWithPrecision(hash uint64, bits uint) (lat, lng float64)
将提供的整数 geohash 解码为 (lat, lng) 点的精度位。
func Encode(lat, lng float64) string
函数编码
将(lat、lng)编码为具有标准 12 个字符精度的字符串 geohash
func EncodeInt(lat, lng float64) uint64
将 (lat, lng) 编码为 64 位整数 geohash
func EncodeIntWithPrecision(lat, lng float64, bits uint) uint64
将 (lat, lng) 编码为具有指定位数的整数
func EncodeWithPrecision(lat, lng float64, chars uint) string
将 (lat, lng) 编码为具有指定精度字符数(最多 12)的字符串 geohash
func Neighbor(hash string, direction Direction) string
返回一个 geohash 字符串,该字符串对应于所提供方向上所提供的 geohash 的邻居
func NeighborInt(hash uint64, direction Direction) uint64
返回一个 uint64,它对应于所提供的散列在所提供的方向上以 64 位精度的邻居
func NeighborIntWithPrecision(hash uint64, bits uint, direction Direction) uint64
返回一个 uint64s,它对应于给定精度的指定方向上所提供的哈希的邻居
func Neighbors(hash string) []string
返回与所提供的 geohash 邻居相对应的 geohash 字符串切片
func NeighborsInt(hash uint64) []uint64
返回一个 uint64 切片,对应于所提供的哈希的 64 位精度的邻居
func NeighborsIntWithPrecision(hash uint64, bits uint) []uint64
返回 uint64 的切片,该切片对应于给定精度下提供的哈希的邻居
func Validate(hash string) error
验证字符串 geohash
type Box struct {
MinLat float64
MaxLat float64
MinLng float64
MaxLng float64
}
表示纬度/经度空间中的矩形
func BoundingBox(hash string) Box
返回由给定字符串 geohash 编码的区域
func BoundingBoxInt(hash uint64) Box
返回由给定 64 位整数 geohash 编码的区域
func BoundingBoxIntWithPrecision(hash uint64, bits uint) Box
返回由具有指定精度的整数 geohash 编码的区域
func (b Box) Center() (lat, lng float64)
返回盒子的中心
func (b Box) Contains(lat, lng float64) bool
决定 (lat, lng) 是否包含在框中
func (b Box) Round() (lat, lng float64)
返回框内的一个点,努力舍入到最小精度
type Direction int
表示纬度/经度空间中的方向
const (
North Direction = iota // 0
NorthEast
East
SouthEast
South
SouthWest
West
NorthWest
)
基本方向和基本方向,从正北开始,顺时针旋转一圈
Geohash算法
利用分层的网格系统来表示地球上的给定区域
在每个网格中,Geohash单元格将地球表面的区域划分为更小的子区域
每个 Geohash 单元格都可以映射到一个不同的 Geohash 字符串
由于 Geohash 单元格始终是正方形的,因此每个 Geohash 单元格的实际外观是类似于矩形的。
geohash.DecodeCenter 函数使用该矩形,以确定 Geohash 编码本身所代表的地理区域的标准矩形外接盒子,并返回该盒子的中心点经纬度坐标作为解码结果。
Geohash 字符串本身并不包含与其对应的矩形精确的边界坐标,因此在使用 geohash.DecodeCenter 函数时,应该了解解码结果可能与实际位置略有偏差的事实。
示例
纬度 lat:39.9257460000,经度 lng:116.5998310000
纬度二进制计算
范围 | 负区间 | 正区间 | 取值 |
---|---|---|---|
(-90, 90) | (-90,0] | (0, 90) | 1 |
(0, 90) | (0,45] | (45,90) | 0 |
(0, 45) | (0,22.5) | [22.5,45) | 1 |
(22.5,45) | (22.5,33.75) | [33.75,45) | 1 |
… | … | … | 1 |
… | … | … | 0 |
… | … | … | 0 |
… | … | … | 0 |
… | … | … | 1 |
纬度落于区间使用1和0进行标识,最后可以组成一个二进制数字101110001100011;同理,经度二进制计算 110100101100010。
最后,奇数位放纬度,偶数位放经度:
得到30位的字节数组:
11100 11101 00100 01111 00000 01101
每五位二进制转为一个十进制:
t1 := 0b11100
t2 := 0b11101
t3 := 0b00100
t4 := 0b01111
t5 := 0b00000
t6 := 0b01101
fmt.Println(t1, t2, t3, t4, t5, t6) // 28 29 4 15 0 13
对应base32编码 wx4g0e
原理
将整个地图或者某个分割所得的区域进行进一步划分,由于采用的是base32编码方式,这样可以将整个地图区域分为32个区域,通过00000 ~ 11111来标识这32个区域。
第一次对地图划分后的情况如下图所示(每个区域中的编号对应于该区域所对应的编码)
Geohash的0、1串序列是经度0、1序列和纬度0、1序列中的数字交替进行排列,偶数位对应的序列为经度序列(从0位开始),奇数位对应的序列为纬度序列。
在进行第一次划分时,Geohash0、1序列中的前5个bits(11100),那么这5bits中有3bits是表示经度,2bits表示纬度。
将经度划分成8个区段(2^3 = 8)
将纬度划分为4个区段(2^2 = 4)
形成了32个区域(对应Base32)
缺点
将每一个区域画成一块块矩形块,每个矩形块使用一个字符串表示,当需要查询附近的点时,通过自己的坐标计算出一个字符串,根据这个字符串定位到所在的矩形块,然后返回这个矩形块中的点。
例如 wx4e就包含wx4e0e,也就是说wx4e0e在wx4e范围内。
通行做法:不仅获取当前所在的矩形区域,还获取周围8个矩形块中的点(已经知道自己的经纬度,只需要用自己的经纬度减去最小划分单位的经纬度)。
通过上面这张图,就能很容易的计算出周围8个点的经纬度。有了经纬度就能定位到周围8个矩形块。这样就能获取包括自己所在矩形块的9个矩形块中的所有的点。
最后分别计算这些点和自己的距离(由于范围很小,点的数量就也很少,计算量就很少)过滤掉不满足条件的点。
redis中的geohash
存储地理位置信息,并对存储的信息进行操作。
geoadd:添加地理位置的坐标。
geopos:获取地理位置的坐标。
geodist:计算两个位置之间的距离。
georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
geohash:返回一个或多个位置对象的 geohash 值。
geoadd
存储指定的地理空间位置,将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定 key
GEOADD key longitude latitude member [longitude latitude member …]
geopos
从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil
GEOPOS key member [member …]
geodist
返回两个给定位置之间的距离
GEODIST key member1 member2 [m|km|ft|mi]
- member1 member2 为两个地理位置
- 最后一个距离单位参数说明:
m米,默认单位 / km千米 / mi英里 / ft英尺。
georadius、georadiusbymember
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
WITHCOORD: 将位置元素的经度和纬度也一并返回。
WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
COUNT 限定返回的记录数。
ASC: 查找结果根据距离从近到远排序。
DESC: 查找结果根据从远到近排序。
geohash
Redis GEO 使用 geohash 来保存地理位置的坐标。
geohash 用于获取一个或多个位置元素的 geohash 值。
geohash 语法格式如下:
GEOHASH key member [member …]
参考文档
https://zhuanlan.zhihu.com/p/35940647
附录
geohash编码
Base32
将数字 0~9 ,加上26个字母(去除a,i,l,o 四个)进行组合而成的32个字符编码形式