|
@@ -10,12 +10,30 @@
|
|
|
*/
|
|
|
package com.ruoyi.ems.service.forecast;
|
|
|
|
|
|
+import com.huashe.common.domain.model.WeatherForecast;
|
|
|
+import com.huashe.common.utils.DateUtils;
|
|
|
+import com.ruoyi.ems.domain.ElecProdForecast;
|
|
|
+import com.ruoyi.ems.domain.ElecPvSupplyH;
|
|
|
+import com.ruoyi.ems.mapper.ElecProdForecastMapper;
|
|
|
+import com.ruoyi.ems.model.QueryMeter;
|
|
|
import com.ruoyi.ems.service.IElecPvSupplyHService;
|
|
|
import com.ruoyi.ems.service.IWeatherService;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.time.LocalDate;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
|
|
|
/**
|
|
|
* 产能预测
|
|
@@ -28,9 +46,17 @@ import java.time.LocalDate;
|
|
|
*/
|
|
|
@Service
|
|
|
public class ElecProdForecastService {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ElecProdForecastService.class);
|
|
|
+
|
|
|
+ // 天气影响因子
|
|
|
+ private final static Map<String, Double> weatherFactorMap = new HashMap<>();
|
|
|
+
|
|
|
+ private final DateTimeFormatter dateForm = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+
|
|
|
/**
|
|
|
* 光伏产能预测数据服务
|
|
|
*/
|
|
|
+ @Resource
|
|
|
private IElecPvSupplyHService pvSupplyHService;
|
|
|
|
|
|
/**
|
|
@@ -39,15 +65,177 @@ public class ElecProdForecastService {
|
|
|
@Resource
|
|
|
private IWeatherService weatherService;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private ElecProdForecastMapper forecastMapper;
|
|
|
+
|
|
|
+ static {
|
|
|
+ // 初始化默认影响因子
|
|
|
+ weatherFactorMap.put("1101", 1.0); // 晴
|
|
|
+ weatherFactorMap.put("1103", 0.8); // 多云
|
|
|
+ weatherFactorMap.put("1106", 0.4); // 阴天
|
|
|
+ weatherFactorMap.put("2100", 0.5); // 雨
|
|
|
+ weatherFactorMap.put("2101", 0.6); // 小雨
|
|
|
+ weatherFactorMap.put("2102", 0.5); // 中雨
|
|
|
+ weatherFactorMap.put("2103", 0.4); // 大雨
|
|
|
+ weatherFactorMap.put("2104", 0.3); // 暴雨
|
|
|
+ weatherFactorMap.put("2111", 0.7); // 阵雨
|
|
|
+ weatherFactorMap.put("2121", 0.7); // 雷阵雨
|
|
|
+ weatherFactorMap.put("2200", 0.3); // 雪
|
|
|
+ weatherFactorMap.put("2201", 0.3); // 小雪
|
|
|
+ weatherFactorMap.put("2202", 0.3); // 中雪
|
|
|
+ weatherFactorMap.put("2203", 0.2); // 大雪
|
|
|
+ weatherFactorMap.put("2204", 0.1); // 暴雪
|
|
|
+ weatherFactorMap.put("2211", 0.4); // 雨夹雪
|
|
|
+ weatherFactorMap.put("4101", 0.8); // 轻雾
|
|
|
+ weatherFactorMap.put("4102", 0.7); // 大雾
|
|
|
+ weatherFactorMap.put("4103", 0.6); // 浓雾
|
|
|
+ weatherFactorMap.put("4200", 0.5); // 霾
|
|
|
+ weatherFactorMap.put("4301", 0.5); // 浮尘
|
|
|
+ weatherFactorMap.put("4302", 0.4); // 扬沙
|
|
|
+ weatherFactorMap.put("4303", 0.3); // 沙尘暴
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 执行光伏产能预测
|
|
|
*
|
|
|
- * @param areaCode 园区代码
|
|
|
- * @param facsCode 设施编码
|
|
|
- * @param today 当前日期
|
|
|
+ * @param areaCode 园区代码
|
|
|
+ * @param facsCode 设施编码
|
|
|
+ * @param today 当前日期
|
|
|
* @param forecastDay 预测天数
|
|
|
*/
|
|
|
- public void executePvForecast(String areaCode, String facsCode, LocalDate today, int forecastDay) {
|
|
|
+ public void executePvForecast(String adcode, String areaCode, String facsCode, LocalDate today, int forecastDay) {
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 读取历史数据 (使用过去30天的数据作为训练集)
|
|
|
+ LocalDate historyStart = today.minusDays(30);
|
|
|
+ LocalDate historyEnd = today.minusDays(1);
|
|
|
+ QueryMeter queryMeter = new QueryMeter(areaCode, facsCode, historyStart.format(dateForm),
|
|
|
+ historyEnd.format(dateForm));
|
|
|
+ List<ElecPvSupplyH> historicalData = pvSupplyHService.selectPvSupplyDayList(queryMeter);
|
|
|
+
|
|
|
+ if (historicalData.isEmpty()) {
|
|
|
+ // 没有历史数据,无法进行预测
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 读取天气预报
|
|
|
+ LocalDate forecastStart = today;
|
|
|
+ LocalDate forecastEnd = today.plusDays(forecastDay - 1);
|
|
|
+ List<WeatherForecast> weatherForecasts = weatherService.getWeatherForecast(adcode,
|
|
|
+ forecastStart.format(dateForm), forecastEnd.format(dateForm));
|
|
|
+ Map<String, WeatherForecast> weatherMap = convertToWeatherMap(weatherForecasts);
|
|
|
+ List<ElecProdForecast> result = new ArrayList<>();
|
|
|
+
|
|
|
+ while (forecastStart.isBefore(forecastEnd) || forecastStart.isEqual(forecastEnd)) {
|
|
|
+ WeatherForecast weatherForecast = weatherMap.get(forecastStart.format(dateForm));
|
|
|
+ ElecProdForecast elecProdForecast = predictDailyProduction(areaCode, facsCode, forecastStart,
|
|
|
+ historicalData, weatherForecast);
|
|
|
+ result.add(elecProdForecast);
|
|
|
+ forecastStart = forecastStart.plusDays(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存数据到数据库
|
|
|
+ forecastMapper.deleteByCondition(areaCode, facsCode, today.format(dateForm), forecastEnd.format(dateForm));
|
|
|
+
|
|
|
+ if (CollectionUtils.isNotEmpty(result)) {
|
|
|
+ forecastMapper.insertBatch(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e) {
|
|
|
+ log.error("execute Pv forecast fail! areaCode:{}, facsCode:{}", areaCode, facsCode, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ElecProdForecast predictDailyProduction(String areaCode, String facsCode, LocalDate date,
|
|
|
+ List<ElecPvSupplyH> trainingData, WeatherForecast weatherForecast) {
|
|
|
+ // 计算历史平均日发电量(保留两位小数)
|
|
|
+ double totalGenElec = trainingData.stream().mapToDouble(ElecPvSupplyH::getGenElecQuantity).sum();
|
|
|
+ double historicalAvgDailyProduction = divideAndRound(totalGenElec, trainingData.size(), 2);
|
|
|
+
|
|
|
+ // 计算历史平均上网电量(保留两位小数)
|
|
|
+ double totalUpElec = trainingData.stream().mapToDouble(ElecPvSupplyH::getUpElecQuantity).sum();
|
|
|
+ double upElecQuantity = divideAndRound(totalUpElec, trainingData.size(), 2);
|
|
|
+
|
|
|
+ // 获取天气影响因子
|
|
|
+ double weatherFactor = getWeatherFactor(weatherForecast);
|
|
|
+
|
|
|
+ // 计算预测值 = 历史平均值 * 天气影响因子
|
|
|
+ double forecastProduction = multiplyAndRound(historicalAvgDailyProduction, weatherFactor, 2);
|
|
|
+
|
|
|
+ // 计算平均功率 (kW)
|
|
|
+ double avgPower = divideAndRound(forecastProduction, 24.0, 2);
|
|
|
+
|
|
|
+ ElecProdForecast forecast = new ElecProdForecast();
|
|
|
+ forecast.setAreaCode(areaCode);
|
|
|
+ forecast.setFacsCode(facsCode);
|
|
|
+ forecast.setDate(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()));
|
|
|
+ forecast.setElecProdQuantity(forecastProduction);
|
|
|
+ forecast.setAvgPower(avgPower);
|
|
|
+ forecast.setUpElecQuantity(upElecQuantity);
|
|
|
+ return forecast;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double getWeatherFactor(WeatherForecast forecast) {
|
|
|
+ Double factor = null;
|
|
|
+
|
|
|
+ if (null != forecast) {
|
|
|
+ // 优先使用日间天气
|
|
|
+ String weatherCode = forecast.getDayWeatherType();
|
|
|
+ factor = weatherFactorMap.get(weatherCode);
|
|
|
+
|
|
|
+ // 如果日间天气没有影响因子,使用夜间天气
|
|
|
+ if (factor == null) {
|
|
|
+ weatherCode = forecast.getNightWeatherType();
|
|
|
+ factor = weatherFactorMap.get(weatherCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果仍然没有,使用默认值
|
|
|
+ factor = factor != null ? factor : 0.8;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ factor = 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ return factor;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, WeatherForecast> convertToWeatherMap(List<WeatherForecast> weatherForecasts) {
|
|
|
+ Map<String, WeatherForecast> weatherMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (WeatherForecast weatherForecast : weatherForecasts) {
|
|
|
+ String date = DateUtils.dateToString(weatherForecast.getDate(), "yyyy-MM-dd");
|
|
|
+ weatherMap.put(date, weatherForecast);
|
|
|
+ }
|
|
|
+
|
|
|
+ return weatherMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 除法运算并保留指定小数位数
|
|
|
+ * @param dividend 被除数
|
|
|
+ * @param divisor 除数
|
|
|
+ * @param scale 保留小数位数
|
|
|
+ * @return 四舍五入后的结果
|
|
|
+ */
|
|
|
+ private double divideAndRound(double dividend, double divisor, int scale) {
|
|
|
+ if (divisor == 0) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+ return BigDecimal.valueOf(dividend / divisor)
|
|
|
+ .setScale(scale, RoundingMode.HALF_UP)
|
|
|
+ .doubleValue();
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * 乘法运算并保留指定小数位数
|
|
|
+ * @param a 乘数1
|
|
|
+ * @param b 乘数2
|
|
|
+ * @param scale 保留小数位数
|
|
|
+ * @return 四舍五入后的结果
|
|
|
+ */
|
|
|
+ private double multiplyAndRound(double a, double b, int scale) {
|
|
|
+ return BigDecimal.valueOf(a * b)
|
|
|
+ .setScale(scale, RoundingMode.HALF_UP)
|
|
|
+ .doubleValue();
|
|
|
}
|
|
|
}
|