/// <summary>
/// 道格拉斯数据压缩算法
/// 2021.10.12
/// </summary>
public class Douglaser
{
/// <summary>
/// 数组长度
/// </summary>
private int sourceLenght = 0;
/// <summary>
/// 阈值
/// </summary>
private double threshold = 0;
/// <summary>
/// 原始数据的坐标
/// </summary>
private ArrayList sourceArray = new ArrayList();
/// <summary>
/// 存入抽稀后的数据的坐标
/// </summary>
private ArrayList newArray = new ArrayList();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="_sourceArray">原始数据</param>
/// <param name="threshold">阈值</param>
public Douglaser(ArrayList _sourceArray,double _threshold)
{
sourceArray = _sourceArray;
threshold = _threshold;
}
/// <summary>
/// 运行结果
/// </summary>
public string Message { get; private set; }
/// <summary>
/// 开始计算,返回压缩后的数据字典
/// </summary>
/// <returns></returns>
public Dictionary<long, double> DoWork()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Dictionary<long, double> result = new Dictionary<long, double>();
//用自己的结构取出来数制成曲线图
newArray.Clear();
sourceLenght = sourceArray.Count;
newArray.Add((DouglasPoint)sourceArray[0]);
Douglas(0, sourceLenght);
if (newArray.Contains((DouglasPoint)sourceArray[sourceLenght - 1]))
{ }
else
{
newArray.Add((DouglasPoint)sourceArray[sourceLenght - 1]);
}
newArray.Sort(new SourceArrayCompare());
result.Clear();
for (int i = 0; i < newArray.Count; i++)
{
result.Add(((DouglasPoint)newArray[i]).x, ((DouglasPoint)newArray[i]).y);
}
sw.Stop();
this.Message = "计算耗时:" + Math.Round(sw.ElapsedTicks / (decimal)Stopwatch.Frequency * 1000, 3).ToString() + "毫秒";
return result;
}
public class Line//记录直线参数的类
{
public double k;
public double b;
}
/// <summary>
/// 坐标数据类
/// </summary>
public class DouglasPoint
{
public long x;
public double y;
}
/// <summary>
/// 求斜率
/// </summary>
/// <param name="p1">第一个点</param>
/// <param name="p2">第二个点</param>
/// <returns></returns>
public Line Parameter(DouglasPoint p1, DouglasPoint p2)
{
double k, b;
Line line = new Line();
k = (p2.y - p1.y) / (p2.x - p1.x);
b = p1.y - k * p1.x;
line.k = k;
line.b = b;
return line;
}
/// <summary>
/// 求点到直线距离
/// </summary>
/// <param name="dot"></param>
/// <param name="line"></param>
/// <returns></returns>
public float Distance(DouglasPoint dot, Line line)
{
float dis = (float)(Math.Abs(line.k * dot.x - dot.y + line.b)) / (float)Math.Sqrt(line.k * line.k + 1);//点(x0,y0)到直线Ax+By+c=0的距离d=|Ax0+By0+c|/(A2+B2)1/2
return dis;
}
public void Douglas(int number1, int number2)
{
int max = 0;//定义拥有最大距离值的点的编号
var line = Parameter((DouglasPoint)sourceArray[number1], (DouglasPoint)sourceArray[number2 - 1]);
max = 0;
float maxx = Distance((DouglasPoint)sourceArray[number1 + 1], line);//假设第二个点为最大距离点
for (int i = number1 + 1; i < number2 - 1; i++)//从第二个点遍历到最后一个点前方的点
{
var distance = Distance((DouglasPoint)sourceArray[i], line);
if (distance > threshold && distance >= maxx)//找出拥有最大距离的点
{
max = i;
maxx = distance;
}
}
if (max == 0)//若不存在最大距离点,则只将首尾点存入arraylist,结束这一次的道格拉斯抽稀
{
if (newArray.Contains((DouglasPoint)sourceArray[number2 - 1]))
{
return;
}
else
{
newArray.Add((DouglasPoint)sourceArray[number2 - 1]);
return;
}
}
else if (number1 + 1 == max && number2 - 2 != max)//如果第二个点是最大距离点,则以下一个点和尾点作为参数进行道格拉斯抽稀释
{
newArray.Add((DouglasPoint)sourceArray[max]);
Douglas(max, number2);
}
else if (number2 - 2 == max && number1 + 1 != max)//<span style="font-family: Arial;">如果倒数第二个点是最大距离点,则以首点和倒数第三点作为参数进行道格拉斯抽稀
{
newArray.Add((DouglasPoint)sourceArray[max]);
Douglas(number1, max + 1);
}
else if (number1 + 1 == max && number2 - 2 == max)//如果首点尾点夹住最大距离点,则将最大距离点和尾点存入arraylist
{
newArray.Add((DouglasPoint)sourceArray[max]);
return;
}
else
{
newArray.Add((DouglasPoint)sourceArray[max]);
Douglas(number1, max + 1);
Douglas(max, number2);
}
}
//newar里边的坐标按横坐标进行排序
public class SourceArrayCompare : IComparer
{
public int Compare(object pA, object pB)
{
DouglasPoint p1 = (DouglasPoint)pA;
DouglasPoint p2 = (DouglasPoint)pB;
return p1.x.CompareTo(p2.x);
}
}
}
使用方法
ArrayList temperatureArray = new ArrayList();
ArrayList pressureArray = new ArrayList();
if (list == null) return;
//生成原始数据
list.ForEach(t =>
{
temperatureArray.Add(new DouglasPoint { x = t.InsertDate.Ticks, y = t.Temperature });
pressureArray.Add(new DouglasPoint { x = t.InsertDate.Ticks, y = t.Pressure });
});
var temperatureList = new Douglaser(temperatureArray, AppData.Instance.Setting.ChartThreshold.Value).DoWork();
var pressureList = new Douglaser(pressureArray, AppData.Instance.Setting.ChartThreshold.Value).DoWork();