初始提交:添加分时处理
This commit is contained in:
parent
c6da443080
commit
0ff1fd3cef
@ -5,6 +5,7 @@ import com.southern.power.grid.entity.DnerHourlyPowerOutageEvent;
|
|||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,4 +26,8 @@ public interface DnerHourlyPowerOutageEventMapper extends BaseMapper<DnerHourlyP
|
|||||||
* @return 插入成功的条数
|
* @return 插入成功的条数
|
||||||
*/
|
*/
|
||||||
int batchInsert(List<DnerHourlyPowerOutageEvent> list);
|
int batchInsert(List<DnerHourlyPowerOutageEvent> list);
|
||||||
|
|
||||||
|
List<DnerHourlyPowerOutageEvent> selectByDistrictsAndTime(@Param("districtCodes") List<String> districtCodes,
|
||||||
|
@Param("startTime") LocalDateTime startTime,
|
||||||
|
@Param("endTime") LocalDateTime endTime);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.southern.power.grid.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分时统计结果DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class HourlyStatisticDTO {
|
||||||
|
private List<String> timePoints; // 时间点列表,格式:yyyy-MM-dd HH:00:00
|
||||||
|
private List<Long> userCounts; // 停电影响用户数
|
||||||
|
private List<Double> precipitations; // 小时降水量
|
||||||
|
private List<Double> avgTemperatures; // 平均气温
|
||||||
|
private List<Double> maxTemperatures; // 最高气温
|
||||||
|
private List<Double> minTemperatures; // 最低气温
|
||||||
|
private List<Double> maxWindSpeeds; // 极大风速
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ public class WeatherDataDTO {
|
|||||||
private String temperature;
|
private String temperature;
|
||||||
private String hourlyMaxTemperature;
|
private String hourlyMaxTemperature;
|
||||||
private String hourlyMinTemperature;
|
private String hourlyMinTemperature;
|
||||||
private String hourlyPrecipitation;
|
private Double hourlyPrecipitation;
|
||||||
private String dailyPrecipitation;
|
private String dailyPrecipitation;
|
||||||
private String extremeWindSpeedHourly;
|
private String extremeWindSpeedHourly;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import com.southern.power.grid.entity.WeatherDataDTO;
|
|||||||
import com.southern.power.grid.entity.WeatherSiteAreaConfiguration;
|
import com.southern.power.grid.entity.WeatherSiteAreaConfiguration;
|
||||||
import com.southern.power.grid.service.WeatherDataService;
|
import com.southern.power.grid.service.WeatherDataService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -51,7 +50,7 @@ public class HourlyOutageExcelProcessService {
|
|||||||
* 对外主入口:处理一批 Excel 解析后的数据,按逻辑补齐气象数据并批量入库
|
* 对外主入口:处理一批 Excel 解析后的数据,按逻辑补齐气象数据并批量入库
|
||||||
*/
|
*/
|
||||||
public void process(List<DataExcelEntity> excelRows) {
|
public void process(List<DataExcelEntity> excelRows) {
|
||||||
// 1. 加载南网配置表
|
// 1. 加载南网区划配置表
|
||||||
loadNwAreaConfig();
|
loadNwAreaConfig();
|
||||||
// 2. 加载气象区划配置表
|
// 2. 加载气象区划配置表
|
||||||
loadWeatherAreaConfig();
|
loadWeatherAreaConfig();
|
||||||
@ -90,6 +89,9 @@ public class HourlyOutageExcelProcessService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载南网区划配置
|
||||||
|
* **/
|
||||||
private void loadNwAreaConfig() {
|
private void loadNwAreaConfig() {
|
||||||
nwAreaMap.clear();
|
nwAreaMap.clear();
|
||||||
List<NwSiteAreaConfiguration> list = nwSiteAreaConfigurationMapper.selectAll();
|
List<NwSiteAreaConfiguration> list = nwSiteAreaConfigurationMapper.selectAll();
|
||||||
@ -103,6 +105,9 @@ public class HourlyOutageExcelProcessService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载气象区划配置表
|
||||||
|
* **/
|
||||||
private void loadWeatherAreaConfig() {
|
private void loadWeatherAreaConfig() {
|
||||||
weatherAreaMap.clear();
|
weatherAreaMap.clear();
|
||||||
List<WeatherSiteAreaConfiguration> list = weatherSiteAreaConfigurationMapper.selectAll();
|
List<WeatherSiteAreaConfiguration> list = weatherSiteAreaConfigurationMapper.selectAll();
|
||||||
@ -146,7 +151,7 @@ public class HourlyOutageExcelProcessService {
|
|||||||
e.setTemperature(weatherData.getTemperature());
|
e.setTemperature(weatherData.getTemperature());
|
||||||
e.setHourlyMaxTemperature(weatherData.getHourlyMaxTemperature());
|
e.setHourlyMaxTemperature(weatherData.getHourlyMaxTemperature());
|
||||||
e.setHourlyMinTemperature(weatherData.getHourlyMinTemperature());
|
e.setHourlyMinTemperature(weatherData.getHourlyMinTemperature());
|
||||||
e.setHourlyPrecipitation(weatherData.getHourlyPrecipitation());
|
// e.setHourlyPrecipitation(weatherData.getHourlyPrecipitation());
|
||||||
e.setDailyPrecipitation(weatherData.getDailyPrecipitation());
|
e.setDailyPrecipitation(weatherData.getDailyPrecipitation());
|
||||||
e.setExtremeWindSpeedHourly(weatherData.getExtremeWindSpeedHourly());
|
e.setExtremeWindSpeedHourly(weatherData.getExtremeWindSpeedHourly());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,411 @@
|
|||||||
|
package com.southern.power.grid.service.impl;
|
||||||
|
|
||||||
|
import com.southern.power.grid.dao.DnerHourlyPowerOutageEventMapper;
|
||||||
|
import com.southern.power.grid.dao.NwSiteAreaConfigurationMapper;
|
||||||
|
import com.southern.power.grid.dao.WeatherSiteAreaConfigurationMapper;
|
||||||
|
import com.southern.power.grid.entity.DnerHourlyPowerOutageEvent;
|
||||||
|
import com.southern.power.grid.entity.HourlyStatisticDTO;
|
||||||
|
import com.southern.power.grid.entity.NwSiteAreaConfiguration;
|
||||||
|
import com.southern.power.grid.entity.WeatherDataDTO;
|
||||||
|
import com.southern.power.grid.entity.WeatherSiteAreaConfiguration;
|
||||||
|
import com.southern.power.grid.service.WeatherDataService;
|
||||||
|
import com.southern.power.grid.utils.TimeUtil;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class HourlyStatisticService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private NwSiteAreaConfigurationMapper nwSiteAreaConfigurationMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WeatherSiteAreaConfigurationMapper weatherSiteAreaConfigurationMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WeatherDataService weatherDataService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DnerHourlyPowerOutageEventMapper dnerHourlyPowerOutageEventMapper;
|
||||||
|
|
||||||
|
private static final DateTimeFormatter TIME_POINT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分时统计全量数据(包含所有指标)
|
||||||
|
* @param areaType 区域类型:province-省,city-市,county-县
|
||||||
|
* @param areaCode 区域编码(省/市/县编码,县为org_code)
|
||||||
|
* @param startTime 开始时间(包含)
|
||||||
|
* @param endTime 结束时间(包含)
|
||||||
|
* @return 分时统计结果
|
||||||
|
*/
|
||||||
|
public HourlyStatisticDTO getHourlyStatistics(String areaType, String areaCode,
|
||||||
|
LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
// 1. 生成时间点列表(整点)
|
||||||
|
List<LocalDateTime> timePoints = generateTimePoints(startTime, endTime);
|
||||||
|
if (timePoints.isEmpty()) {
|
||||||
|
return new HourlyStatisticDTO();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 根据区域类型获取需要统计的区县编码列表
|
||||||
|
List<String> districtCodes = getDistrictCodesByArea(areaType, areaCode);
|
||||||
|
if (districtCodes.isEmpty()) {
|
||||||
|
return new HourlyStatisticDTO();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 分别计算各指标
|
||||||
|
HourlyStatisticDTO result = new HourlyStatisticDTO();
|
||||||
|
result.setTimePoints(timePoints.stream().map(t -> t.format(TIME_POINT_FORMATTER)).collect(Collectors.toList()));
|
||||||
|
|
||||||
|
// 3.1 停电用户数(直接查分时停电事件表)
|
||||||
|
Map<LocalDateTime, Long> userCountMap = calculateUserCount(districtCodes, timePoints);
|
||||||
|
result.setUserCounts(fillList(timePoints, userCountMap, 0L));
|
||||||
|
|
||||||
|
// 3.2 降水量(需要多站点平均)
|
||||||
|
Map<LocalDateTime, Double> precipMap = calculatePrecipitation(districtCodes, timePoints);
|
||||||
|
result.setPrecipitations(fillList(timePoints, precipMap, 0.0));
|
||||||
|
|
||||||
|
// 3.3 气温(平均/最高/最低)
|
||||||
|
TemperatureResult tempResult = calculateTemperature(districtCodes, timePoints);
|
||||||
|
result.setAvgTemperatures(fillList(timePoints, tempResult.getAvgMap(), 0.0));
|
||||||
|
result.setMaxTemperatures(fillList(timePoints, tempResult.getMaxMap(), 0.0));
|
||||||
|
result.setMinTemperatures(fillList(timePoints, tempResult.getMinMap(), 0.0));
|
||||||
|
|
||||||
|
// 3.4 风速(极大值)
|
||||||
|
Map<LocalDateTime, Double> windMap = calculateWindSpeed(districtCodes, timePoints);
|
||||||
|
result.setMaxWindSpeeds(fillList(timePoints, windMap, 0.0));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成整点时间序列(从startTime的整点开始,到endTime的整点结束,包含首尾)
|
||||||
|
*/
|
||||||
|
private List<LocalDateTime> generateTimePoints(LocalDateTime start, LocalDateTime end) {
|
||||||
|
List<LocalDateTime> points = new ArrayList<>();
|
||||||
|
LocalDateTime current = start.withMinute(0).withSecond(0).withNano(0);
|
||||||
|
LocalDateTime endPoint = end.withMinute(0).withSecond(0).withNano(0);
|
||||||
|
while (!current.isAfter(endPoint)) {
|
||||||
|
points.add(current);
|
||||||
|
current = current.plusHours(1);
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据区域类型获取所有需要统计的区县编码列表
|
||||||
|
*/
|
||||||
|
private List<String> getDistrictCodesByArea(String areaType, String areaCode) {
|
||||||
|
if ("county".equalsIgnoreCase(areaType)) {
|
||||||
|
return Collections.singletonList(areaCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询南网配置表,根据省/市编码获取所有区县编码
|
||||||
|
List<NwSiteAreaConfiguration> list;
|
||||||
|
if ("province".equalsIgnoreCase(areaType)) {
|
||||||
|
list = new ArrayList<>();// nwSiteAreaConfigurationMapper.selectByProvince(areaCode);
|
||||||
|
} else if ("city".equalsIgnoreCase(areaType)) {
|
||||||
|
list = new ArrayList<>(); // nwSiteAreaConfigurationMapper.selectByCity(areaCode);
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(list)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return list.stream()
|
||||||
|
.map(NwSiteAreaConfiguration::getDistrictCode)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 停电用户数统计 =====================
|
||||||
|
private Map<LocalDateTime, Long> calculateUserCount(List<String> districtCodes, List<LocalDateTime> timePoints) {
|
||||||
|
// 查询所有区县在时间范围内的停电事件记录
|
||||||
|
List<DnerHourlyPowerOutageEvent> events = dnerHourlyPowerOutageEventMapper.selectByDistrictsAndTime(
|
||||||
|
districtCodes, timePoints.get(0), timePoints.get(timePoints.size() - 1).plusHours(1));
|
||||||
|
|
||||||
|
// 按区县和整点时刻聚合user_count
|
||||||
|
Map<LocalDateTime, Long> result = new HashMap<>();
|
||||||
|
for (LocalDateTime tp : timePoints) {
|
||||||
|
result.put(tp, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(events)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:data_time是停电开始时间,需按照“停电开始时间 > 整点时刻”的规则计入该整点
|
||||||
|
// 但我们表中没有结束时间,无法精确统计“正在停电中”,此处采用简化规则:按开始时间所在小时计入
|
||||||
|
// 若未来表添加end_time字段,可修改为更精确的逻辑
|
||||||
|
for (DnerHourlyPowerOutageEvent event : events) {
|
||||||
|
if (event.getDataTime() == null) continue;
|
||||||
|
LocalDateTime start = TimeUtil.parseToLocalDateTime(event.getDataTime()); // 需要工具类解析字符串
|
||||||
|
if (start == null) continue;
|
||||||
|
|
||||||
|
// 找到大于start的最小整点(即start所在的下一个整点)
|
||||||
|
LocalDateTime targetHour = start.withMinute(0).withSecond(0).withNano(0).plusHours(1);
|
||||||
|
if (result.containsKey(targetHour)) {
|
||||||
|
result.merge(targetHour, event.getUserCount() == null ? 0L : event.getUserCount().longValue(), Long::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 降水量统计(多站平均) =====================
|
||||||
|
private Map<LocalDateTime, Double> calculatePrecipitation(List<String> districtCodes, List<LocalDateTime> timePoints) {
|
||||||
|
// 1. 获取所有区县关联的气象站ID
|
||||||
|
List<String> stationIds = getStationIdsByDistricts(districtCodes);
|
||||||
|
if (stationIds.isEmpty()) {
|
||||||
|
return emptyMap(timePoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 批量获取所有站点的气象数据(按小时)
|
||||||
|
Map<String, Map<LocalDateTime, WeatherDataDTO>> stationDataMap = batchFetchWeatherData(stationIds, timePoints);
|
||||||
|
|
||||||
|
// 3. 按小时计算各县平均降水量
|
||||||
|
// 由于降水量县级规则是“国家站+区域站平均”,但我们有所有站的数据,需要先计算各县的平均值,再汇总
|
||||||
|
// 简化:直接对所有站的数据按小时取平均(因为最终市级还需要再平均,最终结果等价于所有站的平均)
|
||||||
|
// 但需求要求县级统计时是县级内平均,市级统计时是各县平均再平均,因此必须按县分组先计算平均值
|
||||||
|
// 此处我们实现为:先按县分组,每个小时取该县所有站的降水量平均值,得到各县小时降水量;然后所有县的小时降水量再平均(市级)或直接使用(县级)
|
||||||
|
// 但本方法返回值是总体结果,需要在调用前已经确定是县级还是市级,但调用时districtCodes已经是一个集合,方法需要返回这个集合整体的平均降水量。
|
||||||
|
// 因此:对每个小时,计算所有区县在该小时的平均降水量(即先计算每个县内所有站的平均,再对这些县的平均取平均)
|
||||||
|
return calculateAverageByDistrict(stationDataMap, districtCodes, timePoints,
|
||||||
|
WeatherDataDTO::getHourlyPrecipitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 气温统计 =====================
|
||||||
|
private TemperatureResult calculateTemperature(List<String> districtCodes, List<LocalDateTime> timePoints) {
|
||||||
|
// 1. 获取所有区县关联的气象站ID
|
||||||
|
List<String> stationIds = getStationIdsByDistricts(districtCodes);
|
||||||
|
if (stationIds.isEmpty()) {
|
||||||
|
return new TemperatureResult(emptyMap(timePoints), emptyMap(timePoints), emptyMap(timePoints));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取所有站点的气象数据
|
||||||
|
Map<String, Map<LocalDateTime, WeatherDataDTO>> stationDataMap = batchFetchWeatherData(stationIds, timePoints);
|
||||||
|
|
||||||
|
// 3. 分别计算平均、最高、最低
|
||||||
|
Map<LocalDateTime, Double> avgMap = calculateAverageByDistrict(stationDataMap, districtCodes, timePoints,
|
||||||
|
d -> parseDouble(d.getTemperature()));
|
||||||
|
Map<LocalDateTime, Double> maxMap = calculateExtremeByDistrict(stationDataMap, districtCodes, timePoints,
|
||||||
|
d -> parseDouble(d.getHourlyMaxTemperature()), true);
|
||||||
|
Map<LocalDateTime, Double> minMap = calculateExtremeByDistrict(stationDataMap, districtCodes, timePoints,
|
||||||
|
d -> parseDouble(d.getHourlyMinTemperature()), false);
|
||||||
|
|
||||||
|
return new TemperatureResult(avgMap, maxMap, minMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 风速统计(极大值) =====================
|
||||||
|
private Map<LocalDateTime, Double> calculateWindSpeed(List<String> districtCodes, List<LocalDateTime> timePoints) {
|
||||||
|
List<String> stationIds = getStationIdsByDistricts(districtCodes);
|
||||||
|
if (stationIds.isEmpty()) {
|
||||||
|
return emptyMap(timePoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Map<LocalDateTime, WeatherDataDTO>> stationDataMap = batchFetchWeatherData(stationIds, timePoints);
|
||||||
|
|
||||||
|
// 风速规则:先取各县内所有站的最大值,再取所有县的最大值(市级)
|
||||||
|
return calculateExtremeByDistrict(stationDataMap, districtCodes, timePoints,
|
||||||
|
d -> parseDouble(d.getExtremeWindSpeedHourly()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== 通用辅助方法 =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据区县编码列表获取所有气象站ID(去重)
|
||||||
|
*/
|
||||||
|
private List<String> getStationIdsByDistricts(List<String> districtCodes) {
|
||||||
|
if (CollectionUtils.isEmpty(districtCodes)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<WeatherSiteAreaConfiguration> configs = new ArrayList<>(); // new weatherSiteAreaConfigurationMapper.selectByDistrictCodes(districtCodes);
|
||||||
|
if (CollectionUtils.isEmpty(configs)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return configs.stream()
|
||||||
|
.map(WeatherSiteAreaConfiguration::getStationId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取多个站点在多个时间点的气象数据
|
||||||
|
* @return Map<stationId, Map<小时, WeatherDataDTO>>
|
||||||
|
*/
|
||||||
|
private Map<String, Map<LocalDateTime, WeatherDataDTO>> batchFetchWeatherData(List<String> stationIds,
|
||||||
|
List<LocalDateTime> timePoints) {
|
||||||
|
Map<String, Map<LocalDateTime, WeatherDataDTO>> result = new HashMap<>();
|
||||||
|
for (String stationId : stationIds) {
|
||||||
|
Map<LocalDateTime, WeatherDataDTO> stationHourly = new HashMap<>();
|
||||||
|
for (LocalDateTime hour : timePoints) {
|
||||||
|
WeatherDataDTO data = weatherDataService.getWeatherData(stationId, hour);
|
||||||
|
if (data != null) {
|
||||||
|
stationHourly.put(hour, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.put(stationId, stationHourly);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算按县平均后的指标(先求每个县内所有站的平均,再对各县平均取平均)
|
||||||
|
* @param stationDataMap 站点原始数据
|
||||||
|
* @param districtCodes 区县列表
|
||||||
|
* @param timePoints 时间点
|
||||||
|
* @param valueExtractor 从WeatherDataDTO提取数值的函数
|
||||||
|
* @return 每个小时的平均值
|
||||||
|
*/
|
||||||
|
private Map<LocalDateTime, Double> calculateAverageByDistrict(Map<String, Map<LocalDateTime, WeatherDataDTO>> stationDataMap,
|
||||||
|
List<String> districtCodes,
|
||||||
|
List<LocalDateTime> timePoints,
|
||||||
|
Function<WeatherDataDTO, Double> valueExtractor) {
|
||||||
|
// 1. 建立区县 -> 站点ID列表的映射
|
||||||
|
Map<String, List<String>> districtStationMap = getDistrictStationMap(districtCodes);
|
||||||
|
|
||||||
|
Map<LocalDateTime, Double> result = new HashMap<>();
|
||||||
|
for (LocalDateTime hour : timePoints) {
|
||||||
|
List<Double> districtAverages = new ArrayList<>();
|
||||||
|
for (String districtCode : districtCodes) {
|
||||||
|
List<String> stations = districtStationMap.getOrDefault(districtCode, Collections.emptyList());
|
||||||
|
List<Double> values = new ArrayList<>();
|
||||||
|
for (String stationId : stations) {
|
||||||
|
WeatherDataDTO data = stationDataMap.getOrDefault(stationId, Collections.emptyMap()).get(hour);
|
||||||
|
Double val = valueExtractor.apply(data);
|
||||||
|
if (val != null) {
|
||||||
|
values.add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!values.isEmpty()) {
|
||||||
|
double avg = values.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
||||||
|
districtAverages.add(avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 所有县的平均再平均
|
||||||
|
if (!districtAverages.isEmpty()) {
|
||||||
|
double overallAvg = districtAverages.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
||||||
|
result.put(hour, overallAvg);
|
||||||
|
} else {
|
||||||
|
result.put(hour, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算按县极值后的总体极值(先求每个县内所有站的极值,再取所有县的极值)
|
||||||
|
* @param isMax true-最大值,false-最小值
|
||||||
|
*/
|
||||||
|
private Map<LocalDateTime, Double> calculateExtremeByDistrict(Map<String, Map<LocalDateTime, WeatherDataDTO>> stationDataMap,
|
||||||
|
List<String> districtCodes,
|
||||||
|
List<LocalDateTime> timePoints,
|
||||||
|
Function<WeatherDataDTO, Double> valueExtractor,
|
||||||
|
boolean isMax) {
|
||||||
|
Map<String, List<String>> districtStationMap = getDistrictStationMap(districtCodes);
|
||||||
|
|
||||||
|
Map<LocalDateTime, Double> result = new HashMap<>();
|
||||||
|
for (LocalDateTime hour : timePoints) {
|
||||||
|
List<Double> districtExtremes = new ArrayList<>();
|
||||||
|
for (String districtCode : districtCodes) {
|
||||||
|
List<String> stations = districtStationMap.getOrDefault(districtCode, Collections.emptyList());
|
||||||
|
Double extreme = null;
|
||||||
|
for (String stationId : stations) {
|
||||||
|
WeatherDataDTO data = stationDataMap.getOrDefault(stationId, Collections.emptyMap()).get(hour);
|
||||||
|
Double val = valueExtractor.apply(data);
|
||||||
|
if (val != null) {
|
||||||
|
if (extreme == null) {
|
||||||
|
extreme = val;
|
||||||
|
} else if (isMax) {
|
||||||
|
extreme = Math.max(extreme, val);
|
||||||
|
} else {
|
||||||
|
extreme = Math.min(extreme, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extreme != null) {
|
||||||
|
districtExtremes.add(extreme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 取所有县极值的极值
|
||||||
|
if (!districtExtremes.isEmpty()) {
|
||||||
|
double overall = isMax ?
|
||||||
|
districtExtremes.stream().mapToDouble(Double::doubleValue).max().orElse(0.0) :
|
||||||
|
districtExtremes.stream().mapToDouble(Double::doubleValue).min().orElse(0.0);
|
||||||
|
result.put(hour, overall);
|
||||||
|
} else {
|
||||||
|
result.put(hour, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取区县到站点ID列表的映射
|
||||||
|
*/
|
||||||
|
private Map<String, List<String>> getDistrictStationMap(List<String> districtCodes) {
|
||||||
|
List<WeatherSiteAreaConfiguration> configs = new ArrayList<>(); // weatherSiteAreaConfigurationMapper.selectByDistrictCodes(districtCodes);
|
||||||
|
if (CollectionUtils.isEmpty(configs)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return configs.stream()
|
||||||
|
.filter(c -> c.getStationId() != null)
|
||||||
|
.collect(Collectors.groupingBy(WeatherSiteAreaConfiguration::getDistrictCode,
|
||||||
|
Collectors.mapping(WeatherSiteAreaConfiguration::getStationId, Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充列表,确保每个时间点都有值
|
||||||
|
*/
|
||||||
|
private <T> List<T> fillList(List<LocalDateTime> timePoints, Map<LocalDateTime, T> map, T defaultValue) {
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
for (LocalDateTime tp : timePoints) {
|
||||||
|
list.add(map.getOrDefault(tp, defaultValue));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<LocalDateTime, Double> emptyMap(List<LocalDateTime> timePoints) {
|
||||||
|
Map<LocalDateTime, Double> map = new HashMap<>();
|
||||||
|
for (LocalDateTime tp : timePoints) {
|
||||||
|
map.put(tp, 0.0);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double parseDouble(String str) {
|
||||||
|
try {
|
||||||
|
return str == null ? null : Double.parseDouble(str);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部类,用于返回气温的三个Map
|
||||||
|
private static class TemperatureResult {
|
||||||
|
private final Map<LocalDateTime, Double> avgMap;
|
||||||
|
private final Map<LocalDateTime, Double> maxMap;
|
||||||
|
private final Map<LocalDateTime, Double> minMap;
|
||||||
|
|
||||||
|
public TemperatureResult(Map<LocalDateTime, Double> avgMap,
|
||||||
|
Map<LocalDateTime, Double> maxMap,
|
||||||
|
Map<LocalDateTime, Double> minMap) {
|
||||||
|
this.avgMap = avgMap;
|
||||||
|
this.maxMap = maxMap;
|
||||||
|
this.minMap = minMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<LocalDateTime, Double> getAvgMap() { return avgMap; }
|
||||||
|
public Map<LocalDateTime, Double> getMaxMap() { return maxMap; }
|
||||||
|
public Map<LocalDateTime, Double> getMinMap() { return minMap; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -93,7 +93,7 @@ public class WeatherDataServiceImpl implements WeatherDataService {
|
|||||||
dto.setTemperature(n.getTemperature());
|
dto.setTemperature(n.getTemperature());
|
||||||
dto.setHourlyMaxTemperature(n.getHourlyMaxTemperature());
|
dto.setHourlyMaxTemperature(n.getHourlyMaxTemperature());
|
||||||
dto.setHourlyMinTemperature(n.getHourlyMinTemperature());
|
dto.setHourlyMinTemperature(n.getHourlyMinTemperature());
|
||||||
dto.setHourlyPrecipitation(n.getHourlyPrecipitation());
|
// dto.setHourlyPrecipitation(n.getHourlyPrecipitation());
|
||||||
dto.setDailyPrecipitation(n.getDailyPrecipitation());
|
dto.setDailyPrecipitation(n.getDailyPrecipitation());
|
||||||
dto.setExtremeWindSpeedHourly(n.getExtremeWindSpeedHourly());
|
dto.setExtremeWindSpeedHourly(n.getExtremeWindSpeedHourly());
|
||||||
return dto;
|
return dto;
|
||||||
@ -107,7 +107,7 @@ public class WeatherDataServiceImpl implements WeatherDataService {
|
|||||||
dto.setTemperature(r.getTemperature());
|
dto.setTemperature(r.getTemperature());
|
||||||
dto.setHourlyMaxTemperature(r.getHourlyMaxTemperature());
|
dto.setHourlyMaxTemperature(r.getHourlyMaxTemperature());
|
||||||
dto.setHourlyMinTemperature(r.getHourlyMinTemperature());
|
dto.setHourlyMinTemperature(r.getHourlyMinTemperature());
|
||||||
dto.setHourlyPrecipitation(r.getHourlyPrecipitation());
|
// dto.setHourlyPrecipitation(r.getHourlyPrecipitation());
|
||||||
dto.setDailyPrecipitation(r.getDailyPrecipitation());
|
dto.setDailyPrecipitation(r.getDailyPrecipitation());
|
||||||
dto.setExtremeWindSpeedHourly(r.getExtremeWindSpeedHourly());
|
dto.setExtremeWindSpeedHourly(r.getExtremeWindSpeedHourly());
|
||||||
return dto;
|
return dto;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.southern.power.grid.utils;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -122,4 +123,17 @@ public class TimeUtil {
|
|||||||
// String strings = getBeforeNumDays("2026-03-15", 5);
|
// String strings = getBeforeNumDays("2026-03-15", 5);
|
||||||
System.out.println(14/5);
|
System.out.println(14/5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public static LocalDateTime parseToLocalDateTime(String dateTimeStr) {
|
||||||
|
if (dateTimeStr == null || dateTimeStr.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return LocalDateTime.parse(dateTimeStr, DEFAULT_FORMATTER);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,4 +42,12 @@
|
|||||||
)
|
)
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectByDistrictsAndTime" resultType="com.southern.power.grid.entity.DnerHourlyPowerOutageEvent">
|
||||||
|
SELECT * FROM dner_hourly_power_outage_event
|
||||||
|
WHERE org_code IN
|
||||||
|
<foreach collection="districtCodes" item="code" open="(" separator="," close=")">
|
||||||
|
#{code}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
Loading…
x
Reference in New Issue
Block a user