纬度和经度作为保留区域的字符串
nibble:一小块食物被咬了。在计算中:一半的信息。每个nibble都在五分钟内解释了计算科学或软件工程的想法或系统。
照片地球仪在一个简单的URL-SAFE字符串中编码经度和纬度,用于geohash.org。它们很有趣,因为两个共享相同前缀的地理峰非常接近。例如,柏林的Alexanderplatz拥有Geohash u33dc1r4。附近的Brandenburger Tor拥有Geohash u33db2jx-请注意共享的前缀u33d
。
顺便说一句,我以前没有编程过,只是跑了Hello World。我不会把那个寓言放在桌子上!
什么是Geohash?
Geohash将纬度和经度编码为一串数字和字母。它的基本32编码,因此使用32个可能的字符:10位数字0-9和22个字母A-Z不包括A,I,L和O。
例如,沙特阿拉伯的Geohash sunny解码为23.7 42.5,或N 23°42.000'E 42°30.000'。大多数地球什台不是那么令人难忘的单词,但是如果您无聊,您可以检查单词或名称的地理位置位置。 My name在马达加斯加的海岸附近。
对于像我一样在地理上受到挑战的读者而言:当表示为小数时,积极的纬度位于赤道的北部,而对南方则负面。纬度分别在南极和北极分别为-90°至90°。积极的经度位于主要子午线以东(通过英国格林威治),而否定是西部的。经度的范围为-180°至180°。好ol'Cycyclopaedia Britannica有一些nice visualizations。
正如我们将看到的那样,通过施工地理用品有一些有趣的属性。
-
两个共同前缀的GeoHashes具有靠近的坐标。
-
地理船的时间越长,它就越准确。较长的地峰描述了较小的区域。
-
一个将字符添加到另一个GeoHash的地理船,是该GeoHash的子区域。
另一方面,不一定会有两个附近的地点共享一个地理前缀。在赤道,主要子午线和极点周围,附近的位置可能具有完全不同的纬度和纵向,从而导致不同的地理句。
Z阶曲线
地球什台地图上的纬度和经度在z-order curve上。 Z阶曲线是从二维坐标到一维坐标的一般映射 - 线上的一个点。映射保留了区域:如果两个2D坐标靠近,则它们的1D坐标也接近。 Z阶曲线是space-filling curve的特定类型:范围包含整个二维正方形的曲线。简而
z订单曲线易于构造:交织x和y坐标的位以获得1D坐标。如果正确布置轴,则会在二维平面中创建递归Z形曲线:
该表在顶部水平水平的0-3坐标,y在左侧垂直坐标。虚线显示了由此产生的z订单,从左上角开始。
z阶曲线可以推广到超过两个维度,并且当需要顺序布置多维数据时,通常出于性能原因。示例是:
-
位置的数据库索引。允许通过寻找类似的前缀来搜索附近的位置。
-
GPU中纹理图的内存布局。优化内存布局以减少缓存失误。
-
有效的矩阵乘法,以减少高速缓存。
解码GeoHash
首先,让我们尝试将给定的地球什台解码为纬度和经度。
简单的部分是找到像“ GBSUV”这样的地理弦字符串的位表示。由于GeoHash使用Base-32编码,因此每个字符对应于5位。我们只需要一个常数包含允许字符。字符串中字符的索引包含我们感兴趣的5位:
const base32 = "0123456789bcdefghjkmnpqrstuvwxyz"
c := strings.IndexRune(base32, letter)
现在如何将这些位处理成纬度和经度?
首先,我们知道GeoHash是Z阶曲线,因此其位置是经度和纬度交错的。地球上的均匀位是经度,奇数是纬度。
第二,GeoHash没有描述确切的位置。坐标GeoHash.org返回确实是“矩形”区域的中点。矩形在恐慌行情中,因为它是一个矩形,该矩形投射在球体上 - 在杆子上,这是一个三角形!该区域可以用最小和最大的经度和纬度描述,对于矩形的两个角。
这是一个动画,即10位如何收敛到越来越小的区域(橙色) - 每个位移动一个角落:
空的Geohash代表整个地球。第一位确定位置是在东半球还是西半球。第二位确定该地区是在北半球还是南半球 - 我们知道,地点是地球的哪个象限。每个位置大约将该区域沿东西向东或南北延伸。
地理船的时间越长,区域越小。为了校准您的直觉,一个8个字符的地理座描述了40m乘40m的区域。
这是解码GeoHash的GO代码:
const latMin, latMax = -90.0, 90.0
const lonMin, lonMax = -180.0, 180.0
func decode(geohash string) (lat, lon float64) {
curMinLat, curMaxLat := latMin, latMax
curMinLon, curMaxLon := lonMin, lonMax
bitIsLongitude := true
// loop through each character in the geohash from left to right
for _, character := range geohash {
// decode the base32 representation
nibblebit := strings.IndexRune(base32, character)
// each character represents 5 bits
for b := 4; b >= 0; b-- {
// get the bit at position b
bit := nibblebit & (1 << uint(b))
if bitIsLongitude {
if bit == 0 {
curMaxLon = (curMinLon + curMaxLon) / 2
} else {
curMinLon = (curMinLon + curMaxLon) / 2
}
} else {
if bit == 0 {
curMaxLat = (curMinLat + curMaxLat) / 2
} else {
curMinLat = (curMinLat + curMaxLat) / 2
}
}
bitIsLongitude = !bitIsLongitude
}
}
// we now have a region - return the midpoint
return (curMinLat + curMaxLat) / 2, (curMinLon + curMaxLon) / 2
}
编码GeoHash
编码从纬度和经度的地理船类似的工作。
再次,我们从一个包含整个地球的区域开始。首先,我们确定经度是在西半球还是在西半球,并分别添加0或1。然后,我们查看纬度以确定下一位,再次将区域减半。
func encode(lat, lon float64, precision int) string {
var geohash []rune
if precision > 12 {
precision = 12
}
curMinLat, curMaxLat := latMin, latMax
curMinLon, curMaxLon := lonMin, lonMax
bitIsLongitude := true
nibblebit_idx := 4
nibblebit := 0
for len(geohash) < precision {
if bitIsLongitude {
mid := (curMinLon + curMaxLon) / 2
if lon > mid {
nibblebit |= 1 << uint(nibblebit_idx)
curMinLon = mid
} else {
curMaxLon = mid
}
} else {
mid := (curMinLat + curMaxLat) / 2
if lat > mid {
nibblebit |= 1 << uint(nibblebit_idx)
curMinLat = mid
} else {
curMaxLat = mid
}
}
bitIsLongitude = !bitIsLongitude
nibblebit_idx--
if nibblebit_idx == -1 {
// we have a full base32 character
geohash = append(geohash, rune(base32[nibblebit]))
nibblebit_idx = 4
nibblebit = 0
}
}
return string(geohash)
}
我没有意识到何时停止的速度 - 每个区域都可以永远分为一半(直到您达到浮点精度的极限)。为了解决这个问题,我添加了一个precision
参数来指定生成的GeoHash应该具有的字符数。
这是一个包装
结论地理句和Z阶曲线。 Z阶曲线将对创建四轮脉(具有进一步的地理和计算机几何应用程序)的数据结构很有用。我将在下一个bble中讨论四分之一,所以请继续关注。
关于GO的注释:我知道为什么它很受欢迎。它立即感到熟悉,并避免了其他一些语言积累的荒谬功能的荒谬爆炸。我也喜欢收集的垃圾。 GC非常适合大量应用程序,请使用from John Carmack!
感谢您的阅读!我每个月写一个小叮当。有关更多信息,subscribe to my newletter和follow me on Twitter或Mastodon。
参考和致谢
Encode and decode geohashes in the browser and reference JavaScript code
-
github的副驾驶生成了很多GO代码。令人难忘的实例:我键入
base32
,并填充了常数。我输入了decode(geohash
,并填充了很多decode
函数,包括算法的轮廓。该代码确实有几个错误和问题。 -
用Manim community创建的插图。