diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WeatherStepConstants.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WeatherStepConstants.java index 70d1e68..01cbd26 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WeatherStepConstants.java +++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/WeatherStepConstants.java @@ -4,4 +4,24 @@ public class WeatherStepConstants { 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 + }; } diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/NcUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/NcUtil.java index 2efb27e..8213643 100644 --- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/NcUtil.java +++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/NcUtil.java @@ -1,10 +1,7 @@ package org.jeecg.common.util; import lombok.val; -import ucar.ma2.Array; -import ucar.ma2.ArrayDouble; -import ucar.ma2.DataType; -import ucar.ma2.Index; +import ucar.ma2.*; import ucar.nc2.Attribute; import ucar.nc2.NetcdfFile; 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); + } + } + /** * 读取三维数据,如果为四维数据,层级固定读取第一层数据,降为三维数据 */ diff --git a/jeecg-module-event/src/main/java/org/jeecg/baseAPI/service/impl/BaseAPIServiceImpl.java b/jeecg-module-event/src/main/java/org/jeecg/baseAPI/service/impl/BaseAPIServiceImpl.java index 2fec852..4204a87 100644 --- a/jeecg-module-event/src/main/java/org/jeecg/baseAPI/service/impl/BaseAPIServiceImpl.java +++ b/jeecg-module-event/src/main/java/org/jeecg/baseAPI/service/impl/BaseAPIServiceImpl.java @@ -3,6 +3,7 @@ package org.jeecg.baseAPI.service.impl; import lombok.RequiredArgsConstructor; import org.jeecg.baseAPI.service.BaseAPIService; import org.jeecg.common.properties.DiffusionProperties; +import org.jeecg.common.properties.EventServerProperties; import org.springframework.stereotype.Service; import java.io.File; @@ -21,7 +22,7 @@ import static java.lang.Math.toRadians; @RequiredArgsConstructor public class BaseAPIServiceImpl implements BaseAPIService { - private final DiffusionProperties diffusionProperties; + private final EventServerProperties eventServerProperties; @Override 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 public String buildEngineeringFilePath(String modelPath, String createBy, String engineeringName) { - return diffusionProperties.getDiffusionPath() + + return eventServerProperties.getBaseHome() + File.separator + modelPath + File.separator + diff --git a/jeecg-module-event/src/main/java/org/jeecg/diffusion/service/impl/DiffusionDataServiceImpl.java b/jeecg-module-event/src/main/java/org/jeecg/diffusion/service/impl/DiffusionDataServiceImpl.java index 5eb065b..698f0d4 100644 --- a/jeecg-module-event/src/main/java/org/jeecg/diffusion/service/impl/DiffusionDataServiceImpl.java +++ b/jeecg-module-event/src/main/java/org/jeecg/diffusion/service/impl/DiffusionDataServiceImpl.java @@ -11,6 +11,7 @@ import org.jeecg.common.aspect.annotation.AutoLog; import org.jeecg.common.constant.DiffusionPrefixConstants; import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.properties.DiffusionProperties; +import org.jeecg.common.properties.EventServerProperties; import org.jeecg.common.properties.SystemStorageProperties; import org.jeecg.common.util.NcUtil; import org.jeecg.diffusion.service.DiffusionDataService; @@ -41,7 +42,7 @@ import java.util.List; @RequiredArgsConstructor public class DiffusionDataServiceImpl implements DiffusionDataService { - private final DiffusionProperties diffusionProperties; + private final EventServerProperties eventServerProperties; private final BaseAPIService baseAPIService; private final WrfMapper wrfMapper; private final CmaqMapper cmaqMapper; @@ -51,14 +52,13 @@ public class DiffusionDataServiceImpl implements DiffusionDataService { public DiffusionResultVO getDiffusionResult(String enginId, int hour, int layer) { Engineering engineering = engineeringMapper.selectById(enginId); Wrf wrf = wrfMapper.selectOne(new LambdaQueryWrapper().eq(Wrf::getEnginId,enginId)); - String wrfFilePath = baseAPIService.buildEngineeringFilePath(diffusionProperties.getWrfPath() ,engineering.getCreateBy(), engineering.getEngineeringName()); - String cmaqFilePath = baseAPIService.buildEngineeringFilePath(diffusionProperties.getWrfPath() ,engineering.getCreateBy(), engineering.getEngineeringName()); + String wrfFilePath = baseAPIService.buildEngineeringFilePath(eventServerProperties.getResultFilePrefix() ,engineering.getCreateBy(), engineering.getEngineeringName()); + String cmaqFilePath = baseAPIService.buildEngineeringFilePath(eventServerProperties.getResultFilePrefix() ,engineering.getCreateBy(), engineering.getEngineeringName()); try (NetcdfFile wrfNcFile = getWrfNetcdfFile(wrfFilePath, wrf.getStartTime()); NetcdfFile cmaqNcFile = getCmaqNetcdfFile(cmaqFilePath, wrf.getStartTime());){ DiffusionResultVO diffusionResultVO = new DiffusionResultVO(); - List>> dataList = new ArrayList<>(); List> values = NcUtil.get2DNCByName(cmaqNcFile, "CO", layer, hour); List> xlats = NcUtil.get2DNCByName(wrfNcFile, "XLAT", layer, hour); diff --git a/jeecg-module-event/src/main/java/org/jeecg/runProcess/service/impl/RunProcessServiceImpl.java b/jeecg-module-event/src/main/java/org/jeecg/runProcess/service/impl/RunProcessServiceImpl.java index c79579e..888d511 100644 --- a/jeecg-module-event/src/main/java/org/jeecg/runProcess/service/impl/RunProcessServiceImpl.java +++ b/jeecg-module-event/src/main/java/org/jeecg/runProcess/service/impl/RunProcessServiceImpl.java @@ -2,6 +2,7 @@ package org.jeecg.runProcess.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; +import org.jeecg.baseAPI.service.BaseAPIService; import org.jeecg.cmaq.service.CmaqService; import org.jeecg.common.api.vo.Result; import org.jeecg.common.properties.EventServerProperties; @@ -28,6 +29,7 @@ public class RunProcessServiceImpl implements RunProcessService { private final WrfService wrfService; private final CmaqService cmaqService; private final EngineeringService engineeringService; + private final BaseAPIService baseAPIService; private final EventServerProperties eventServerProperties; @Override @@ -38,9 +40,8 @@ public class RunProcessServiceImpl implements RunProcessService { Wrf wrf = wrfService.getOne(new LambdaQueryWrapper().eq(Wrf::getEnginId,engineeringId)); //各种路径前缀 - String allRunPath = eventServerProperties.getBaseHome() + File.separator + engineering.getCreateBy() + File.separator + engineering.getEngineeringName() + File.separator; - String resultFilePath = eventServerProperties.getBaseHome() + File.separator + eventServerProperties.getResultFilePrefix() + - File.separator + engineering.getCreateBy() + File.separator + engineering.getEngineeringName() + File.separator; + String allRunPath = baseAPIService.buildEngineeringFilePath("" ,engineering.getCreateBy(), engineering.getEngineeringName()); + String resultFilePath = baseAPIService.buildEngineeringFilePath(eventServerProperties.getResultFilePrefix() ,engineering.getCreateBy(), engineering.getEngineeringName()); String workDirPath = allRunPath + "workdir/"; String mcipPath = allRunPath + "workdir/MCIP/"; String scriptsPath = allRunPath + "scripts/"; diff --git a/jeecg-module-event/src/main/java/org/jeecg/wrf/controller/WrfController.java b/jeecg-module-event/src/main/java/org/jeecg/wrf/controller/WrfController.java index fa38a4f..e4baaa6 100644 --- a/jeecg-module-event/src/main/java/org/jeecg/wrf/controller/WrfController.java +++ b/jeecg-module-event/src/main/java/org/jeecg/wrf/controller/WrfController.java @@ -40,13 +40,13 @@ public class WrfController extends JeecgController { } /** - * 编辑 + * 查询wrf参数信息 * * @param enginId * @return */ - @AutoLog(value = "编辑") - @Operation(summary = "编辑") + @AutoLog(value = "查询wrf参数信息") + @Operation(summary = "查询wrf参数信息") @RequestMapping(value = "/getByEnginId", method = {RequestMethod.GET}) public Result getByEnginId(String enginId) { return Result.OK(wrfService.getOne(new LambdaQueryWrapper().eq(Wrf::getEnginId,enginId))); diff --git a/jeecg-module-weather/src/main/java/org/jeecg/controller/WeatherDataController.java b/jeecg-module-weather/src/main/java/org/jeecg/controller/WeatherDataController.java index 1f05ccd..4e0b169 100644 --- a/jeecg-module-weather/src/main/java/org/jeecg/controller/WeatherDataController.java +++ b/jeecg-module-weather/src/main/java/org/jeecg/controller/WeatherDataController.java @@ -101,10 +101,29 @@ public class WeatherDataController { @AutoLog(value = "气象预测-气象折线图") @Operation(summary = "气象预测-气象折线图") @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 + */ + @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 = "删除气象数据") @Operation(summary = "删除气象数据") @DeleteMapping("delete") diff --git a/jeecg-module-weather/src/main/java/org/jeecg/service/WeatherDataService.java b/jeecg-module-weather/src/main/java/org/jeecg/service/WeatherDataService.java index 484edf6..aa6c736 100644 --- a/jeecg-module-weather/src/main/java/org/jeecg/service/WeatherDataService.java +++ b/jeecg-module-weather/src/main/java/org/jeecg/service/WeatherDataService.java @@ -5,11 +5,9 @@ import com.baomidou.mybatisplus.extension.service.IService; import org.jeecg.common.system.query.PageRequest; import org.jeecg.modules.base.entity.StasDataSource; import org.jeecg.modules.base.entity.WeatherData; -import org.jeecg.vo.FileExistVo; -import org.jeecg.vo.FileUploadResultVo; -import org.jeecg.vo.FileVo; -import org.jeecg.vo.WeatherResultVO; +import org.jeecg.vo.*; +import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -19,7 +17,8 @@ public interface WeatherDataService extends IService { WeatherResultVO getWeatherData(Integer dataType, Integer weatherType, LocalDateTime startTime, int hour); WeatherResultVO getWeatherDataPreview(String weatherId, Integer weatherType); - Map> getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, double longitude, double latitude); + WindDataLineVO getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, double longitude, double latitude); + List getWindRose(Integer dataType, LocalDateTime startTime, LocalDateTime endTime,double longitude, double latitude); /** * 分页查询气象数据 diff --git a/jeecg-module-weather/src/main/java/org/jeecg/service/impl/WeatherDataServiceImpl.java b/jeecg-module-weather/src/main/java/org/jeecg/service/impl/WeatherDataServiceImpl.java index 9900c71..abbb81a 100644 --- a/jeecg-module-weather/src/main/java/org/jeecg/service/impl/WeatherDataServiceImpl.java +++ b/jeecg-module-weather/src/main/java/org/jeecg/service/impl/WeatherDataServiceImpl.java @@ -24,10 +24,8 @@ import org.jeecg.common.util.NcUtil; import org.jeecg.modules.base.entity.WeatherData; import org.jeecg.modules.base.mapper.WeatherDataMapper; import org.jeecg.service.WeatherDataService; -import org.jeecg.vo.FileExistVo; -import org.jeecg.vo.FileUploadResultVo; -import org.jeecg.vo.FileVo; -import org.jeecg.vo.WeatherResultVO; +import org.jeecg.utils.WindRoseDataGenerator; +import org.jeecg.vo.*; import org.springframework.beans.BeanUtils; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Service; @@ -137,7 +135,7 @@ public class WeatherDataServiceImpl extends ServiceImpl> getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, + public WindDataLineVO getDataLine(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, double longitude, double latitude) { // 参数校验 @@ -150,7 +148,7 @@ public class WeatherDataServiceImpl extends ServiceImpl> result = new LinkedHashMap<>(); // 保持插入顺序 + WindDataLineVO windDataLineVO = new WindDataLineVO(); List timeList = new ArrayList<>(); List temperatureList = new ArrayList<>(); List pressureList = new ArrayList<>(); @@ -180,13 +178,117 @@ public class WeatherDataServiceImpl extends ServiceImpl 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 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 getWindRose(Integer dataType, LocalDateTime startTime, LocalDateTime endTime, + double longitude, double latitude) { + // 参数校验 + if (startTime == null || endTime == null || startTime.isAfter(endTime)) { + throw new IllegalArgumentException("时间参数无效"); + } + + List weatherDataList = weatherDataMapper.selectList( + new LambdaQueryWrapper() + .between(WeatherData::getDataStartTime, startTime, endTime) + .eq(WeatherData::getDataSource, dataType) + ); + + if (weatherDataList == null || weatherDataList.isEmpty()) { + throw new IllegalArgumentException("时间范围内没有气象数据"); + } + + int[] gridIndex = getGridIndex(latitude, longitude); + Map variables = getVariableNames(dataType); + List uValues = new ArrayList<>(); + List vValues = new ArrayList<>(); + + // 处理每个时间点数据 + LocalDateTime currentTime = startTime; + while (!currentTime.isAfter(endTime)) { + try { + if (WeatherDataSourceEnum.CRA40.getKey().equals(dataType)) { + // CRA40数据源:U、V分量在不同文件 + 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 { + // 标准数据源:U、V分量在同一文件 + 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 variables = getVariableNames(dataType); if(WeatherDataSourceEnum.CRA40.getKey() != dataType){ try (NetcdfFile ncFile = NetcdfFile.open(filePath)) { - // 读取数据(使用通用NcUtil方法) - List> tData = getVariableData(ncFile, variables.get("temperature")); - List> pData = getVariableData(ncFile, variables.get("pressure")); - List> hData = getVariableData(ncFile, variables.get("humidity")); - List> uData = getVariableData(ncFile, variables.get("windU")); - List> vData = getVariableData(ncFile, variables.get("windV")); - - // 数据处理逻辑(与第一个方法保持一致) - processAndAddData(gridIndex, currentTime, timeList, temperatureList, pressureList, - humidityList, windSpeedList, tData, pData, hData, uData, vData); + Double tValue = getNcValueByIndex(ncFile, variables.get("temperature"), gridIndex[0], gridIndex[1]) - 273.15; + Double pValue = getNcValueByIndex(ncFile, variables.get("pressure"), gridIndex[0], gridIndex[1]) / 1000; + Double hValue = getNcValueByIndex(ncFile, variables.get("humidity"), gridIndex[0], gridIndex[1]); + Double uValue = getNcValueByIndex(ncFile, variables.get("windU"), gridIndex[0], gridIndex[1]); + Double vValue = getNcValueByIndex(ncFile, variables.get("windV"), gridIndex[0], gridIndex[1]); + Double windSpeed = Math.sqrt(uValue * uValue + vValue * vValue); + // 添加格式化数据 + addFormattedData(currentTime, timeList, temperatureList, pressureList, + humidityList, windSpeedList, tValue, pValue, hValue, windSpeed); } }else { // 循环处理每个时间点的数据 @@ -560,40 +661,37 @@ public class WeatherDataServiceImpl extends ServiceImpl> tData = null; - List> pData = null; - List> hData = null; - List> uData = null; - List> vData = null; + Double tValue = null; + Double pValue = null; + Double hValue = null; + Double windSpeed = null; if (isFileValid(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)) { 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)) { 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)) { NetcdfFile uNcFile = NetcdfFile.open(uFilePath); NetcdfFile vNcFile = NetcdfFile.open(vFilePath); - uData = getVariableData(uNcFile, variables.get("windU")); - vData = getVariableData(vNcFile, variables.get("windV")); + Double uValue = getNcValueByIndex(uNcFile, variables.get("windU"), gridIndex[0], gridIndex[1]); + Double vValue = getNcValueByIndex(vNcFile, variables.get("windV"), gridIndex[0], gridIndex[1]); + windSpeed = Math.sqrt(uValue * uValue + vValue * vValue); } - - // 数据处理逻辑(与第一个方法保持一致) - processAndAddData(gridIndex, currentTime, timeList, temperatureList, pressureList, - humidityList, windSpeedList, tData, pData, hData, uData, vData); + // 添加格式化数据 + addFormattedData(currentTime, timeList, temperatureList, pressureList, + humidityList, windSpeedList, tValue, pValue, hValue, windSpeed); }catch (Exception e) { e.printStackTrace(); throw new JeecgBootException("处理气象数据失败!"); } - } - } /** @@ -632,76 +730,8 @@ public class WeatherDataServiceImpl extends ServiceImpl> getVariableData(NetcdfFile ncFile, String variableName) { - return NcUtil.get2DNCByName(ncFile, variableName, 0, 0); - } - - /** - * 数据处理与添加(增强安全性版本) - */ - private void processAndAddData(int[] gridIndex, LocalDateTime currentTime, - List timeList, List temperatureList, - List pressureList, List humidityList, - List windSpeedList, List> tData, - List> pData, List> hData, - List> uData, List> 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> 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> uData, List> vData, int[] gridIndex) { - Double uValue = getSafeGridValue(uData, gridIndex, 0.0); - Double vValue = getSafeGridValue(vData, gridIndex, 0.0); - return Math.sqrt(uValue * uValue + vValue * vValue); + private Double getNcValueByIndex(NetcdfFile ncFile, String variableName, int latSize, int lonSize) { + return NcUtil.getNcValueByIndex(ncFile, variableName, 0, 0, latSize, lonSize); } /** @@ -809,21 +839,6 @@ public class WeatherDataServiceImpl extends ServiceImpl> u, List> v) { - List> 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); - } - /** * 处理变量数据 */ diff --git a/jeecg-module-weather/src/main/java/org/jeecg/utils/WindRoseDataGenerator.java b/jeecg-module-weather/src/main/java/org/jeecg/utils/WindRoseDataGenerator.java new file mode 100644 index 0000000..9671538 --- /dev/null +++ b/jeecg-module-weather/src/main/java/org/jeecg/utils/WindRoseDataGenerator.java @@ -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 generateWindRoseData16( + List uList, + List 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 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 uList, + List vList) { + if (uList == null || vList == null) { + throw new IllegalArgumentException("U、V分量数据不能为null"); + } + + if (uList.size() != vList.size()) { + throw new IllegalArgumentException("U、V分量数据长度不一致"); + } + } +} \ No newline at end of file diff --git a/jeecg-module-weather/src/main/java/org/jeecg/vo/WindDataLineVO.java b/jeecg-module-weather/src/main/java/org/jeecg/vo/WindDataLineVO.java new file mode 100644 index 0000000..a0bf379 --- /dev/null +++ b/jeecg-module-weather/src/main/java/org/jeecg/vo/WindDataLineVO.java @@ -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 time; + public List temperature; + public List pressure; + public List humidity; + public List windSpeed; + public String mean; // 平均风速 + public String max; // 最大风速 + public String min; // 最小风速 + public String stdDev; // 风速标准差 +} diff --git a/jeecg-module-weather/src/main/java/org/jeecg/vo/WindRoseData.java b/jeecg-module-weather/src/main/java/org/jeecg/vo/WindRoseData.java new file mode 100644 index 0000000..0dd0f5e --- /dev/null +++ b/jeecg-module-weather/src/main/java/org/jeecg/vo/WindRoseData.java @@ -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; + } +}