道格拉斯矢量曲線抽稀算法C#語言非遞歸實(shí)現(xiàn)

道格拉斯矢量曲線抽稀算法C#語言非遞歸實(shí)現(xiàn)

1. 道格拉斯-普克算法簡介[1]

道格拉斯-普克算法(Douglas–Peucker algorithm,亦稱為拉默-道格拉斯-普克算法、迭代適應(yīng)點(diǎn)算法、分裂與合并算法)是將曲線近似表示為一系列點(diǎn),并減少點(diǎn)的數(shù)量的一種算法。該算法的原始類型分別由烏爾斯·拉默(Urs Ramer)于1972年以及大衛(wèi)·道格拉斯(David Douglas)和托馬斯·普克(Thomas Peucker)于1973年提出,并在之后的數(shù)十年中由其他學(xué)者予以完善。

經(jīng)典的Douglas-Peucker算法描述如下:

  1. 在曲線首尾兩點(diǎn)A,B之間連接一條直線AB,該直線為曲線的弦;
  2. 得到曲線上離該直線段距離最大的點(diǎn)C,計算其與AB的距離d;
  3. 比較該距離與預(yù)先給定的閾值threshold的大小,如果小于threshold,則該直線段作為曲線的近似,該段曲線處理完畢。
  4. 如果距離大于閾值,則用C將曲線分為兩段AC和BC,并分別對兩段取信進(jìn)行1~3的處理。
  5. 當(dāng)所有曲線都處理完畢時,依次連接各個分割點(diǎn)形成的折線,即可以作為曲線的近似。

由上述可見,道格拉斯-普克算法是一種過程遞歸的算法。在實(shí)際使用道格拉斯抽稀算法時,使用遞歸會使得在遞歸過程中存在大量的臨時變量。現(xiàn)參考吳銘杰[2]論文中的非遞歸方式實(shí)現(xiàn)道格拉斯抽稀算法。該方法簡單實(shí)用,容易編寫代碼,具有較高的安全性。

2. C#語言非遞歸實(shí)現(xiàn)

下面給出該算法的C#語言非遞歸實(shí)現(xiàn),所用到的類方法均使用靜態(tài)方法以減少開銷提高效率:
其中SeriesPoint類型為System.Windows.Point的類化;DataSeries類型為SeriesPoint集合的封裝(實(shí)現(xiàn)IEnumerable和ICollection<>接口)。

// 道格拉斯抽稀算法
public static DataSeries DouglasThinningMachine(DataSeries seriesPoints)
{
    if (seriesPoints == null)
    {
        throw new ArgumentNullException(nameof(seriesPoints));
    }
    List<SeriesPoint> spList = seriesPoints.series;
    int max = spList.Count;
    if (max < 500)
    {
        return seriesPoints;
    }
    // 此處限定了距離閾值
    double threshold = 10;
    int location = 0;
    // 創(chuàng)建兩個棧記錄索引值
    Stack<int> A = new Stack<int>();
    Stack<int> B = new Stack<int>();
    A.Push(0);
    B.Push(max - 1);
    do
    {
        var d = FindMostDistance(spList, A.Peek(), B.Peek(), ref location);
        if (d > threshold)
        {
            B.Push(location);
        }
        else
        {
            A.Push(location);
            B.Pop();
        }
    } while (B.Count > 0);
    List<int> listOfIndex = A.ToList();
    listOfIndex.Sort();
    DataSeries result = new DataSeries();
    foreach (int index in listOfIndex)
    {
        result.Add(spList[index].Clone());
    }
    return result;
}
// 計算最大距離
private static double FindMostDistance(List<SeriesPoint> seriesPoints,int start,int end,ref int location)
{
    if (end - start <= 1)
    {
        location = end;
        return 0;
    }
    double result = 0;
    SeriesPoint startPoint = seriesPoints[start];
    SeriesPoint endPoint = seriesPoints[end];
    for (int i = start + 1; i < end; i++)
    {
        // 調(diào)用 MathUtil Class 中的靜態(tài)方法GetDistanceToLine
        var d = MathUtil.GetDistanceToLine(startPoint, endPoint, seriesPoints[i]);
        if (d > result)
        {
            result = d;
            location = i;
        }
    }
    return result;
}

// 在 MathUtil Class 中

public static double GetDistanceToLine(double p1x, double p1y, double p2x, double p2y, double refpx, double refpy) 
    => Math.Abs(((p2y - p1y) * (refpx - p1x)) - ((refpy - p1y) * (p2x - p1x))) / Math.Pow(((p2y - p1y) * (p2y - p1y)) + ((p2x - p1x) * (p2x - p1x)), 0.5);

public static double GetDistanceToLine(Point point1, Point point2, Point refPoint) 
    => MathUtil.GetDistanceToLine(point1.X, point1.Y, point2.X, point2.Y, refPoint.X, refPoint.Y);

public static double GetDistanceToLine(SeriesPoint point1, SeriesPoint point2, SeriesPoint refPoint)
    => MathUtil.GetDistanceToLine(point1.X, point1.Y, point2.X, point2.Y, refPoint.X, refPoint.Y);

3. 抽稀算法測試與結(jié)果對比

通過Random類構(gòu)造一些帶有一定規(guī)律的數(shù)據(jù)點(diǎn)共1000個,使用抽稀算法(閾值為10)后保留點(diǎn)數(shù)430個,算法用時約4毫秒。現(xiàn)取前100個樣本值作為對比觀察:


抽稀前后對比

由圖可見,該算法具有較好的抽稀效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。