From dfbd552218c76a6e5549114b3662349d59dda2a4 Mon Sep 17 00:00:00 2001 From: yexb <553699424@qq.com> Date: Fri, 17 Apr 2026 08:11:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A2=E5=BD=A2=E8=A7=A3=E6=9E=90=E7=9B=B8?= =?UTF-8?q?=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entrance/src/main/resources/application.yml | 7 +- tools/README.md | 11 +- tools/wave-tool/README.md | 205 +++++++++ .../component/WaveFileComponentTestMain.java | 143 ++++++ .../wave/component/WaveVectorComponent.java | 422 ++++++++++++++++++ .../tool/wave/controller/WaveController.java | 24 +- .../wave/pojo/dto/WaveCycleVectorDTO.java | 44 ++ .../tool/wave/pojo/dto/WaveHarmonicDTO.java | 31 ++ .../wave/pojo/dto/WavePhaseDifferenceDTO.java | 27 ++ .../wave/pojo/dto/WavePhaseVectorDTO.java | 48 ++ .../pojo/dto/WaveSequenceUnbalanceDTO.java | 23 + .../wave/pojo/dto/WaveSequenceVectorDTO.java | 31 ++ .../wave/pojo/dto/WaveVectorGroupDTO.java | 36 ++ .../pojo/vo/WaveComtradeVectorResultVO.java | 36 ++ .../gather/tool/wave/service/WaveService.java | 11 + .../wave/service/impl/WaveServiceImpl.java | 110 ++--- 16 files changed, 1135 insertions(+), 74 deletions(-) create mode 100644 tools/wave-tool/README.md create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveVectorComponent.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveCycleVectorDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveHarmonicDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseDifferenceDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseVectorDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceUnbalanceDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceVectorDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveVectorGroupDTO.java create mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeVectorResultVO.java diff --git a/entrance/src/main/resources/application.yml b/entrance/src/main/resources/application.yml index 4d3d925..b9b7cf4 100644 --- a/entrance/src/main/resources/application.yml +++ b/entrance/src/main/resources/application.yml @@ -7,9 +7,12 @@ spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://192.168.1.22:13306/cn_tool?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true + # url: jdbc:mysql://192.168.1.22:13306/cn_tool?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true + # username: root + # password: njcnpqs + url: jdbc:mysql://127.0.0.1:3306/cn_tool?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: root - password: njcnpqs + password: '@#001njcnpqs' initial-size: 5 min-idle: 5 max-active: 50 diff --git a/tools/README.md b/tools/README.md index 54bcf15..37b3426 100644 --- a/tools/README.md +++ b/tools/README.md @@ -32,12 +32,13 @@ tools/ ## wave-tool 的职责 -`wave-tool` 当前提供的能力主要围绕波形文本解析与查看数据组装: +`wave-tool` 当前提供的能力主要围绕波形解析、查看数据组装与图片生成: -- 解析单列幅值波形文本 -- 解析双列时间/幅值波形文本 -- 统计点位范围、均值、点数等摘要信息 -- 按查看场景输出下采样后的点位集合 +- 解析单列/双列文本波形 +- 解析 COMTRADE `cfg/dat` 波形文件 +- 计算 RMS 与特征值 +- 组装前端查看明细 +- 生成瞬时、RMS、治理场景波形图片 从接口层看,当前主要围绕 `/wave/*` 路径提供能力。 diff --git a/tools/wave-tool/README.md b/tools/wave-tool/README.md new file mode 100644 index 0000000..5def628 --- /dev/null +++ b/tools/wave-tool/README.md @@ -0,0 +1,205 @@ +# wave-tool 模块说明 + +## 模块定位 + +`wave-tool` 是 `tools` 下的波形处理模块,当前承担以下能力: + +- 文本波形解析 +- COMTRADE `cfg/dat` 文件解析 +- RMS 波形计算 +- 波形明细组装 +- 波形特征值计算 +- 波形图片生成与上传 + +当前实现以 Spring Bean 方式对外提供能力,并由 `entrance` 模块直接聚合。 + +## 代码结构 + +```text +wave-tool/ +├── src/main/java/com/njcn/gather/tool/wave/ +│ ├── bo +│ ├── component +│ ├── controller +│ ├── dto +│ ├── enums +│ ├── param +│ ├── service +│ ├── utils +│ └── vo +└── temp/ +``` + +说明: + +- `component/WaveFileComponent` + - 负责 COMTRADE 波形文件解析、RMS 计算、特征值计算 +- `component/WavePicComponent` + - 负责波形图片绘制结果合成与上传 +- `service/impl/WaveServiceImpl` + - 负责统一编排文本波形解析与 COMTRADE 解析链路 +- `utils/WaveUtil` + - 负责前端查看明细组装 +- `utils/BitConverter` + - 负责波形二进制字节转换 +- `temp/` + - 保留原始参考代码,不作为正式运行入口 + +## 对外接口 + +### 1. 文本波形解析 + +- 路径:`POST /wave/parse` +- Content-Type:`application/json` + +请求体字段: + +- `waveformText` + - 波形文本内容,必填 +- `separator` + - 分隔符,支持 `AUTO`、`TAB`、`SPACE` 或直接传入具体字符 +- `containsXAxis` + - 是否显式包含 X 轴 +- `xColumnIndex` + - X 轴列下标 +- `yColumnIndex` + - Y 轴列下标 +- `skipHeaderLines` + - 跳过的表头行数 +- `samplingInterval` + - 单列波形采样间隔 +- `maxPointCount` + - 返回最大点位数 + +返回字段: + +- `containsXAxis` +- `sourcePointCount` +- `displayPointCount` +- `ignoredLineCount` +- `sampled` +- `minX` +- `maxX` +- `minY` +- `maxY` +- `averageY` +- `points` + +### 2. COMTRADE 波形解析 + +- 路径:`POST /wave/parseComtrade` +- Content-Type:`multipart/form-data` + +文件字段: + +- `cfgFile` + - COMTRADE 配置文件 +- `datFile` + - COMTRADE 数据文件 + +表单字段: + +- `parseType` + - 解析类型 + - `0`: 高级算法采样率 32-128 + - `1`: 普通展示 + - `2`: App 抽点 + - `3`: 原始波形 +- `ptType` + - PT 接线方式 + - `0`: 星形 + - `1`: 三角 + - `2`: 开口三角 +- `pt` + - PT 变比 +- `ct` + - CT 变比 +- `monitorName` + - 测点名称 +- `calculateRms` + - 是否计算 RMS +- `buildDetails` + - 是否组装前端查看明细 +- `calculateEigenvalue` + - 是否计算特征值 +- `dynamicThreshold` + - 特征值算法是否使用浮动门槛 +- `generateInstantImage` + - 是否生成瞬时波形图 +- `generateRmsImage` + - 是否生成 RMS 波形图 +- `generateInstantZlImage` + - 是否生成治理场景瞬时波形图 +- `generateRmsZlImage` + - 是否生成治理场景 RMS 波形图 + +返回字段: + +- `waveData` + - 波形基础数据,包含 `cfg`、原始波形、RMS、标题等 +- `waveDataDetails` + - 前端查看明细 +- `eigenvalues` + - 特征值结果 +- `instantImagePath` + - 瞬时波形图路径 +- `rmsImagePath` + - RMS 波形图路径 +- `instantZlImagePath` + - 治理场景瞬时波形图路径 +- `rmsZlImagePath` + - 治理场景 RMS 波形图路径 + +## 使用示例 + +### 文本波形 + +```json +{ + "waveformText": "0,220\n1,221\n2,219", + "separator": "AUTO", + "containsXAxis": true, + "maxPointCount": 2000 +} +``` + +### COMTRADE 波形 + +```bash +curl -X POST "http://localhost:8080/wave/parseComtrade" \ + -F "cfgFile=@D:/data/test.cfg" \ + -F "datFile=@D:/data/test.dat" \ + -F "parseType=1" \ + -F "ptType=0" \ + -F "pt=1" \ + -F "ct=1" \ + -F "monitorName=测试测点" \ + -F "calculateRms=true" \ + -F "buildDetails=true" \ + -F "calculateEigenvalue=true" \ + -F "dynamicThreshold=true" \ + -F "generateInstantImage=true" \ + -F "generateRmsImage=true" +``` + +## 依赖说明 + +模块自身直接声明的依赖仍然较轻,核心包括: + +- `com.njcn:njcn-common` +- `com.njcn:spingboot2.3.12` + +但当前图片链路还依赖私有组件能力: + +- `DrawPicUtil` +- `FileStorageUtil` +- `OssPath` +- `PicCommonData` + +因此,若要真实运行图片生成功能,需要确保这些依赖在实际环境中可用。 + +## 当前限制 + +- 当前未执行 `mvn` 编译或测试验证 +- `temp/` 中仍保留原始参考代码,后续如确认正式模块稳定,可再决定是否清理 +- 图片生成依赖私有绘图与文件存储组件,当前仅完成代码接入,未做运行态验证 diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java index 02339c9..f81c5ec 100644 --- a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java @@ -1,7 +1,13 @@ package com.njcn.gather.tool.wave.component; import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveCycleVectorDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveHarmonicDTO; +import com.njcn.gather.tool.wave.pojo.dto.WavePhaseVectorDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO; import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO; +import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeVectorResultVO; import java.io.InputStream; import java.nio.file.Files; @@ -20,6 +26,8 @@ public final class WaveFileComponentTestMain { private static final String DEFAULT_DAT_PATH = "D:\\00-B7-8D-00-E4-09\\PQMonitor_PQM2_006970_20260320_175033_734.DAT"; /** 默认解析类型,1 表示普通展示。 */ private static final int DEFAULT_PARSE_TYPE = 1; + /** 向量调试固定使用原始波形。 */ + private static final int VECTOR_PARSE_TYPE = 3; /** 默认使用浮动门槛计算特征值。 */ private static final boolean DEFAULT_DYNAMIC_THRESHOLD = true; @@ -53,6 +61,141 @@ public final class WaveFileComponentTestMain { List eigenvalues = waveFileComponent.getEigenvalue(validWaveDataDTO, DEFAULT_DYNAMIC_THRESHOLD); printEigenvalueResult(eigenvalues); } + + runParseComtradeVectorTest(cfgPath, datPath); + } + + /** + * 调试 parseComtradeVector 逻辑。 + */ + private static void runParseComtradeVectorTest(Path cfgPath, Path datPath) throws Exception { + System.out.println("=== parseComtradeVector 结果 ==="); + WaveFileComponent waveFileComponent = new WaveFileComponent(); + WaveVectorComponent waveVectorComponent = new WaveVectorComponent(); + WaveComtradeParseParam param = new WaveComtradeParseParam(); + param.setParseType(VECTOR_PARSE_TYPE); + param.setPtType(0); + param.setPt(1D); + param.setCt(1D); + param.setMonitorName("main方法调试测点"); + + try (InputStream cfgStream = Files.newInputStream(cfgPath); + InputStream datStream = Files.newInputStream(datPath)) { + WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgStream, datStream, VECTOR_PARSE_TYPE); + applyWaveMetadata(waveDataDTO, param); + + List vectorGroups = waveVectorComponent.calculateVectors(waveDataDTO); + WaveComtradeVectorResultVO result = new WaveComtradeVectorResultVO(); + result.setMonitorName(waveDataDTO.getMonitorName()); + result.setTime(waveDataDTO.getTime()); + result.setSamplePerCycle(waveDataDTO.getComtradeCfgDTO() == null ? null : waveDataDTO.getComtradeCfgDTO().getFinalSampleRate()); + result.setCycleCount(resolveCycleCount(vectorGroups)); + result.setVectorGroups(vectorGroups); + printParseComtradeVectorResult(result); + } + } + + /** + * 给向量调试结果补齐 PT/CT/测点信息。 + */ + private static void applyWaveMetadata(WaveDataDTO waveDataDTO, WaveComtradeParseParam param) { + waveDataDTO.setPt(param.getPt() == null || param.getPt() <= 0 ? 1D : param.getPt()); + waveDataDTO.setCt(param.getCt() == null || param.getCt() <= 0 ? 1D : param.getCt()); + waveDataDTO.setPtType(param.getPtType() == null ? 0 : param.getPtType()); + waveDataDTO.setMonitorName(param.getMonitorName() == null || param.getMonitorName().trim().isEmpty() + ? "未命名测点" + : param.getMonitorName().trim()); + } + + /** + * 打印 parseComtradeVector 的关键摘要。 + */ + private static void printParseComtradeVectorResult(WaveComtradeVectorResultVO result) { + System.out.println("测点名称: " + result.getMonitorName()); + System.out.println("事件时间: " + result.getTime()); + System.out.println("每周波采样点数: " + result.getSamplePerCycle()); + System.out.println("可计算周波数: " + result.getCycleCount()); + System.out.println("分组数量: " + sizeOf(result.getVectorGroups())); + if (result.getVectorGroups() == null || result.getVectorGroups().isEmpty()) { + return; + } + + for (WaveVectorGroupDTO group : result.getVectorGroups()) { + System.out.println("-- 分组: channelName=" + group.getChannelName() + + ", unit=" + group.getUnit() + + ", phaseCount=" + group.getPhaseCount() + + ", phaseNames=" + group.getPhaseNames()); + if (group.getVectorSeries() == null || group.getVectorSeries().isEmpty()) { + continue; + } + WaveCycleVectorDTO firstCycle = group.getVectorSeries().get(0); + System.out.println(" 首周波: cycleIndex=" + firstCycle.getCycleIndex() + ", time=" + firstCycle.getTime()); + printPhaseVectorSummary(firstCycle.getPhaseVectors()); + if (firstCycle.getPositiveSequence() != null) { + System.out.println(" 正序: amplitude=" + firstCycle.getPositiveSequence().getAmplitude() + + ", rms=" + firstCycle.getPositiveSequence().getRms() + + ", phaseAngle=" + firstCycle.getPositiveSequence().getPhaseAngle()); + } + if (firstCycle.getNegativeSequence() != null) { + System.out.println(" 负序: amplitude=" + firstCycle.getNegativeSequence().getAmplitude() + + ", rms=" + firstCycle.getNegativeSequence().getRms() + + ", phaseAngle=" + firstCycle.getNegativeSequence().getPhaseAngle()); + } + if (firstCycle.getZeroSequence() != null) { + System.out.println(" 零序: amplitude=" + firstCycle.getZeroSequence().getAmplitude() + + ", rms=" + firstCycle.getZeroSequence().getRms() + + ", phaseAngle=" + firstCycle.getZeroSequence().getPhaseAngle()); + } + if (firstCycle.getUnbalance() != null) { + System.out.println(" 不平衡度: negative=" + firstCycle.getUnbalance().getNegativeUnbalanceRate() + + ", zero=" + firstCycle.getUnbalance().getZeroUnbalanceRate()); + } + } + } + + /** + * 打印单相电能质量结果摘要。 + */ + private static void printPhaseVectorSummary(List phaseVectors) { + if (phaseVectors == null || phaseVectors.isEmpty()) { + return; + } + for (WavePhaseVectorDTO phaseVector : phaseVectors) { + System.out.println(" 相别=" + phaseVector.getPhaseName() + + ", totalRms=" + phaseVector.getTotalRms() + + ", fundamentalAmplitude=" + phaseVector.getFundamentalAmplitude() + + ", fundamentalRms=" + phaseVector.getFundamentalRms() + + ", fundamentalPhaseAngle=" + phaseVector.getFundamentalPhaseAngle() + + ", harmonicDistortionRate=" + phaseVector.getHarmonicDistortionRate()); + printHarmonicSummary("电压谐波", phaseVector.getHarmonicVoltageContentRates()); + printHarmonicSummary("电流谐波", phaseVector.getHarmonicCurrentAmplitudes()); + } + } + + /** + * 打印前几个谐波结果,避免控制台过长。 + */ + private static void printHarmonicSummary(String label, List harmonics) { + if (harmonics == null || harmonics.isEmpty()) { + return; + } + int limit = Math.min(3, harmonics.size()); + for (int i = 0; i < limit; i++) { + WaveHarmonicDTO harmonic = harmonics.get(i); + System.out.println(" " + label + "-第" + harmonic.getHarmonicOrder() + "次: amplitude=" + harmonic.getAmplitude() + + ", rms=" + harmonic.getRms() + + ", rate=" + harmonic.getRate()); + } + } + + /** + * 解析向量结果中的可计算周波数。 + */ + private static Integer resolveCycleCount(List vectorGroups) { + if (vectorGroups == null || vectorGroups.isEmpty() || vectorGroups.get(0).getVectorSeries() == null) { + return 0; + } + return vectorGroups.get(0).getVectorSeries().size(); } /** diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveVectorComponent.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveVectorComponent.java new file mode 100644 index 0000000..15e8396 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveVectorComponent.java @@ -0,0 +1,422 @@ +package com.njcn.gather.tool.wave.component; + +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.gather.tool.wave.pojo.dto.WaveCycleVectorDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveHarmonicDTO; +import com.njcn.gather.tool.wave.pojo.dto.WavePhaseVectorDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveSequenceUnbalanceDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveSequenceVectorDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO; +import com.njcn.gather.tool.wave.pojo.enums.WaveFileResponseEnum; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 波形向量计算组件。 + */ +@Component +public class WaveVectorComponent { + + /** 最低谐波次数。 */ + private static final int MIN_HARMONIC_ORDER = 1; + /** 最高谐波次数。 */ + private static final int MAX_HARMONIC_ORDER = 50; + + /** + * 基于原始波形按周波计算向量结果。 + */ + public List calculateVectors(WaveDataDTO waveDataDTO) { + if (waveDataDTO == null || waveDataDTO.getComtradeCfgDTO() == null) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + List> listWaveData = waveDataDTO.getListWaveData(); + Integer samplePerCycle = waveDataDTO.getComtradeCfgDTO().getFinalSampleRate(); + if (listWaveData == null || listWaveData.isEmpty() || samplePerCycle == null || samplePerCycle <= 0 || listWaveData.size() < samplePerCycle) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + + int phaseCount = waveDataDTO.getIPhasic() == null || waveDataDTO.getIPhasic() <= 0 ? 1 : waveDataDTO.getIPhasic(); + int totalValueColumnCount = listWaveData.get(0).size() - 1; + if (totalValueColumnCount < phaseCount) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + + int groupCount = totalValueColumnCount / phaseCount; + int cycleCount = listWaveData.size() / samplePerCycle; + List vectorGroups = new ArrayList<>(); + for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) { + int titleIndex = groupIndex * phaseCount + 1; + WaveVectorGroupDTO groupDTO = new WaveVectorGroupDTO(); + groupDTO.setChannelName(resolveChannelName(waveDataDTO.getChannelNames(), titleIndex, groupIndex)); + groupDTO.setUnit(resolveVectorUnit(waveDataDTO, titleIndex)); + groupDTO.setPhaseCount(phaseCount); + groupDTO.setPhaseNames(resolvePhaseNames(waveDataDTO, titleIndex, phaseCount)); + groupDTO.setVectorSeries(buildVectorSeries(waveDataDTO, groupIndex, phaseCount, samplePerCycle, cycleCount)); + vectorGroups.add(groupDTO); + } + return vectorGroups; + } + + /** + * 构建单组通道的周波结果序列。 + */ + private List buildVectorSeries(WaveDataDTO waveDataDTO, int groupIndex, int phaseCount, + int samplePerCycle, int cycleCount) { + List vectorSeries = new ArrayList<>(); + int titleIndex = groupIndex * phaseCount + 1; + List phaseNames = resolvePhaseNames(waveDataDTO, titleIndex, phaseCount); + float ratio = resolveVectorRatio(waveDataDTO, titleIndex); + for (int cycleIndex = 0; cycleIndex < cycleCount; cycleIndex++) { + int startIndex = cycleIndex * samplePerCycle; + vectorSeries.add(buildCycleVector(waveDataDTO.getListWaveData(), phaseNames, groupIndex, phaseCount, + samplePerCycle, startIndex, cycleIndex, ratio, resolveVectorUnit(waveDataDTO, titleIndex))); + } + return vectorSeries; + } + + /** + * 计算单个周波的电能质量结果。 + */ + private WaveCycleVectorDTO buildCycleVector(List> listWaveData, List phaseNames, int groupIndex, + int phaseCount, int samplePerCycle, int startIndex, int cycleIndex, + float ratio, String unit) { + WaveCycleVectorDTO cycleVectorDTO = new WaveCycleVectorDTO(); + cycleVectorDTO.setCycleIndex(cycleIndex); + cycleVectorDTO.setTime(resolveCycleTime(listWaveData, startIndex, samplePerCycle)); + + List phaseVectors = new ArrayList<>(); + Map spectrumMap = new HashMap<>(); + for (int phaseIndex = 0; phaseIndex < phaseCount; phaseIndex++) { + int columnIndex = 1 + groupIndex * phaseCount + phaseIndex; + HarmonicSpectrum spectrum = calculateSpectrum(listWaveData, startIndex, samplePerCycle, columnIndex, ratio); + String phaseName = phaseNames.get(phaseIndex); + phaseVectors.add(buildPhaseVector(phaseName, spectrum, unit)); + spectrumMap.put(phaseName, spectrum); + } + cycleVectorDTO.setPhaseVectors(phaseVectors); + + if (phaseCount >= 3) { + HarmonicSpectrum phaseA = findSpectrum(spectrumMap, "A"); + HarmonicSpectrum phaseB = findSpectrum(spectrumMap, "B"); + HarmonicSpectrum phaseC = findSpectrum(spectrumMap, "C"); + ComplexValue alpha = new ComplexValue(-0.5D, Math.sqrt(3D) / 2D); + ComplexValue alphaSquare = new ComplexValue(-0.5D, -Math.sqrt(3D) / 2D); + ComplexValue positive = phaseA.fundamental.add(alpha.multiply(phaseB.fundamental)).add(alphaSquare.multiply(phaseC.fundamental)).divide(3D); + ComplexValue negative = phaseA.fundamental.add(alphaSquare.multiply(phaseB.fundamental)).add(alpha.multiply(phaseC.fundamental)).divide(3D); + ComplexValue zero = phaseA.fundamental.add(phaseB.fundamental).add(phaseC.fundamental).divide(3D); + cycleVectorDTO.setPositiveSequence(buildSequenceVector("正序", positive)); + cycleVectorDTO.setNegativeSequence(buildSequenceVector("负序", negative)); + cycleVectorDTO.setZeroSequence(buildSequenceVector("零序", zero)); + cycleVectorDTO.setUnbalance(buildUnbalance(positive, negative, zero)); + } + return cycleVectorDTO; + } + + /** + * 组装单相结果。 + */ + private WavePhaseVectorDTO buildPhaseVector(String phaseName, HarmonicSpectrum spectrum, String unit) { + WavePhaseVectorDTO dto = new WavePhaseVectorDTO(); + dto.setPhaseName(phaseName); + dto.setTotalRms(roundValue(spectrum.totalRms)); + dto.setFundamentalAmplitude(roundValue(spectrum.fundamental.magnitude())); + dto.setFundamentalRms(roundValue(spectrum.fundamental.magnitude() / Math.sqrt(2D))); + dto.setFundamentalPhaseAngle(roundValue(resolveDisplayAngle(spectrum.fundamental))); + dto.setHarmonicDistortionRate(roundValue(calculateThdRate(spectrum))); + if ("kV".equalsIgnoreCase(unit)) { + dto.setHarmonicVoltageContentRates(buildHarmonicList(spectrum, true)); + } else { + dto.setHarmonicCurrentAmplitudes(buildHarmonicList(spectrum, false)); + } + return dto; + } + + /** + * 计算单相一个周波的频谱信息。 + */ + private HarmonicSpectrum calculateSpectrum(List> listWaveData, int startIndex, int samplePerCycle, + int columnIndex, float ratio) { + double totalSquare = 0D; + ComplexValue[] harmonics = new ComplexValue[MAX_HARMONIC_ORDER + 1]; + for (int order = MIN_HARMONIC_ORDER; order <= MAX_HARMONIC_ORDER; order++) { + double real = 0D; + double imag = 0D; + for (int i = 0; i < samplePerCycle; i++) { + List row = listWaveData.get(startIndex + i); + if (row == null || row.size() <= columnIndex) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + double sample = row.get(columnIndex) * ratio; + if (order == 1) { + totalSquare += sample * sample; + } + double angle = 2D * Math.PI * order * i / samplePerCycle; + real += sample * Math.cos(angle); + imag -= sample * Math.sin(angle); + } + harmonics[order] = new ComplexValue(2D * real / samplePerCycle, 2D * imag / samplePerCycle); + } + HarmonicSpectrum spectrum = new HarmonicSpectrum(); + spectrum.harmonics = harmonics; + spectrum.fundamental = harmonics[1]; + spectrum.totalRms = Math.sqrt(totalSquare / samplePerCycle); + return spectrum; + } + + /** + * 构建谐波列表。 + */ + private List buildHarmonicList(HarmonicSpectrum spectrum, boolean buildRate) { + List harmonics = new ArrayList<>(); + double fundamentalAmplitude = spectrum.fundamental == null ? 0D : spectrum.fundamental.magnitude(); + for (int order = 2; order <= MAX_HARMONIC_ORDER; order++) { + ComplexValue value = spectrum.harmonics[order]; + if (value == null) { + continue; + } + WaveHarmonicDTO harmonicDTO = new WaveHarmonicDTO(); + harmonicDTO.setHarmonicOrder(order); + harmonicDTO.setAmplitude(roundValue(value.magnitude())); + harmonicDTO.setRms(roundValue(value.magnitude() / Math.sqrt(2D))); + if (buildRate) { + harmonicDTO.setRate(roundValue(fundamentalAmplitude == 0D ? 0D : value.magnitude() / fundamentalAmplitude * 100D)); + } + harmonics.add(harmonicDTO); + } + return harmonics; + } + + /** + * 计算 THD。 + */ + private double calculateThdRate(HarmonicSpectrum spectrum) { + double sum = 0D; + for (int order = 2; order <= MAX_HARMONIC_ORDER; order++) { + ComplexValue value = spectrum.harmonics[order]; + if (value != null) { + double rms = value.magnitude() / Math.sqrt(2D); + sum += rms * rms; + } + } + double fundamentalRms = spectrum.fundamental == null ? 0D : spectrum.fundamental.magnitude() / Math.sqrt(2D); + if (fundamentalRms == 0D) { + return 0D; + } + return Math.sqrt(sum) / fundamentalRms * 100D; + } + + /** + * 组装序分量结果。 + */ + private WaveSequenceVectorDTO buildSequenceVector(String sequenceName, ComplexValue value) { + WaveSequenceVectorDTO dto = new WaveSequenceVectorDTO(); + dto.setSequenceName(sequenceName); + dto.setAmplitude(roundValue(value.magnitude())); + dto.setRms(roundValue(value.magnitude() / Math.sqrt(2D))); + dto.setPhaseAngle(roundValue(resolveDisplayAngle(value))); + return dto; + } + + /** + * 组装不平衡度。 + */ + private WaveSequenceUnbalanceDTO buildUnbalance(ComplexValue positive, ComplexValue negative, ComplexValue zero) { + WaveSequenceUnbalanceDTO dto = new WaveSequenceUnbalanceDTO(); + double positiveMagnitude = positive.magnitude(); + if (positiveMagnitude == 0D) { + dto.setNegativeUnbalanceRate(0F); + dto.setZeroUnbalanceRate(0F); + return dto; + } + dto.setNegativeUnbalanceRate(roundValue(negative.magnitude() / positiveMagnitude * 100D)); + dto.setZeroUnbalanceRate(roundValue(zero.magnitude() / positiveMagnitude * 100D)); + return dto; + } + + /** + * 查找频谱。 + */ + private HarmonicSpectrum findSpectrum(Map spectrumMap, String phaseFlag) { + for (Map.Entry entry : spectrumMap.entrySet()) { + if (StrUtil.containsIgnoreCase(entry.getKey(), phaseFlag)) { + return entry.getValue(); + } + } + return HarmonicSpectrum.empty(); + } + + /** + * 解析通道名称。 + */ + private String resolveChannelName(List channelNames, int titleIndex, int groupIndex) { + if (channelNames != null && channelNames.size() > titleIndex) { + return channelNames.get(titleIndex); + } + return "CH" + (groupIndex + 1); + } + + /** + * 解析相别名称。 + */ + private List resolvePhaseNames(WaveDataDTO waveDataDTO, int titleIndex, int phaseCount) { + List phaseNames = new ArrayList<>(); + List waveTitle = waveDataDTO.getWaveTitle(); + for (int phaseIndex = 0; phaseIndex < phaseCount; phaseIndex++) { + if (waveTitle != null && waveTitle.size() > titleIndex + phaseIndex) { + String title = waveTitle.get(titleIndex + phaseIndex); + phaseNames.add(title.length() > 1 ? title.substring(1) : title); + } else { + phaseNames.add(defaultPhaseName(phaseIndex)); + } + } + return phaseNames; + } + + /** + * 解析向量单位。 + */ + private String resolveVectorUnit(WaveDataDTO waveDataDTO, int titleIndex) { + if (waveDataDTO.getWaveTitle() != null && waveDataDTO.getWaveTitle().size() > titleIndex + && StrUtil.startWithIgnoreCase(waveDataDTO.getWaveTitle().get(titleIndex), "U")) { + return "kV"; + } + return "A"; + } + + /** + * 解析向量比例。 + */ + private float resolveVectorRatio(WaveDataDTO waveDataDTO, int titleIndex) { + if (waveDataDTO.getWaveTitle() != null && waveDataDTO.getWaveTitle().size() > titleIndex + && StrUtil.startWithIgnoreCase(waveDataDTO.getWaveTitle().get(titleIndex), "U")) { + return waveDataDTO.getPt() == null ? 1F : waveDataDTO.getPt().floatValue() / 1000F; + } + return waveDataDTO.getCt() == null ? 1F : waveDataDTO.getCt().floatValue(); + } + + /** + * 取当前周波中点时刻。 + */ + private Float resolveCycleTime(List> listWaveData, int startIndex, int samplePerCycle) { + int middleIndex = Math.min(startIndex + samplePerCycle / 2, listWaveData.size() - 1); + List row = listWaveData.get(middleIndex); + if (row == null || row.isEmpty()) { + throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID); + } + return roundValue(row.get(0)); + } + + /** + * 傅里叶相角换算成正弦基准显示角度。 + */ + private double resolveDisplayAngle(ComplexValue vectorValue) { + return normalizeAngle(Math.toDegrees(vectorValue.angle()) + 90D); + } + + /** + * 归一化角度。 + */ + private double normalizeAngle(double angle) { + double normalizedAngle = angle; + while (normalizedAngle > 180D) { + normalizedAngle -= 360D; + } + while (normalizedAngle <= -180D) { + normalizedAngle += 360D; + } + return normalizedAngle; + } + + /** + * 统一保留 4 位小数。 + */ + private Float roundValue(double value) { + return BigDecimal.valueOf(value).setScale(4, RoundingMode.HALF_UP).floatValue(); + } + + /** + * 默认相名。 + */ + private String defaultPhaseName(int phaseIndex) { + switch (phaseIndex) { + case 0: + return "A相"; + case 1: + return "B相"; + case 2: + return "C相"; + default: + return "相" + (phaseIndex + 1); + } + } + + /** + * 频谱对象。 + */ + private static final class HarmonicSpectrum { + /** 各次谐波复向量。 */ + private ComplexValue[] harmonics; + /** 基波。 */ + private ComplexValue fundamental; + /** 总有效值。 */ + private double totalRms; + + private static HarmonicSpectrum empty() { + HarmonicSpectrum spectrum = new HarmonicSpectrum(); + spectrum.harmonics = new ComplexValue[MAX_HARMONIC_ORDER + 1]; + spectrum.fundamental = ComplexValue.zero(); + spectrum.totalRms = 0D; + return spectrum; + } + } + + /** + * 复向量。 + */ + private static final class ComplexValue { + /** 实部。 */ + private final double real; + /** 虚部。 */ + private final double imag; + + private ComplexValue(double real, double imag) { + this.real = real; + this.imag = imag; + } + + private static ComplexValue zero() { + return new ComplexValue(0D, 0D); + } + + private ComplexValue add(ComplexValue other) { + return new ComplexValue(this.real + other.real, this.imag + other.imag); + } + + private ComplexValue multiply(ComplexValue other) { + return new ComplexValue( + this.real * other.real - this.imag * other.imag, + this.real * other.imag + this.imag * other.real + ); + } + + private ComplexValue divide(double divisor) { + return new ComplexValue(this.real / divisor, this.imag / divisor); + } + + private double magnitude() { + return Math.hypot(this.real, this.imag); + } + + private double angle() { + return Math.atan2(this.imag, this.real); + } + } +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/controller/WaveController.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/controller/WaveController.java index 73ba8bd..a2c0900 100644 --- a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/controller/WaveController.java +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/controller/WaveController.java @@ -7,9 +7,10 @@ import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.LogUtil; import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam; import com.njcn.gather.tool.wave.pojo.param.WaveParseParam; -import com.njcn.gather.tool.wave.service.WaveService; import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeVectorResultVO; import com.njcn.gather.tool.wave.pojo.vo.WaveParseResultVO; +import com.njcn.gather.tool.wave.service.WaveService; import com.njcn.web.controller.BaseController; import com.njcn.web.utils.HttpResultUtil; import io.swagger.annotations.Api; @@ -77,4 +78,25 @@ public class WaveController extends BaseController { WaveComtradeResultVO result = waveService.parseComtrade(cfgFile.getInputStream(), datFile.getInputStream(), param); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } + + /** + * 解析 COMTRADE 原始波形并计算向量结果。 + * + * @param cfgFile cfg 文件 + * @param datFile dat 文件 + * @param param 解析参数 + * @return 向量结果 + * @throws IOException 读取上传文件流失败时抛出 + */ + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("解析 COMTRADE 向量") + @PostMapping("/parseComtradeVector") + public HttpResult parseComtradeVector(@RequestParam("cfgFile") MultipartFile cfgFile, + @RequestParam("datFile") MultipartFile datFile, + @ModelAttribute WaveComtradeParseParam param) throws IOException { + String methodDescribe = getMethodDescribe("parseComtradeVector"); + LogUtil.njcnDebug(log, "{},开始解析 COMTRADE 向量", methodDescribe); + WaveComtradeVectorResultVO result = waveService.parseComtradeVector(cfgFile.getInputStream(), datFile.getInputStream(), param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } } diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveCycleVectorDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveCycleVectorDTO.java new file mode 100644 index 0000000..90ef079 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveCycleVectorDTO.java @@ -0,0 +1,44 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 单个周波的电能质量结果。 + */ +@Data +@ApiModel("单个周波的电能质量结果") +public class WaveCycleVectorDTO implements Serializable { + + /** 周波序号,从 0 开始。 */ + @ApiModelProperty("周波序号,从 0 开始") + private Integer cycleIndex; + + /** 当前周波中点时刻,单位毫秒。 */ + @ApiModelProperty("当前周波中点时刻,单位毫秒") + private Float time; + + /** 各相结果。 */ + @ApiModelProperty("各相结果") + private List phaseVectors; + + /** 正序分量。 */ + @ApiModelProperty("正序分量") + private WaveSequenceVectorDTO positiveSequence; + + /** 负序分量。 */ + @ApiModelProperty("负序分量") + private WaveSequenceVectorDTO negativeSequence; + + /** 零序分量。 */ + @ApiModelProperty("零序分量") + private WaveSequenceVectorDTO zeroSequence; + + /** 不平衡度。 */ + @ApiModelProperty("不平衡度") + private WaveSequenceUnbalanceDTO unbalance; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveHarmonicDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveHarmonicDTO.java new file mode 100644 index 0000000..6219ea6 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveHarmonicDTO.java @@ -0,0 +1,31 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 单次谐波结果。 + */ +@Data +@ApiModel("单次谐波结果") +public class WaveHarmonicDTO implements Serializable { + + /** 谐波次数。 */ + @ApiModelProperty("谐波次数") + private Integer harmonicOrder; + + /** 谐波幅值。 */ + @ApiModelProperty("谐波幅值") + private Float amplitude; + + /** 谐波有效值。 */ + @ApiModelProperty("谐波有效值") + private Float rms; + + /** 谐波占基波比率,百分比。 */ + @ApiModelProperty("谐波占基波比率,百分比") + private Float rate; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseDifferenceDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseDifferenceDTO.java new file mode 100644 index 0000000..9560c8e --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseDifferenceDTO.java @@ -0,0 +1,27 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 相位差结果。 + */ +@Data +@ApiModel("相位差结果") +public class WavePhaseDifferenceDTO implements Serializable { + + /** A 相与 B 相相位差。 */ + @ApiModelProperty("A 相与 B 相相位差") + private Float ab; + + /** B 相与 C 相相位差。 */ + @ApiModelProperty("B 相与 C 相相位差") + private Float bc; + + /** C 相与 A 相相位差。 */ + @ApiModelProperty("C 相与 A 相相位差") + private Float ca; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseVectorDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseVectorDTO.java new file mode 100644 index 0000000..4eae215 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WavePhaseVectorDTO.java @@ -0,0 +1,48 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 单相电能质量结果。 + */ +@Data +@ApiModel("单相电能质量结果") +public class WavePhaseVectorDTO implements Serializable { + + /** 相别名称。 */ + @ApiModelProperty("相别名称") + private String phaseName; + + /** 总有效值。 */ + @ApiModelProperty("总有效值") + private Float totalRms; + + /** 基波幅值。 */ + @ApiModelProperty("基波幅值") + private Float fundamentalAmplitude; + + /** 基波有效值。 */ + @ApiModelProperty("基波有效值") + private Float fundamentalRms; + + /** 基波相角,单位度。 */ + @ApiModelProperty("基波相角,单位度") + private Float fundamentalPhaseAngle; + + /** 谐波电压含有率。 */ + @ApiModelProperty("谐波电压含有率") + private List harmonicVoltageContentRates; + + /** 谐波电流幅值。 */ + @ApiModelProperty("谐波电流幅值") + private List harmonicCurrentAmplitudes; + + /** 谐波畸变率,百分比。 */ + @ApiModelProperty("谐波畸变率,百分比") + private Float harmonicDistortionRate; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceUnbalanceDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceUnbalanceDTO.java new file mode 100644 index 0000000..5d8c374 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceUnbalanceDTO.java @@ -0,0 +1,23 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 序分量不平衡度结果。 + */ +@Data +@ApiModel("序分量不平衡度结果") +public class WaveSequenceUnbalanceDTO implements Serializable { + + /** 负序不平衡度,百分比。 */ + @ApiModelProperty("负序不平衡度,百分比") + private Float negativeUnbalanceRate; + + /** 零序不平衡度,百分比。 */ + @ApiModelProperty("零序不平衡度,百分比") + private Float zeroUnbalanceRate; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceVectorDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceVectorDTO.java new file mode 100644 index 0000000..fbd677c --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveSequenceVectorDTO.java @@ -0,0 +1,31 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 序分量结果。 + */ +@Data +@ApiModel("序分量结果") +public class WaveSequenceVectorDTO implements Serializable { + + /** 序分量名称。 */ + @ApiModelProperty("序分量名称") + private String sequenceName; + + /** 幅值。 */ + @ApiModelProperty("幅值") + private Float amplitude; + + /** 有效值。 */ + @ApiModelProperty("有效值") + private Float rms; + + /** 相角,单位度。 */ + @ApiModelProperty("相角,单位度") + private Float phaseAngle; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveVectorGroupDTO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveVectorGroupDTO.java new file mode 100644 index 0000000..14b1063 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/dto/WaveVectorGroupDTO.java @@ -0,0 +1,36 @@ +package com.njcn.gather.tool.wave.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 单组通道的电能质量结果。 + */ +@Data +@ApiModel("单组通道的电能质量结果") +public class WaveVectorGroupDTO implements Serializable { + + /** 通道名称。 */ + @ApiModelProperty("通道名称") + private String channelName; + + /** 单位。 */ + @ApiModelProperty("单位") + private String unit; + + /** 相别数量。 */ + @ApiModelProperty("相别数量") + private Integer phaseCount; + + /** 当前组的相别名称。 */ + @ApiModelProperty("当前组的相别名称") + private List phaseNames; + + /** 当前组的周波结果序列。 */ + @ApiModelProperty("当前组的周波结果序列") + private List vectorSeries; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeVectorResultVO.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeVectorResultVO.java new file mode 100644 index 0000000..1b56cd2 --- /dev/null +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/pojo/vo/WaveComtradeVectorResultVO.java @@ -0,0 +1,36 @@ +package com.njcn.gather.tool.wave.pojo.vo; + +import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * COMTRADE 向量计算结果。 + */ +@Data +@ApiModel("COMTRADE 向量计算结果") +public class WaveComtradeVectorResultVO { + + /** 测点名称。 */ + @ApiModelProperty("测点名称") + private String monitorName; + + /** 事件发生时刻。 */ + @ApiModelProperty("事件发生时刻") + private String time; + + /** 每周波采样点数。 */ + @ApiModelProperty("每周波采样点数") + private Integer samplePerCycle; + + /** 可计算周波数。 */ + @ApiModelProperty("可计算周波数") + private Integer cycleCount; + + /** 向量分组结果。 */ + @ApiModelProperty("向量分组结果") + private List vectorGroups; +} diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/WaveService.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/WaveService.java index 22c1333..de5b069 100644 --- a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/WaveService.java +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/WaveService.java @@ -3,6 +3,7 @@ package com.njcn.gather.tool.wave.service; import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam; import com.njcn.gather.tool.wave.pojo.param.WaveParseParam; import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeVectorResultVO; import com.njcn.gather.tool.wave.pojo.vo.WaveParseResultVO; import java.io.InputStream; @@ -29,4 +30,14 @@ public interface WaveService { * @return COMTRADE 解析结果 */ WaveComtradeResultVO parseComtrade(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param); + + /** + * 基于 COMTRADE 原始波形按周波计算向量结果。 + * + * @param cfgStream cfg 输入流 + * @param datStream dat 输入流 + * @param param 解析参数 + * @return 向量计算结果 + */ + WaveComtradeVectorResultVO parseComtradeVector(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param); } diff --git a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/impl/WaveServiceImpl.java b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/impl/WaveServiceImpl.java index e00da9a..743e8cf 100644 --- a/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/impl/WaveServiceImpl.java +++ b/tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/service/impl/WaveServiceImpl.java @@ -4,16 +4,19 @@ import cn.hutool.core.util.StrUtil; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.exception.BusinessException; import com.njcn.gather.tool.wave.component.WaveFileComponent; +import com.njcn.gather.tool.wave.component.WaveVectorComponent; import com.njcn.gather.tool.wave.pojo.bo.WaveDataDetail; import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO; import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO; +import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO; import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam; import com.njcn.gather.tool.wave.pojo.param.WaveParseParam; -import com.njcn.gather.tool.wave.service.WaveService; -import com.njcn.gather.tool.wave.utils.WaveUtil; import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeVectorResultVO; import com.njcn.gather.tool.wave.pojo.vo.WaveParseResultVO; import com.njcn.gather.tool.wave.pojo.vo.WavePointVO; +import com.njcn.gather.tool.wave.service.WaveService; +import com.njcn.gather.tool.wave.utils.WaveUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -40,6 +43,8 @@ public class WaveServiceImpl implements WaveService { private static final String AUTO_SEPARATOR = "AUTO"; /** COMTRADE 默认解析类型。 */ private static final int DEFAULT_PARSE_TYPE = 1; + /** 向量计算固定使用原始波形解析类型。 */ + private static final int RAW_WAVE_PARSE_TYPE = 3; /** PT/CT 默认变比。 */ private static final double DEFAULT_RATIO = 1D; /** PT 默认接线方式,0 表示星形。 */ @@ -47,13 +52,9 @@ public class WaveServiceImpl implements WaveService { /** 波形文件解析组件。 */ private final WaveFileComponent waveFileComponent; + /** 波形向量计算组件。 */ + private final WaveVectorComponent waveVectorComponent; - /** - * 解析文本波形。 - * - * @param param 波形解析参数 - * @return 文本波形结果 - */ @Override public WaveParseResultVO parse(WaveParseParam param) { if (param == null || StrUtil.isBlank(param.getWaveformText())) { @@ -103,14 +104,6 @@ public class WaveServiceImpl implements WaveService { return buildTextResult(sourcePoints, displayPoints, ignoredLineCount, containsXAxis); } - /** - * 解析 COMTRADE 波形文件。 - * - * @param cfgStream cfg 输入流 - * @param datStream dat 输入流 - * @param param 解析参数 - * @return COMTRADE 解析结果 - */ @Override public WaveComtradeResultVO parseComtrade(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) { if (cfgStream == null || datStream == null) { @@ -132,9 +125,8 @@ public class WaveServiceImpl implements WaveService { WaveComtradeResultVO result = new WaveComtradeResultVO(); result.setWaveData(waveDataDTO); - List waveDataDetails = null; if (buildDetails) { - waveDataDetails = WaveUtil.filterWaveData(waveDataDTO); + List waveDataDetails = WaveUtil.filterWaveData(waveDataDTO); result.setWaveDataDetails(waveDataDetails); } if (calculateEigenvalue) { @@ -153,9 +145,40 @@ public class WaveServiceImpl implements WaveService { } } - /** - * 将请求中的 PT、CT、测点等参数写回波形结果。 - */ + @Override + public WaveComtradeVectorResultVO parseComtradeVector(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) { + if (cfgStream == null || datStream == null) { + throw new BusinessException(CommonResponseEnum.FAIL, "cfg 或 dat 文件不能为空"); + } + + WaveComtradeParseParam resolvedParam = param == null ? new WaveComtradeParseParam() : param; + try (InputStream cfgInputStream = cfgStream; InputStream datInputStream = datStream) { + WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgInputStream, datInputStream, RAW_WAVE_PARSE_TYPE); + applyWaveMetadata(waveDataDTO, resolvedParam); + + List vectorGroups = waveVectorComponent.calculateVectors(waveDataDTO); + WaveComtradeVectorResultVO result = new WaveComtradeVectorResultVO(); + result.setMonitorName(waveDataDTO.getMonitorName()); + result.setTime(waveDataDTO.getTime()); + result.setSamplePerCycle(waveDataDTO.getComtradeCfgDTO().getFinalSampleRate()); + result.setCycleCount(resolveCycleCount(vectorGroups)); + result.setVectorGroups(vectorGroups); + return result; + } catch (BusinessException ex) { + throw ex; + } catch (Exception ex) { + log.error("COMTRADE 向量计算失败", ex); + throw new BusinessException(CommonResponseEnum.FAIL, "COMTRADE 向量计算失败"); + } + } + + private Integer resolveCycleCount(List vectorGroups) { + if (vectorGroups == null || vectorGroups.isEmpty() || vectorGroups.get(0).getVectorSeries() == null) { + return 0; + } + return vectorGroups.get(0).getVectorSeries().size(); + } + private void applyWaveMetadata(WaveDataDTO waveDataDTO, WaveComtradeParseParam param) { waveDataDTO.setPt(defaultIfNull(param.getPt(), DEFAULT_RATIO)); waveDataDTO.setCt(defaultIfNull(param.getCt(), DEFAULT_RATIO)); @@ -163,23 +186,14 @@ public class WaveServiceImpl implements WaveService { waveDataDTO.setMonitorName(StrUtil.blankToDefault(param.getMonitorName(), "未命名测点")); } - /** - * 判断是否需要计算 RMS 数据。 - */ private boolean shouldCalculateRms(WaveComtradeParseParam param) { return param.getCalculateRms() == null || param.getCalculateRms(); } - /** - * 判断是否需要构建前端查看明细。 - */ private boolean shouldBuildDetails(WaveComtradeParseParam param) { return param.getBuildDetails() == null || param.getBuildDetails(); } - /** - * 规范化解析类型参数。 - */ private int sanitizeParseType(Integer parseType) { if (parseType == null || parseType < 0 || parseType > 3) { return DEFAULT_PARSE_TYPE; @@ -187,9 +201,6 @@ public class WaveServiceImpl implements WaveService { return parseType; } - /** - * 为空或非正数时返回默认比例。 - */ private double defaultIfNull(Double value, double defaultValue) { if (value == null || value <= 0) { return defaultValue; @@ -197,18 +208,12 @@ public class WaveServiceImpl implements WaveService { return value; } - /** - * 从指定列构建一个波形点。 - */ private WavePointVO buildPoint(String[] columns, int xColumnIndex, int yColumnIndex) { BigDecimal yValue = parseNumber(readColumn(columns, yColumnIndex)); BigDecimal xValue = parseNumber(readColumn(columns, xColumnIndex)); return new WavePointVO(xValue, yValue); } - /** - * 将单列数值按采样间隔扩展为点位列表。 - */ private List buildSingleColumnPoints(String[] columns, BigDecimal samplingInterval, int startIndex) { List points = new ArrayList<>(); for (int i = 0; i < columns.length; i++) { @@ -219,9 +224,6 @@ public class WaveServiceImpl implements WaveService { return points; } - /** - * 读取指定列内容。 - */ private String readColumn(String[] columns, int columnIndex) { if (columnIndex < 0 || columnIndex >= columns.length) { throw new IllegalArgumentException("列下标超出范围"); @@ -229,9 +231,6 @@ public class WaveServiceImpl implements WaveService { return columns[columnIndex]; } - /** - * 将字符串转换为数值。 - */ private BigDecimal parseNumber(String value) { if (StrUtil.isBlank(value)) { throw new IllegalArgumentException("数值不能为空"); @@ -239,9 +238,6 @@ public class WaveServiceImpl implements WaveService { return new BigDecimal(value.trim()); } - /** - * 按分隔符规则拆分文本列。 - */ private String[] splitColumns(String line, String separator) { String trimmedLine = line.trim(); if (StrUtil.isBlank(trimmedLine)) { @@ -263,9 +259,6 @@ public class WaveServiceImpl implements WaveService { .toArray(String[]::new); } - /** - * 对过多的点位执行简单下采样。 - */ private List downSample(List sourcePoints, int maxPointCount) { if (sourcePoints.size() <= maxPointCount) { return sourcePoints; @@ -285,9 +278,6 @@ public class WaveServiceImpl implements WaveService { return result; } - /** - * 组装文本波形解析结果。 - */ private WaveParseResultVO buildTextResult(List sourcePoints, List displayPoints, int ignoredLineCount, boolean containsXAxis) { BigDecimal minX = sourcePoints.get(0).getX(); @@ -327,9 +317,6 @@ public class WaveServiceImpl implements WaveService { return result; } - /** - * 规范化跳过表头行数。 - */ private int sanitizeSkipHeaderLines(Integer skipHeaderLines) { if (skipHeaderLines == null || skipHeaderLines < 0) { return 0; @@ -337,9 +324,6 @@ public class WaveServiceImpl implements WaveService { return skipHeaderLines; } - /** - * 规范化最大点数限制。 - */ private int sanitizeMaxPointCount(Integer maxPointCount) { if (maxPointCount == null || maxPointCount <= 0) { return DEFAULT_MAX_POINT_COUNT; @@ -347,9 +331,6 @@ public class WaveServiceImpl implements WaveService { return maxPointCount; } - /** - * 规范化列下标。 - */ private int sanitizeColumnIndex(Integer columnIndex, int defaultValue) { if (columnIndex == null || columnIndex < 0) { return defaultValue; @@ -357,9 +338,6 @@ public class WaveServiceImpl implements WaveService { return columnIndex; } - /** - * 规范化采样间隔。 - */ private BigDecimal sanitizeSamplingInterval(BigDecimal samplingInterval) { if (samplingInterval == null || samplingInterval.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ONE;