Showing posts with label GeoHash. Show all posts
Showing posts with label GeoHash. Show all posts

Monday, November 30, 2015

程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索




第三十六章、搜索关键词智能提示suggestion

题目详情:百度搜索框中,输入“北京”,搜索框下面会以北京为前缀,展示“北京爱情故事”、“北京公交”、“北京医院”等等搜索词,输入“结构之”,会提示“结构之法”,“结构之法 算法之道”等搜索词。
请问,如何设计此系统,使得空间和时间复杂度尽量低。
题目分析:本题来源于去年2012年百度的一套实习生笔试题中的系统设计题(为尊重愿题,本章主要使用百度搜索引擎展开论述,而不是google等其它搜索引擎,但原理不会差太多。然脱离本题,平时搜的时候,鼓励用...),题目比较开放,考察的目的在于看应聘者解决问题的思路是否清晰明确,其次便是看能考虑到多少细节。
    我去年整理此题的时候,曾简单解析过,提出的方法是:
  • 直接上Trie树「Trie树的介绍见:从Trie树(字典树)谈到后缀树」 +  TOP K「hashmap+堆,hashmap+堆 统计出如10个近似的热词,也就是说,只存与关键词近似的比如10个热词」
    方法就是这样子的:Trie树+TOP K算法,但在实际中,真的只要Trie树 + TOP K算法就够了么,有什么需要考虑的细节?OK,请看下文娓娓道来。

解法一、Trie树 + TOP K

 步骤一、trie树存储前缀后缀

   若看过博客内这篇介绍Trie树和后缀树的文章http://blog.csdn.net/v_july_v/article/details/6897097的话,应该就能对trie树有个大致的了解,为示本文完整性,引用下原文内容,如下:
“1.1、什么是Trie树
    Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
    Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
它有3个基本性质:

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  3. 每个节点的所有子节点包含的字符都不相同。

1.2、树的构建

举个在网上流传颇广的例子,如下:
    题目:给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置。
    分析:这题当然可以用hash来解决,但是本文重点介绍的是trie树,因为在某些方面它的用途更大。比如说对于某一个单词,我们要询问它的前缀是否出现过。这样hash就不好搞了,而用trie还是很简单。
    现在回到例子中,如果我们用最傻的方法,对于每一个单词,我们都要去查找它前面的单词中是否有它。那么这个算法的复杂度就是O(n^2)。显然对于100000的范围难以接受。现在我们换个思路想。假设我要查询的单词是abcd,那么在他前面的单词中,以b,c,d,f之类开头的我显然不必考虑。而只要找以a开头的中是否存在abcd就可以了。同样的,在以a开头中的单词中,我们只要考虑以b作为第二个字母的,一次次缩小范围和提高针对性,这样一个树的模型就渐渐清晰了。
    好比假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:
  当时第一次看到这幅图的时候,便立马感到此树之不凡构造了。单单从上幅图便可窥知一二,好比大海搜人,立马就能确定东南西北中的到底哪个方位,如此迅速缩小查找的范围和提高查找的针对性,不失为一创举。
    ok,如上图所示,对于每一个节点,从根遍历到他的过程就是一个单词,如果这个节点被标记为红色,就表示这个单词存在,否则不存在。
    那么,对于一个单词,我只要顺着他从根走到对应的节点,再看这个节点是否被标记为红色就可以知道它是否出现过了。把这个节点标记为红色,就相当于插入了这个单词。
”
    借用上面的图,当用户输入前缀a的时候,搜索框可能会展示以a为前缀的“abcd”,“abd”等关键词,再当用户输入前缀b的时候,搜索框下面可能会提示以b为前缀的“bcd”等关键词,如此,实现搜索引擎智能提示suggestion的第一个步骤便清晰了,即用trie树存储大量字符串,当前缀固定时,存储相对来说比较热的后缀。那又如何统计热词呢?请看下文步骤二、TOP K算法统计热词。

 步骤二、TOP K算法统计热词

    当每个搜索引擎输入一个前缀时,下面它只会展示0~10个候选词,但若是碰到那种候选词很多的时候,如何取舍,哪些展示在前面,哪些展示在后面?这就是一个搜索热度的问题。
    如本题描述所说,在去年的这个时候,当我在搜索框内搜索“北京”时,它下面会提示以“北京”为前缀的诸如“北京爱情故事”,“北京公交”,“北京医院”,且“ 北京爱情故事”展示在第一个:
    为何输入“北京”,会首先提示“北京爱情故事”呢?因为去年的这个时候,正是《北京爱情故事》这部电视剧上映正火的时候(其上映日期为2012年1月8日,火了至少一年),那个时候大家都一个劲的搜索这部电视剧的相关信息,当10个人中输入“北京”后,其中有8个人会继续敲入“爱情故事”(连起来就是“北京爱情故事”)的时候,搜索引擎对此当然不会无动于衷。
    也就是说,搜索引擎知道了这个时间段,大家都在疯狂查找北京爱情故事,故当用户输入以“北京”为前缀的时候,搜索引擎猜测用户有80%的机率是要查找“北京爱情故事”,故把“北京爱情故事”在下面提示出来,并放在第一个位置上。
    但为何今年这个时候再次搜索“北京”的时候,它展示出来的词不同了呢?
    
    原因在于随着时间变化,人们对《北京爱情故事》这部电视剧的关注度逐渐下降,与此同时,又出现了新的热词,或新的电影,故现在虽然同样是输入“北京”,后面提示的词也相应跟着起了变化。那解决这个问题的办法是什么呢?如开头所说:定期分析某段时间内的人们搜索的关键词,统计出搜索次数比较多的热词,继而当用户输入某个前缀时,优先展示热词。
    故说白了,这个问题的第二个步骤便是统计热词,我们把统计热词的方法称为TOP K算法,此算法的应用场景便是此文http://blog.csdn.net/v_july_v/article/details/7382693中的第2个问题,再次原文引用:
“寻找热门查询,300万个查询字符串中统计最热门的10个查询
    原题:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请你统计最热门的10个查询串,要求使用的内存不能超过1G。
    解答:由上面第1题,我们知道,数据大则划为小的,如一亿个Ip求Top 10,可先%1000将ip分到1000个小文件中去,并保证一种ip只出现在一个文件中,再对每个小文件中的ip进行hashmap计数统计并按数量排序,最后归并或者最小堆依次处理每个小文件的top10以得到最后的结果。
    但如果数据规模本身就比较小,能一次性装入内存呢?比如这第2题,虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query255Byte,因此我们可以考虑把他们都放进内存中去(300万个字符串假设没有重复,都是最大长度,那么最多占用内存3M*1K/4=0.75G。所以可以将所有字符串都存放在内存中进行处理),而现在只是需要一个合适的数据结构,在这里,HashTable绝对是我们优先的选择。
    所以我们放弃分而治之/hash映射的步骤,直接上hash统计,然后排序。So,针对此类典型的TOP K问题,采取的对策往往是:hashmap + 堆。如下所示:
  1. hashmap统计:先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内用Hash表完成了统计;
  2. 堆排序:第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,我们最终的时间复杂度是:O(N) + N' * O(logK),(N为1000万,N’为300万)。
    别忘了这篇文章中所述的堆排序思路:‘维护k个元素的最小堆,即用容量为k的最小堆存储最先遍历到的k个数,并假设它们即是最大的k个数,建堆费时O(k),并调整堆(费时O(logk))后,有k1>k2>...kmin(kmin设为小顶堆中最小元素)。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,若x>kmin,则更新堆(x入堆,用时logk),否则不更新堆。这样下来,总费时O(k*logk+(n-k)*logk)=O(n*logk)。此方法得益于在堆中,查找等各项操作时间复杂度均为logk。’--第三章续、Top K算法问题的实现。
    当然,你也可以采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。
”
    相信,如此,也就不难理解开头所提出的方法了:Trie树+  TOP K「hashmap+堆,hashmap+堆 统计出如10个近似的热词,也就是说,只存与关键词近似的比如10个热词」。
    而且你以后就可以告诉你身边的伙伴们,为何输入“结构之”,会提示出来一堆以“结构之”为前缀的词了:
    
    方法貌似成型了,但有哪些需要注意的细节呢?如@江申_Johnson所说:“实际工作里,比如当前缀很短的时候,候选词很多的时候,查询和排序性能可能有问题,也许可以加一层索引trie(这层索引可以只索引频率高于某一个阈值的词,很短的时候查这个就可以了。数量不够的话再去查索引了全部词的trie树);而且有时候不能根据query频率来排,而要引导用户输入信息量更全面的query,或者或不仅仅是前缀匹配这么简单。”

扩展阅读

    除了上文提到的trie树,三叉树或许也是一个不错的解决方案:http://igoro.com/archive/efficient-auto-complete-with-a-ternary-search-tree/。此外,StackOverflow上也有两个讨论帖子,大家可以看看:①http://stackoverflow.com/questions/2901831/algorithm-for-autocomplete,②http://stackoverflow.com/questions/1783652/what-is-the-best-autocomplete-suggest-algorithm-datastructure-c-c。

第三十七章、附近地点搜索

题目详情:找一个点集中与给定点距离最近的点,同时,给定的二维点集都是固定的,查询可能有很多次,时间复杂度O(n)无法接受,请设计数据结构和相应的算法。
题目分析:此题是去年微软的三面题,类似于一朋友@陈利人 出的这题:附近地点搜索,就是搜索用户附近有哪些地点。随着GPS和带有GPS功能的移动设备的普及,附近地点搜索也变得炙手可热。在庞大的地理数据库中搜索地点,索引是很重要的。但是,我们的需求是搜索附近地点,例如,坐标(39.91, 116.37)附近500米内有什么餐馆,那么让你来设计,该怎么做?

解法一、R树二维搜索

    假定只允许你初中数学知识,那么你可能建一个X-Y坐标系,即以坐标(39.91, 116.37)为圆心,以500的长度为半径,画一个园,然后一个一个坐标点的去查找。此法看似可行,但复杂度可想而知,即便你自以为聪明的说把整个平面划分为四个象限,一个一个象限的查找,此举虽然优化程度不够,但也说明你一步步想到点子上去了。
    即不一个一个坐标点的查找,而是一个一个区域的查找,相对来说,其平均查找速度和效率会显著提升。如此,便自然而然的想到了有没有一种一次查找定位于一个区域的数据结构呢?
    若看过博客内之前介绍R树的这篇文章http://blog.csdn.net/v_JULY_v/article/details/6530142#t2 的读者立马便能意识到,R树就是解决这个区域查找继而不断缩小规模的问题。特直接引用原文:

“R树的数据结构

    R树是B树在高维空间的扩展,是一棵平衡树。每个R树的叶子结点包含了多个指向不同数据的指针,这些数据可以是存放在硬盘中的,也可以是存在内存中。根据R树的这种数据结构,当我们需要进行一个高维空间查询时,我们只需要遍历少数几个叶子结点所包含的指针,查看这些指针指向的数据是否满足要求即可。这种方式使我们不必遍历所有数据即可获得答案,效率显著提高。下图1是R树的一个简单实例:
    我们在上面说过,R树运用了空间分割的理念,这种理念是如何实现的呢?R树采用了一种称为MBR(Minimal Bounding Rectangle)的方法,在此我把它译作“最小边界矩形”。从叶子结点开始用矩形(rectangle)将空间框起来,结点越往上,框住的空间就越大,以此对空间进行分割。有点不懂?没关系,继续往下看。在这里我还想提一下,R树中的R应该代表的是Rectangle(此处参考wikipedia上关于R树的介绍),而不是大多数国内教材中所说的Region(很多书把R树称为区域树,这是有误的)。我们就拿二维空间来举例。下图是Guttman论文中的一幅图:
 
    我来详细解释一下这张图。
  1. 先来看图(b),首先我们假设所有数据都是二维空间下的点,图中仅仅标志了R8区域中的数据,也就是那个shape of data object。别把那一块不规则图形看成一个数据,我们把它看作是多个数据围成的一个区域。为了实现R树结构,我们用一个最小边界矩形恰好框住这个不规则区域,这样,我们就构造出了一个区域:R8。R8的特点很明显,就是正正好好框住所有在此区域中的数据。
  2. 其他实线包围住的区域,如R9,R10,R12等都是同样的道理。这样一来,我们一共得到了12个最最基本的最小矩形。这些矩形都将被存储在子结点中。
  3. 下一步操作就是进行高一层次的处理。我们发现R8,R9,R10三个矩形距离最为靠近,因此就可以用一个更大的矩形R3恰好框住这3个矩形。
  4. 同样道理,R15,R16被R6恰好框住,R11,R12被R4恰好框住,等等。所有最基本的最小边界矩形被框入更大的矩形中之后,再次迭代,用更大的框去框住这些矩形。
    我想大家都应该理解这个数据结构的特征了。用地图的例子来解释,就是所有的数据都是餐厅所对应的地点,先把相邻的餐厅划分到同一块区域,划分好所有餐厅之后,再把邻近的区域划分到更大的区域,划分完毕后再次进行更高层次的划分,直到划分到只剩下两个最大的区域为止。要查找的时候就方便了。
    下面就可以把这些大大小小的矩形存入我们的R树中去了。根结点存放的是两个最大的矩形,这两个最大的矩形框住了所有的剩余的矩形,当然也就框住了所有的数据。下一层的结点存放了次大的矩形,这些矩形缩小了范围。每个叶子结点都是存放的最小的矩形,这些矩形中可能包含有n个数据。
地图查找的实例
    讲完了基本的数据结构,我们来讲个实例,如何查询特定的数据。又以餐厅为例,假设我要查询广州市天河区天河城附近一公里的所有餐厅地址怎么办?
  1. 打开地图(也就是整个R树),先选择国内还是国外(也就是根结点);
  2. 然后选择华南地区(对应第一层结点),选择广州市(对应第二层结点),
  3. 再选择天河区(对应第三层结点);
  4. 最后选择天河城所在的那个区域(对应叶子结点,存放有最小矩形);
    遍历所有在此区域内的结点,看是否满足我们的要求即可。怎么样,其实R树的查找规则跟查地图很像吧?对应下图:
        
一棵R树满足如下的性质:
  1. 除非它是根结点之外,所有叶子结点包含有m至M个记录索引(条目)。作为根结点的叶子结点所具有的记录个数可以少于m。通常,m=M/2。
  2. 对于所有在叶子中存储的记录(条目),I是最小的可以在空间中完全覆盖这些记录所代表的点的矩形(注意:此处所说的“矩形”是可以扩展到高维空间的)。
  3. 每一个非叶子结点拥有m至M个孩子结点,除非它是根结点。
  4. 对于在非叶子结点上的每一个条目,i是最小的可以在空间上完全覆盖这些条目所代表的店的矩形(同性质2)。
  5. 所有叶子结点都位于同一层,因此R树为平衡树。

叶子结点的结构

先来探究一下叶子结点的结构。叶子结点所保存的数据形式为:(I, tuple-identifier)。
      其中,tuple-identifier表示的是一个存放于数据库中的tuple,也就是一条记录,它是n维的。I是一个n维空间的矩形,并可以恰好框住这个叶子结点中所有记录代表的n维空间中的点。I=(I0,I1,…,In-1)。其结构如下图所示:
    下图描述的就是在二维空间中的叶子结点所要存储的信息。
 
    在这张图中,I所代表的就是图中的矩形,其范围是a<=I0<=b,c<=I1<=d。有两个tuple-identifier,在图中即表示为那两个点。这种形式完全可以推广到高维空间。大家简单想想三维空间中的样子就可以了。这样,叶子结点的结构就介绍完了。

非叶子结点

      非叶子结点的结构其实与叶子结点非常类似。想象一下B树就知道了,B树的叶子结点存放的是真实存在的数据,而非叶子结点存放的是这些数据的“边界”,或者说也算是一种索引(有疑问的读者可以回顾一下上述第一节中讲解B树的部分)。
      同样道理,R树的非叶子结点存放的数据结构为:(I, child-pointer)。
      其中,child-pointer是指向孩子结点的指针,I是覆盖所有孩子结点对应矩形的矩形。这边有点拗口,但我想不是很难懂?给张图:
 
    D,E,F,G为孩子结点所对应的矩形。A为能够覆盖这些矩形的更大的矩形。这个A就是这个非叶子结点所对应的矩形。这时候你应该悟到了吧?无论是叶子结点还是非叶子结点,它们都对应着一个矩形。树形结构上层的结点所对应的矩形能够完全覆盖它的孩子结点所对应的矩形。根结点也唯一对应一个矩形,而这个矩形是可以覆盖所有我们拥有的数据信息在空间中代表的点的。
    我个人感觉这张图画的不那么精确,应该是矩形A要恰好覆盖D,E,F,G,而不应该再留出这么多没用的空间了。但为尊重原图的绘制者,特不作修改。”
    但R树有些什么问题呢?如@宋枭_CD所说:“单纯用R树来作索引,搜索附近的地点,可能会遍历树的很多个分支。而且当全国的地图或者全省的地图时候,树的叶节点数目很多,树的深度也会是一个问题。一般会把地理位置上附近的节点(二维地图中点线面)预处理成page(大小为4K的倍数),在这些page上建立R树的索引。”

解法二、GeoHash算法索引地理位置信息

    我在微博上跟一些朋友讨论这个附近点搜索的问题时,除了谈到R树,有几个朋友都指出GeoHash算法可以解决,故才了解了下GeoHash算法,此文http://blog.nosqlfan.com/html/1811.html 清晰阐述了MongoDB借助GeoHash算法实现地理位置索引的原理,特引用其内容加以说明,如下:
“支持地理位置索引是MongoDB的一大亮点,这也是全球最流行的LBS服务foursquare 选择MongoDB的原因之一。我们知道,通常的数据库索引结构是B+ Tree,如何将地理位置转化为可建立B+Tree的形式。首先假设我们将需要索引的整个地图分成16×16的方格,如下图(左下角为坐标0,0 右上角为坐标16,16):
    单纯的[x,y]的数据是无法建立索引的,所以MongoDB在建立索引的时候,会根据相应字段的坐标计算一个可以用来做索引的hash值,这个值叫做geohash,下面我们以地图上坐标为[4,6]的点(图中红叉位置)为例。我们第一步将整个地图分成等大小的四块,如下图:
    划分成四块后我们可以定义这四块的值,如下(左下为00,左上为01,右下为10,右上为11):
    
    这样[4,6]点的geohash值目前为 00然后再将四个小块每一块进行切割,如下:
    这时[4,6]点位于右上区域,右上的值为11,这样[4,6]点的geohash值变为:0011继续往下做两次切分:
    最终得到[4,6]点的geohash值为:00110100
    这样我们用这个值来做索引,则地图上点相近的点就可以转化成有相同前缀的geohash值了。
    我们可以看到,这个geohash值的精确度是与划分地图的次数成正比的,上例对地图划分了四次。而MongoDB默认是进行26次划分,这个值在建立索引时是可控的。具体建立二维地理位置索引的命令如下:
db.map.ensureIndex({point : "2d"}, {min : 0, max : 16, bits : 4})
    其中的bits参数就是划分几次,默认为26次。 ”
    读者点评@yuotulck:首先多谢博主的文章,不过如果是新手(例如我)看到geohash那里可能会有误解:是否相邻可以靠前缀来比较?其实这是错的,例如边界那一块的相邻区域编码的前缀从第一个就不一样了,也就是说在geohash里相近的点hash值不一定相近。
    上面的知识点了解自:http://www.cnblogs.com/step1/archive/2009/04/22/1441689.html,而geohash的进一步用法在这里可以了解到:
http://tech.idv2.com/2011/07/05/geohash-intro/。
    本章完。

参考链接及推荐阅读

  1. 2012年九月十月笔试面试八十题:http://blog.csdn.net/v_july_v/article/details/7974418;
  2. 从Trie树(字典树)谈到后缀树:http://blog.csdn.net/v_july_v/article/details/6897097;
  3. 教你如何迅速秒杀掉:99%的海量数据处理面试题:http://blog.csdn.net/v_july_v/article/details/7382693;
  4. 从B树、B+树、B*树谈到R树:http://blog.csdn.net/v_july_v/article/details/6530142;
  5. 图解 MongoDB 地理位置索引的实现原理:http://blog.nosqlfan.com/html/1811.html;
  6. 《Hbase实战》第8章、在HBase上查询地理信息系统;

Monday, June 29, 2015

How to Implement Geospatial Search Using Solr | Whitepages blog



How to Implement Geospatial Search Using Solr | Whitepages blog
One approach is to use a bounding box to create a range-based query based on upper and lower bounds for latitude and longitude fields. The problem with this approach is that Lucene will have to potentially scan some very large document sets.

Cartesian Tiers
Cartesian tiers map locations to a series of boxes at increasing levels of precision. At each tier level, the box gets smaller and more precise. We can then query for a number of specific Cartesian tier boxes based on the level of precision we required.

Geohashing

http://tech.meituan.com/solr-spatial-search.html
Cartesian Tiers 笛卡尔层

笛卡尔分层模型的思想是将经纬度转换成更大粒度的分层网格,该模型创建了很多的地理层,每一层在前一层的基础上细化切分粒度,每一个网格被分配一个ID,代表一个地理位置。

基于Solr的空间搜索实践
在Solr中,针对上述的2种原理提供了不同的索引方式去实现,分别是GeohashPrefixTree类和QuadPrefixTree类,其中GeohashPrefixTree对应GeoHash算法(也采用了笛卡尔层的分层思想),QuadPrefixTree对应笛卡尔层的实现。

GeohashPrefixTree与QuadPrefixTree都继承SpatialPrefixTree这个前缀树基类,都使用了分层策略,主要索引和查询逻辑⼀样。不同点在于它们的maxLevels不一样,获取子cell的方式不一样,GeohashPrefixTree有32个子cell(编码为0-z),QuadPrefixTree 只有有4个子cell(编码为ABCD。A:左上,B:右上,C:左下,D:右下)。
Read full article from How to Implement Geospatial Search Using Solr | Whitepages blog

POI-GeoHash



http://www.learn4master.com/interview-questions/system-design/poi-geohash
地图上分割成不同区域这个设计题的核心是什么来着?
https://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=301057
记得有个什么算法为核心,就是分割成一块一块,然后有不同情况下的优化,
但是想不起来具体是什么来了。还看过一篇中文的文章讲得不错,可惜记不清细节了
GeoHash算法,地理编码
https://dzone.com/articles/designing-spacial-index
Geographic Information Systems (GIS) is an interesting area of exploration because it poses two significant challenges: latency at scale and modeling spatial locality.

Let's say you're visiting NYC and need an Internet connection. Where's the nearest Wi-Fi hotspot?How might an HBase application help you answer that question? What kind of design decisions go into solving that problem, and how can it be solved in a scalable way?
You know you want fast access to a relevant subset of the data. To achieve that, let's start with two simple and related goals:
  1. You want to store location points on disk such that points close to each other in space are close to each other on disk.
  2. You want to retrieve as few points as possible when responding to a query.
Starting with a compound rowkey
Concatenating X- and Y-axis values as the rowkey won't cut it for an efficient schema.
This sorting results in lots of hopping between the northern and southern clusters because of sorting first on longitude, then latitude.

Using the geohash as a spatially aware rowkey
The geohash makes a great choice for the rowkey because it's inexpensive to calculate and the prefix gets you a long way toward finding nearest neighbors. 

Compared to the target query area, the six-character prefix match areas are huge. Worse still, the query spans two of those larger prefixes. Seen in this context, those five characters of common prefix include far more data than you need. 

Relying on prefix match results in scanning a huge amount of extra area. Of course, there's a trade-off. If your data isn't dense at this precision level, executing fewer, longer scans isn't such a big deal. The scans don't return too much superfluous data, and you can minimize the remote procedure call (RPC) overhead. 

If your data is dense, running more, shorter scans will reduce the number of excess points transported over the wire. Plus, if there's one thing that computers are getting good at these days, it's parallelism. Execute each of those shorter scans on its own CPU core, and the query is still as fast as the slowest scan.

https://www.elastic.co/guide/en/elasticsearch/guide/current/geohashes.html
Geohashes are a way of encoding lat/lon points as strings.

Geohashes divide the world into a grid of 32 cells—4 rows and 8 columns—each represented by a letter or number. The g cell covers half of Greenland, all of Iceland, and most of Great Britian. Each cell can be further divided into another 32 cells, which can be divided into another 32 cells, and so on. The gc cell covers Ireland and England, gcp covers most of London and part of Southern England, and gcpuuz94k is the entrance to Buckingham Palace, accurate to about 5 meters.

In other words, the longer the geohash string, the more accurate it is. If two geohashes share a prefix— and gcpuuz—then it implies that they are near each other. The longer the shared prefix, the closer they are.

That said, two locations that are right next to each other may have completely different geohashes. For instance, the Millenium Dome in London has geohash u10hbp, because it falls into the u cell, the next top-level cell to the east of the g cell.

http://www.bigfastblog.com/geohash-intro
We can keep sub-dividing the space until we get to street-level or beyond. At that point our Geohash will be a bit longer and will look something like this…
0010110101011100011000110001101111000111
This binary can be represented as alphanumeric characters (32 bit encoded). Each 5 bits is converted to one character.
00101  10101  01110  00110  00110  00110  11110  00111
Which comes out as
  5      p      f      6      6      6      y      7
So our Geohash for the party on Saturday night is “5pf666y7″.
Why Use A GeoHash?
Single Simple String Representation
Grouping Of Points
Zooming And Aggregation

Caching At Scale

The Gotcha Solution
There is an algorithm for calculating the geohash values of the 8 surrounding grid-squares of a given grid square, which widens our search net in all directions, allowing us to see nearby points that reside on the other side of a grid’s boundary.
Simple way to encode lat/long into a string
Compact string encoding of geographic coordinate with
arbitrary precision
Bangalore - TDR1, Domlur - TDR1WX

Why do we need it when we have LL?
Big address of this venue
Complicated Lat/Long {12.963787,77.637789}

Subdivides space into "buckets" of grid shape
● Doesn't really represent a point, rather a bounding
area in which the point is present
● Hierarchical spatial structure with gradual degradation
● Longer the geohash
 -> Smaller the area

 Grouping and Zooming
Easy grouping
○ tdr1wxyp5dn7v -> {12.9637069, 77.6377931}
● 4th Main Rd, Domlur II Stage, Domlur, Bengaluru, Karnataka
○ tdr1wxyp5dn7v -> {12.9637, 77.6378}
● Domlur, Bengaluru, Karnataka
○ tdr1wxyp5dn7v -> {12.96, 77.64}
● Bengaluru, Karnataka
○ tdr1wxyp5dn7v -> {13, 78}
● Malur-Bangarapettu Rd

Nearby (Proximity) Search
● Nearby locations usually share similar prefixes.
● Long common prefixes indicate two places are near,
however two nearby places do not always have common prefixes


Finding Nearby Places
Prefix Match:
SELECT * FROM table WHERE place_geohash LIKE hashcode%
● Expand/Neighbors Match
SELECT * FROM table WHERE place_geohash IN (geohash.expand('tzxy1'))

Limitations
● Locality Anomalies
○ Complicates Proximity Searches
○ Need to use bounding box extensively making
search an expensive operation
● Projection based model
○ A geohash of given length will denote different
region size in poles than in equator region
http://blog.factual.com/how-geohashes-work
The geohash format
A geohash is a series of bits that repeatedly bisects a search space of latitudes and longitudes. The first bit bisects the longitude, the next one latitude, the next longitude, etc. As a result, some geohashes specify square-ish (in spherical coordinates) regions, while others specify 2:1 rectangles. Because geohashes are fundamentally binary data, they’re often written in base-32. The alphabet is a little unusual in that it omits some letters that might be confused with numbers.
Typical encoding strategy
long encodeGeohash(double lat, double lng, int bits) {
  double minLat = -90,  maxLat = 90;
  double minLng = -180, maxLng = 180;
  long result = 0;
  for (int i = 0; i < bits; ++i)
    if (i % 2 == 0) {                 // even bit: bisect longitude
      double midpoint = (minLng + maxLng) / 2;
      if (lng < midpoint) {
        result <<= 1;                 // push a zero bit
        maxLng = midpoint;            // shrink range downwards
      } else {
        result = result << 1 | 1;     // push a one bit
        minLng = midpoint;            // shrink range upwards
      }
    } else {                          // odd bit: bisect latitude
      double midpoint = (minLat + maxLat) / 2;
      if (lat < midpoint) {
        result <<= 1;                 // push a zero bit
        maxLat = midpoint;            // shrink range downwards
      } else {
        result = result << 1 | 1;     // push a one bit
        minLat = midpoint;            // shrink range upwards
      }
    }
  return result;
}


First, data indexed by geohash will have all points for a given rectangular area in contiguous slices (the number of slices depends on the precision required and the presence of geohash "fault lines"). This is especially useful in database systems where queries on a single index are much easier or faster than multiple-index queries. Second, this index structure can be used for a quick-and-dirty proximity search - the closest points are often among the closest geohashes. 

1)GeoHash将二维的经纬度转换成字符串
2)字符串越长,表示的范围越精确。
3)字符串相似的表示距离相近
GeoHash就是一种将经纬度转换成字符串的方法,并且使得在大部分情况下,字符串前缀匹配越多的距离越近
只需要将所在位置经纬度转换成GeoHash字符串,并与各个餐馆的GeoHash字符串进行前缀匹配,匹配越多的距离越近。


当geohash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右,编码长度需要根据数据情况进行选择。
这种类型的空间填充曲线的优点是将二维空间转换成一维曲线(事实上是分形维),对大部分而言,编码相似的距离也相近, 但Peano空间填充曲线最大的缺点就是突变性,有些编码相邻但距离却相差很远,比如0111与1000,编码是相邻的,但距离相差很大。
1)由于GeoHash是将区域划分为一个个规则矩形,并对每个矩形进行编码,这样在查询附近POI信息时会导致以下问题,比如红色的点是我们的位置,绿色的两个点分别是附近的两个餐馆,但是在查询的时候会发现距离较远餐馆的GeoHash编码与我们一样(因为在同一个GeoHash区域块上),而较近餐馆的GeoHash编码与我们不一致。这个问题往往产生在边界处。
解决的思路很简单,我们查询时,除了使用定位点的GeoHash编码进行匹配外,还使用周围8个区域的GeoHash编码,这样可以避免这个问题。 
2)我们已经知道现有的GeoHash算法使用的是Peano空间填充曲线,这种曲线会产生突变,造成了编码虽然相似但距离可能相差很大的问题,因此在查询附近餐馆时候,首先筛选GeoHash编码相似的POI点,然后进行实际距离计算。
      geohash只是空间索引的一种方式,特别适合点数据,而对线、面数据采用R树索引更有优势(可参考:深入浅出空间索引:为什么需要空间索引)。

Where’s the nearest wifi hotspot?
You want to store location points on disk such that points close to each other in space are close to each other on disk.
You want to retrieve as few points as possible when responding to a query.

design of the rowkey is the single most important thing you can do in your HBase schema

We claimed earlier that concatenating X- and Y-axis values as the rowkey won’t cut it for an efficient schema.
longitude and latitude are equally important in defining the location of a point. In order to use them as the basis for the spatial index, you need an algorithm to combine them.

You’ll use 12 geohash characters for the precision because that’s the highest precision you can fit in an 8-byte Long and still represent a meaningful character string. By truncating characters from the end of the hash, you get a less precise geohash and a correspondingly less precise selection of the map. Where full precision represents a point, partial precision gives you an area on the map, effectively a bounding box around an area in space.

The geohashes are all represented as character strings in the Base32 encoding alphabet.
Like all such algorithms, it exhibits locality anomalies where two points that are physically close together may have very different geohashes or two points that are very far apart may have nearly identical geohashes, complicating proximity searches. There are adequate workarounds for this (some patented) at the cost of making searches more expensive. 

Using the geohash as a spatially aware rowkey

the optimal approach for the data is to scan the center tile and its eight neighbors. This approach will guarantee correct results while minimizing the amount of unnecessary network IO. Luckily, calculating those neighbors is a simple bit-manipulation away.
http://www.mitbbs.ca/article_t/JobHunting/32476139.html
里面讲的很清楚怎么样apply geohash to query.如果我没有理解错的话,用里面说的
方法可以省去前面大牛讲的design中的index server,就是存放每个grid中的PO壹的信息的
给一个point and range,应该可以计算出minimum geohash prefix that contains thearea

然后用这个a list of geohash prefix去storage layer 拿所有符合要求的POI就可以了。
https://github.com/yinqiwen/ardb/blob/master/doc/spatial-index.md
http://gis.stackexchange.com/questions/18330/would-it-be-possible-to-use-geohash-for-proximity-searches
Store Spatial Data
ZADD key <GeoHash50Bits>  <value>

Encode longitude/latitude coordinate with estimate bits by geohash-int.
Find surrounding 8 neighbors' geohash integer value by geohash-int.
For each geohash integer value, we generate a pair (GeoHashIneger, GeoHashIneger + 1). Then we got 9 pairs.
For each pair, we convert it to a score range. Any integer value in the pair should be left shift to 52 bits. The shifted value is the smallest 52 bits geohash value in the geohash box represent by unshifted GeoHashInteger.
For example, if we need search points in radius 3000m, then we should encode the longitude/latitude coordinate to a integer with 26 bits, then left shift it 26 bits, then we got a 52 bits integer.
For each score range, use 'ZRANGEBYSCORE key min max WITHSCORES' to retrieve all point's value and it's score.
For each point value and it's score, we can decode the score to a GeoHash area by geohash-int and compute the distance with given longitude/latitude , then compare the distance with given radius value to exclude the point not in radius.
Solr Spatial Search
For indexing geodetic points (latitude and longitude), supply the pair of numbers as a string with a comma separating them in latitude then longitude order. For non-geodetic points, the order is x,y for PointType, and for RPT you must use a space instead of a comma, or use WKT.
  • LatLonType and its non-geodetic twin PointType
  • SpatialRecursivePrefixTreeFieldType (RPT for short), including RptWithGeometrySpatialField, a derivative
  • BBoxField
RPT offers more features than LatLonType and fast filter performance, although LatLonType is more appropriate when efficient distance sorting/boosting is desired. They can both be used simultaneously for what each does best – LatLonType for sorting/boosting, RPT for filtering.  If you need to index shapes other than points (e.g. a circle or polygon) then use RPT.
sfield a spatial indexed field
fq={!geofilt sfield=store}&pt=45.15,-93.85&d=5. This filter returns all results within a circle of the given radius around the initial point:
fq={!bbox sfield=store}&pt=45.15,-93.85&d=5. The rectangular shape is faster to compute and so it's sometimes used as an alternative to geofilt when it's acceptable to return points outside of the radius.

Coding

Products
GeoFire
https://www.firebase.com/blog/2013-09-25-location-queries-geofire.html

A point of interest, or POI, is a specific point location that someone may find useful or interesting. 
Q1. Given the current location, how to find the most closest k points.
Q2. Given the current location, how to find all the points within k miles. 

A1. Geohash 
A2. K-D tree 
A3. Space-filling Curve 

intersection of intervals -- search points in a range.
intersection of rectangles (using bst)   search overlapped intervals
https://www.youtube.com/watch?v=Igr6yONkpIQ

TODO:

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts