道格拉斯矢量曲線抽稀算法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算法描述如下:
- 在曲線首尾兩點(diǎn)A,B之間連接一條直線AB,該直線為曲線的弦;
- 得到曲線上離該直線段距離最大的點(diǎn)C,計算其與AB的距離d;
- 比較該距離與預(yù)先給定的閾值threshold的大小,如果小于threshold,則該直線段作為曲線的近似,該段曲線處理完畢。
- 如果距離大于閾值,則用C將曲線分為兩段AC和BC,并分別對兩段取信進(jìn)行1~3的處理。
- 當(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個樣本值作為對比觀察:
抽稀前后對比
由圖可見,該算法具有較好的抽稀效果。