S2算法應用


需求:計算不同區域范圍,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框,再次計算
  • 至此完成此區域內場站命中的六角形。

關鍵代碼


  • 根據中心點坐標、邊長、偏差角度(中心點至頂點開始)計算下一個點坐標
  •         public const double Ea = 6378137;     // 赤道半徑(米) 
            public const double Eb = 6356725;     // 極半徑 (米)
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="lat"></param>
            /// <param name="lng"></param>
            /// <param name="distance"></param>
            /// <param name="angle"></param>
            /// <returns></returns>
            public static Point GetPoint(double lat, double lng, double distance, double angle) { double dx = distance * 1000 * Math.Sin(angle * Math.PI / 180.0); double dy = distance * 1000 * Math.Cos(angle * Math.PI / 180.0); double ec = Eb + (Ea - Eb) * (90.0 - lat) / 90.0; double ed = ec * Math.Cos(lat * Math.PI / 180.0); double newLon = (dx / ed + lng * Math.PI / 180.0) * 180.0 / Math.PI; double newLat = (dy / ec + lat * Math.PI / 180.0) * 180.0 / Math.PI; return new Point(newLat, newLon); }

     

  • 計算場站所屬六邊形
  • /// <summary>
            /// 
            /// </summary>
            /// <param name="destRows"></param>
            /// <param name="cell"></param>
            /// <param name="level"></param>
            /// <param name="staid"></param>
            /// <param name="hashMap"></param>
            /// <param name="geohashValue"></param>
            /// <returns></returns>
            private string GetPgID(DataTable dest, S2Cell cell,string geohashValue)
            {
                //先找當前geohash4的值
                string pgID = this.GetPGIDByHash(dest, cell, geohashValue);
                if (string.IsNullOrEmpty(pgID) == false)
                    return pgID;
                
    
                //當前hash未命中時,找相鄰8格
                List<string> hashLst = GeoHashService.Default.GetGeoHashExpand(geohashValue);
                foreach (string ghValue in hashLst)
                {
                    pgID = this.GetPGIDByHash(dest, cell, ghValue);
                    if (string.IsNullOrEmpty(pgID) == false)
                        return pgID;
                }
    
                return string.Empty;
            }
            
           /// <summary>
           /// 
           /// </summary>
           /// <param name="dest"></param>
           /// <param name="cell"></param>
           /// <param name="geohashValue"></param>
           /// <returns></returns>
            private string GetPGIDByHash(DataTable dest, S2Cell cell, string geohashValue)
            {
                DataRow[] destRows = dest.Select(string.Format("{0} = '{1}'", M_GEOHASH, geohashValue));  //城市均分的網格
    
                foreach (DataRow dRow in destRows)
                {
                    string pgID = Convert.ToString(dRow["ID"]);
                    IS2Region cells = this.BuildPolygon(dRow);
    
                    if (cells.Contains(cell) == true)
                    {
                        return pgID;
                    }
                }
    
                return string.Empty;
            }
    
            /// <summary>
            /// 構造容器
            /// </summary>
            /// <param name="row"></param>
            /// <returns></returns>
            private IS2Region BuildPolygon(DataRow row)
            {
                List<S2Point> lst = new List<S2Point>();
                lst.Add(this.GetPoint(Convert.ToDouble(row["lat1"]), Convert.ToDouble(row["lng1"])));
                lst.Add(this.GetPoint(Convert.ToDouble(row["lat2"]), Convert.ToDouble(row["lng2"])));
                lst.Add(this.GetPoint(Convert.ToDouble(row["lat3"]), Convert.ToDouble(row["lng3"])));
                lst.Add(this.GetPoint(Convert.ToDouble(row["lat4"]), Convert.ToDouble(row["lng4"])));
                lst.Add(this.GetPoint(Convert.ToDouble(row["lat5"]), Convert.ToDouble(row["lng5"])));
                lst.Add(this.GetPoint(Convert.ToDouble(row["lat6"]), Convert.ToDouble(row["lng6"])));
    
                S2Loop loop = new S2Loop(lst);
                loop.Normalize();
                return loop;
            }
    View Code

     

示例效果


  •  

 

參考資料



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM