不采用降低采样率,严格对比对应窗口数据。避免没必要的降采抽点工作
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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("========================================");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查文件是否存在
|
* 检查文件是否存在
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; // 默认返回最后一段
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user