diff --git "a/Doc/\351\231\215\351\207\207\346\240\267/LTTB\351\231\215\351\207\207\346\240\267.png" "b/Doc/\351\231\215\351\207\207\346\240\267/LTTB\351\231\215\351\207\207\346\240\267.png"
new file mode 100644
index 0000000..f9277f0
Binary files /dev/null and "b/Doc/\351\231\215\351\207\207\346\240\267/LTTB\351\231\215\351\207\207\346\240\267.png" differ
diff --git "a/Doc/\351\231\215\351\207\207\346\240\267/LTTB\351\231\215\351\207\207\346\240\267.xlsx" "b/Doc/\351\231\215\351\207\207\346\240\267/LTTB\351\231\215\351\207\207\346\240\267.xlsx"
new file mode 100644
index 0000000..4502162
Binary files /dev/null and "b/Doc/\351\231\215\351\207\207\346\240\267/LTTB\351\231\215\351\207\207\346\240\267.xlsx" differ
diff --git a/NewLife.Core/Algorithms/LTOBDownSampling.cs b/NewLife.Core/Algorithms/LTOBDownSampling.cs
index 7883a66..c461396 100644
--- a/NewLife.Core/Algorithms/LTOBDownSampling.cs
+++ b/NewLife.Core/Algorithms/LTOBDownSampling.cs
@@ -42,14 +42,14 @@ namespace NewLife.Algorithms
var step = (Double)(data_length - 2) / (threshold - 2);
// 三角形选择当前同相邻三个ABC点,选择B,使得三角形有效面积最大
- for (var i = 1; i < threshold - 1; i++)
+ for (var i = 0; i < threshold - 2; i++)
{
// 计算每个点的有效区域,并选取有效区域最大的点作为桶的代表点
TimePoint point = default;
// 获取当前桶的范围
- var start = (Int32)Math.Round((i - 1 + 0) * step) + 1;
- var end = (Int32)Math.Round((i - 1 + 1) * step) + 1;
+ var start = (Int32)Math.Round((i + 0) * step) + 1;
+ var end = (Int32)Math.Round((i + 1) * step) + 1;
end = end < data_length ? end : data_length;
var max_area = -1.0;
@@ -70,7 +70,7 @@ namespace NewLife.Algorithms
}
}
- sampled[i] = point;
+ sampled[i + 1] = point;
}
// 第一个点和最后一个点
diff --git a/NewLife.Core/Algorithms/LTTBDownSampling.cs b/NewLife.Core/Algorithms/LTTBDownSampling.cs
index f047995..81b1456 100644
--- a/NewLife.Core/Algorithms/LTTBDownSampling.cs
+++ b/NewLife.Core/Algorithms/LTTBDownSampling.cs
@@ -33,15 +33,14 @@ namespace NewLife.Algorithms
if (data == null || data.Length < 2) return data;
if (threshold < 2 || threshold >= data.Length) return data;
+ if (AlignMode != AlignModes.None) return ProcessByAlign(data, threshold);
+
var data_length = data.Length;
var sampled = new TimePoint[threshold];
// 桶大小,预留开始结束位置
var step = (Double)(data_length - 2) / (threshold - 2);
- // 第一个点
- sampled[0] = data[0];
-
// 三角形选择相邻三个桶的ABC点,A是前一个桶选择点,C是后一个桶平均点,当前桶选择B,使得三角形有效面积最大
TimePoint pointA = default;
for (var i = 0; i < threshold - 2; i++)
@@ -49,8 +48,8 @@ namespace NewLife.Algorithms
// 计算下一个桶的平均点作为C
TimePoint pointC = default;
{
- var start = (Int32)Math.Floor((i + 1) * step) + 1;
- var end = (Int32)Math.Floor((i + 2) * step) + 1;
+ var start = (Int32)Math.Round((i + 1) * step) + 1;
+ var end = (Int32)Math.Round((i + 2) * step) + 1;
end = end < data_length ? end : data_length;
var length = end - start;
@@ -67,14 +66,14 @@ namespace NewLife.Algorithms
TimePoint point = default;
{
// 获取当前桶的范围
- var start = (Int32)Math.Floor((i + 0) * step) + 1;
- var end = (Int32)Math.Floor((i + 1) * step) + 1;
+ var start = (Int32)Math.Round((i + 0) * step) + 1;
+ var end = (Int32)Math.Round((i + 1) * step) + 1;
var max_area = -1.0;
- for (; start < end; start++)
+ for (var j = start; j < end; j++)
{
// 选择一个点B,计算ABC三角形面积
- var pointB = data[start];
+ var pointB = data[j];
var area = Math.Abs(
(pointA.Time - pointC.Time) * (pointB.Value - pointA.Value) -
(pointA.Time - pointB.Time) * (pointC.Value - pointA.Value)
@@ -91,10 +90,88 @@ namespace NewLife.Algorithms
pointA = point;
}
- // 最后一个点
+ // 第一个和最后一个点
+ sampled[0] = data[0];
sampled[threshold - 1] = data[data_length - 1];
return sampled;
}
+
+ private TimePoint[] ProcessByAlign(TimePoint[] data, Int32 threshold)
+ {
+ var data_length = data.Length;
+ var sampled = new TimePoint[threshold];
+
+ var step = (Double)data_length / threshold;
+
+ // 三角形选择相邻三个桶的ABC点,A是前一个桶选择点,C是后一个桶平均点,当前桶选择B,使得三角形有效面积最大
+ TimePoint pointA = default;
+ for (var i = 0; i < threshold; i++)
+ {
+ // 计算下一个桶的平均点作为C
+ TimePoint pointC = default;
+ if (i == threshold - 1)
+ pointC = data[data.Length - 1];
+ else
+ {
+ var start = (Int32)Math.Round((i + 1) * step);
+ var end = (Int32)Math.Round((i + 2) * step);
+ end = end < data_length ? end : data_length;
+
+ var length = end - start;
+ for (; start < end; start++)
+ {
+ pointC.Time += data[start].Time;
+ pointC.Value += data[start].Value;
+ }
+ pointC.Time /= length;
+ pointC.Value /= length;
+ }
+
+ // 计算每个点的有效区域,并选取有效区域最大的点作为桶的代表点
+ TimePoint point = default;
+ {
+ // 获取当前桶的范围
+ var start = (Int32)Math.Round((i + 0) * step);
+ var end = (Int32)Math.Round((i + 1) * step);
+
+ var max_area = -1.0;
+ for (var j = start; j < end; j++)
+ {
+ // 选择一个点B,计算ABC三角形面积
+ var pointB = data[j];
+ var area = Math.Abs(
+ (pointA.Time - pointC.Time) * (pointB.Value - pointA.Value) -
+ (pointA.Time - pointB.Time) * (pointC.Value - pointA.Value)
+ ) / 2;
+ if (area > max_area)
+ {
+ max_area = area;
+ point = pointB;
+ }
+ }
+
+ pointA = point;
+
+ // 对齐
+ switch (AlignMode)
+ {
+ case AlignModes.Left:
+ point.Time = data[start].Time;
+ break;
+ case AlignModes.Right:
+ point.Time = data[end - 1].Time;
+ break;
+ case AlignModes.Center:
+ point.Time = data[(Int32)Math.Round((start + end) / 2.0)].Time;
+ break;
+ }
+
+ sampled[i] = point;
+ }
+ }
+
+ return sampled;
+ }
}
}
\ No newline at end of file
diff --git a/XUnitTest.Core/Algorithms/LttbDownSamplingTests.cs b/XUnitTest.Core/Algorithms/LttbDownSamplingTests.cs
index 8e50598..c3b9330 100644
--- a/XUnitTest.Core/Algorithms/LttbDownSamplingTests.cs
+++ b/XUnitTest.Core/Algorithms/LttbDownSamplingTests.cs
@@ -11,7 +11,31 @@ namespace XUnitTest.Algorithms
public class LttbDownSamplingTests
{
[Fact]
- public void Test1()
+ public void Normal500()
+ {
+ var data = ReadPoints();
+ var lttb = new LTTBDownSampling();
+ var sampled = lttb.Process(data, 500);
+ Assert.NotNull(sampled);
+ Assert.Equal(500, sampled.Length);
+
+ //var k = 0;
+ //using var csv2 = new CsvFile("Algorithms/sampled.csv");
+ //while (true)
+ //{
+ // var line = csv2.ReadLine();
+ // if (line == null) break;
+
+ // Assert.Equal(line[0].ToInt(), sampled[k].Time);
+ // Assert.True(Math.Abs(line[1].ToDouble() - sampled[k].Value) < 0.0001);
+
+ // k++;
+ //}
+
+ WritePoints(sampled, lttb.AlignMode);
+ }
+
+ private TimePoint[] ReadPoints()
{
using var csv = new CsvFile("Algorithms/source.csv");
var data = new List<TimePoint>();
@@ -22,33 +46,55 @@ namespace XUnitTest.Algorithms
data.Add(new TimePoint { Time = line[0].ToInt(), Value = (Single)line[1].ToDouble() });
}
+ return data.ToArray();
+ }
- var lttb = new LTTBDownSampling();
- var sampled = lttb.Process(data.ToArray(), 500);
+ private void WritePoints(TimePoint[] data, AlignModes mode)
+ {
+ var f = $"Algorithms/lttb_{mode}_sampled.csv".GetFullPath();
+ if (File.Exists(f)) File.Delete(f);
+ using var csv = new CsvFile(f, true);
+ for (var i = 0; i < data.Length; i++)
+ {
+ csv.WriteLine(data[i].Time, data[i].Value);
+ }
+ csv.Dispose();
+ }
+
+ [Fact]
+ public void AlignLeftTest()
+ {
+ var data = ReadPoints();
+ var lttb = new LTTBDownSampling { AlignMode = AlignModes.Left };
+ var sampled = lttb.Process(data, 100);
Assert.NotNull(sampled);
- Assert.Equal(500, sampled.Length);
+ Assert.Equal(100, sampled.Length);
- var k = 0;
- using var csv2 = new CsvFile("Algorithms/sampled.csv");
- while (true)
- {
- var line = csv2.ReadLine();
- if (line == null) break;
+ WritePoints(sampled, lttb.AlignMode);
+ }
- Assert.Equal(line[0].ToInt(), sampled[k].Time);
- Assert.True(Math.Abs(line[1].ToDouble() - sampled[k].Value) < 0.0001);
+ [Fact]
+ public void AlignRightTest()
+ {
+ var data = ReadPoints();
+ var lttb = new LTTBDownSampling { AlignMode = AlignModes.Right };
+ var sampled = lttb.Process(data, 500);
+ Assert.NotNull(sampled);
+ Assert.Equal(500, sampled.Length);
- k++;
- }
+ WritePoints(sampled, lttb.AlignMode);
+ }
- var f = $"Algorithms/lttb_{lttb.AlignMode}_sampled.csv".GetFullPath();
- if (File.Exists(f)) File.Delete(f);
- using var csv3 = new CsvFile(f, true);
- for (var i = 0; i < sampled.Length; i++)
- {
- csv3.WriteLine(sampled[i].Time, sampled[i].Value);
- }
- csv3.Dispose();
+ [Fact]
+ public void AlignCenterTest()
+ {
+ var data = ReadPoints();
+ var lttb = new LTTBDownSampling { AlignMode = AlignModes.Center };
+ var sampled = lttb.Process(data, 500);
+ Assert.NotNull(sampled);
+ Assert.Equal(500, sampled.Length);
+
+ WritePoints(sampled, lttb.AlignMode);
}
}
}
\ No newline at end of file