波形解析相关
This commit is contained in:
@@ -32,12 +32,13 @@ tools/
|
||||
|
||||
## wave-tool 的职责
|
||||
|
||||
`wave-tool` 当前提供的能力主要围绕波形文本解析与查看数据组装:
|
||||
`wave-tool` 当前提供的能力主要围绕波形解析、查看数据组装与图片生成:
|
||||
|
||||
- 解析单列幅值波形文本
|
||||
- 解析双列时间/幅值波形文本
|
||||
- 统计点位范围、均值、点数等摘要信息
|
||||
- 按查看场景输出下采样后的点位集合
|
||||
- 解析单列/双列文本波形
|
||||
- 解析 COMTRADE `cfg/dat` 波形文件
|
||||
- 计算 RMS 与特征值
|
||||
- 组装前端查看明细
|
||||
- 生成瞬时、RMS、治理场景波形图片
|
||||
|
||||
从接口层看,当前主要围绕 `/wave/*` 路径提供能力。
|
||||
|
||||
|
||||
205
tools/wave-tool/README.md
Normal file
205
tools/wave-tool/README.md
Normal file
@@ -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/` 中仍保留原始参考代码,后续如确认正式模块稳定,可再决定是否清理
|
||||
- 图片生成依赖私有绘图与文件存储组件,当前仅完成代码接入,未做运行态验证
|
||||
@@ -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<EigenvalueDTO> 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<WaveVectorGroupDTO> 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<WavePhaseVectorDTO> 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<WaveHarmonicDTO> 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<WaveVectorGroupDTO> vectorGroups) {
|
||||
if (vectorGroups == null || vectorGroups.isEmpty() || vectorGroups.get(0).getVectorSeries() == null) {
|
||||
return 0;
|
||||
}
|
||||
return vectorGroups.get(0).getVectorSeries().size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<WaveVectorGroupDTO> calculateVectors(WaveDataDTO waveDataDTO) {
|
||||
if (waveDataDTO == null || waveDataDTO.getComtradeCfgDTO() == null) {
|
||||
throw new BusinessException(WaveFileResponseEnum.WAVE_DATA_INVALID);
|
||||
}
|
||||
List<List<Float>> 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<WaveVectorGroupDTO> 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<WaveCycleVectorDTO> buildVectorSeries(WaveDataDTO waveDataDTO, int groupIndex, int phaseCount,
|
||||
int samplePerCycle, int cycleCount) {
|
||||
List<WaveCycleVectorDTO> vectorSeries = new ArrayList<>();
|
||||
int titleIndex = groupIndex * phaseCount + 1;
|
||||
List<String> 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<List<Float>> listWaveData, List<String> 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<WavePhaseVectorDTO> phaseVectors = new ArrayList<>();
|
||||
Map<String, HarmonicSpectrum> 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<List<Float>> 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<Float> 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<WaveHarmonicDTO> buildHarmonicList(HarmonicSpectrum spectrum, boolean buildRate) {
|
||||
List<WaveHarmonicDTO> 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<String, HarmonicSpectrum> spectrumMap, String phaseFlag) {
|
||||
for (Map.Entry<String, HarmonicSpectrum> entry : spectrumMap.entrySet()) {
|
||||
if (StrUtil.containsIgnoreCase(entry.getKey(), phaseFlag)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return HarmonicSpectrum.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析通道名称。
|
||||
*/
|
||||
private String resolveChannelName(List<String> channelNames, int titleIndex, int groupIndex) {
|
||||
if (channelNames != null && channelNames.size() > titleIndex) {
|
||||
return channelNames.get(titleIndex);
|
||||
}
|
||||
return "CH" + (groupIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析相别名称。
|
||||
*/
|
||||
private List<String> resolvePhaseNames(WaveDataDTO waveDataDTO, int titleIndex, int phaseCount) {
|
||||
List<String> phaseNames = new ArrayList<>();
|
||||
List<String> 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<List<Float>> listWaveData, int startIndex, int samplePerCycle) {
|
||||
int middleIndex = Math.min(startIndex + samplePerCycle / 2, listWaveData.size() - 1);
|
||||
List<Float> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<WaveComtradeVectorResultVO> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<WavePhaseVectorDTO> phaseVectors;
|
||||
|
||||
/** 正序分量。 */
|
||||
@ApiModelProperty("正序分量")
|
||||
private WaveSequenceVectorDTO positiveSequence;
|
||||
|
||||
/** 负序分量。 */
|
||||
@ApiModelProperty("负序分量")
|
||||
private WaveSequenceVectorDTO negativeSequence;
|
||||
|
||||
/** 零序分量。 */
|
||||
@ApiModelProperty("零序分量")
|
||||
private WaveSequenceVectorDTO zeroSequence;
|
||||
|
||||
/** 不平衡度。 */
|
||||
@ApiModelProperty("不平衡度")
|
||||
private WaveSequenceUnbalanceDTO unbalance;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<WaveHarmonicDTO> harmonicVoltageContentRates;
|
||||
|
||||
/** 谐波电流幅值。 */
|
||||
@ApiModelProperty("谐波电流幅值")
|
||||
private List<WaveHarmonicDTO> harmonicCurrentAmplitudes;
|
||||
|
||||
/** 谐波畸变率,百分比。 */
|
||||
@ApiModelProperty("谐波畸变率,百分比")
|
||||
private Float harmonicDistortionRate;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<String> phaseNames;
|
||||
|
||||
/** 当前组的周波结果序列。 */
|
||||
@ApiModelProperty("当前组的周波结果序列")
|
||||
private List<WaveCycleVectorDTO> vectorSeries;
|
||||
}
|
||||
@@ -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<WaveVectorGroupDTO> vectorGroups;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<WaveDataDetail> waveDataDetails = null;
|
||||
if (buildDetails) {
|
||||
waveDataDetails = WaveUtil.filterWaveData(waveDataDTO);
|
||||
List<WaveDataDetail> 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<WaveVectorGroupDTO> 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<WaveVectorGroupDTO> 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<WavePointVO> buildSingleColumnPoints(String[] columns, BigDecimal samplingInterval, int startIndex) {
|
||||
List<WavePointVO> 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<WavePointVO> downSample(List<WavePointVO> sourcePoints, int maxPointCount) {
|
||||
if (sourcePoints.size() <= maxPointCount) {
|
||||
return sourcePoints;
|
||||
@@ -285,9 +278,6 @@ public class WaveServiceImpl implements WaveService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装文本波形解析结果。
|
||||
*/
|
||||
private WaveParseResultVO buildTextResult(List<WavePointVO> sourcePoints, List<WavePointVO> 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;
|
||||
|
||||
Reference in New Issue
Block a user