需求:计算不同区域范围,X公里半径内实体店或场站覆盖率。

实现思路:


  • 为了便于理解,将地球看成一个基于经纬度线的坐标系。将经度和纬度看成二维坐标系中的两个纬度,横轴表示经度[-180o, 0o),(0o, 180o],纵轴表示纬度[-90o, 0o),(0o, 90o]。
  • 以最小纬度和经度对应坐标为第一个六角形中心点,在经度方向循环计算六角形各顶点(顶点开始,顺时针,命名:Point1,Point2,Point3,Point4,Point5,Point6)及中心点(Point0)坐标,直至六角形中心点经度大于等于最大经度。 存储六角形标记为(0,0),(0,1),(0,2) ......., 表示六边形位于第0行第N列
  • 第一行计算完成后,开始计算第2行,如下图,第2行(奇数行), 除第1个和最后一个点作特殊处理外,其它点的 Point3、Point4、Point5是重叠的,注意坐标的处理,否则会出现偏差
  • 在维度方向上按上一步循环。完成整个区域范围内六角边分割,注意:为了快速定位,还需计算出每个六角形中心点对应的Geohash,根据半径不一致,可选择不同的Geohash级别
  • 根据场站经纬度计算出30级 Cell ID 值 及 Geohash 码
  • 根据 Geohash 码找出附近的六角形,通过六顶点坐标 构造 IRegion, 判断场站是否包含在六边形内,如果不包含,再次计算出当前Geohash码周边8个Geohash框,再次计算
  • 至此完成此区域内场站命中的六角形。

关键代码


  • 根据中心点坐标、边长、偏差角度(中心点至顶点开始)计算下一个点坐标
    1. public const double Ea = ; // 赤道半径(米)
    2. public const double Eb = ; // 极半径 (米)
    3.  
    4. /// <summary>
    5. ///
    6. /// </summary>
    7. /// <param name="lat"></param>
    8. /// <param name="lng"></param>
    9. /// <param name="distance"></param>
    10. /// <param name="angle"></param>
    11. /// <returns></returns>
    12. public static Point GetPoint(double lat, double lng, double distance, double angle)
    13. {
    14.  
    15. double dx = distance * * Math.Sin(angle * Math.PI / 180.0);
    16. double dy = distance * * Math.Cos(angle * Math.PI / 180.0);
    17.  
    18. double ec = Eb + (Ea - Eb) * (90.0 - lat) / 90.0;
    19. double ed = ec * Math.Cos(lat * Math.PI / 180.0);
    20.  
    21. double newLon = (dx / ed + lng * Math.PI / 180.0) * 180.0 / Math.PI;
    22. double newLat = (dy / ec + lat * Math.PI / 180.0) * 180.0 / Math.PI;
    23.  
    24. return new Point(newLat, newLon);
    25. }
  • 计算场站所属六边形
    1. /// <summary>
    2. ///
    3. /// </summary>
    4. /// <param name="destRows"></param>
    5. /// <param name="cell"></param>
    6. /// <param name="level"></param>
    7. /// <param name="staid"></param>
    8. /// <param name="hashMap"></param>
    9. /// <param name="geohashValue"></param>
    10. /// <returns></returns>
    11. private string GetPgID(DataTable dest, S2Cell cell,string geohashValue)
    12. {
    13. //先找当前geohash4的值
    14. string pgID = this.GetPGIDByHash(dest, cell, geohashValue);
    15. if (string.IsNullOrEmpty(pgID) == false)
    16. return pgID;
    17.  
    18. //当前hash未命中时,找相邻8格
    19. List<string> hashLst = GeoHashService.Default.GetGeoHashExpand(geohashValue);
    20. foreach (string ghValue in hashLst)
    21. {
    22. pgID = this.GetPGIDByHash(dest, cell, ghValue);
    23. if (string.IsNullOrEmpty(pgID) == false)
    24. return pgID;
    25. }
    26.  
    27. return string.Empty;
    28. }
    29.  
    30. /// <summary>
    31. ///
    32. /// </summary>
    33. /// <param name="dest"></param>
    34. /// <param name="cell"></param>
    35. /// <param name="geohashValue"></param>
    36. /// <returns></returns>
    37. private string GetPGIDByHash(DataTable dest, S2Cell cell, string geohashValue)
    38. {
    39. DataRow[] destRows = dest.Select(string.Format("{0} = '{1}'", M_GEOHASH, geohashValue)); //城市均分的网格
    40.  
    41. foreach (DataRow dRow in destRows)
    42. {
    43. string pgID = Convert.ToString(dRow["ID"]);
    44. IS2Region cells = this.BuildPolygon(dRow);
    45.  
    46. if (cells.Contains(cell) == true)
    47. {
    48. return pgID;
    49. }
    50. }
    51.  
    52. return string.Empty;
    53. }
    54.  
    55. /// <summary>
    56. /// 构造容器
    57. /// </summary>
    58. /// <param name="row"></param>
    59. /// <returns></returns>
    60. private IS2Region BuildPolygon(DataRow row)
    61. {
    62. List<S2Point> lst = new List<S2Point>();
    63. lst.Add(this.GetPoint(Convert.ToDouble(row["lat1"]), Convert.ToDouble(row["lng1"])));
    64. lst.Add(this.GetPoint(Convert.ToDouble(row["lat2"]), Convert.ToDouble(row["lng2"])));
    65. lst.Add(this.GetPoint(Convert.ToDouble(row["lat3"]), Convert.ToDouble(row["lng3"])));
    66. lst.Add(this.GetPoint(Convert.ToDouble(row["lat4"]), Convert.ToDouble(row["lng4"])));
    67. lst.Add(this.GetPoint(Convert.ToDouble(row["lat5"]), Convert.ToDouble(row["lng5"])));
    68. lst.Add(this.GetPoint(Convert.ToDouble(row["lat6"]), Convert.ToDouble(row["lng6"])));
    69.  
    70. S2Loop loop = new S2Loop(lst);
    71. loop.Normalize();
    72. return loop;
    73. }

示例效果


参考资料


S2算法应用的更多相关文章

  1. C#实现Google S2算法

    S2其实是来自几何数学中的一个数学符号 S²,它表示的是单位球.S2 这个库其实是被设计用来解决球面上各种几何问题的.值得提的一点是,除去 golang 官方 repo 里面的 geo/s2 完成度目 ...

  2. 高效的多维空间点索引算法 — Geohash 和 Google S2

    原文地址:https://www.jianshu.com/p/7332dcb978b2   引子 每天我们晚上加班回家,可能都会用到滴滴或者共享单车.打开 app 会看到如下的界面:     app ...

  3. 空间数据库系列二:空间索引S2与Z3分析对比

    S2与Z3对比分析 1. S2 2. Geohash 3. Geomesa Z3 4. S2对比geohash 4.1. geohash存在的问题 4.2. S2优势 4.3. 实际对比例子 5. 测 ...

  4. [luoguP1578] 奶牛浴场(DP)

    传送门 O(s2)算法 详见论文 王知昆--浅谈用极大化思想解决最大子矩形问题 我就复制你能把我怎么样QAQ #include <cstdio> #include <iostream ...

  5. 深入解密来自未来的缓存-Caffeine

    1.前言 读这篇文章之前希望你能好好的阅读: 你应该知道的缓存进化史 和 如何优雅的设计和使用缓存? .这两篇文章主要从一些实战上面去介绍如何去使用缓存.在这两篇文章中我都比较推荐Caffeine这款 ...

  6. 基于 Google-S2 的地理相册服务实现及应用

    马蜂窝技术原创内容,更多干货请关注公众号:mfwtech 随着智能手机存储容量的增大,以及相册备份技术的普及,我们可以随时随地用手机影像记录生活,在手机中存储几千张甚至上万张照片已经是很常见的事情.但 ...

  7. 分布式一致性算法--Paxos

    Paxos算法是莱斯利·兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法.Paxos算法解决的问题是一个分布式系统如何就某个值(决议)达成一致.在工程实践意义上来说, ...

  8. C#实现Levenshtein distance最小编辑距离算法

    Levenshtein distance,中文名为最小编辑距离,其目的是找出两个字符串之间需要改动多少个字符后变成一致.该算法使用了动态规划的算法策略,该问题具备最优子结构,最小编辑距离包含子最小编辑 ...

  9. Hihocoder 太阁最新面经算法竞赛18

    Hihocoder 太阁最新面经算法竞赛18 source: https://hihocoder.com/contest/hihointerview27/problems 题目1 : Big Plus ...

随机推荐

  1. 解决 canvas 将图片转为base64报错

    var canvas=document.getElementById("canvas"),//获取canvas ctx = canvas.getContext("2d&q ...

  2. HDU 1796 How many integers can you find(容斥原理)

    题目传送:http://acm.hdu.edu.cn/diy/contest_showproblem.php?cid=20918&pid=1002 Problem Description    ...

  3. HslCommunication组件库使用说明

    一个由个人开发的组件库,携带了一些众多的功能,包含了数据网络通信,文件上传下载,日志组件,PLC访问类,还有一些其他的基础类库. nuget地址:https://www.nuget.org/packa ...

  4. django配置Ueditor富文本编辑器

    1.https://github.com/twz915/DjangoUeditor3下载包,进入包文件夹,找到DjangoUeditor包拷贝到项目下,和xadmin同级目录 2.找到项目的setti ...

  5. Redis分布式锁实现

    redis分布式锁的一个简单直接的实现方法就是用 SET NX 命令设置一个设定了存活周期 TTL 的 Key 来获取锁,通过删除 Key 来释放锁,通过存活周期来避免死锁.不过这个方法存在单点故障风 ...

  6. WPA2 Key Reinstallation 漏洞

    漏洞形成: 必要条件1:WPA2 协议存在一个消息重放漏洞,导致多组相同数据被使用了相同的密钥加密. ciphertext = plaintext xor AES(key, IV||counter) ...

  7. logminer实战之生产环境写入数据字典,dg环境查询拷贝日志,测试环境进行挖掘,输出结果

    应客户需要,对某一天的日志进行挖掘,分析日均归档日志切换数量20增长至40的原因,是什么表的dml操作导致的日志量剧增,最终定位某个应用(需要客户自己进行甄别) 操作说明及介绍: 1.客户10.2.0 ...

  8. shell脚本-预定义常量

    $0 这个程式的执行名字$n 这个程式的第n个参数值,n=1..9$* 这个程式的所有参数,此选项参数可超过9个.$# 这个程式的参数个数$$ 这个程式的PID(脚本运行的当前进程ID号)$! 执行上 ...

  9. HDU - 5201 :The Monkey King (组合数 & 容斥)

    As everyone known, The Monkey King is Son Goku. He and his offspring live in Mountain of Flowers and ...

  10. 软件安装配置笔记(二)——SQL Server安装

    客户端安装: 服务器端安装: