refactor(wave): 优化波形数据处理逻辑

- 移除波形数据抽点窗口大小配置参数wave.sample.interval
- 使用NPush参数替代固定的抽点窗口大小计算方式
- 修改图像生成方法的参数传递方式,直接传入nPush参数
- 调整波形数据抽点算法,移除MinMax抽点逻辑
- 优化数据段合并处理逻辑,简化null点插入策略
- 改进关键时间点处理,移除固定的关键时间点集合
- 调整波形数据范围计算,使用动态的nPush值代替固定毫秒值
This commit is contained in:
xy
2026-06-04 18:29:24 +08:00
parent cf691db0a6
commit 16c0620dd3

View File

@@ -126,11 +126,6 @@ public class CsEventPOServiceImpl extends ServiceImpl<CsEventPOMapper, CsEventPO
private final EventAnalysisService eventAnalysisService;
private final EventCauseFeignClient eventCauseFeignClient;
private final MsgSendFeignClient msgSendFeignClient;
/** 波形数据抽点窗口大小每N个点为一个窗口进行MinMax抽点可通过Nacos配置wave.sample.interval调整 */
@Value("${wave.sample.interval:0}")
private int waveSampleInterval;
/** 两段数据间隔区域补null点数量可通过Nacos配置wave.gap.null.points调整 */
@Value("${wave.gap.null.points:5}")
private int waveGapNullPoints;
@@ -704,23 +699,24 @@ public class CsEventPOServiceImpl extends ServiceImpl<CsEventPOMapper, CsEventPO
if (StrUtil.isBlank(eventDetail.getInstantPics())) {
//获取波形数据。然后绘图
WaveDataDTO waveDataDTO = this.analyseWave(eventId, iType ,true);
Integer nPush = Objects.isNull(waveDataDTO.getComtradeCfgDTO().getNPush())? 100: waveDataDTO.getComtradeCfgDTO().getNPush();
//数据筛选,如果是双路电压的话会存在2个波形数据
List<WaveDataDetail> waveDataDetails = WaveUtil.filterWaveData(waveDataDTO);
//单通道处理
if (ObjectUtils.isNotEmpty(waveDataDetails) && waveDataDetails.size() == 2) {
String instantPath = wavePicComponent.generateImageShun(waveDataDTO,waveDataDetails);
String instantPath = wavePicComponent.generateImageShun(nPush,waveDataDetails,eventDetail.getPersistTime()*1000);
eventDetail.setInstantPics(instantPath);
if (StrUtil.isBlank(eventDetail.getRmsPics())) {
String rmsPath = wavePicComponent.generateImageRms(waveDataDTO,waveDataDetails);
String rmsPath = wavePicComponent.generateImageRms(nPush,waveDataDetails,eventDetail.getPersistTime()*1000);
eventDetail.setRmsPics(rmsPath);
}
}
//双通道处理
else if (ObjectUtils.isNotEmpty(waveDataDetails) && waveDataDetails.size() == 4) {
String instantPath = wavePicComponent.generateInstantImageZl(waveDataDetails);
String instantPath = wavePicComponent.generateInstantImageZl(nPush,waveDataDetails,eventDetail.getPersistTime()*1000);
eventDetail.setInstantPics(instantPath);
if (StrUtil.isBlank(eventDetail.getRmsPics())) {
String rmsPath = wavePicComponent.generateRmsImageZl(waveDataDetails);
String rmsPath = wavePicComponent.generateRmsImageZl(nPush,waveDataDetails,eventDetail.getPersistTime()*1000);
eventDetail.setRmsPics(rmsPath);
}
}
@@ -735,24 +731,20 @@ public class CsEventPOServiceImpl extends ServiceImpl<CsEventPOMapper, CsEventPO
if (persistTime == null || persistTime <= 0) {
return;
}
Integer nPush = waveDataDTO.getComtradeCfgDTO().getNPush();
if (Objects.isNull(nPush)) {
nPush = 100;
}
double persistTimeMs = persistTime * 1000;
// 开始段:波形起始前后-40ms到100ms
double startSegBegin = -100;
double startSegEnd = 100;
// 开始段:波形起始前后
double startSegBegin = -nPush;
double startSegEnd = nPush;
// 结束段:恢复点前后各40ms
double endSegBegin = persistTimeMs - 40;
double endSegEnd = persistTimeMs + 40;
// 关键时间点(抽点时必须保留,不可被抽掉)
Set<Double> keyTimes = new HashSet<>();
keyTimes.add(startSegBegin); // 开头-100
keyTimes.add(0.0); // 波形开始时间点0
keyTimes.add(persistTimeMs); // 持续时间点
keyTimes.add(endSegBegin); // 持续时间点往前-40
keyTimes.add(endSegEnd); // 持续时间点往后+40
// 结束段:恢复点前后
double endSegBegin = persistTimeMs - nPush;
double endSegEnd = persistTimeMs + nPush;
List<List<Float>> originalWaveData = waveDataDTO.getListWaveData();
List<List<Float>> originalRmsData = waveDataDTO.getListRmsData();
@@ -769,152 +761,56 @@ public class CsEventPOServiceImpl extends ServiceImpl<CsEventPOMapper, CsEventPO
List<List<Float>> filteredRmsData;
if (isMerged) {
// 两段重叠或相邻,合并为一段连续数据
// 两段重叠或相邻,合并为一段连续数据,不做抽点
double mergedEnd = Math.max(startSegEnd, endSegEnd);
List<List<Float>> segWaveData = filterByTimeRange(originalWaveData, startSegBegin, mergedEnd);
List<List<Float>> segRmsData = filterByTimeRange(originalRmsData, startSegBegin, mergedEnd);
// 合并为1段MinMax抽点 + 保留关键节点
filteredWaveData = downsampleMinMaxWithKeyPoints(segWaveData, keyTimes, waveSampleInterval);
filteredRmsData = downsampleMinMaxWithKeyPoints(segRmsData, keyTimes, waveSampleInterval);
filteredWaveData = filterByTimeRange(originalWaveData, startSegBegin, mergedEnd);
filteredRmsData = filterByTimeRange(originalRmsData, startSegBegin, mergedEnd);
} else {
// 两段分离,分别MinMax抽点 + 保留关键节
// 两段分离,各自按时间范围裁剪,不做抽
List<List<Float>> seg1Wave = filterByTimeRange(originalWaveData, startSegBegin, startSegEnd);
List<List<Float>> seg2Wave = filterByTimeRange(originalWaveData, endSegBegin, endSegEnd);
List<List<Float>> seg1Rms = filterByTimeRange(originalRmsData, startSegBegin, startSegEnd);
List<List<Float>> seg2Rms = filterByTimeRange(originalRmsData, endSegBegin, endSegEnd);
List<List<Float>> ds1Wave = downsampleMinMaxWithKeyPoints(seg1Wave, keyTimes, waveSampleInterval);
List<List<Float>> ds2Wave = downsampleMinMaxWithKeyPoints(seg2Wave, keyTimes, waveSampleInterval);
List<List<Float>> ds1Rms = downsampleMinMaxWithKeyPoints(seg1Rms, keyTimes, waveSampleInterval);
List<List<Float>> ds2Rms = downsampleMinMaxWithKeyPoints(seg2Rms, keyTimes, waveSampleInterval);
// 两段之间补固定数量的null点时间均匀分布
double gapStart = getTimeValue(ds1Wave.get(ds1Wave.size() - 1));
double gapEnd = getTimeValue(ds2Wave.get(0));
List<List<Float>> gapWaveData = createNullGapData(gapStart, gapEnd, waveGapNullPoints, waveColumnCount);
List<List<Float>> gapRmsData = createNullGapData(gapStart, gapEnd, waveGapNullPoints, rmsColumnCount);
// 合并段1 + null间隔 + 段2
filteredWaveData = new ArrayList<>();
filteredWaveData.addAll(ds1Wave);
filteredWaveData.addAll(gapWaveData);
filteredWaveData.addAll(ds2Wave);
filteredRmsData = new ArrayList<>();
filteredRmsData.addAll(ds1Rms);
filteredRmsData.addAll(gapRmsData);
filteredRmsData.addAll(ds2Rms);
}
// 段1非空才加入
if (!seg1Wave.isEmpty()) {
filteredWaveData.addAll(seg1Wave);
}
if (!seg1Rms.isEmpty()) {
filteredRmsData.addAll(seg1Rms);
}
// 两段都非空时才补null间隔
boolean waveGapNeeded = !seg1Wave.isEmpty() && !seg2Wave.isEmpty();
boolean rmsGapNeeded = !seg1Rms.isEmpty() && !seg2Rms.isEmpty();
if (waveGapNeeded) {
double waveGapStart = getTimeValue(seg1Wave.get(seg1Wave.size() - 1));
double waveGapEnd = getTimeValue(seg2Wave.get(0));
List<List<Float>> gapWaveData = createNullGapData(waveGapStart, waveGapEnd, waveGapNullPoints, waveColumnCount);
filteredWaveData.addAll(gapWaveData);
}
if (rmsGapNeeded) {
double rmsGapStart = getTimeValue(seg1Rms.get(seg1Rms.size() - 1));
double rmsGapEnd = getTimeValue(seg2Rms.get(0));
List<List<Float>> gapRmsData = createNullGapData(rmsGapStart, rmsGapEnd, waveGapNullPoints, rmsColumnCount);
filteredRmsData.addAll(gapRmsData);
}
// 段2非空才加入
if (!seg2Wave.isEmpty()) {
filteredWaveData.addAll(seg2Wave);
}
if (!seg2Rms.isEmpty()) {
filteredRmsData.addAll(seg2Rms);
}
}
waveDataDTO.setListWaveData(filteredWaveData);
waveDataDTO.setListRmsData(filteredRmsData);
}
/**
* MinMax抽点每N个点为一个窗口保留窗口内峰值点、谷值点、首点及关键时间点
* 峰值=参考列最大值的数据行,谷值=参考列最小值的数据行
* 这样正弦波的峰谷特征得以保留,波形形状不会被破坏
*
* @param dataList 已按时间范围筛选的数据
* @param keyTimes 关键时间点集合(ms)
* @param sampleInterval 窗口大小(N)
*/
private List<List<Float>> downsampleMinMaxWithKeyPoints(List<List<Float>> dataList, Set<Double> keyTimes, int sampleInterval) {
if (dataList.isEmpty()) {
return dataList;
}
// 找到关键时间点对应的数据索引
Set<Integer> keyIndices = new HashSet<>();
for (Double keyTime : keyTimes) {
int closestIdx = findClosestIndex(dataList, keyTime);
if (closestIdx >= 0) {
keyIndices.add(closestIdx);
}
}
List<List<Float>> result = new ArrayList<>();
int size = dataList.size();
// 使用第1列第一个值列通常是A相电压作为峰谷检测参考列
int refCol = 1;
for (int windowStart = 0; windowStart < size; windowStart += sampleInterval) {
int windowEnd = Math.min(windowStart + sampleInterval, size);
// 在窗口内找参考列的最大值点(峰值)和最小值点(谷值)
int maxIdx = windowStart;
int minIdx = windowStart;
float maxVal = getFloatValue(dataList.get(windowStart), refCol);
float minVal = getFloatValue(dataList.get(windowStart), refCol);
for (int i = windowStart + 1; i < windowEnd; i++) {
float val = getFloatValue(dataList.get(i), refCol);
if (val > maxVal) {
maxVal = val;
maxIdx = i;
}
if (val < minVal) {
minVal = val;
minIdx = i;
}
}
// 保留:窗口首点 + 峰值 + 谷值 + 窗口内的关键时间点
Set<Integer> keepSet = new HashSet<>();
keepSet.add(windowStart);
keepSet.add(maxIdx);
keepSet.add(minIdx);
for (int i = windowStart; i < windowEnd; i++) {
if (keyIndices.contains(i)) {
keepSet.add(i);
}
}
// 按原始顺序输出(索引顺序即时间顺序)
List<Integer> sortedKeep = new ArrayList<>(keepSet);
Collections.sort(sortedKeep);
for (int idx : sortedKeep) {
result.add(dataList.get(idx));
}
}
return result;
}
/**
* 安全获取Float值null时返回0
*/
private float getFloatValue(List<Float> data, int colIdx) {
if (colIdx >= data.size()) {
return 0f;
}
Float val = data.get(colIdx);
return val != null ? val : 0f;
}
/**
* 找到与目标时间最接近的数据点索引
* 仅在容差1.0ms内匹配,超出容差视为该段数据中无此关键点
*/
private int findClosestIndex(List<List<Float>> dataList, double targetTime) {
int closestIdx = -1;
double minDiff = Double.MAX_VALUE;
for (int i = 0; i < dataList.size(); i++) {
double time = getTimeValue(dataList.get(i));
double diff = Math.abs(time - targetTime);
if (diff < minDiff) {
minDiff = diff;
closestIdx = i;
}
}
// 容差1.0ms,超出则认为该段不含此关键点
if (minDiff > 1.0) {
return -1;
}
return closestIdx;
}
private double getTimeValue(List<Float> data) {
return data.get(0).doubleValue();
}