diff --git a/detection/src/main/java/com/njcn/gather/detection/handler/SocketContrastResponseService.java b/detection/src/main/java/com/njcn/gather/detection/handler/SocketContrastResponseService.java index cdaef49a..96fc42e8 100644 --- a/detection/src/main/java/com/njcn/gather/detection/handler/SocketContrastResponseService.java +++ b/detection/src/main/java/com/njcn/gather/detection/handler/SocketContrastResponseService.java @@ -2902,7 +2902,7 @@ public class SocketContrastResponseService { InputStream targetCfgStream = new FileInputStream(devCfgPath); InputStream targetDatStream = new FileInputStream(devDatPath)) { - CompareWaveDTO compareWaveDTO = compareWaveService.analyzeAndCompareWithStreams( + CompareWaveDTO compareWaveDTO = compareWaveService.analyzeWithSegmentPreservation( sourceCfgStream, sourceDatStream, targetCfgStream, diff --git a/entrance/src/test/java/com/njcn/AnalysisServiceStreamTest.java b/entrance/src/test/java/com/njcn/AnalysisServiceStreamTest.java index 6a813182..6f7e91b8 100644 --- a/entrance/src/test/java/com/njcn/AnalysisServiceStreamTest.java +++ b/entrance/src/test/java/com/njcn/AnalysisServiceStreamTest.java @@ -25,10 +25,10 @@ public class AnalysisServiceStreamTest { 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_DAT_PATH = "C:\\Users\\hongawen\\Desktop\\Event\\192.168.1.239\\PQ_PQLD1_000574_20250910_135244_231.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_DAT_PATH = "C:\\Users\\hongawen\\Desktop\\Event\\192.168.1.238\\PQ_PQLD2_000508_20250910_135244_197.dat"; + 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\\Administrator\\Desktop\\wavedata\\192.168.1.200\\PQ_PQLD1_000020_20251017_140358_193.dat"; + 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\\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"; @@ -105,6 +105,54 @@ public class AnalysisServiceStreamTest { } } + /** + * 测试段信息保留模式的波形分析(新方法) + *

不进行跨段的统一降采样,保留每个段的原始采样率(超过512才降到256)

+ *

跨段的窗口会被丢弃,只计算不跨段的窗口

+ */ + @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("========================================"); + + } + } + /** * 检查文件是否存在 */ diff --git a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/io/ComtradeReader.java b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/io/ComtradeReader.java index 03a52e53..9ab253d9 100644 --- a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/io/ComtradeReader.java +++ b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/io/ComtradeReader.java @@ -782,4 +782,222 @@ public class ComtradeReader { return (float) cfgInfo.getSampleRate() / lineFreq; } + /** + * 读取COMTRADE文件并保留段信息(新方法,不影响原有逻辑) + *

核心改进:

+ *
    + *
  1. 不进行跨段的统一降采样,保留每个段的原始采样率
  2. + *
  3. 只对超过512点/周波的段降采样到256
  4. + *
  5. 记录每个段的边界和采样率信息
  6. + *
  7. 后续可以判断200ms窗口是否跨段,跨段窗口丢弃
  8. + *
+ * + * @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; + } + } + } \ No newline at end of file diff --git a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/model/DataPq.java b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/model/DataPq.java index ca2091a2..e776ce39 100644 --- a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/model/DataPq.java +++ b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/core/model/DataPq.java @@ -46,6 +46,15 @@ public class DataPq { /** 每周波采样点数(对应C代码中的smp_rate) */ private float smpRate; + /** 段数量(用于多段采样率文件) */ + private int segmentCount = 0; + + /** 每个段的起始采样点索引(降采样后的索引) */ + private int[] segmentStartIndices = new int[10]; + + /** 每个段的采样率(点/周波) */ + private float[] segmentSampleRates = new float[10]; + /** 系统频率 (Hz) */ private float f; diff --git a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/ICompareWaveService.java b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/ICompareWaveService.java index 23142b41..d5f9a8e4 100644 --- a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/ICompareWaveService.java +++ b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/ICompareWaveService.java @@ -20,4 +20,21 @@ public interface ICompareWaveService { InputStream targetCfgStream, InputStream targetDatStream, int lineConfig); + /** + * 使用段信息保留模式分析波形(新方法) + *

不进行跨段的统一降采样,保留每个段的原始采样率

+ *

跨段的窗口会被丢弃,只计算不跨段的窗口

+ * + * @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); + } diff --git a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/impl/CompareWaveServiceImpl.java b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/impl/CompareWaveServiceImpl.java index d8250412..7f2951c2 100644 --- a/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/impl/CompareWaveServiceImpl.java +++ b/tools/wave-comtrade/src/main/java/com/njcn/gather/tools/comtrade/comparewave/service/impl/CompareWaveServiceImpl.java @@ -274,4 +274,414 @@ public class CompareWaveServiceImpl implements ICompareWaveService { return results; } + /** + * 使用段信息保留模式分析波形(新方法,不影响原有逻辑) + *

核心改进:

+ *
    + *
  1. 不进行跨段的统一降采样,每个段保留原始采样率(超过512才降到256)
  2. + *
  3. 对齐后判断每个200ms窗口是否跨段
  4. + *
  5. 跨段的窗口丢弃,只计算不跨段的窗口
  6. + *
  7. 每个窗口使用其所在段的实际采样率进行计算
  8. + *
+ * + * @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; + } + } + + /** + * 同步计算两个文件的电能质量参数(确保窗口对应) + *

如果某个窗口在任一文件中跨段,两个文件都丢弃该窗口

+ * + * @param sourceDataBuf 源文件数据缓冲区 + * @param targetDataBuf 目标文件数据缓冲区 + * @param maxCalNum 最大窗口数 + * @param compareWaveDTO 结果对象 + */ + private void calculatePowerQualityWithAlignedWindows( + DataPq sourceDataBuf, DataPq targetDataBuf, int maxCalNum, CompareWaveDTO compareWaveDTO) { + + List sourceResults = new ArrayList<>(); + List 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 calculatePowerQualityWithSegmentFilter( + DataPq dataBuf, int startCalPos, int maxCalNum) { + + List 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; // 默认返回最后一段 + } + }