不采用降低采样率,严格对比对应窗口数据。避免没必要的降采抽点工作

This commit is contained in:
2025-10-17 17:29:47 +08:00
parent 66786200bd
commit 0a85b433ba
6 changed files with 707 additions and 5 deletions

View File

@@ -2902,7 +2902,7 @@ public class SocketContrastResponseService {
InputStream targetCfgStream = new FileInputStream(devCfgPath); InputStream targetCfgStream = new FileInputStream(devCfgPath);
InputStream targetDatStream = new FileInputStream(devDatPath)) { InputStream targetDatStream = new FileInputStream(devDatPath)) {
CompareWaveDTO compareWaveDTO = compareWaveService.analyzeAndCompareWithStreams( CompareWaveDTO compareWaveDTO = compareWaveService.analyzeWithSegmentPreservation(
sourceCfgStream, sourceCfgStream,
sourceDatStream, sourceDatStream,
targetCfgStream, targetCfgStream,

View File

@@ -25,10 +25,10 @@ public class AnalysisServiceStreamTest {
private ICompareWaveService compareWaveServiceImpl; private ICompareWaveService compareWaveServiceImpl;
// 测试文件路径 - 请根据实际情况修改 // 测试文件路径 - 请根据实际情况修改
private static final String SOURCE_CFG_PATH = "C:\\Users\\hongawen\\Desktop\\Event\\192.168.1.239\\PQ_PQLD1_000574_20250910_135244_231.cfg"; private static final String SOURCE_CFG_PATH = "C:\\Users\\Administrator\\Desktop\\wavedata\\192.168.1.200\\PQ_PQLD1_000020_20251017_140358_193.cfg";
private static final String SOURCE_DAT_PATH = "C:\\Users\\hongawen\\Desktop\\Event\\192.168.1.239\\PQ_PQLD1_000574_20250910_135244_231.dat"; private static final String SOURCE_DAT_PATH = "C:\\Users\\Administrator\\Desktop\\wavedata\\192.168.1.200\\PQ_PQLD1_000020_20251017_140358_193.dat";
private static final String TARGET_CFG_PATH = "C:\\Users\\hongawen\\Desktop\\Event\\192.168.1.238\\PQ_PQLD2_000508_20250910_135244_197.cfg"; private static final String TARGET_CFG_PATH = "C:\\Users\\Administrator\\Desktop\\wavedata\\192.168.1.168\\PQ_PQLD1_000018_20251017_140357_625.cfg";
private static final String TARGET_DAT_PATH = "C:\\Users\\hongawen\\Desktop\\Event\\192.168.1.238\\PQ_PQLD2_000508_20250910_135244_197.dat"; private static final String TARGET_DAT_PATH = "C:\\Users\\Administrator\\Desktop\\wavedata\\192.168.1.168\\PQ_PQLD1_000018_20251017_140357_625.dat";
// private static final String SOURCE_CFG_PATH = "F:\\hatch\\wavecompare\\数据比对\\统计数据1\\B码\\217\\PQMonitor_PQM1_000006_20200430_115517_889.cfg"; // private static final String SOURCE_CFG_PATH = "F:\\hatch\\wavecompare\\数据比对\\统计数据1\\B码\\217\\PQMonitor_PQM1_000006_20200430_115517_889.cfg";
@@ -105,6 +105,54 @@ public class AnalysisServiceStreamTest {
} }
} }
/**
* 测试段信息保留模式的波形分析(新方法)
* <p>不进行跨段的统一降采样保留每个段的原始采样率超过512才降到256</p>
* <p>跨段的窗口会被丢弃,只计算不跨段的窗口</p>
*/
@Test
public void testAnalyzeWithSegmentPreservation() throws Exception {
System.out.println("========================================");
System.out.println("开始执行电能质量分析(段信息保留模式)...");
System.out.println("========================================");
// 检查文件是否存在
checkFileExists(SOURCE_CFG_PATH, "源CFG文件");
checkFileExists(SOURCE_DAT_PATH, "源DAT文件");
checkFileExists(TARGET_CFG_PATH, "目标CFG文件");
checkFileExists(TARGET_DAT_PATH, "目标DAT文件");
long startTime = System.currentTimeMillis();
try (InputStream sourceCfgStream = new FileInputStream(SOURCE_CFG_PATH);
InputStream sourceDatStream = new FileInputStream(SOURCE_DAT_PATH);
InputStream targetCfgStream = new FileInputStream(TARGET_CFG_PATH);
InputStream targetDatStream = new FileInputStream(TARGET_DAT_PATH)) {
CompareWaveDTO result = compareWaveServiceImpl.analyzeWithSegmentPreservation(
sourceCfgStream,
sourceDatStream,
targetCfgStream,
targetDatStream,
0 // 接线方式: 0=星型接线, 1=V型接线
);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 输出分析结果
System.out.println("========================================");
System.out.println("分析完成!");
System.out.println("总耗时: " + duration + " ms (" + String.format("%.2f", duration / 1000.0) + " 秒)");
System.out.println("源文件有效窗口数: " + (result.getSourceResults() != null ? result.getSourceResults().size() : 0));
System.out.println("目标文件有效窗口数: " + (result.getTargetResults() != null ? result.getTargetResults().size() : 0));
System.out.println("========================================");
System.out.println("段信息保留模式测试完成!");
System.out.println("========================================");
}
}
/** /**
* 检查文件是否存在 * 检查文件是否存在
*/ */

View File

@@ -782,4 +782,222 @@ public class ComtradeReader {
return (float) cfgInfo.getSampleRate() / lineFreq; return (float) cfgInfo.getSampleRate() / lineFreq;
} }
/**
* 读取COMTRADE文件并保留段信息新方法不影响原有逻辑
* <p>核心改进:</p>
* <ol>
* <li>不进行跨段的统一降采样,保留每个段的原始采样率</li>
* <li>只对超过512点/周波的段降采样到256</li>
* <li>记录每个段的边界和采样率信息</li>
* <li>后续可以判断200ms窗口是否跨段跨段窗口丢弃</li>
* </ol>
*
* @param cfgStream CFG配置文件输入流
* @param datStream DAT数据文件输入流
* @param dataBuf 数据缓冲区
* @param encoding 文件编码格式
* @return 读取是否成功
*/
public static boolean readSampleFilePreserveSegments(InputStream cfgStream, InputStream datStream,
DataPq dataBuf, String encoding) {
try {
// 读取CFG配置文件
ComtradeData.CfgInfo cfgInfo = readCfgFile(cfgStream, encoding);
if (cfgInfo == null) {
log.error("Failed to read CFG from stream");
return false;
}
// 获取数据文件类型
String dataFileType = cfgInfo.getDataFileType();
if (dataFileType == null || dataFileType.isEmpty()) {
dataFileType = FILE_TYPE_BINARY;
log.info("数据文件类型未指定默认使用BINARY格式");
}
// 读取DAT数据文件保留段信息
boolean success = readDatFilePreserveSegments(datStream, cfgInfo, dataBuf, dataFileType);
if (!success) {
log.error("Failed to read DAT from stream");
return false;
}
// 处理CFG信息
return processCfgAndDataPreserveSegments(cfgInfo, dataBuf);
} catch (Exception e) {
log.error("Error reading COMTRADE with segment preservation", e);
return false;
}
}
/**
* 读取DAT文件并保留段信息
*/
private static boolean readDatFilePreserveSegments(InputStream datStream, ComtradeData.CfgInfo cfgInfo,
DataPq dataBuf, String dataFileType) throws IOException {
if (FILE_TYPE_BINARY.equalsIgnoreCase(dataFileType)) {
return readBinaryDatFilePreserveSegments(datStream, cfgInfo, dataBuf);
} else {
log.error("ASCII格式暂不支持段信息保留请使用BINARY格式");
return false;
}
}
/**
* 读取二进制DAT文件并保留段信息
*/
private static boolean readBinaryDatFilePreserveSegments(InputStream datStream, ComtradeData.CfgInfo cfgInfo,
DataPq dataBuf) throws IOException {
// 计算每个采样记录的字节数
int analogBytes = cfgInfo.getAnalogChannels() * 2;
int digitalBytes = (cfgInfo.getDigitalChannels() + 15) / 16 * 2;
int recordSize = 4 + 4 + analogBytes + digitalBytes;
float lineFreq = (float) cfgInfo.getLineFreq();
if (lineFreq <= 0) {
lineFreq = 50.0f;
}
byte[] buffer = new byte[recordSize];
int sampleIndex = 0; // 输出索引
try (DataInputStream dis = new DataInputStream(new BufferedInputStream(datStream))) {
// 处理多段数据
if (cfgInfo.getNrates() > 0 && cfgInfo.getSampleRates() != null) {
log.info("=== 多段数据处理(保留段信息模式) ===");
dataBuf.setSegmentCount(cfgInfo.getNrates());
for (int seg = 0; seg < cfgInfo.getNrates(); seg++) {
float segmentSamplesPerCycle = (float) (cfgInfo.getSampleRates()[seg] / lineFreq);
int segmentSamples = cfgInfo.getSegmentSamples()[seg];
// 记录段的起始位置
dataBuf.getSegmentStartIndices()[seg] = sampleIndex;
// 只对超过512的段降采样到256
float targetRate = segmentSamplesPerCycle;
int downsampleMod = 1;
if (segmentSamplesPerCycle > 512) {
targetRate = 256;
downsampleMod = Math.round(segmentSamplesPerCycle / 256);
log.info("段{} - 原始: {}点/周波 > 512, 降采样到256, 模数: {}",
seg, segmentSamplesPerCycle, downsampleMod);
} else {
log.info("段{} - 原始: {}点/周波, 保持不变",
seg, segmentSamplesPerCycle);
}
// 记录段的采样率(降采样后的)
dataBuf.getSegmentSampleRates()[seg] = targetRate;
// 读取当前段的数据
for (int i = 0; i < segmentSamples; i++) {
int bytesRead = dis.read(buffer, 0, recordSize);
if (bytesRead != recordSize) {
break;
}
// 降采样(仅当>512时
if (i % downsampleMod == 0) {
ByteBuffer bb = ByteBuffer.wrap(buffer);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.getInt(); // 序号
bb.getInt(); // 时间戳
// 读取模拟通道数据
for (int ch = 0; ch < cfgInfo.getAnalogChannels() && ch < MAX_CH_NUM; ch++) {
short rawValue = bb.getShort();
dataBuf.getSmpData()[ch][sampleIndex] = rawValue;
}
sampleIndex++;
}
}
log.info("段{} 完成 - 起始索引: {}, 输出采样点: {}, 采样率: {}点/周波",
seg, dataBuf.getSegmentStartIndices()[seg],
sampleIndex - dataBuf.getSegmentStartIndices()[seg],
targetRate);
}
log.info("=== 多段数据处理完成 - 总采样点: {} ===", sampleIndex);
} else {
// 单段处理
dataBuf.setSegmentCount(1);
dataBuf.getSegmentStartIndices()[0] = 0;
float samplesPerCycle = (float) cfgInfo.getSampleRate() / lineFreq;
float targetRate = samplesPerCycle;
int downsampleMod = 1;
if (samplesPerCycle > 512) {
targetRate = 256;
downsampleMod = Math.round(samplesPerCycle / 256);
}
dataBuf.getSegmentSampleRates()[0] = targetRate;
int totalSamples = cfgInfo.getTotalSamples();
for (int i = 0; i < totalSamples; i++) {
int bytesRead = dis.read(buffer, 0, recordSize);
if (bytesRead != recordSize) {
break;
}
if (i % downsampleMod == 0) {
ByteBuffer bb = ByteBuffer.wrap(buffer);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.getInt();
bb.getInt();
for (int ch = 0; ch < cfgInfo.getAnalogChannels() && ch < MAX_CH_NUM; ch++) {
short rawValue = bb.getShort();
dataBuf.getSmpData()[ch][sampleIndex] = rawValue;
}
sampleIndex++;
}
}
log.info("单段数据处理完成 - 采样点: {}, 采样率: {}点/周波", sampleIndex, targetRate);
}
}
dataBuf.setSmpNum(sampleIndex);
dataBuf.setSmpRate(dataBuf.getSegmentSampleRates()[0]); // 使用第一段的采样率作为默认值
// 设置增益系数
for (int ch = 0; ch < cfgInfo.getAnalogChannels() && ch < MAX_CH_NUM; ch++) {
ComtradeData.ChannelInfo chInfo = cfgInfo.getAnalogChannelInfos()[ch];
dataBuf.getUiGainXs()[ch] = (float) chInfo.getA();
}
return true;
}
/**
* 处理CFG信息保留段信息模式
*/
private static boolean processCfgAndDataPreserveSegments(ComtradeData.CfgInfo cfgInfo, DataPq dataBuf) {
try {
// 设置录波开始时间
dataBuf.setLbStartTime(cfgInfo.getStartTime());
dataBuf.setStartCalTime(cfgInfo.getStartTime());
dataBuf.setF((float) cfgInfo.getLineFreq());
log.info("CFG信息处理完成段信息保留模式 - 段数: {}, 总采样点: {}",
dataBuf.getSegmentCount(), dataBuf.getSmpNum());
return true;
} catch (Exception e) {
log.error("处理CFG信息时发生错误", e);
return false;
}
}
} }

View File

@@ -46,6 +46,15 @@ public class DataPq {
/** 每周波采样点数对应C代码中的smp_rate */ /** 每周波采样点数对应C代码中的smp_rate */
private float smpRate; private float smpRate;
/** 段数量(用于多段采样率文件) */
private int segmentCount = 0;
/** 每个段的起始采样点索引(降采样后的索引) */
private int[] segmentStartIndices = new int[10];
/** 每个段的采样率(点/周波) */
private float[] segmentSampleRates = new float[10];
/** 系统频率 (Hz) */ /** 系统频率 (Hz) */
private float f; private float f;

View File

@@ -20,4 +20,21 @@ public interface ICompareWaveService {
InputStream targetCfgStream, InputStream targetDatStream, InputStream targetCfgStream, InputStream targetDatStream,
int lineConfig); int lineConfig);
/**
* 使用段信息保留模式分析波形(新方法)
* <p>不进行跨段的统一降采样,保留每个段的原始采样率</p>
* <p>跨段的窗口会被丢弃,只计算不跨段的窗口</p>
*
* @param sourceCfgStream 源文件CFG流
* @param sourceDatStream 源文件DAT流
* @param targetCfgStream 目标文件CFG流
* @param targetDatStream 目标文件DAT流
* @param lineConfig 接线方式
* @return 分析结果
*/
CompareWaveDTO analyzeWithSegmentPreservation(
InputStream sourceCfgStream, InputStream sourceDatStream,
InputStream targetCfgStream, InputStream targetDatStream,
int lineConfig);
} }

View File

@@ -274,4 +274,414 @@ public class CompareWaveServiceImpl implements ICompareWaveService {
return results; return results;
} }
/**
* 使用段信息保留模式分析波形(新方法,不影响原有逻辑)
* <p>核心改进:</p>
* <ol>
* <li>不进行跨段的统一降采样每个段保留原始采样率超过512才降到256</li>
* <li>对齐后判断每个200ms窗口是否跨段</li>
* <li>跨段的窗口丢弃,只计算不跨段的窗口</li>
* <li>每个窗口使用其所在段的实际采样率进行计算</li>
* </ol>
*
* @param sourceCfgStream 源文件CFG流
* @param sourceDatStream 源文件DAT流
* @param targetCfgStream 目标文件CFG流
* @param targetDatStream 目标文件DAT流
* @param lineConfig 接线方式
* @return 分析结果
*/
public CompareWaveDTO analyzeWithSegmentPreservation(
InputStream sourceCfgStream, InputStream sourceDatStream,
InputStream targetCfgStream, InputStream targetDatStream, int lineConfig) {
CompareWaveDTO compareWaveDTO = new CompareWaveDTO();
DataPq sourceDataBuf = new DataPq();
DataPq targetDataBuf = new DataPq();
try {
log.info("开始电能质量分析(段信息保留模式)...");
// 初始化数据缓冲区
sourceDataBuf.init();
targetDataBuf.init();
// 设置配置参数
applyConfiguration(sourceDataBuf, lineConfig);
applyConfiguration(targetDataBuf, lineConfig);
// 使用新方法读取文件(保留段信息)
log.info("读取源文件(保留段信息)...");
boolean sourceSuccess = ComtradeReader.readSampleFilePreserveSegments(
sourceCfgStream, sourceDatStream, sourceDataBuf,
powerQualityConfig.getReading().getEncoding()
);
if (!sourceSuccess) {
log.error("读取源文件失败");
return compareWaveDTO;
}
log.info("读取目标文件(保留段信息)...");
boolean targetSuccess = ComtradeReader.readSampleFilePreserveSegments(
targetCfgStream, targetDatStream, targetDataBuf,
powerQualityConfig.getReading().getEncoding()
);
if (!targetSuccess) {
log.error("读取目标文件失败");
return compareWaveDTO;
}
// 执行波形对齐
log.info("开始波形对齐...");
WaveformAligner.AlignmentResult alignmentResult = WaveformAligner.findStartPosition(sourceDataBuf, targetDataBuf);
int calNum = alignmentResult.getCalNum();
if (calNum <= 0) {
log.warn("波形对齐失败,可计算窗口数: {}", calNum);
return compareWaveDTO;
}
// 应用对齐结果到数据缓冲区
WaveformAligner.applyAlignment(sourceDataBuf, targetDataBuf, alignmentResult);
log.info("波形对齐完成 - 源文件起始位置: {}, 目标文件起始位置: {}, 可计算窗口数: {}",
sourceDataBuf.getStartCalPos(), targetDataBuf.getStartCalPos(), calNum);
// 同步计算两个文件的电能质量参数(确保窗口对应)
calculatePowerQualityWithAlignedWindows(sourceDataBuf, targetDataBuf, calNum, compareWaveDTO);
log.info("电能质量分析完成 - 源文件有效窗口: {}, 目标文件有效窗口: {}",
compareWaveDTO.getSourceResults() != null ? compareWaveDTO.getSourceResults().size() : 0,
compareWaveDTO.getTargetResults() != null ? compareWaveDTO.getTargetResults().size() : 0);
return compareWaveDTO;
} catch (Exception e) {
log.error("分析过程中发生错误", e);
return compareWaveDTO;
}
}
/**
* 同步计算两个文件的电能质量参数(确保窗口对应)
* <p>如果某个窗口在任一文件中跨段,两个文件都丢弃该窗口</p>
*
* @param sourceDataBuf 源文件数据缓冲区
* @param targetDataBuf 目标文件数据缓冲区
* @param maxCalNum 最大窗口数
* @param compareWaveDTO 结果对象
*/
private void calculatePowerQualityWithAlignedWindows(
DataPq sourceDataBuf, DataPq targetDataBuf, int maxCalNum, CompareWaveDTO compareWaveDTO) {
List<PqsDataStruct> sourceResults = new ArrayList<>();
List<PqsDataStruct> targetResults = new ArrayList<>();
int sourceStartPos = sourceDataBuf.getStartCalPos();
int targetStartPos = targetDataBuf.getStartCalPos();
log.info("开始同步计算 - 源文件起始: {}, 目标文件起始: {}, 最大窗口数: {}",
sourceStartPos, targetStartPos, maxCalNum);
// 获取起始时间
ClockStruct sourceStartTime = sourceDataBuf.getStartCalTime();
ClockStruct targetStartTime = targetDataBuf.getStartCalTime();
if (sourceStartTime == null || targetStartTime == null) {
log.error("起始时间为空,无法计算");
return;
}
// 遍历每个窗口
int sourceCurrentPos = sourceStartPos;
int targetCurrentPos = targetStartPos;
int validWindowCount = 0;
for (int window = 0; window < maxCalNum; window++) {
// 检查源文件窗口
int sourceSegment = findSegmentIndex(sourceDataBuf, sourceCurrentPos);
float sourceSampleRate = sourceDataBuf.getSegmentSampleRates()[sourceSegment];
int sourceSamplesPerWindow = (int) (sourceSampleRate * 10);
int sourceWindowEnd = sourceCurrentPos + sourceSamplesPerWindow;
// 检查目标文件窗口
int targetSegment = findSegmentIndex(targetDataBuf, targetCurrentPos);
float targetSampleRate = targetDataBuf.getSegmentSampleRates()[targetSegment];
int targetSamplesPerWindow = (int) (targetSampleRate * 10);
int targetWindowEnd = targetCurrentPos + targetSamplesPerWindow;
// 检查是否超出数据范围
if (sourceWindowEnd > sourceDataBuf.getSmpNum() || targetWindowEnd > targetDataBuf.getSmpNum()) {
log.warn("窗口 {} 超出数据范围,结束计算", window);
break;
}
// 判断源文件窗口是否跨段
int sourceEndSegment = findSegmentIndex(sourceDataBuf, sourceWindowEnd - 1);
boolean sourceCrossSegment = (sourceSegment != sourceEndSegment);
// 判断目标文件窗口是否跨段
int targetEndSegment = findSegmentIndex(targetDataBuf, targetWindowEnd - 1);
boolean targetCrossSegment = (targetSegment != targetEndSegment);
// 如果任一文件跨段,两个文件都丢弃该窗口
if (sourceCrossSegment || targetCrossSegment) {
if (sourceCrossSegment) {
log.warn("窗口 {} - 源文件跨段(段{} -> 段{}),该窗口被丢弃",
window, sourceSegment, sourceEndSegment);
}
if (targetCrossSegment) {
log.warn("窗口 {} - 目标文件跨段(段{} -> 段{}),该窗口被丢弃",
window, targetSegment, targetEndSegment);
}
// 推进位置到下一个窗口
sourceCurrentPos = sourceWindowEnd;
targetCurrentPos = targetWindowEnd;
continue;
}
// 两个文件都不跨段,计算该窗口
// 计算时间戳
ClockStruct sourceDataTime = calculateWindowTime(sourceStartTime, window);
ClockStruct targetDataTime = calculateWindowTime(targetStartTime, window);
// 计算源文件窗口
float sourceOriginalRate = sourceDataBuf.getSmpRate();
sourceDataBuf.setSmpRate(sourceSampleRate);
sourceDataBuf.setDataPoint(validWindowCount);
PowerQualityCalculator.pqs200msDataCal(sourceDataBuf, sourceDataTime, sourceWindowEnd);
sourceDataBuf.setSmpRate(sourceOriginalRate);
PqsDataStruct sourceResult = sourceDataBuf.getPqData()[validWindowCount];
sourceResults.add(sourceResult);
// 计算目标文件窗口
float targetOriginalRate = targetDataBuf.getSmpRate();
targetDataBuf.setSmpRate(targetSampleRate);
targetDataBuf.setDataPoint(validWindowCount);
PowerQualityCalculator.pqs200msDataCal(targetDataBuf, targetDataTime, targetWindowEnd);
targetDataBuf.setSmpRate(targetOriginalRate);
PqsDataStruct targetResult = targetDataBuf.getPqData()[validWindowCount];
targetResults.add(targetResult);
log.info("有效窗口 {} (总窗口{}) - 源文件段{}/{}点, 目标文件段{}/{}点, UA: {}/{}, IA: {}/{}",
validWindowCount, window,
sourceSegment, sourceSampleRate, targetSegment, targetSampleRate,
sourceResult.getRms()[0], targetResult.getRms()[0],
sourceResult.getRms()[3], targetResult.getRms()[3]);
validWindowCount++;
// 推进到下一个窗口
sourceCurrentPos = sourceWindowEnd;
targetCurrentPos = targetWindowEnd;
}
// 校验结果数组长度是否一致(保险措施)
if (sourceResults.size() != targetResults.size()) {
int minSize = Math.min(sourceResults.size(), targetResults.size());
log.warn("结果数组长度不一致!源文件: {}, 目标文件: {}, 截取到: {}",
sourceResults.size(), targetResults.size(), minSize);
// 截取到较小的长度
if (sourceResults.size() > minSize) {
sourceResults = sourceResults.subList(0, minSize);
}
if (targetResults.size() > minSize) {
targetResults = targetResults.subList(0, minSize);
}
}
compareWaveDTO.setSourceResults(sourceResults);
compareWaveDTO.setTargetResults(targetResults);
}
/**
* 计算窗口的时间戳
*/
private ClockStruct calculateWindowTime(ClockStruct startTime, int windowIndex) {
ClockStruct dataTime = new ClockStruct();
dataTime.setYear(startTime.getYear());
dataTime.setMonth(startTime.getMonth());
dataTime.setDay(startTime.getDay());
dataTime.setHour(startTime.getHour());
dataTime.setMinute(startTime.getMinute());
dataTime.setSecond(startTime.getSecond());
dataTime.setMicroSecond(startTime.getMicroSecond());
// 添加时间偏移每个窗口200ms
int totalMs = dataTime.getMicroSecond() / 1000 + windowIndex * 200;
if (totalMs >= 1000) {
dataTime.setSecond(dataTime.getSecond() + totalMs / 1000);
totalMs = totalMs % 1000;
if (dataTime.getSecond() >= 60) {
dataTime.setMinute(dataTime.getMinute() + dataTime.getSecond() / 60);
dataTime.setSecond(dataTime.getSecond() % 60);
if (dataTime.getMinute() >= 60) {
dataTime.setHour(dataTime.getHour() + dataTime.getMinute() / 60);
dataTime.setMinute(dataTime.getMinute() % 60);
}
}
}
dataTime.setMicroSecond(totalMs * 1000);
return dataTime;
}
/**
* 计算电能质量参数(过滤跨段窗口)
*
* @param dataBuf 数据缓冲区(包含段信息)
* @param startCalPos 起始计算位置
* @param maxCalNum 最大窗口数
* @return 有效窗口的计算结果
*/
private List<PqsDataStruct> calculatePowerQualityWithSegmentFilter(
DataPq dataBuf, int startCalPos, int maxCalNum) {
List<PqsDataStruct> results = new ArrayList<>();
// 获取起始时间
ClockStruct startCalTime = dataBuf.getStartCalTime();
if (startCalTime == null) {
startCalTime = new ClockStruct();
startCalTime.setYear(2024);
startCalTime.setMonth(1);
startCalTime.setDay(1);
startCalTime.setHour(0);
startCalTime.setMinute(0);
startCalTime.setSecond(0);
startCalTime.setMicroSecond(0);
}
// 重置数据点位置
dataBuf.setDataPoint(0);
log.info("开始计算电能质量参数 - 段数: {}, 起始位置: {}, 最大窗口数: {}",
dataBuf.getSegmentCount(), startCalPos, maxCalNum);
// 找出起始位置所在的段
int currentSegment = findSegmentIndex(dataBuf, startCalPos);
float currentSegmentRate = dataBuf.getSegmentSampleRates()[currentSegment];
log.info("起始位置{}在段{}, 采样率: {}点/周波", startCalPos, currentSegment, currentSegmentRate);
// 遍历每个窗口
int validWindowCount = 0;
int currentPos = startCalPos; // 使用累计位置
for (int window = 0; window < maxCalNum; window++) {
// 找出窗口所在的段
int windowStartSegment = findSegmentIndex(dataBuf, currentPos);
float segmentSampleRate = dataBuf.getSegmentSampleRates()[windowStartSegment];
int samplesPerWindow = (int) (segmentSampleRate * 10); // 10个周波
int windowStartPos = currentPos;
int windowEndPos = currentPos + samplesPerWindow;
// 检查窗口是否超出数据范围
if (windowEndPos > dataBuf.getSmpNum()) {
log.warn("窗口 {} 超出数据范围,结束计算", window);
break;
}
// 判断窗口是否跨段
int endSegment = findSegmentIndex(dataBuf, windowEndPos - 1);
if (windowStartSegment != endSegment) {
log.warn("窗口 {} 跨段(段{} -> 段{}),丢弃该窗口", window, windowStartSegment, endSegment);
// 跨段窗口丢弃,但仍然推进位置到下一个窗口
currentPos = windowEndPos;
continue;
}
// 不跨段,计算窗口结束点
int smpWrPoint = windowEndPos;
// 计算当前窗口的时间戳
ClockStruct dataTime = new ClockStruct();
dataTime.setYear(startCalTime.getYear());
dataTime.setMonth(startCalTime.getMonth());
dataTime.setDay(startCalTime.getDay());
dataTime.setHour(startCalTime.getHour());
dataTime.setMinute(startCalTime.getMinute());
dataTime.setSecond(startCalTime.getSecond());
dataTime.setMicroSecond(startCalTime.getMicroSecond());
// 添加时间偏移
int totalMs = dataTime.getMicroSecond() / 1000 + window * 200;
if (totalMs >= 1000) {
dataTime.setSecond(dataTime.getSecond() + totalMs / 1000);
totalMs = totalMs % 1000;
if (dataTime.getSecond() >= 60) {
dataTime.setMinute(dataTime.getMinute() + dataTime.getSecond() / 60);
dataTime.setSecond(dataTime.getSecond() % 60);
if (dataTime.getMinute() >= 60) {
dataTime.setHour(dataTime.getHour() + dataTime.getMinute() / 60);
dataTime.setMinute(dataTime.getMinute() % 60);
}
}
}
dataTime.setMicroSecond(totalMs * 1000);
// 临时设置采样率为该段的采样率
float originalSmpRate = dataBuf.getSmpRate();
dataBuf.setSmpRate(segmentSampleRate);
// 执行200ms数据计算
PowerQualityCalculator.pqs200msDataCal(dataBuf, dataTime, smpWrPoint);
// 恢复原始采样率
dataBuf.setSmpRate(originalSmpRate);
// 获取计算结果
PqsDataStruct result = dataBuf.getPqData()[validWindowCount];
results.add(result);
log.info("有效窗口 {} (总窗口{}) - 段{}, 采样率: {}点/周波, UA有效值: {}, IA有效值: {}",
validWindowCount, window, windowStartSegment, segmentSampleRate,
result.getRms()[0], result.getRms()[3]);
validWindowCount++;
// 推进到下一个窗口的起始位置
currentPos = windowEndPos;
}
return results;
}
/**
* 查找指定采样点所在的段索引
*
* @param dataBuf 数据缓冲区
* @param samplePos 采样点位置
* @return 段索引
*/
private int findSegmentIndex(DataPq dataBuf, int samplePos) {
for (int seg = 0; seg < dataBuf.getSegmentCount(); seg++) {
int segmentStart = dataBuf.getSegmentStartIndices()[seg];
int segmentEnd;
if (seg < dataBuf.getSegmentCount() - 1) {
segmentEnd = dataBuf.getSegmentStartIndices()[seg + 1];
} else {
segmentEnd = dataBuf.getSmpNum();
}
if (samplePos >= segmentStart && samplePos < segmentEnd) {
return seg;
}
}
return dataBuf.getSegmentCount() - 1; // 默认返回最后一段
}
} }