1 前言
最近做了一个项目,需求是某一个母体,通过手机摇一摇寻找身边同时在摇的人,然后把自己的红包分给这些人.其实有点类似微信的约炮功能.都是基于地理位置找附近的人.两者的区别就是微信约炮只需要找附近的一个人,而母体裂变是一对多的.而且微信也没有母体的概念(也就是没有一个是主动方,一个是被动方)
2 需求分析
找附近的人,这个需求说起来简单,其实还是挺麻烦的.首先,像这种活动(送红包),参与人数肯定不在少数.据我事后的统计,同时参与的人数峰值是每两分钟32W,大概是每秒3K上下.可以明确的是,我们不可能针对 每一次请求都计算所有用户之间的距离.那么,我们自然而然的就会想到,将用户进行分区.每次请求只关心自己区里的用户距离.而其中,当时我想到的分区方式有两种
2 将地球表面按照经纬度进行分割.每一块分割的区域作为一个池子.
这两种方式的优略点也很明显. 第一种优点是,实现方便,现在高德,google,阿里云都支持通过经纬度直接获取对应的地址信息,缺点也相对较多,1 最大的缺点是效率,我所了解的根据经纬度查询地址的接口都是http接口,速度非常慢.2 无法控制区域大小.每个城市大小都是固定的.而且每个城市大小之间相差很大. 第二种的优点是可控,只需要完成一个相对合理的分区域算法,那么这个区域大小就由你自己控制,而且一般来说效率会非常高.缺点也同样明显,需要一个靠谱的分区域算法.
最后,我选择的是第二种方式.那么下面就是考虑怎么根据经纬度做一个分区算法了.
3 如何分区
经纬度的单位有度,分,秒,毫秒.那么可以认为
维度从-90.000000~90000000
通过经纬度,我们需要确定当前的位置应该落入哪个区域中.从技术的角度来说,就是需要合并两个值生成一个值,这个值作为当前经纬度的一个序列值(这里说序列值主要是强调唯一性).我想到的法子是,
类似的效果就是这样
对于的代码
private long encode(long lon,long lat){ long area = 0; //先设置经度,避免负数,直接加180 long newLon = lon + 180000000; for(int i = 0; i < 64; i += 2){ if(theBitTrue(newLon,i)){ area = setTheBitTrue(area,i); } else { area = setTheBitFalse(area,i); } } //再设置维度 long newLat = lat + 90000000; for(int i = 0; i < 64; i += 2){ if(theBitTrue(newLat,i)){ area = setTheBitTrue(area,i+1); } else { area = setTheBitFalse(area,i+1); } } return area; } /** * 判断num上 第point位上是否为1 * point 从0开始. */ private boolean theBitTrue(long num,int point){ long bits = 1l << point; return (bits & num) == bits; } /** * 将num的第point位设置成1,然后返回 */ private long setTheBitTrue(long num,int point){ return num |= (1l << point); } /** * 将num的第point位设置成0,然后返回 */ private long setTheBitFalse(long num,int point){ return num &= ~(1l << point); }
分区弄好了,那么每一个区有多大呢.
360000000 = 10101011101010010101000000000 一共是29位.然后加入维度,那么应该是58位.这样的话,我们可以近似的认为区域的总数为
4^29 = 288230376151711744.
地球总表面积是510072000平方千米=5100720000000000000平方厘米.
那么,每一个区域的面积应该是
5100720000000000000/288230376151711744 = 17平方厘米.
计算好每个最小区域的大小,那么就很容易了.比如你需要17平方千米作为范围,那么就是 17平方千米/17平方厘米 = 10^10 如果是170平方千米,那就是10^11. 这个系数作为可变参数调节就好了..然后对应的序列值%这个系数,就是对应的池子的最终序列值啦.
这里再补充一种算法,是同事写的,其实核心思想类似.
// 编码经纬度 private long encode(long lon, long lat) { long mergeBits = 0L; mergeBits = processBitset(mergeBits, 59, lon, -180000000, 180000000); mergeBits = processBitset(mergeBits, 58, lat, -90000000, 90000000); return mergeBits; } // 纬度 下限 上限 private long processBitset(long mergeBits, int start, long lat, long floor, long ceiling) { double dfloor = floor, dceiling = ceiling; for (int i = start; i >= 0; i -= 2) { double mid = (dfloor + dceiling) / 2; if (lat >= mid) { // 上半阙 mergeBits |= (1L << i); // 该为置1 dfloor = mid; // 下限抬高 } else { // 下半阙 mergeBits &= ~(1L << i); // bit位设为0 dceiling = mid; // 上限降低 } } return mergeBits; }
这个算法里面有一些有趣的东西.比如为什么要设置59和58,还有他这个看上去的区域总数是4^30次.比之前的那个算法多了4倍的精确率.等等.但是其实并不是表面的那样.具体的不多解释啦.自己理解了.
后续
本来本篇文章到这里应该就已经结束了.但是在项目结束以后,我忽然想到另外一个问题..我们知道,上面说的根据经纬度划分区域,然后在区域内计算彼此之间距离的方式有一个非常大的弊端
也就是说,就算某两个坐标 ,虽然距离很近 ,但是由于坐标A刚好落入区域1中,坐标B刚好落入区域2中,那么这两个坐标永远不会落在一个区域内.两个人也就摇不到一个房间内.那么继续思考,如何可以解决这个问题..最简单的方式(当然,也是最不考虑性能的方式)肯定是
相信大家会本能的觉得,这样做肯定不靠谱.我最开始也是这么认为的,但是我在深入考虑以后我发现,我忽视了一个很大的问题.
在上面,我们为每一个经纬度组合生成了一个彼此完全不一致的区域ID,其实,两个经纬度之间距离的大小,完全可以通过两个经纬度对应的最小区域ID异或操作得出.也就是说,
坐标A对应的序列值为long类型的数值 ida ,坐标B对应的序列值为 idb.两者之间的距离可以近似的认为是 ida^idb
我们知道,异或操作对于计算机是非常快的..根据事后的数据观察,求包者请求的峰值是3k qps.每次请求有效期为10s,也就是说,同时有效的求包者应该是3W左右.那么,我们可以通过代码演示一下如果裂变者每次请求都与3W个求包者计算距离的消耗是多少
package location; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * */ public class LocationTest { private static final int NUM = 30000; static List<Long> locationList = new ArrayList<Long>(NUM); static List<Long> temp = new ArrayList<Long>(NUM); static final long location; static { Random random = new Random(); location = random.nextLong(); for (int i = 0; i < NUM; i++) { locationList.add(random.nextLong()); } } public static void main(String[] args) { //为了预热 for (Long locationA: locationList) { temp.add(locationA ^ location); } temp.clear(); long start = System.currentTimeMillis(); for (Long locationA: locationList) { temp.add(locationA ^ location); } long end = System.currentTimeMillis(); System.out.println(end - start); } }
我在本机运行了一下,结论是6ms.也就是说,我们可以认为,如果每个裂变者与所有求包者计算一次距离,只要短短的6ms.我们是否可以认为,不分区域是完全可行的.
可惜下面的内容都是我在项目结束以后才想到的.不然可以在项目中试验一把,那就真的完美了.
总结
项目中后续当然还有其他的内容,比如池子获取到以后,然后再每次计算池子里的人与母体的距离然后进行排序.不过这个就相对容易很多了.根据经纬度计算距离的方法google一下就好了.很多..这里就不多说了.呵呵,当码农几年,第一次用了二进制的位运算,也挺有趣的.
相关推荐
本话题将深入探讨如何在uni-app中实现基于经纬度定位的签到功能。 首先,我们要理解uni-app中的定位功能。uni-app提供了`uni.getLocation`接口,这个接口用于获取当前的地理位置、速度。开发者可以通过设置参数,...
全国3000多个高铁火车站数据-包括经纬度坐标 全国3000多个高铁火车站数据-包括经纬度坐标 全国3000多个高铁火车站数据-包括经纬度坐标
经纬度坐标是地球表面位置的标准表示方式,其中经度表示东西方向,纬度表示南北方向。本篇文章将详细介绍如何利用MySQL的内置函数和数学公式来计算两个经纬度坐标点之间的距离。 首先,我们需要了解地球上两点之间...
"经纬度"是地理坐标系统的一部分,是描述地球表面位置的关键参数。 压缩包内的文件"**cpr.c**"和"cpr.h"揭示了代码的核心部分和头文件。"cpr.c"很可能包含了实现CPR算法的具体函数和逻辑,而"cpr.h"可能定义了相关...
省市区县数据-含经纬度Excel文件
总的来说,通过ArcGIS,我们可以方便地利用经纬度坐标计算地球表面两点间的距离,这在各种GIS应用中都具有重要的实用价值。无论是在科学研究、城市规划还是物流配送等领域,理解并掌握这种计算方法都是至关重要的。
经纬度转地球表面cgcs2000大地坐标
高斯-克吕格投影是一种常见的地图投影方法,它将地球表面的曲面投影到平面上,以减小形状失真。3度带是指将地球赤道两侧的区域划分为一系列宽3度的带状区域,每带自本初子午线起算,向东或向西延伸。6度带则是将地球...
经纬度数据是将这些遥感图像与地球表面位置对应的关键,没有它,就无法准确地将海冰密度数据投影到地图上,进而进行有效的空间分析和可视化。 `latlon.h5`是一个HDF5(Hierarchical Data Format 5)文件,这是一种...
总结起来,"库函数-根据经纬度计算基础日落时间-博途V13-20170316"是一个实用的工具,可以帮助工程师们在PLC项目中集成天文学计算,以获得特定地点的日出日落时间。通过理解和运用这个库函数,我们可以更好地利用...
在本项目“微信小程序-实现电子围栏-半径-经纬度-是否在围栏内-画圆等操作-master.zip”中,我们将探讨如何在微信小程序中实现基于经纬度的电子围栏功能,包括判断用户位置是否在围栏内以及画圆等操作。 1. **电子...
中国城市名录、代码大全,省、市、县齐全 并有附带整理的一些字段,可根据自己的需要裁剪数据 包含经纬度。
本程序是海洋地质制图常用地图投影系列小程序,程序能用于WGS84、北京54、西安80基准面上单点及批量数据的投影正反转换, 本套系列程序目前包括“3°、6°带高斯-克吕格投影正反转换程序”、“墨卡托投影正反转换程序...
2019年3月最新权威(www.ip2location.com)全球ip地址库,为.csv格式,类型为[DB5.LITE] IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE Database,即提供按 国家-地区-城市-经纬度 来区分的数据
2017最新全国省市区行政区域数据库-包含编码-经纬度-拼音-邮编
行政区划数据库_with+经纬度-省市区-邮编-区号-拼音-简称
2019年7月最新权威(www.ip2location.com)全球ip地址库,为.csv格式,类型为[DB5.LITE] IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE Database,即提供按 国家-地区-城市-经纬度 来区分的共5字段数据
经纬度是地球上位置的一个坐标参考系,经度表示东西方向的位置,以本初子午线(通过英国格林尼治的经线)为0°,向东至180°,向西至180°;纬度表示南北方向的位置,以赤道为0°,向北至90°(北极),向南至90°...
而经纬度坐标系则是一种基于地球椭球体表面的二维坐标系,通常用来表达地理位置的经度和纬度。 1. 大地坐标转经纬度坐标: 大地坐标转换为经纬度坐标的步骤主要包括以下几个关键点: - 输入大地坐标数据时,需要...
### GPS经纬度转换为百度经纬度 在地理信息系统(GIS)和定位技术中,不同的地图服务提供商使用不同的坐标系统来表示地理位置。例如,GPS设备通常使用的是WGS-84坐标系,而百度地图则使用了经过加密处理的BD-09坐标...