http://www.cnblogs.com/LBSer/p/4471742.htmlhtml
地理围栏(Geo-fencing)是LBS的一种应用,就是用一个虚拟的栅栏围出一个虚拟地理边界,当手机进入、离开某个特定地理区域,或在该区域内活动时,手机能够接收自动通知和警告。以下图所示,假设地图上有三个商场,当用户进入某个商场的时候,手机自动收到相应商场发送的优惠券push消息。地理围栏应用很是普遍,当今移动互联网主要app如美团、大众点评、手淘等均可看到其应用身影。python
图1 地理围栏示意图git
地理围栏的核心问题就是判断用户是否落在某多边形围栏内部。本文将介绍实际应用中经常使用的解决方法。github
地理围栏通常是多边形,如何判断点在多边形内部呢?能够经过射线法来判断点是否在多边形内部。以下图所示,从该点出发沿着X轴画一条射线,依次判断该射线与每条边的交点,并统计交点个数,若是交点数为奇数,则在多边形内部(如图3个交点),若是焦点数是偶数,则在外部,射线法对凸和非凸多边形都适用,复杂度为O(N),其它N是边数。源码可参考(http://alienryderflex.com/polygon/)算法
图2 射线法判断点在多边形内外app
当地理围栏多边形数目较少时,咱们能够依次遍历每个多边形(暴力遍历法),而后用射线法进行判断,这样效率也很高。而当多边形数目较多时,好比有10万个多边形,这个时候须要执行10万次射线法,响应时间达到3.9秒,这在互联网应用几乎不可忍受。下表是本人的简单测试,多边形边数均为7。post
表1 射线法性能测试性能
暴力遍历法效率低下的缘由是与每个多边形都进行了射线法判断,若是能减小射线法的调用次数性能就能提高。所以咱们的优化思路很直接,首先经过粗筛的方法快速找到符合条件的少许多边形,而后对粗筛后的多边形使用射线法判断,这样射线法的执行次数大大下降,效率也能大大提升。怎么粗筛呢?对于一维数据咱们经常使用索引的方法,好比经过B树索引找到某一个范围区间段,而后对此范围区间段进行遍历查找,对于二维空间数据经常使用空间索引的方法,好比经过R树找到范围区间内的多边形,而后对此范围内的多边形进行精确判断,下面介绍最常使用的空间索引R树的解决思路。测试
因为多边形形状各异,咱们须要以一种统一的方式来对多边形进行近似,最简单的方式就是用最小外包矩形来表示多边形。flex
图3 最小外包矩形(MBR)表达多边形
图4 对最小外包矩形进行R树索引
a)首先经过R树迅速判断用户所在位置(粗红点)是否被外包矩形覆盖(图5,红色点表明用户所在位置;R树平均查询复杂度为O(Log(N)),N为多边形个数);
b)若是不被任何外包矩形覆盖则返回不在地理围栏多边形内;
c)若是被外包矩形覆盖则还须要进一步判断是否在此外包矩形的多边形内部,采用上文提到的射线法判断(图2)。
图5 R树查询示例
大多数应用的地理围栏多边形都比较简单,但有时也会遇到一些特别复杂的多边形,好比单个多边形的边数就超过十几万条,这时候对此复杂多边形执行一次射线法也很是耗时(由于射线法时间复杂度为O(N),N为多边形边数)。
如何提升对复杂多边形执行射线法的计算效率呢?一样使用R树索引!笔者在实际应用中对边数较多(如超过1万)的多边形的边再单独进行R树索引,具体如图6所示,首先对多边形的每条边构建最小外包矩形,而后在这些最小外包矩形基础上构建R树索引(R树索引上的外包矩形未画出),这样射线法求交点的时候首先经过R树判断射线是否与外包矩形相交,最后对R树粗筛后的边进行精确求交判断,时间复杂度从O(N)降到O(Log(N)),大大提升了计算效率。
图6 对多边形的边进行R树索引
某线上应用服务有30万个地理围栏多边形,经过在内存中构建R树索引,使得线上实时地理围栏查询平均响应时间在1ms之内,而暴力查询响应时间是9秒左右。
https://pypi.python.org/pypi/Rtree/ (Python)
http://jsi.sourceforge.net/ (Java)
https://github.com/leaflet-extras/RTree (Javascript)
http://sourceforge.net/p/cspatialindexrt/code/HEAD/tree/ (C#)