空间点像素索引(一)

给你一个需求,查找定位点附近的一定范围内所有餐馆,你会怎么实现呢?

一一计算定位点与所有餐馆的距离,然后取出最小的距离?如果同时有很多遍布全国的请求都在查找附近的餐馆,按照上述的做法,你的服务有能力及时响应么?本文介绍了两种高效的空间点查找方法,并且该方法在打车领域和地图领域有广泛的实际应用。文章很长,如果来不及看完,只需要记得,如果你需要一种高效的空间点索引算法来处理海量的空间点查找需求,那么Geohash和Google S2可以帮助到你。

引论

每天我们晚上加班回家,可能都会用到滴滴或者共享单车。打开 app 会看到如下的界面:

app 界面上会显示出自己附近一个范围内可用的出租车或者共享单车。假设地图上会显示以自己为圆心,5公里为半径,这个范围内的车。如何实现呢?最直观的想法就是去数据库里面查表,计算并查询车距离用户小于等于5公里的,筛选出来,把数据返回给客户端。这种做法比较笨,一般也不会这么做。为什么呢?因为这种做法需要对整个表里面的每一项都计算一次相对距离。太耗时了。既然数据量太大,我们就需要分而治之。那么就会想到把地图分块。这样即使每一块里面的每条数据都计算一次相对距离,也比之前全表都计算一次要快很多。我们也都知道,现在用的比较多的数据库 MySQL、PostgreSQL 都原生支持 B+ 树。这种数据结构能高效的查询。地图分块的过程其实就是一种添加索引的过程,如果能想到一个办法,把地图上的点添加一个合适的索引,并且能够排序,那么就可以利用类似二分查找的方法进行快速查询。问题就来了,地图上的点是二维的,有经度和纬度,这如何索引呢?如果只针对其中的一个维度,经度或者纬度进行搜索,那搜出来一遍以后还要进行二次搜索。那要是更高维度呢?三维。可能有人会说可以设置维度的优先级,比如拼接一个联合键,那在三维空间中,x,y,z 谁的优先级高呢?设置优先级好像并不是很合理。本篇文章就来介绍2种比较通用的空间点索引算法。

一. GeoHash 算法

  1. Geohash 算法简介

Geohash 是一种地理编码,由 Gustavo Niemeyer 发明的。它是一种分级的数据结构,把空间划分为网格。Geohash 属于空间填充曲线中的 Z 阶曲线(Z-order curve)的实际应用。何为 Z 阶曲线?

上图就是 Z 阶曲线。这个曲线比较简单,生成它也比较容易,只需要把每个 Z 首尾相连即可。

Z 阶曲线同样可以扩展到三维空间。只要 Z 形状足够小并且足够密,也能填满整个三维空间。说到这里可能读者依旧一头雾水,不知道 Geohash 和 Z 曲线究竟有啥关系?其实 Geohash算法 的理论基础就是基于 Z 曲线的生成原理。继续说回 Geohash。Geohash 能够提供任意精度的分段级别。一般分级从 1-12 级。

还记得引语里面提到的问题么?这里我们就可以用 Geohash 来解决这个问题。我们可以利用 Geohash 的字符串长短来决定要划分区域的大小。这个对应关系可以参考上面表格里面 cell 的宽和高。一旦选定 cell 的宽和高,那么 Geohash 字符串的长度就确定下来了。这样我们就把地图分成了一个个的矩形区域了。地图上虽然把区域划分好了,但是还有一个问题没有解决,那就是如何快速的查找一个点附近邻近的点和区域呢?Geohash 有一个和 Z 阶曲线相关的性质,那就是一个点附近的地方(但不绝对) hash 字符串总是有公共前缀,并且公共前缀的长度越长,这两个点距离越近。由于这个特性,Geohash 就常常被用来作为唯一标识符。用在数据库里面可用 Geohash 来表示一个点。Geohash 这个公共前缀的特性就可以用来快速的进行邻近点的搜索。越接近的点通常和目标点的 Geohash 字符串公共前缀越长(但是这不一定,也有特殊情况,下面举例会说明)Geohash 也有几种编码形式,常见的有2种,base 32 和 base 36。

base 36 的版本对大小写敏感,用了36个字符,“23456789bBCdDFgGhHjJKlLMnNPqQrRtTVWX”。

  1. geohash实际应用举例

接下来的举例以 base-32 为例。举个例子。

上图是一个地图,地图中间有一个美罗城,假设需要查询距离美罗城最近的餐馆,该如何查询?第一步我们需要把地图网格化,利用 geohash。通过查表,我们选取字符串长度为6的矩形来网格化这张地图。经过查询,美罗城的经纬度是[31.1932993, 121.43960190000007]。先处理纬度。地球的纬度区间是[-90,90]。把这个区间分为2部分,即[-90,0),[0,90]。31.1932993位于(0,90]区间,即右区间,标记为1。然后继续把(0,90]区间二分,分为[0,45),[45,90],31.1932993位于[0,45)区间,即左区间,标记为0。一直划分下去。

再处理经度,一样的处理方式。地球经度区间是[-180,180]

纬度产生的二进制是101011000101110,经度产生的二进制是110101100101101,按照**“偶数位放经度,奇数位放纬度”**的规则,重新组合经度和纬度的二进制串,生成新的:111001100111100000110011110110,最后一步就是把这个最终的字符串转换成字符,对应需要查找 base-32 的表。11100 11001 11100 00011 00111 10110转换成十进制是 28 25 28 3 7 22,查表编码得到最终结果,wtw37q。我们还可以把这个网格周围8个各自都计算出来。

从地图上可以看出,这邻近的9个格子,前缀都完全一致。都是wtw37。如果我们把字符串再增加一位,会有什么样的结果呢?Geohash 增加到7位。

当Geohash 增加到7位的时候,网格更小了,美罗城的 Geohash 变成了 wtw37qt。看到这里,读者应该已经清楚了 Geohash 的算法原理了。咱们把6位和7位都组合到一张图上面来看。

可以看到中间大格子的 Geohash 的值是 wtw37q,那么它里面的所有小格子前缀都是 wtw37q。可以想象,当 Geohash 字符串长度为5的时候,Geohash 肯定就为 wtw37 了。接下来解释之前说的 Geohash 和 Z 阶曲线的关系。回顾最后一步合并经纬度字符串的规则,“偶数位放经度,奇数位放纬度”。读者一定有点好奇,这个规则哪里来的?凭空瞎想的?其实并不是,这个规则就是 Z 阶曲线。看下图:

x 轴就是纬度,y轴就是经度。经度放偶数位,纬度放奇数位就是这样而来的。最后有一个精度的问题,下面的表格数据一部分来自 Wikipedia。

  1. Geohash具体实现

到此,读者应该对 Geohash 的算法都很明了了。接下来用 Go 实现一下 Geohash 算法。

package geohash

import (

“bytes”

)

const (

BASE32 = “0123456789bcdefghjkmnpqrstuvwxyz”

MAX_LATITUDE float64 = 90

MIN_LATITUDE float64 = -90

MAX_LONGITUDE float64 = 180

MIN_LONGITUDE float64 = -180

)

var (

bits = []int{16, 8, 4, 2, 1}

base32 = []byte(BASE32)

)

type Box struct {

MinLat, MaxLat float64 // 纬度

MinLng, MaxLng float64 // 经度

}

func (this
*Box) Width() float64 {

return this.MaxLng

  • this.MinLng

}

func (this
*Box) Height() float64 {

return
this.MaxLat - this.MinLat

}

// 输入值:纬度,经度,精度(geohash的长度)

// 返回geohash, 以及该点所在的区域

func Encode(latitude, longitude float64, precision int) (string, *Box) {

var geohash
bytes.Buffer

var minLat,
maxLat float64 =
MIN_LATITUDE, MAX_LATITUDE

var minLng,
maxLng float64 =
MIN_LONGITUDE, MAX_LONGITUDE

var mid float64 = 0

bit, ch, length, isEven := 0, 0, 0, true

for length
< precision {

if isEven {

if mid =
(minLng + maxLng) / 2; mid < longitude {

ch |= bits[bit]

minLng = mid

} else {

maxLng = mid

}

} else {

if mid =
(minLat + maxLat) / 2; mid < latitude {

ch |= bits[bit]

minLat = mid

} else {

maxLat = mid

}

}

isEven = !isEven

if bit <
4 {

bit++

} else {

geohash.WriteByte(base32[ch])

length, bit, ch = length+1, 0, 0

}

}

b := &Box{

MinLat: minLat,

MaxLat: maxLat,

MinLng: minLng,

MaxLng: maxLng,

}

return
geohash.String(), b

}

  1. Geohash优缺点

Geohash 的优点很明显,它利用 Z 阶曲线进行编码。而 Z 阶曲线可以将二维或者多维空间里的所有点都转换成一维曲线。在数学上成为分形维。并且 Z 阶曲线还具有局部保序性。Z 阶曲线通过交织点的坐标值的二进制表示来简单地计算多维度中的点的z值。一旦将数据被加到该排序中,任何一维数据结构,例如二叉搜索树,B树,跳跃表或(具有低有效位被截断)哈希表 都可以用来处理数据。通过 Z 阶曲线所得到的顺序可以等同地被描述为从四叉树的深度优先遍历得到的顺序。这也是 Geohash 的另外一个优点,搜索查找邻近点比较快。Geohash 的缺点之一也来自 Z 阶曲线。Z 阶曲线有一个比较严重的问题,虽然有局部保序性,但是它也有突变性。在每个 Z 字母的拐角,都有可能出现顺序的突变。

看上图中标注出来的蓝色的点点。每两个点虽然是相邻的,但是距离相隔很远。看右下角的图,两个数值邻近红色的点两者距离几乎达到了整个正方形的边长。两个数值邻近绿色的点也达到了正方形的一半的长度。Geohash 的另外一个缺点是,如果选择不好合适的网格大小,判断邻近点可能会比较麻烦。

看上图,如果选择 Geohash 字符串为6的话,就是蓝色的大格子。红星是美罗城,紫色的圆点是搜索出来的目标点。如果用 Geohash 算法查询的话,距离比较近的可能是 wtw37p,wtw37r,wtw37w,wtw37m。但是其实距离最近的点就在 wtw37q。如果选择这么大的网格,就需要再查找周围的8个格子。如果选择 Geohash 字符串为7的话,那变成黄色的小格子。这样距离红星星最近的点就只有一个了。就是 wtw37qw。如果网格大小,精度选择的不好,那么查询最近点还需要再次查询周围8个点。

二. 空间填充曲线和 分形

在介绍第二种多维空间点索引算法之前,要先谈谈空间填充曲线(Space-filling curve)和分形。解决多维空间点索引需要解决2个问题,第一,如何把多维降为低维或者一维?第二,一维的曲线如何分形?

  1. 空间曲线

在数学分析中,有这样一个难题:能否用一条无限长的线,穿过任意维度空间里面的所有点?

在1890年,Giuseppe Peano 发现了一条连续曲线,现在称为 Peano 曲线,它可以穿过单位正方形上的每个点。他的目的是构建一个可以从单位区间到单位正方形的连续映射。Peano 受到 Georg Cantor 早期违反直觉的研究结果的启发,即单位区间中无限数量的点与任何有限维度流型(manifold)中无限数量的点,基数相同。Peano 解决的问题实质就是,是否存在这样一个连续的映射,一条能填充满平面的曲线。上图就是他找到的一条曲线。一般来说,一维的东西是不可能填满2维的方格的。但是皮亚诺曲线恰恰给出了反例。皮亚诺曲线是一条连续的但处处不可导的曲线。皮亚诺曲线的构造方法如下:取一个正方形并且把它分出九个相等的小正方形,然后从左下角的正方形开始至右上角的正方形结束,依次把小正方形的中心用线段连接起来;下一步把每个小正方形分成九个相等的正方形,然后上述方式把其中中心连接起来……将这种操作手续无限进行下去,最终得到的极限情况的曲线就被称作皮亚诺曲线。皮亚诺对区间[0,1]上的点和正方形上的点的映射作了详细的数学描述。实际上,正方形的这些点对于,可找到两个连续函数 x = f(t) 和 y = g(t),使得 x 和 y 取属于单位正方形的每一个值。一年后,即1891年,希尔伯特就作出了这条曲线,叫希尔伯特曲线(Hilbert curve)。

上图就是1-6阶的希尔伯特曲线。具体构造方式在下一章再说。

上图是希尔伯特曲线填充满3维空间。之后还有很多变种的空间填充曲线,龙曲线(Dragon curve)、
高斯帕曲线(Gosper curve)、Koch曲线(Koch curve)、摩尔定律曲线(Moore curve)、谢尔宾斯基曲线(Sierpiński curve)、奥斯古德曲线(Osgood curve)。这些曲线和本文无关,就不详细介绍了。

在数学分析中,空间填充曲线是一个参数化的注入函数,它将单位区间映射到单位正方形,立方体,更广义的,n维超立方体等中的连续曲线,随着参数的增加,它可以任意接近单位立方体中的给定点。除了数学重要性之外,空间填充曲线也可用于降维,数学规划,稀疏多维数据库索引,电子学和生物学。空间填充曲线的现在被用在互联网地图中。

  1. 分形

皮亚诺曲线的出现,说明了人们对维数的认识是有缺陷的,有必要重新考察维数的定义。这就是分形几何考虑的问题。在分形几何中,维数可以是分数叫做分维。多维空间降维以后,如何分形,也是一个问题。分形的方式有很多种,这里有一个列表,可以查看如何分形,以及每个分形的分形维数,即豪斯多夫分形维(Hausdorff fractals dimension)和拓扑维数。这里就不细说分形的问题了,感兴趣的可以仔细阅读链接里面的内容。接下来继续来说多维空间点索引算法,下面一个算法的理论基础来自希尔伯特曲线,先来仔细说说希尔伯特曲线。

空间点像素索引(一)相关推荐

  1. 空间点像素索引(三)

    空间点像素索引(三) 点与坐标轴点相互转换 在 S2 算法中,默认划分 Cell 的等级是30,也就是说把一个正方形划分为 2^30 * 2^30个小的正方形.那么上一步的s,t映射到这个正方形上面来 ...

  2. 空间点像素索引(二)

    空间点像素索引(二) 三. Hilbert Curve 希尔伯特曲线 希尔伯特曲线的定义 希尔伯特曲线一种能填充满一个平面正方形的分形曲线(空间填充曲线),由大卫·希尔伯特在1891年提出.由于它能填 ...

  3. ITK:向索引添加偏移量

    ITK:向索引添加偏移量 内容提要 输出结果 C++实现代码 内容提要 向像素索引添加偏移量 输出结果 index: [5, 5] offset: [1, 1] index + offset: [6,

  4. 图像的几何变换—平移、旋转、镜像、缩放、剪切(原理+调用函数+像素操作)

    目录 一.平移 1.调用函数(平移矩阵) 2.像素操作(遍历赋值) 二.旋转 1.调用函数(旋转矩阵) 2.像素操作(反向映射) 三.镜像 1.调用函数(镜像矩阵) 2.像素操作(反向映射) 四.缩放 ...

  5. Qt QImage类详解(QImage类型转换、QImage类函数及QImage像素操作)

    打开Qt帮助文档,会看到有关于QImage的描述如下:The QImage class provides a hardware-independent image representation tha ...

  6. 从五个经典工作看语义SLAM

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文试图概括语义(Semantic) SLAM的主要思路和近年工作 ...

  7. 语义SLAM近5年主要技术进展及研究趋势

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文试图概括语义(Semantic) SLAM的主要思路和近年工作 ...

  8. 从5个经典工作开始看语义SLAM

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 本文试图概括语义(Semantic) SLAM的主要思路和近年工作,⻓期更新. 但因水平有限,若有错漏 ...

  9. SfM多视图三维点云重建--【VS2015+OpenCV3.4+PCL1.8】

    难点 在完成两视图三维重建之后,接下来就是进行多视图重建.多视图重建的难点在于如何确定第 i i i( i i i>2)个相机到世界坐标系的位姿变换矩阵. 两视图重建时,是将第一个相机所在的坐标 ...

最新文章

  1. python调用有道词典_菜鸟上手Python最有野心的库Asyncio
  2. Linux零基础初级教程
  3. 网站开发之DIV+CSS简单布局网站入门篇(五)
  4. 2017.3.29 报表统计 思考记录
  5. HTTP返回代码代表的含义(403,404,500,502,504)
  6. html保存按钮代码_如何防止Joomla编辑器删除HTML代码
  7. solr7在tomcat上的部署
  8. 双线性插值(超级易懂的)
  9. 2020.7.16.h5面试题
  10. getParameter和getParameterValues
  11. Python中randn()函数的作用
  12. 拉格朗日乘子法和KTT条件
  13. 量子计算机采用超导技术吗,华人学者一作论文发现不寻常超导体,或可作为量子计算机的“硅”...
  14. Ubuntu解决外接2K屏分辨率低问题
  15. android 根据图片路径获取图片缩略图
  16. c语言实验心得100字,实验心得100字_100个面试常见经典问题_100个面试问题和答案...
  17. 企业办公最强“扫雷”秘籍,低代码避坑有一套
  18. c语言大作业开题报告,C语言大作业报告.doc
  19. sql注入详解 一文了解sql注入所有常见方法
  20. 盘点2016:京东云助力区域打造新动能 成就新经济

热门文章

  1. 2022-2028年中国环卫行业产业链深度调研及投资前景预测报告
  2. bert as service
  3. Bert代码详解(一)重点详细
  4. 原子层沉积(ALD)和化学气相沉积(CVD)微电子制造铜金属化的研究进展
  5. Apollo 自动驾驶开发套件(D-KIT)
  6. 标准自编码器(TensorFlow实现)
  7. nvGRAPH API参考分析(一)
  8. 视觉SLAM技术应用
  9. 2021年大数据常用语言Scala(六):基础语法学习 数据类型与操作符
  10. CentOS7在防火墙开启与关闭以及端口操作