气象降采样

This commit is contained in:
hekaiyu 2025-11-03 11:39:17 +08:00
parent b54df8b5f8
commit 20625bdcad
3 changed files with 271 additions and 71 deletions

View File

@ -0,0 +1,7 @@
package org.jeecg.common.constant;
public class WeatherStepConstants {
public static final double DEFAULT_STEP = 0.5;
}

View File

@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.WeatherPrefixConstants;
import org.jeecg.common.constant.WeatherStepConstants;
import org.jeecg.common.constant.WeatherSuffixConstants;
import org.jeecg.common.constant.enums.*;
import org.jeecg.common.exception.JeecgBootException;
@ -94,7 +95,7 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
}
/**
* 根据类型和小时数获取天气数据
* 气象预览
* @param weatherId 气象数据id
* @param weatherType 天气类型
* @return 天气数据列表
@ -669,11 +670,6 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
*/
private WeatherResultVO processWeatherData(Integer weatherType, LocalDateTime targetTime, WeatherDataSourceEnum dataTypeEnum) {
WeatherResultVO weatherResultVO = new WeatherResultVO();
if (WeatherTypeEnum.WIND.getKey().equals(weatherType)) {
return processWindData(weatherType, targetTime, dataTypeEnum, weatherResultVO);
}
String filePath = buildFilePath(targetTime, weatherType, dataTypeEnum);
validateFile(filePath);
@ -682,9 +678,27 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
List<Double> latData = NcUtil.getNCList(ncFile, "lat");
validateLonLatData(lonData, latData, filePath);
List<List<Double>> dataList = processWeatherTypeData(ncFile, lonData, latData, weatherType, dataTypeEnum.getKey());
processResultData(weatherResultVO, dataList, lonData, latData);
ValueConverter converter = null;
if (WeatherTypeEnum.WIND.getKey().equals(weatherType)) {
converter = value -> value;
List<List<List<Double>>> windDataList = processWindData(weatherType, targetTime, dataTypeEnum, weatherResultVO);
processResultDataInternal(weatherResultVO, null, windDataList.get(0), windDataList.get(1), lonData, latData, converter);
}else{
List<List<Double>> dataList;
if (WeatherTypeEnum.TEMPERATURE.getKey().equals(weatherType)) {
converter = value -> value - 273.15;
dataList = processVariableData(ncFile, dataTypeEnum.getKey(), WeatherTypeEnum.TEMPERATURE);
} else if (WeatherTypeEnum.PRESSURE.getKey().equals(weatherType)) {
converter = value -> value / 1000;
dataList = processVariableData(ncFile, dataTypeEnum.getKey(), WeatherTypeEnum.PRESSURE);
} else if (WeatherTypeEnum.HUMIDITY.getKey().equals(weatherType)) {
converter = value -> value;
dataList = processVariableData(ncFile, dataTypeEnum.getKey(), WeatherTypeEnum.HUMIDITY);
} else {
throw new JeecgBootException("未知天气类型!");
}
processResultDataInternal(weatherResultVO, dataList, null, null, lonData, latData, converter);
}
} catch (IOException e) {
log.error("NetCDF文件处理失败: {}", filePath, e);
throw new JeecgBootException("文件读取失败", e);
@ -696,8 +710,9 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
/**
* 处理风场数据
*/
private WeatherResultVO processWindData(Integer weatherType, LocalDateTime targetTime,
private List<List<List<Double>>> processWindData(Integer weatherType, LocalDateTime targetTime,
WeatherDataSourceEnum dataTypeEnum, WeatherResultVO weatherResultVO) {
List<List<List<Double>>> windDataList = new ArrayList<>();
if (!WeatherDataSourceEnum.CRA40.equals(dataTypeEnum)) {
String filePath = buildFilePath(targetTime, weatherType, dataTypeEnum);
validateFile(filePath);
@ -707,8 +722,8 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
WeatherVariableNameEnum.getValueByTypeAndKey(dataTypeEnum.getKey(), WeatherTypeEnum.WIND.getKey()), 0, 0);
List<List<Double>> v = NcUtil.get2DNCByName(ncFile,
WeatherVariableNameEnum.getValueByTypeAndKey(dataTypeEnum.getKey(), WeatherTypeEnum.WIND.getKey() + 1), 0, 0);
setWindResult(weatherResultVO, u, v);
windDataList.add(u);
windDataList.add(v);
} catch (IOException e) {
log.error("NetCDF文件处理失败", e);
throw new JeecgBootException("文件读取失败", e);
@ -727,13 +742,14 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
List<List<Double>> v = NcUtil.get2DNCByName(vNcFile,
WeatherVariableNameEnum.getValueByTypeAndKey(dataTypeEnum.getKey(), WeatherTypeEnum.WIND.getKey() + 1), 0, 0);
setWindResult(weatherResultVO, u, v);
windDataList.add(u);
windDataList.add(v);
} catch (IOException e) {
log.error("NetCDF文件处理失败", e);
throw new JeecgBootException("文件读取失败", e);
}
}
return weatherResultVO;
return windDataList;
}
/**
@ -751,31 +767,13 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
weatherResultVO.setDataList(results);
}
/**
* 处理天气类型数据
*/
private List<List<Double>> processWeatherTypeData(NetcdfFile ncFile, List<Double> lonData, List<Double> latData,
Integer weatherType, int dataType) {
if (WeatherTypeEnum.TEMPERATURE.getKey().equals(weatherType)) {
return processVariableData(ncFile, lonData, latData, dataType, WeatherTypeEnum.TEMPERATURE, value -> value - 273.15);
} else if (WeatherTypeEnum.PRESSURE.getKey().equals(weatherType)) {
return processVariableData(ncFile, lonData, latData, dataType, WeatherTypeEnum.PRESSURE, value -> value / 1000);
} else if (WeatherTypeEnum.HUMIDITY.getKey().equals(weatherType)) {
return processVariableData(ncFile, lonData, latData, dataType, WeatherTypeEnum.HUMIDITY, value -> value);
} else {
throw new JeecgBootException("未知天气类型!");
}
}
/**
* 处理变量数据
*/
private List<List<Double>> processVariableData(NetcdfFile ncFile, List<Double> lonData, List<Double> latData,
int dataType, WeatherTypeEnum weatherType, ValueConverter converter) {
private List<List<Double>> processVariableData(NetcdfFile ncFile, int dataType, WeatherTypeEnum weatherType) {
try {
String variableName = WeatherVariableNameEnum.getValueByTypeAndKey(dataType, weatherType.getKey());
List<List<Double>> dataList = NcUtil.get2DNCByName(ncFile, variableName, 0, 0);
return processWeatherData(dataList, lonData, latData, converter);
return NcUtil.get2DNCByName(ncFile, variableName, 0, 0);
} catch (Exception e) {
log.error("处理{}数据失败", weatherType.name(), e);
return Collections.emptyList();
@ -850,17 +848,230 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
/**
* 处理结果数据
*/
private void processResultData(WeatherResultVO weatherResultVO, List<List<Double>> dataList,
List<Double> lonData, List<Double> latData) {
List<Double> flattenedList = new ArrayList<>();
flattenList(dataList, flattenedList);
List<Double> maxMin = getMaxMin(flattenedList);
/**
* 内部处理方法
*/
private void processResultDataInternal(WeatherResultVO weatherResultVO, List<List<Double>> dataList,
List<List<Double>> uDataList, List<List<Double>> vDataList,
List<Double> lonData, List<Double> latData, ValueConverter converter) {
// 统一将采样率降为0.5
double originalStep = getOriginalStep(lonData, latData);
double targetStep = WeatherStepConstants.DEFAULT_STEP;
weatherResultVO.setMax(maxMin.get(0));
weatherResultVO.setMin(maxMin.get(1));
if (originalStep < targetStep - 0.001) { // 添加容差避免浮点数比较问题
// 需要降采样
int ratio = calculateSafeRatio(originalStep, targetStep);
// 根据数据类型进行降采样
if (dataList != null) {
dataList = downsampleData(dataList, ratio);
} else if (uDataList != null && vDataList != null) {
uDataList = downsampleData(uDataList, ratio);
vDataList = downsampleData(vDataList, ratio);
}
lonData = downsampleLonLat(lonData, ratio);
latData = downsampleLonLat(latData, ratio);
}
List<Double> maxMinLon = getMaxMin(lonData);
List<Double> maxMinLat = getMaxMin(latData);
weatherResultVO.setMaxLong(maxMinLon.get(0));
weatherResultVO.setMinLong(maxMinLon.get(1));
weatherResultVO.setMaxLat(maxMinLat.get(0));
weatherResultVO.setMinLat(maxMinLat.get(1));
weatherResultVO.setSn(latData.size());
weatherResultVO.setWe(lonData.size());
weatherResultVO.setDataList(dataList);
weatherResultVO.setXSpeed(targetStep);
weatherResultVO.setYSpeed(targetStep);
// 根据数据类型选择不同的处理方法
if (dataList != null) {
// 处理普通气象数据
List<Double> flattenedList = new ArrayList<>();
flattenList(dataList, flattenedList);
List<Double> maxMinValue = getMaxMin(flattenedList);
weatherResultVO.setMax(maxMinValue.get(0));
weatherResultVO.setMin(maxMinValue.get(1));
weatherResultVO.setDataList(resultWeatherData(dataList, lonData, latData, converter));
} else if (uDataList != null && vDataList != null) {
// 处理风场数据
List<Double> flattenedUList = new ArrayList<>();
List<Double> flattenedVList = new ArrayList<>();
flattenList(uDataList, flattenedUList);
flattenList(vDataList, flattenedVList);
// 风场的最大值最小值可以基于风速大小计算或者分别处理UV分量
List<Double> maxMinU = getMaxMin(flattenedUList);
List<Double> maxMinV = getMaxMin(flattenedVList);
weatherResultVO.setMaxU(maxMinU.get(0));
weatherResultVO.setMinU(maxMinU.get(1));
weatherResultVO.setMaxV(maxMinV.get(0));
weatherResultVO.setMinV(maxMinV.get(1));
weatherResultVO.setDataList(resultWindData(uDataList, vDataList, lonData, latData, converter));
}
}
/**
* 处理风场数据只有UV分量
*/
private List<List<Double>> resultWindData(List<List<Double>> uDataList, List<List<Double>> vDataList,
List<Double> lonData, List<Double> latData, ValueConverter converter) {
List<List<Double>> results = new ArrayList<>();
for (int i = 0; i < latData.size(); i++) {
for (int j = 0; j < lonData.size(); j++) {
// 获取风场UV分量
double uValue = j < uDataList.get(i).size() ? uDataList.get(i).get(j) : uDataList.get(i).get(0);
double vValue = j < vDataList.get(i).size() ? vDataList.get(i).get(j) : vDataList.get(i).get(0);
double roundedU = Math.round(converter.convert(uValue) * 1000.0) / 1000.0;
double roundedV = Math.round(converter.convert(vValue) * 1000.0) / 1000.0;
// 风场数据格式[经度, 纬度, U分量, V分量]
results.add(List.of(lonData.get(j), latData.get(i), roundedU, roundedV));
}
}
return results;
}
/**
* 处理普通气象数据保持原有逻辑
*/
private List<List<Double>> resultWeatherData(List<List<Double>> dataList, List<Double> lonData,
List<Double> latData, ValueConverter converter) {
List<List<Double>> results = new ArrayList<>();
for (int i = 0; i < latData.size(); i++) {
for (int j = 0; j < lonData.size(); j++) {
double value = j < dataList.get(i).size() ? dataList.get(i).get(j) : dataList.get(i).get(0);
double roundedValue = Math.round(converter.convert(value) * 1000.0) / 1000.0;
results.add(List.of(lonData.get(j), latData.get(i), roundedValue));
}
}
return results;
}
/**
* 改进的步长计算方法考虑经纬度点数
*/
private double getOriginalStep(List<Double> lonData, List<Double> latData) {
if (lonData.size() < 2 || latData.size() < 2) {
return WeatherStepConstants.DEFAULT_STEP;
}
// 方法1直接计算相邻点的差值
double directLonStep = Math.abs(lonData.get(1) - lonData.get(0));
double directLatStep = Math.abs(latData.get(1) - latData.get(0));
// 方法2根据点数估算步长用于验证
double estimatedLonStep = 360.0 / (lonData.size() - 1);
double estimatedLatStep = 180.0 / (latData.size() - 1);
// 优先使用直接计算的结果如果异常则使用估算值
double lonStep = isValidStep(directLonStep) ? directLonStep : estimatedLonStep;
double latStep = isValidStep(directLatStep) ? directLatStep : estimatedLatStep;
System.out.println("直接计算步长 - 经度: " + directLonStep + ", 纬度: " + directLatStep);
System.out.println("估算步长 - 经度: " + estimatedLonStep + ", 纬度: " + estimatedLatStep);
return Math.min(lonStep, latStep);
}
/**
* 验证步长是否合理
*/
private boolean isValidStep(double step) {
return step > 0.01 && step < 10.0; // 合理的步长范围
}
/**
* 安全计算降采样比例避免浮点数精度问题
*/
private int calculateSafeRatio(double originalStep, double targetStep) {
// 常见分辨率的精确匹配
if (Math.abs(originalStep - 0.1) < 0.001) {
return 5; // 0.1° -> 0.5°比例5:1
} else if (Math.abs(originalStep - 0.25) < 0.001) {
return 2; // 0.25° -> 0.5°比例2:1
} else if (Math.abs(originalStep - 0.5) < 0.001) {
return 1; // 0.5°不降采样
} else {
// 通用计算四舍五入
double ratio = targetStep / originalStep;
return (int) Math.round(ratio);
}
}
/**
* 改进的二维数据降采样处理边界情况
*/
private List<List<Double>> downsampleData(List<List<Double>> originalData, int ratio) {
if (originalData == null || originalData.isEmpty() || ratio <= 1) {
return originalData;
}
int originalRows = originalData.size();
int originalCols = originalData.isEmpty() ? 0 : originalData.get(0).size();
List<List<Double>> downsampled = new ArrayList<>();
for (int i = 0; i < originalRows; i += ratio) {
if (i >= originalData.size()) break;
List<Double> originalRow = originalData.get(i);
List<Double> newRow = new ArrayList<>();
for (int j = 0; j < originalCols; j += ratio) {
if (j < originalRow.size()) {
newRow.add(originalRow.get(j));
}
}
if (!newRow.isEmpty()) {
downsampled.add(newRow);
}
}
System.out.println("数据降采样: " + originalRows + "x" + originalCols + " -> " +
downsampled.size() + "x" + (downsampled.isEmpty() ? 0 : downsampled.get(0).size()));
return downsampled;
}
/**
* 改进的经纬度降采样
*/
private List<Double> downsampleLonLat(List<Double> originalList, int ratio) {
if (originalList == null || originalList.isEmpty() || ratio <= 1) {
return originalList;
}
List<Double> downsampled = new ArrayList<>();
for (int i = 0; i < originalList.size(); i += ratio) {
downsampled.add(originalList.get(i));
}
System.out.println("经纬度降采样: " + originalList.size() + " -> " + downsampled.size());
return downsampled;
}
// 原有的辅助方法保持不变
private void flattenList(List<List<Double>> dataList, List<Double> flattenedList) {
for (List<Double> row : dataList) {
flattenedList.addAll(row);
}
}
private List<Double> getMaxMin(List<Double> list) {
if (list == null || list.isEmpty()) {
return Arrays.asList(0.0, 0.0);
}
double max = Collections.max(list);
double min = Collections.min(list);
return Arrays.asList(max, min);
}
/**
@ -900,34 +1111,6 @@ public class WeatherDataServiceImpl extends ServiceImpl<WeatherDataMapper, Weath
return Files.exists(path) && !Files.isDirectory(path);
}
private List<List<Double>> processWeatherData(List<List<Double>> dataList, List<Double> lonData,
List<Double> latData, ValueConverter converter) {
List<List<Double>> results = new ArrayList<>();
for (int i = 0; i < latData.size(); i++) {
for (int j = 0; j < lonData.size(); j++) {
double value = j < dataList.get(i).size() ? dataList.get(i).get(j) : dataList.get(i).get(0);
double roundedValue = Math.round(converter.convert(value) * 1000.0) / 1000.0;
results.add(List.of(lonData.get(j), latData.get(i), roundedValue));
}
}
return results;
}
public void flattenList(List<List<Double>> nestedList, List<Double> flattenedList) {
for (List<Double> subList : nestedList) {
if (subList.size() >= 3) {
flattenedList.add(subList.get(2));
}
}
}
public static List<Double> getMaxMin(List<Double> flattenedList) {
if (flattenedList == null || flattenedList.isEmpty()) {
return List.of(0.0, 0.0);
}
return List.of(Collections.max(flattenedList), Collections.min(flattenedList));
}
/**
* 根据经纬度获取网格下标
* @param latitude 纬度(-90到90)

View File

@ -6,9 +6,19 @@ import java.util.List;
@Data
public class WeatherResultVO {
private List<List<Double>> dataList;
private double max;
private double min;
private double maxU;
private double minU;
private double maxV;
private double minV;
private double maxLat;
private double minLat;
private double maxLong;
private double minLong;
private int sn;
private int we;
private double xSpeed ;
private double ySpeed;
private List<List<Double>> dataList;
}