玫瑰图,H事件地址

This commit is contained in:
hekaiyu 2025-11-09 20:04:16 +08:00
parent 457da7d6aa
commit 6013997d31
12 changed files with 379 additions and 145 deletions

View File

@ -4,4 +4,24 @@ public class WeatherStepConstants {
public static final double DEFAULT_STEP = 0.5; public static final double DEFAULT_STEP = 0.5;
/**
* 16方向名称
*/
public static final String[] DIRECTIONS_16 = {
"", "北北东", "东北", "东北东",
"", "东南东", "东南", "东南南",
"", "西南南", "西南", "西南西",
"西", "西北西", "西北", "西北北"
};
/**
* 16方向的角度范围
*/
public static final double[] DIRECTION_ANGLES = {
0, 22.5, 45, 67.5,
90, 112.5, 135, 157.5,
180, 202.5, 225, 247.5,
270, 292.5, 315, 337.5
};
} }

View File

@ -1,10 +1,7 @@
package org.jeecg.common.util; package org.jeecg.common.util;
import lombok.val; import lombok.val;
import ucar.ma2.Array; import ucar.ma2.*;
import ucar.ma2.ArrayDouble;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.nc2.Attribute; import ucar.nc2.Attribute;
import ucar.nc2.NetcdfFile; import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFileWriter; import ucar.nc2.NetcdfFileWriter;
@ -46,6 +43,48 @@ public final class NcUtil {
} }
} }
/**
* 根据经纬度下标取数据
*/
public static Double getNcValueByIndex(NetcdfFile ncfile, String name, int layer, Integer hour, int latSize, int lonSize) {
Objects.requireNonNull(ncfile, "NetcdfFile cannot be null");
Objects.requireNonNull(name, "Variable name cannot be null");
try {
Variable variable = ncfile.findVariable(name);
if (variable == null) {
throw new IllegalArgumentException("Variable '" + name + "' not found in NetCDF file");
}
// 根据变量的维度数创建合适的origin数组
int[] origin;
int[] section;
// 假设变量有4个维度(time, layer, lat, lon)
if (variable.getRank() == 4) {
origin = new int[]{hour, layer, latSize, lonSize};
section = new int[]{1, 1, 1, 1};
}
// 假设变量有3个维度(time, lat, lon) (layer, lat, lon)
else if (variable.getRank() == 3) {
origin = new int[]{hour, latSize, lonSize};
section = new int[]{1, 1, 1};
} else {
throw new IllegalArgumentException("Unsupported variable rank: " + variable.getRank());
}
Array data = variable.read(origin, section);
Index index = data.getIndex();
double value = data.getDouble(index);
return Math.round(value * 1000.0) / 1000.0;
} catch (IOException e) {
logger.error("Error reading variable {} from NetCDF file", name, e);
throw new RuntimeException("Failed to read NetCDF data", e);
} catch (InvalidRangeException e) {
throw new RuntimeException("Invalid range specified for variable reading", e);
}
}
/** /**
* 读取三维数据如果为四维数据层级固定读取第一层数据降为三维数据 * 读取三维数据如果为四维数据层级固定读取第一层数据降为三维数据
*/ */

View File

@ -3,6 +3,7 @@ package org.jeecg.baseAPI.service.impl;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jeecg.baseAPI.service.BaseAPIService; import org.jeecg.baseAPI.service.BaseAPIService;
import org.jeecg.common.properties.DiffusionProperties; import org.jeecg.common.properties.DiffusionProperties;
import org.jeecg.common.properties.EventServerProperties;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
@ -21,7 +22,7 @@ import static java.lang.Math.toRadians;
@RequiredArgsConstructor @RequiredArgsConstructor
public class BaseAPIServiceImpl implements BaseAPIService { public class BaseAPIServiceImpl implements BaseAPIService {
private final DiffusionProperties diffusionProperties; private final EventServerProperties eventServerProperties;
@Override @Override
public String calcAngle(Double centerLon, Double centerLat, Double targetLon, Double targetLat,Double u,Double v) { public String calcAngle(Double centerLon, Double centerLat, Double targetLon, Double targetLat,Double u,Double v) {
@ -153,7 +154,7 @@ public class BaseAPIServiceImpl implements BaseAPIService {
@Override @Override
public String buildEngineeringFilePath(String modelPath, String createBy, String engineeringName) { public String buildEngineeringFilePath(String modelPath, String createBy, String engineeringName) {
return diffusionProperties.getDiffusionPath() + return eventServerProperties.getBaseHome() +
File.separator + File.separator +
modelPath + modelPath +
File.separator + File.separator +

View File

@ -11,6 +11,7 @@ import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.constant.DiffusionPrefixConstants; import org.jeecg.common.constant.DiffusionPrefixConstants;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.properties.DiffusionProperties; import org.jeecg.common.properties.DiffusionProperties;
import org.jeecg.common.properties.EventServerProperties;
import org.jeecg.common.properties.SystemStorageProperties; import org.jeecg.common.properties.SystemStorageProperties;
import org.jeecg.common.util.NcUtil; import org.jeecg.common.util.NcUtil;
import org.jeecg.diffusion.service.DiffusionDataService; import org.jeecg.diffusion.service.DiffusionDataService;
@ -41,7 +42,7 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class DiffusionDataServiceImpl implements DiffusionDataService { public class DiffusionDataServiceImpl implements DiffusionDataService {
private final DiffusionProperties diffusionProperties; private final EventServerProperties eventServerProperties;
private final BaseAPIService baseAPIService; private final BaseAPIService baseAPIService;
private final WrfMapper wrfMapper; private final WrfMapper wrfMapper;
private final CmaqMapper cmaqMapper; private final CmaqMapper cmaqMapper;
@ -51,14 +52,13 @@ public class DiffusionDataServiceImpl implements DiffusionDataService {
public DiffusionResultVO getDiffusionResult(String enginId, int hour, int layer) { public DiffusionResultVO getDiffusionResult(String enginId, int hour, int layer) {
Engineering engineering = engineeringMapper.selectById(enginId); Engineering engineering = engineeringMapper.selectById(enginId);
Wrf wrf = wrfMapper.selectOne(new LambdaQueryWrapper<Wrf>().eq(Wrf::getEnginId,enginId)); Wrf wrf = wrfMapper.selectOne(new LambdaQueryWrapper<Wrf>().eq(Wrf::getEnginId,enginId));
String wrfFilePath = baseAPIService.buildEngineeringFilePath(diffusionProperties.getWrfPath() ,engineering.getCreateBy(), engineering.getEngineeringName()); String wrfFilePath = baseAPIService.buildEngineeringFilePath(eventServerProperties.getResultFilePrefix() ,engineering.getCreateBy(), engineering.getEngineeringName());
String cmaqFilePath = baseAPIService.buildEngineeringFilePath(diffusionProperties.getWrfPath() ,engineering.getCreateBy(), engineering.getEngineeringName()); String cmaqFilePath = baseAPIService.buildEngineeringFilePath(eventServerProperties.getResultFilePrefix() ,engineering.getCreateBy(), engineering.getEngineeringName());
try (NetcdfFile wrfNcFile = getWrfNetcdfFile(wrfFilePath, wrf.getStartTime()); try (NetcdfFile wrfNcFile = getWrfNetcdfFile(wrfFilePath, wrf.getStartTime());
NetcdfFile cmaqNcFile = getCmaqNetcdfFile(cmaqFilePath, wrf.getStartTime());){ NetcdfFile cmaqNcFile = getCmaqNetcdfFile(cmaqFilePath, wrf.getStartTime());){
DiffusionResultVO diffusionResultVO = new DiffusionResultVO(); DiffusionResultVO diffusionResultVO = new DiffusionResultVO();
List<List<List<Double>>> dataList = new ArrayList<>();
List<List<Double>> values = NcUtil.get2DNCByName(cmaqNcFile, "CO", layer, hour); List<List<Double>> values = NcUtil.get2DNCByName(cmaqNcFile, "CO", layer, hour);
List<List<Double>> xlats = NcUtil.get2DNCByName(wrfNcFile, "XLAT", layer, hour); List<List<Double>> xlats = NcUtil.get2DNCByName(wrfNcFile, "XLAT", layer, hour);

View File

@ -2,6 +2,7 @@ package org.jeecg.runProcess.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jeecg.baseAPI.service.BaseAPIService;
import org.jeecg.cmaq.service.CmaqService; import org.jeecg.cmaq.service.CmaqService;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.properties.EventServerProperties; import org.jeecg.common.properties.EventServerProperties;
@ -28,6 +29,7 @@ public class RunProcessServiceImpl implements RunProcessService {
private final WrfService wrfService; private final WrfService wrfService;
private final CmaqService cmaqService; private final CmaqService cmaqService;
private final EngineeringService engineeringService; private final EngineeringService engineeringService;
private final BaseAPIService baseAPIService;
private final EventServerProperties eventServerProperties; private final EventServerProperties eventServerProperties;
@Override @Override
@ -38,9 +40,8 @@ public class RunProcessServiceImpl implements RunProcessService {
Wrf wrf = wrfService.getOne(new LambdaQueryWrapper<Wrf>().eq(Wrf::getEnginId,engineeringId)); Wrf wrf = wrfService.getOne(new LambdaQueryWrapper<Wrf>().eq(Wrf::getEnginId,engineeringId));
//各种路径前缀 //各种路径前缀
String allRunPath = eventServerProperties.getBaseHome() + File.separator + engineering.getCreateBy() + File.separator + engineering.getEngineeringName() + File.separator; String allRunPath = baseAPIService.buildEngineeringFilePath("" ,engineering.getCreateBy(), engineering.getEngineeringName());
String resultFilePath = eventServerProperties.getBaseHome() + File.separator + eventServerProperties.getResultFilePrefix() + String resultFilePath = baseAPIService.buildEngineeringFilePath(eventServerProperties.getResultFilePrefix() ,engineering.getCreateBy(), engineering.getEngineeringName());
File.separator + engineering.getCreateBy() + File.separator + engineering.getEngineeringName() + File.separator;
String workDirPath = allRunPath + "workdir/"; String workDirPath = allRunPath + "workdir/";
String mcipPath = allRunPath + "workdir/MCIP/"; String mcipPath = allRunPath + "workdir/MCIP/";
String scriptsPath = allRunPath + "scripts/"; String scriptsPath = allRunPath + "scripts/";

View File

@ -40,13 +40,13 @@ public class WrfController extends JeecgController<Wrf, WrfService> {
} }
/** /**
* 编辑 * 查询wrf参数信息
* *
* @param enginId * @param enginId
* @return * @return
*/ */
@AutoLog(value = "编辑") @AutoLog(value = "查询wrf参数信息")
@Operation(summary = "编辑") @Operation(summary = "查询wrf参数信息")
@RequestMapping(value = "/getByEnginId", method = {RequestMethod.GET}) @RequestMapping(value = "/getByEnginId", method = {RequestMethod.GET})
public Result<Wrf> getByEnginId(String enginId) { public Result<Wrf> getByEnginId(String enginId) {
return Result.OK(wrfService.getOne(new LambdaQueryWrapper<Wrf>().eq(Wrf::getEnginId,enginId))); return Result.OK(wrfService.getOne(new LambdaQueryWrapper<Wrf>().eq(Wrf::getEnginId,enginId)));

View File

@ -101,10 +101,29 @@ public class WeatherDataController {
@AutoLog(value = "气象预测-气象折线图") @AutoLog(value = "气象预测-气象折线图")
@Operation(summary = "气象预测-气象折线图") @Operation(summary = "气象预测-气象折线图")
@GetMapping(value = "getDataLine") @GetMapping(value = "getDataLine")
public Result<?> getDataLine(Integer dataType,@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime, double longitude, double latitude) { public Result<?> getDataLine(Integer dataType,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
double longitude,
double latitude) {
return Result.OK(weatherDataService.getDataLine(dataType,startTime,endTime,longitude,latitude)); return Result.OK(weatherDataService.getDataLine(dataType,startTime,endTime,longitude,latitude));
} }
/**
* 风场玫瑰图
* @return
*/
@AutoLog(value = "气象预测-风场玫瑰图")
@Operation(summary = "气象预测-风场玫瑰图")
@GetMapping(value = "getWindRose")
public Result<?> getWindRose(Integer dataType,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
double longitude,
double latitude) {
return Result.OK(weatherDataService.getWindRose(dataType,startTime,endTime,longitude,latitude));
}
@AutoLog(value = "删除气象数据") @AutoLog(value = "删除气象数据")
@Operation(summary = "删除气象数据") @Operation(summary = "删除气象数据")
@DeleteMapping("delete") @DeleteMapping("delete")

View File

@ -5,11 +5,9 @@ import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.system.query.PageRequest; import org.jeecg.common.system.query.PageRequest;
import org.jeecg.modules.base.entity.StasDataSource; import org.jeecg.modules.base.entity.StasDataSource;
import org.jeecg.modules.base.entity.WeatherData; import org.jeecg.modules.base.entity.WeatherData;
import org.jeecg.vo.FileExistVo; import org.jeecg.vo.*;
import org.jeecg.vo.FileUploadResultVo;
import org.jeecg.vo.FileVo;
import org.jeecg.vo.WeatherResultVO;
import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@ -19,7 +17,8 @@ public interface WeatherDataService extends IService<WeatherData> {
WeatherResultVO getWeatherData(Integer dataType, Integer weatherType, LocalDateTime startTime, int hour); WeatherResultVO getWeatherData(Integer dataType, Integer weatherType, LocalDateTime startTime, int hour);
WeatherResultVO getWeatherDataPreview(String weatherId, Integer weatherType); WeatherResultVO getWeatherDataPreview(String weatherId, Integer weatherType);
Map<String, List<String>> getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, double longitude, double latitude); WindDataLineVO getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, double longitude, double latitude);
List<WindRoseData> getWindRose(Integer dataType, LocalDateTime startTime, LocalDateTime endTime,double longitude, double latitude);
/** /**
* 分页查询气象数据 * 分页查询气象数据

View File

@ -24,10 +24,8 @@ import org.jeecg.common.util.NcUtil;
import org.jeecg.modules.base.entity.WeatherData; import org.jeecg.modules.base.entity.WeatherData;
import org.jeecg.modules.base.mapper.WeatherDataMapper; import org.jeecg.modules.base.mapper.WeatherDataMapper;
import org.jeecg.service.WeatherDataService; import org.jeecg.service.WeatherDataService;
import org.jeecg.vo.FileExistVo; import org.jeecg.utils.WindRoseDataGenerator;
import org.jeecg.vo.FileUploadResultVo; import org.jeecg.vo.*;
import org.jeecg.vo.FileVo;
import org.jeecg.vo.WeatherResultVO;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -137,7 +135,7 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
* @return 天气数据列表 * @return 天气数据列表
*/ */
@Override @Override
public Map<String, List<String>> getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, public WindDataLineVO getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime,
double longitude, double latitude) { double longitude, double latitude) {
// 参数校验 // 参数校验
@ -150,7 +148,7 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
throw new IllegalArgumentException("时间范围内没有气象数据"); throw new IllegalArgumentException("时间范围内没有气象数据");
} }
// 初始化结果集 // 初始化结果集
Map<String, List<String>> result = new LinkedHashMap<>(); // 保持插入顺序 WindDataLineVO windDataLineVO = new WindDataLineVO();
List<String> timeList = new ArrayList<>(); List<String> timeList = new ArrayList<>();
List<String> temperatureList = new ArrayList<>(); List<String> temperatureList = new ArrayList<>();
List<String> pressureList = new ArrayList<>(); List<String> pressureList = new ArrayList<>();
@ -180,13 +178,117 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
} }
// 构建结果 // 构建结果
result.put("time", timeList); windDataLineVO.setTime(timeList);
result.put("temperature", temperatureList); windDataLineVO.setTemperature(temperatureList);
result.put("pressure", pressureList); windDataLineVO.setPressure(pressureList);
result.put("humidity", humidityList); windDataLineVO.setHumidity(humidityList);
result.put("windSpeed", windSpeedList); windDataLineVO.setWindSpeed(windSpeedList);
analyzeWithStream(windSpeedList, windDataLineVO);
return result; return windDataLineVO;
}
public void analyzeWithStream(List<String> windSpeedStrings, WindDataLineVO windDataLineVO) {
if (windSpeedStrings == null || windSpeedStrings.isEmpty()) {
throw new IllegalArgumentException("风速数据不能为空");
}
// 使用Stream将String转换为double并获取统计量
DoubleSummaryStatistics basicStats = windSpeedStrings.stream()
.mapToDouble(Double::parseDouble) // String转double
.summaryStatistics();
int count = windSpeedStrings.size();
double mean = basicStats.getAverage();
double max = basicStats.getMax();
double min = basicStats.getMin();
// 计算标准差需要平均值所以需要重新转换计算
List<Double> windSpeeds = windSpeedStrings.stream()
.map(Double::parseDouble)
.collect(Collectors.toList());
double variance = windSpeeds.stream()
.mapToDouble(Double::doubleValue)
.map(speed -> Math.pow(speed - mean, 2))
.sum() / (count - 1);
double stdDev = Math.sqrt(variance);
// 创建格式化对象保留两位小数
DecimalFormat df = new DecimalFormat("#0.00");
windDataLineVO.setMean(df.format(mean)); // 平均风速
windDataLineVO.setMax(df.format(max)); // 最大风速
windDataLineVO.setMin(df.format(min)); // 最小风速
windDataLineVO.setStdDev(df.format(stdDev)); // 风速标准差
}
@Override
public List<WindRoseData> getWindRose(Integer dataType, LocalDateTime startTime, LocalDateTime endTime,
double longitude, double latitude) {
// 参数校验
if (startTime == null || endTime == null || startTime.isAfter(endTime)) {
throw new IllegalArgumentException("时间参数无效");
}
List<WeatherData> weatherDataList = weatherDataMapper.selectList(
new LambdaQueryWrapper<WeatherData>()
.between(WeatherData::getDataStartTime, startTime, endTime)
.eq(WeatherData::getDataSource, dataType)
);
if (weatherDataList == null || weatherDataList.isEmpty()) {
throw new IllegalArgumentException("时间范围内没有气象数据");
}
int[] gridIndex = getGridIndex(latitude, longitude);
Map<String, String> variables = getVariableNames(dataType);
List<Double> uValues = new ArrayList<>();
List<Double> vValues = new ArrayList<>();
// 处理每个时间点数据
LocalDateTime currentTime = startTime;
while (!currentTime.isAfter(endTime)) {
try {
if (WeatherDataSourceEnum.CRA40.getKey().equals(dataType)) {
// CRA40数据源UV分量在不同文件
String uFilePath = getWeatherFilePath(weatherDataList, currentTime,
WeatherTypeEnum.WIND.getKey(), WeatherDataSourceEnum.getInfoByKey(dataType));
String vFilePath = getWeatherFilePath(weatherDataList, currentTime,
WeatherTypeEnum.WIND.getKey() + 1, WeatherDataSourceEnum.getInfoByKey(dataType));
if (isFileValid(uFilePath) && isFileValid(vFilePath)) {
try (NetcdfFile uNcFile = NetcdfFile.open(uFilePath);
NetcdfFile vNcFile = NetcdfFile.open(vFilePath)) {
uValues.add(getNcValueByIndex(uNcFile, variables.get("windU"), gridIndex[0], gridIndex[1]));
vValues.add(getNcValueByIndex(vNcFile, variables.get("windV"), gridIndex[0], gridIndex[1]));
}
}
} else {
// 标准数据源UV分量在同一文件
String filePath = getWeatherFilePath(weatherDataList, currentTime,
WeatherTypeEnum.WIND.getKey(), WeatherDataSourceEnum.getInfoByKey(dataType));
if (isFileValid(filePath)) {
try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
uValues.add(getNcValueByIndex(ncFile, variables.get("windU"), gridIndex[0], gridIndex[1]));
vValues.add(getNcValueByIndex(ncFile, variables.get("windV"), gridIndex[0], gridIndex[1]));
}
}
}
} catch (Exception e) {
log.warn("处理{}时刻数据失败: {}", currentTime, e.getMessage());
// 跳过该时间点继续处理下一个
}
currentTime = currentTime.plusHours(6);
}
// 验证数据有效性
if (uValues.isEmpty() || vValues.isEmpty()) {
throw new JeecgBootException("未读取到有效的风场数据");
}
return WindRoseDataGenerator.generateWindRoseData16(uValues, vValues);
} }
/** /**
@ -540,16 +642,15 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
Map<String, String> variables = getVariableNames(dataType); Map<String, String> variables = getVariableNames(dataType);
if(WeatherDataSourceEnum.CRA40.getKey() != dataType){ if(WeatherDataSourceEnum.CRA40.getKey() != dataType){
try (NetcdfFile ncFile = NetcdfFile.open(filePath)) { try (NetcdfFile ncFile = NetcdfFile.open(filePath)) {
// 读取数据使用通用NcUtil方法 Double tValue = getNcValueByIndex(ncFile, variables.get("temperature"), gridIndex[0], gridIndex[1]) - 273.15;
List<List<Double>> tData = getVariableData(ncFile, variables.get("temperature")); Double pValue = getNcValueByIndex(ncFile, variables.get("pressure"), gridIndex[0], gridIndex[1]) / 1000;
List<List<Double>> pData = getVariableData(ncFile, variables.get("pressure")); Double hValue = getNcValueByIndex(ncFile, variables.get("humidity"), gridIndex[0], gridIndex[1]);
List<List<Double>> hData = getVariableData(ncFile, variables.get("humidity")); Double uValue = getNcValueByIndex(ncFile, variables.get("windU"), gridIndex[0], gridIndex[1]);
List<List<Double>> uData = getVariableData(ncFile, variables.get("windU")); Double vValue = getNcValueByIndex(ncFile, variables.get("windV"), gridIndex[0], gridIndex[1]);
List<List<Double>> vData = getVariableData(ncFile, variables.get("windV")); Double windSpeed = Math.sqrt(uValue * uValue + vValue * vValue);
// 添加格式化数据
// 数据处理逻辑与第一个方法保持一致 addFormattedData(currentTime, timeList, temperatureList, pressureList,
processAndAddData(gridIndex, currentTime, timeList, temperatureList, pressureList, humidityList, windSpeedList, tValue, pValue, hValue, windSpeed);
humidityList, windSpeedList, tData, pData, hData, uData, vData);
} }
}else { }else {
// 循环处理每个时间点的数据 // 循环处理每个时间点的数据
@ -560,40 +661,37 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
String uFilePath = getWeatherFilePath(weatherDataList, currentTime, WeatherTypeEnum.WIND.getKey(), WeatherDataSourceEnum.getInfoByKey(dataType)); String uFilePath = getWeatherFilePath(weatherDataList, currentTime, WeatherTypeEnum.WIND.getKey(), WeatherDataSourceEnum.getInfoByKey(dataType));
String vFilePath = getWeatherFilePath(weatherDataList, currentTime,WeatherTypeEnum.WIND.getKey() + 1, WeatherDataSourceEnum.getInfoByKey(dataType)); String vFilePath = getWeatherFilePath(weatherDataList, currentTime,WeatherTypeEnum.WIND.getKey() + 1, WeatherDataSourceEnum.getInfoByKey(dataType));
List<List<Double>> tData = null; Double tValue = null;
List<List<Double>> pData = null; Double pValue = null;
List<List<Double>> hData = null; Double hValue = null;
List<List<Double>> uData = null; Double windSpeed = null;
List<List<Double>> vData = null;
if (isFileValid(tFilePath)) { if (isFileValid(tFilePath)) {
NetcdfFile ncFile = NetcdfFile.open(tFilePath); NetcdfFile ncFile = NetcdfFile.open(tFilePath);
tData = getVariableData(ncFile, variables.get("temperature")); tValue = getNcValueByIndex(ncFile, variables.get("temperature"), gridIndex[0], gridIndex[1]) - 273.15;
} }
if (isFileValid(pFilePath)) { if (isFileValid(pFilePath)) {
NetcdfFile ncFile = NetcdfFile.open(pFilePath); NetcdfFile ncFile = NetcdfFile.open(pFilePath);
pData = getVariableData(ncFile, variables.get("pressure")); pValue = getNcValueByIndex(ncFile, variables.get("pressure"), gridIndex[0], gridIndex[1]) / 1000;
} }
if (isFileValid(hFilePath)) { if (isFileValid(hFilePath)) {
NetcdfFile ncFile = NetcdfFile.open(hFilePath); NetcdfFile ncFile = NetcdfFile.open(hFilePath);
hData = getVariableData(ncFile, variables.get("humidity")); hValue = getNcValueByIndex(ncFile, variables.get("humidity"), gridIndex[0], gridIndex[1]);
} }
if (isFileValid(uFilePath) && isFileValid(vFilePath)) { if (isFileValid(uFilePath) && isFileValid(vFilePath)) {
NetcdfFile uNcFile = NetcdfFile.open(uFilePath); NetcdfFile uNcFile = NetcdfFile.open(uFilePath);
NetcdfFile vNcFile = NetcdfFile.open(vFilePath); NetcdfFile vNcFile = NetcdfFile.open(vFilePath);
uData = getVariableData(uNcFile, variables.get("windU")); Double uValue = getNcValueByIndex(uNcFile, variables.get("windU"), gridIndex[0], gridIndex[1]);
vData = getVariableData(vNcFile, variables.get("windV")); Double vValue = getNcValueByIndex(vNcFile, variables.get("windV"), gridIndex[0], gridIndex[1]);
windSpeed = Math.sqrt(uValue * uValue + vValue * vValue);
} }
// 添加格式化数据
// 数据处理逻辑与第一个方法保持一致 addFormattedData(currentTime, timeList, temperatureList, pressureList,
processAndAddData(gridIndex, currentTime, timeList, temperatureList, pressureList, humidityList, windSpeedList, tValue, pValue, hValue, windSpeed);
humidityList, windSpeedList, tData, pData, hData, uData, vData);
}catch (Exception e) { }catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
throw new JeecgBootException("处理气象数据失败!"); throw new JeecgBootException("处理气象数据失败!");
} }
} }
} }
/** /**
@ -632,76 +730,8 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
/** /**
* 通用数据获取方法 * 通用数据获取方法
*/ */
private List<List<Double>> getVariableData(NetcdfFile ncFile, String variableName) { private Double getNcValueByIndex(NetcdfFile ncFile, String variableName, int latSize, int lonSize) {
return NcUtil.get2DNCByName(ncFile, variableName, 0, 0); return NcUtil.getNcValueByIndex(ncFile, variableName, 0, 0, latSize, lonSize);
}
/**
* 数据处理与添加增强安全性版本
*/
private void processAndAddData(int[] gridIndex, LocalDateTime currentTime,
List<String> timeList, List<String> temperatureList,
List<String> pressureList, List<String> humidityList,
List<String> windSpeedList, List<List<Double>> tData,
List<List<Double>> pData, List<List<Double>> hData,
List<List<Double>> uData, List<List<Double>> vData) {
// 1. 参数基础校验
Objects.requireNonNull(currentTime, "currentTime不能为null");
validateLists(timeList, temperatureList, pressureList, humidityList, windSpeedList);
Double tValue = null;
Double pValue = null;
Double hValue = null;
Double windSpeed = null;
// 2. 安全获取数据带默认值
if(null != tData && !tData.isEmpty()){
tValue = getSafeGridValue(tData, gridIndex, 0.0) - 273.15;
}
if(null != tData && !tData.isEmpty()){
pValue = getSafeGridValue(pData, gridIndex, 0.0) / 1000;
}
if(null != tData && !tData.isEmpty()){
hValue = getSafeGridValue(hData, gridIndex, 0.0);
}
if(null != uData && !uData.isEmpty() && null != vData && !vData.isEmpty()){
windSpeed = calculateWindSpeed(uData, vData, gridIndex);
}
// 3. 添加格式化数据
addFormattedData(currentTime, timeList, temperatureList, pressureList,
humidityList, windSpeedList, tValue, pValue, hValue, windSpeed);
}
/**
* 校验List参数非空
*/
private void validateLists(List<?>... lists) {
for (List<?> list : lists) {
Objects.requireNonNull(list, "数据列表不能为null");
}
}
/**
* 安全获取网格数据带默认值
*/
private Double getSafeGridValue(List<List<Double>> gridData, int[] gridIndex, Double defaultValue) {
try {
return Optional.ofNullable(gridData.get(gridIndex[0]))
.map(row -> row.get(gridIndex[1]))
.orElse(defaultValue);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 增强版风速计算
*/
private double calculateWindSpeed(List<List<Double>> uData, List<List<Double>> vData, int[] gridIndex) {
Double uValue = getSafeGridValue(uData, gridIndex, 0.0);
Double vValue = getSafeGridValue(vData, gridIndex, 0.0);
return Math.sqrt(uValue * uValue + vValue * vValue);
} }
/** /**
@ -809,21 +839,6 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
return windDataList; return windDataList;
} }
/**
* 设置风场结果
*/
private void setWindResult(WeatherResultVO weatherResultVO, List<List<Double>> u, List<List<Double>> v) {
List<List<Double>> results = new ArrayList<>();
for (int i = 0; i < u.size(); i++) {
for (int j = 0; j < u.get(0).size(); j++) {
results.add(List.of(u.get(i).get(j), v.get(i).get(j)));
}
}
weatherResultVO.setSn(u.size());
weatherResultVO.setWe(u.get(0).size());
weatherResultVO.setDataList(results);
}
/** /**
* 处理变量数据 * 处理变量数据
*/ */

View File

@ -0,0 +1,105 @@
package org.jeecg.utils;
import org.jeecg.common.constant.WeatherStepConstants;
import org.jeecg.vo.WindRoseData;
import java.util.*;
import java.util.stream.Collectors;
public class WindRoseDataGenerator {
/**
* 生成16方向风场玫瑰图数据只统计频次
*
* @param uList U分量风速列表东西方向
* @param vList V分量风速列表南北方向
* @return 16个方向的风场数据列表
*/
public static List<WindRoseData> generateWindRoseData16(
List<Double> uList,
List<Double> vList) {
// 验证输入数据
validateInputData(uList, vList);
// 初始化16个方向的数据容器只需要计数不需要存储具体风速值
int[] directionCounts = new int[16];
int totalPoints = 0;
// 处理每个数据点
for (int i = 0; i < uList.size(); i++) {
Double u = uList.get(i);
Double v = vList.get(i);
// 跳过无效数据
if (u == null || v == null || Double.isNaN(u) || Double.isNaN(v)) {
continue;
}
// 计算风向
double direction = calculateWindDirection(u, v);
// 确定风向属于哪个方向区间并计数
int dirIndex = getDirectionIndex(direction);
directionCounts[dirIndex]++;
totalPoints++;
}
// 计算每个方向的统计信息
List<WindRoseData> result = new ArrayList<>();
for (int i = 0; i < 16; i++) {
WindRoseData data = new WindRoseData(WeatherStepConstants.DIRECTIONS_16[i], WeatherStepConstants.DIRECTION_ANGLES[i]);
data.setCount(directionCounts[i]);
// 计算频率并保留两位小数
double frequency = totalPoints > 0 ? (directionCounts[i] * 100.0 / totalPoints) : 0;
double roundedFrequency = Math.round(frequency * 100.0) / 100.0;
data.setFrequency(roundedFrequency);
result.add(data);
}
return result;
}
/**
* 计算风向角度0-360度正北为0度顺时针增加
*/
private static double calculateWindDirection(double u, double v) {
// 计算角度弧度
double angleRad = Math.atan2(-u, -v);
// 转换为度0-360
double angleDeg = Math.toDegrees(angleRad);
if (angleDeg < 0) {
angleDeg += 360;
}
return angleDeg;
}
/**
* 根据风向角度确定16方向索引
*/
private static int getDirectionIndex(double direction) {
// 将角度调整到0-360范围内
double normalizedDir = direction % 360;
if (normalizedDir < 0) normalizedDir += 360;
// 计算16方向索引每个方向22.5度北风为0-11.25和348.75-360
int index = (int) Math.floor((normalizedDir + 11.25) / 22.5) % 16;
return index;
}
/**
* 验证输入数据
*/
private static void validateInputData(List<Double> uList,
List<Double> vList) {
if (uList == null || vList == null) {
throw new IllegalArgumentException("U、V分量数据不能为null");
}
if (uList.size() != vList.size()) {
throw new IllegalArgumentException("U、V分量数据长度不一致");
}
}
}

View File

@ -0,0 +1,19 @@
package org.jeecg.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class WindDataLineVO {
public List<String> time;
public List<String> temperature;
public List<String> pressure;
public List<String> humidity;
public List<String> windSpeed;
public String mean; // 平均风速
public String max; // 最大风速
public String min; // 最小风速
public String stdDev; // 风速标准差
}

View File

@ -0,0 +1,16 @@
package org.jeecg.vo;
import lombok.Data;
@Data
public class WindRoseData {
private String direction; // 方向名称
private double angle; // 方向角度
private double frequency; // 频率%
private int count;
public WindRoseData(String direction, double angle) {
this.direction = direction;
this.angle = angle;
}
}