本文算法來自
https://skemman.is/bitstream/1946/15343/3/SS_MSthesis.pdf
作者對比了幾個算法,主要突出的是 Largest-Triangle-Three-Buckets算法,我個人翻譯為最大三點【三角】面積特征。同樣作者給出了代碼,我也找到了C#代碼,適當改了下。主要是說說算法
主要說說幾點:
1:算法的主要源於Visvalingam-Whyatt,也就是基於點與前后點形成的面積是否大於閾值,小於閾值則刪除點。
簡單說一下Visvalingam-Whyatt,
①當節點從首位開始,連接前兩個(因為是首位,沒有后面),緊接着是第二點,2點連接首點,連接三點,依次向下,末位點參考首點
②當形成全部的三角后刪除面積最小的結點,並由移至附近兩點,重新形成三角形【或者從頭重新連接~~】
③重復①,確定所有三角形的面積都小於閾值
2:LTTB是可以控制返回點的數量(亮點!!)
基本操作:點集排除首尾點后的 數量與輸出點的數量(同樣減去首尾點,也就是-2)的比值,然后每比值的一個點集,默認第一個點是獨立的一個點集,最后一個點也是獨立的一個點集
理論上第二步是選擇點與下兩個點集中的點所形成的最大的三角形的面積的點,實際上,算法中是 點1確定,點集3通過計算按照第幾個點集,然后計算這個點集中所有的點的平均值(X,Y兩個值的平均值)所創造的點,不一定存在。
這樣子就可以確定點1,點集2,然后不斷篩選點集2的點,經行計算面積來比較大小。最后 依次重復上述步驟到下一個點,直至結束。
同樣,不用平均值,也可以枚舉點集3與點集2的點與點1 所形成的每個三角形的面積。論文中 作者指出 差不多的效果~~(水啊🤣)
最后 視覺效果上真的不錯! 不過論文中說,當波峰值較大時,也有可能表現不好~
代碼實現的步驟:
零 初始點1為首點
① 計算比值(除去首尾,每個點集的數量)
② 計算點集3中點的平均點
③ 枚舉點集2與點集3的平均和點1形成最大的三角形面積,並加入輸出點集
④ 點1設置位點集2的最大面積點,並進入下一次循環步驟②
截圖

代碼 來自https://github.com/cgddrd/CSharp-LargestTriangleThreeBuckets(略微改造)
public static List<Point> Get(List<Point> data, int threshold) { int dataLength = data.Count; if (threshold >= dataLength || threshold == 0) return data; // Nothing to do List<Point> sampled = new List<Point> (threshold); // 點集大小,排除首尾點,閾值減2, double every = (double)(dataLength - 2) / (threshold - 2); int a = 0; Point maxAreaPoint = new Point(); int nextA = 0; sampled.Add(data[a]); //默認添加首點 for (int i = 0; i < threshold - 2; i++) { // 假設第三個點是此前區間的平均值,大概率不存在,憑空捏造一個🤣 double avgX = 0; double avgY = 0; int avgRangeStart = (int)(((i + 1) * every) + 1); int avgRangeEnd = (int)(((i + 2) * every) + 1); avgRangeEnd = avgRangeEnd < dataLength ? avgRangeEnd : dataLength; int avgRangeLength = avgRangeEnd - avgRangeStart; for (; avgRangeStart < avgRangeEnd; avgRangeStart++) { avgX += data[avgRangeStart].X; // * 1 enforces Number (value may be Date) avgY += data[avgRangeStart].Y; } avgX /= avgRangeLength; avgY /= avgRangeLength; // +1是為了排除當前點 int rangeOffs = (int)(Math.Floor((i + 0) * every) + 1); int rangeTo = (int)(Math.Floor((i + 1) * every) + 1); // Point a double pointAx = data[a].X; // enforce Number (value may be Date) double pointAy = data[a].Y; double maxArea = -1; for (; rangeOffs < rangeTo; rangeOffs++) { //計算面積 點1 點集2 虛擬的點3~~ double area = Math.Abs((pointAx - avgX) * (data[rangeOffs].Y - pointAy) - (pointAx - data[rangeOffs].X) * (avgY - pointAy) ) * 0.5; if (area > maxArea) { maxArea = area; maxAreaPoint = data[rangeOffs]; nextA = rangeOffs; //設置下一個點 } } sampled.Add(maxAreaPoint); a = nextA; // 進入下一次循環 } sampled.Add(data[dataLength - 1]); //默認添加尾點 return sampled; }
