作為一家專業的馬拉松技術服務公司,每天都有大量的用戶通過我們的app上傳跑步記錄。 為了能更加直觀的感受用戶的跑步習慣和分布,我將一段時間內北京的用戶跑步記錄簡化后,通過mapbox地圖繪制出來。效果圖如下
image
為什么要簡化
馬拉松跑步用戶一般的跑步距離都會大于10公里,而我們的跑步軟件一般采樣時間為1s,因此一次跑步記錄的大小大概為幾百kb至幾M。如果讓mapbox直接加載幾萬條原始跑步記錄(大小估計上G),那么基本上是繪制不出來的。 為了能在mapbox地圖引擎上繪制如此多的跑步記錄,必須在不影響效果的前提下將跑步記錄簡化到足夠小
如何簡化
一般的圖形圖像算法,提供了直線檢測算法,例如hough變換等。 其算法的基本思想是在一個參數空間中通過計算累計結果的局部最大值得到一個符合該特定形狀的集合。 但跑步記錄有個很大的特征是跑步的起點和終點不能改變,并且跑步軌跡應該由多條收尾相連的線段擬合而成。所以常規的直線檢測算法并不合適。
數據結構
線段:對多個點擬合的線段
軌跡:多條線段首尾相連而成
軌跡集合:一次跑步為一個軌跡集合,由多個軌跡的組成
typedef struct LineParam
{
double a; //直線方程參數
double b; //直線方程參數
double c; //直線方程參數
double maxDist; //最大距離
double variance; //平均方差
double distance; //最后兩個點的距離
double length; //線段長度
}LineParam;//線段方程參數
typedef struct regressionConfig
{
double minPoints; //最少點數量
double maxVariance; //最大方差
double maxDist; //最大距離
double minVariance; //最小方差
}rgConfig; //線段回歸閾值
typedef struct SegmentStruct
{
LineParam lParam; //線性回歸方差參數
vector<LTPoint> points; //組成線段的點集
}Segment; 線段
typedef vector<Segment> Segments;//軌跡
typedef vector<Segments> Tracks; //多條軌跡的集合
簡化步驟
- 計算跑步記錄的2D包圍盒
- 將跑步記錄由經緯度坐標轉換到包圍盒頂點為原點的局部坐標系
- 從跑步記錄起點開始跟蹤,計算當前線段的參數
- 若當前線段的任一參數超過線段回歸閾值,則重開一條線段
- 若當前兩點距離超過閾值,則重開一條軌跡
- 重復步驟4、5直至跑步記錄終點,得到軌跡集合
- 將軌跡集合的所有點轉換到wgs84坐標系
- 將數據輸出成geojson格式
- 啟動web服務,調用mapbox的api加載geojson格式數據
下圖即一條軌跡簡化前后對比,紅色軌跡是原始的跑步記錄,可以看到是由密集的點組成。藍色的軌跡是簡化后的效果,只由幾條線段組成,數據量大概減少了95%左右。
image
異常處理
由于以下因素的存在,導致跑步記錄中可能會出現近似直線的數據,為不合法的數據
- 存在定位漂移的情況
- 跑步引擎的自動補償(當gps信號恢復時,自動與最近一個點連直線)
- 用戶乘坐軌道交通工具
- 用戶跑直線
通過以下策略進行檢測,去除包含直線的跑步記錄
- 跑步記錄中存在相鄰點距離大于1KM
- 跑步記錄簡化成多個線段,存在線段對應的跑步定位點距離線段最大距離小于3cm的線段,且線段長度大于500米
- 將跑步記錄簡化成多個線段,存在線段對應的跑步定位點距離擬合的直線平均方差小于2cm的線段,且線段長度大于500米