From 0a85b433ba975c331f48cc04acd82bfaa61b5d79 Mon Sep 17 00:00:00 2001
From: hongawen <83944980@qq.com>
Date: Fri, 17 Oct 2025 17:29:47 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=8D=E9=87=87=E7=94=A8=E9=99=8D=E4=BD=8E?=
=?UTF-8?q?=E9=87=87=E6=A0=B7=E7=8E=87=EF=BC=8C=E4=B8=A5=E6=A0=BC=E5=AF=B9?=
=?UTF-8?q?=E6=AF=94=E5=AF=B9=E5=BA=94=E7=AA=97=E5=8F=A3=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E3=80=82=E9=81=BF=E5=85=8D=E6=B2=A1=E5=BF=85=E8=A6=81=E7=9A=84?=
=?UTF-8?q?=E9=99=8D=E9=87=87=E6=8A=BD=E7=82=B9=E5=B7=A5=E4=BD=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../SocketContrastResponseService.java | 2 +-
.../com/njcn/AnalysisServiceStreamTest.java | 56 ++-
.../comparewave/core/io/ComtradeReader.java | 218 ++++++++++
.../comparewave/core/model/DataPq.java | 9 +
.../service/ICompareWaveService.java | 17 +
.../service/impl/CompareWaveServiceImpl.java | 410 ++++++++++++++++++
6 files changed, 707 insertions(+), 5 deletions(-)
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文件并保留段信息(新方法,不影响原有逻辑)
+ * 核心改进:
+ *
+ * - 不进行跨段的统一降采样,保留每个段的原始采样率
+ * - 只对超过512点/周波的段降采样到256
+ * - 记录每个段的边界和采样率信息
+ * - 后续可以判断200ms窗口是否跨段,跨段窗口丢弃
+ *
+ *
+ * @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;
}
+ /**
+ * 使用段信息保留模式分析波形(新方法,不影响原有逻辑)
+ * 核心改进:
+ *
+ * - 不进行跨段的统一降采样,每个段保留原始采样率(超过512才降到256)
+ * - 对齐后判断每个200ms窗口是否跨段
+ * - 跨段的窗口丢弃,只计算不跨段的窗口
+ * - 每个窗口使用其所在段的实际采样率进行计算
+ *
+ *
+ * @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; // 默认返回最后一段
+ }
+
}