初始提交:添加分时处理

This commit is contained in:
liuzhiming 2026-03-18 10:01:33 +08:00
parent 600242462e
commit 186a3db27c
7 changed files with 179 additions and 528 deletions

View File

@ -3,7 +3,43 @@ package com.southern.power.grid.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.southern.power.grid.entity.RegionalWeatherData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface RegionalWeatherDataMapper extends BaseMapper<RegionalWeatherData> {
/**
* 插入一条区域气象数据记录
* @param record 实体对象
* @return 影响行数
*/
int insert(RegionalWeatherData record);
/**
* 根据主键ID查询
* @param id 主键ID
* @return 实体对象
*/
RegionalWeatherData selectByPrimaryKey(Long id);
/**
* 根据地区编码和资料时次查询
* @param orgCode 地区编码
* @param dataTime 资料时次
* @return 实体列表
*/
List<RegionalWeatherData> selectByOrgCodeAndDataTime(@Param("orgCode") String orgCode, @Param("dataTime") String dataTime);
/**
* 根据条件动态查询
* @param params 查询参数Map支持
* orgCode精确匹配
* dataTime精确匹配
* startCreateTime创建时间起始格式 yyyy-MM-dd HH:mm:ss
* endCreateTime创建时间结束
* @return 实体列表
*/
List<RegionalWeatherData> selectByCondition(Map<String, Object> params);
}

View File

@ -1,25 +0,0 @@
package com.southern.power.grid.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ExcelHourlyOutageRowDTO {
/** 资料时次,对应分时表的 data_time */
private LocalDateTime dataTime;
/** Excel 中体现的南网省/地市局/区县局(用于映射) */
private String nwProvince;
private String nwCity;
private String nwDistrict;
/** 停电影响用户数等业务字段(示例) */
private Integer userCount;
private Integer outageState;
private Integer outageType;
// ... 根据你的 Excel 列补全
// getter/setter
}

View File

@ -1,18 +0,0 @@
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; // 极大风速
}

View File

@ -1,30 +0,0 @@
package com.southern.power.grid.entity;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 站点气象数据
*
* @author: LIUZHIMING
* @date: 2026/3/17
**/
@Data
public class WeatherAreaDataDTO {
String districtCode;
private LocalDateTime dataTime;
// 平均气温
private String temperature;
// 小时最高气温
private String hourlyMaxTemperature;
// 小时最低气温
private String hourlyMinTemperature;
// 小时降雨量
private Double hourlyPrecipitation;
// 日累计降雨量
private String dailyPrecipitation;
// 小时内极大风速
private String extremeWindSpeedHourly;
}

View File

@ -1,15 +1,14 @@
package com.southern.power.grid.service.impl;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.southern.power.grid.dao.DnerHourlyPowerOutageEventMapper;
import com.southern.power.grid.dao.NwSiteAreaConfigurationMapper;
import com.southern.power.grid.dao.RegionalWeatherDataMapper;
import com.southern.power.grid.dao.WeatherSiteAreaConfigurationMapper;
import com.southern.power.grid.entity.DataExcelEntity;
import com.southern.power.grid.entity.DnerHourlyPowerOutageEvent;
import com.southern.power.grid.entity.NwSiteAreaConfiguration;
import com.southern.power.grid.entity.WeatherDataDTO;
import com.southern.power.grid.entity.RegionalWeatherData;
import com.southern.power.grid.entity.WeatherSiteAreaConfiguration;
import com.southern.power.grid.service.WeatherDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -17,6 +16,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -32,11 +32,15 @@ public class HourlyOutageExcelProcessService {
private WeatherSiteAreaConfigurationMapper weatherSiteAreaConfigurationMapper;
@Resource
private WeatherDataService weatherDataService;
private RegionalWeatherDataMapper regionalWeatherDataMapper;
@Resource
private DnerHourlyPowerOutageEventMapper dnerHourlyPowerOutageEventMapper;
private static final Float lengthOutage = 60F;
// 定义时间格式器建议定义为静态常量避免重复创建
private static final DateTimeFormatter DATA_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 南网区域配置
* key = nw_province + "|" + nw_city + "|" + nw_district value = district_code
@ -61,10 +65,16 @@ public class HourlyOutageExcelProcessService {
// 2. 加载气象区划配置表
loadWeatherAreaConfig();
List<DnerHourlyPowerOutageEvent> toInsert = new ArrayList<>();
// 使用 Map 存储待插入实体Key districtCode + "|" + dataTime
Map<String, DnerHourlyPowerOutageEvent> toInsertMap = new HashMap<>();
// 3 & 4. 循环处理 Excel
for (DataExcelEntity row : excelRows) {
if(row.getLengthOutage()<lengthOutage) {
log.info("lengthOutage is less than 60! {}, {}, {}", row.getProvince(), row.getCity(), row.getDistrict());
continue;
}
// 3.1 根据南网省//区县 district_code
String districtCode = findDistrictCode(row.getProvince(), row.getCity(), row.getDistrict());
if (districtCode == null) {
@ -72,39 +82,51 @@ public class HourlyOutageExcelProcessService {
log.info("districtCode is null! {}, {}, {}", row.getProvince(), row.getCity(), row.getDistrict());
continue;
}
// 3.2 district_code station_id
List<WeatherSiteAreaConfiguration> weatherAreas = weatherAreaMap.get(districtCode);
if (weatherAreas == null) {
// 找不到对应气象站
log.info("weatherAreas is null! {}", districtCode);
continue;
}
List<String> stationIds = new ArrayList<>();
for (WeatherSiteAreaConfiguration weatherArea : weatherAreas) {
String stationId = weatherArea.getStationId();
if (StringUtils.isNotEmpty(stationId) && !stationIds.contains(stationId)) {
stationIds.add(stationId);
}
}
// 3.3 通过 stationId + 时间统一取8天气象数据内部做缓存
LocalDateTime dataTime = row.getStartTime();
List<WeatherDataDTO> weatherData = weatherDataService.getWeatherData(stationIds, dataTime);
// 3.2 获取区域时段内气象数据
List<RegionalWeatherData> regionalWeatherDataList =
regionalWeatherDataMapper.selectByOrgCodeAndDataTime(districtCode, DB_DATETIME_STR.format(row.getStartTime()));
// 3.4 组装分时停电事件实体
for (WeatherDataDTO weatherDataDTO : weatherData) {
DnerHourlyPowerOutageEvent entity = buildHourlyEvent(row, districtCode, weatherDataDTO);
toInsert.add(entity);
for (RegionalWeatherData regionalWeatherData : regionalWeatherDataList) {
LocalDateTime dataDateTime;
try {
dataDateTime = LocalDateTime.parse(regionalWeatherData.getDataTime(), DATA_TIME_FORMATTER);
} catch (DateTimeParseException e) {
log.warn("解析 dataTime 失败: {}", regionalWeatherData.getDataTime());
continue;
}
if (row.getStartTime().isBefore(dataDateTime) && dataDateTime.isBefore(row.getEndTime())) {
String key = districtCode + "|" + regionalWeatherData.getDataTime();
// 检查 Map 中是否已存在相同组合键
DnerHourlyPowerOutageEvent existing = toInsertMap.get(key);
if (existing == null) {
// 不存在创建新实体并放入 Map
DnerHourlyPowerOutageEvent entity = buildHourlyEvent(row, districtCode, regionalWeatherData);
toInsertMap.put(key, entity);
} else {
// 已存在执行累加逻辑
accumulateEvent(existing, row);
}
}
}
}
if (!toInsert.isEmpty()) {
// 4. 批量入库
if (!toInsertMap.isEmpty()) {
// Map 中的 values 转为 List 进行批量插入
List<DnerHourlyPowerOutageEvent> toInsert = new ArrayList<>(toInsertMap.values());
dnerHourlyPowerOutageEventMapper.batchInsert(toInsert);
}
}
/**
* 累加已存在实体的字段
* @param existing 已存在的实体
* @param row 当前 Excel 行数据
*/
private void accumulateEvent(DnerHourlyPowerOutageEvent existing, DataExcelEntity row) {
existing.setUserCount(row.getUserCount() + existing.getUserCount());
}
/**
* 加载南网区划配置
* **/
@ -155,31 +177,31 @@ public class HourlyOutageExcelProcessService {
private DnerHourlyPowerOutageEvent buildHourlyEvent(DataExcelEntity row,
String districtCode,
WeatherDataDTO weatherData) {
DnerHourlyPowerOutageEvent e = new DnerHourlyPowerOutageEvent();
e.setOrgCode(districtCode);
RegionalWeatherData regionalWeatherData) {
DnerHourlyPowerOutageEvent event = new DnerHourlyPowerOutageEvent();
event.setOrgCode(districtCode);
// 时间转成字符串入库到 data_time
if (row.getStartTime() != null) {
e.setDataTime(row.getStartTime().format(DB_DATETIME_STR));
event.setDataTime(regionalWeatherData.getDataTime());
}
// Excel 本身的业务字段
e.setUserCount(row.getUserCount());
e.setOutageState(row.getOutageState());
e.setOutageType(row.getOutageType());
event.setUserCount(row.getUserCount());
event.setOutageState(row.getOutageState());
event.setOutageType(row.getOutageType());
// 气象字段如不存在数据可以为 null
if (weatherData != null) {
e.setTemperature(weatherData.getTemperature());
e.setHourlyMaxTemperature(weatherData.getHourlyMaxTemperature());
e.setHourlyMinTemperature(weatherData.getHourlyMinTemperature());
// e.setHourlyPrecipitation(weatherData.getHourlyPrecipitation());
e.setDailyPrecipitation(weatherData.getDailyPrecipitation());
e.setExtremeWindSpeedHourly(weatherData.getExtremeWindSpeedHourly());
if (regionalWeatherData != null) {
event.setTemperature(regionalWeatherData.getTemperature());
event.setHourlyMaxTemperature(regionalWeatherData.getHourlyMaxTemperature());
event.setHourlyMinTemperature(regionalWeatherData.getHourlyMinTemperature());
event.setHourlyPrecipitation(regionalWeatherData.getHourlyPrecipitation());
event.setDailyPrecipitation(regionalWeatherData.getDailyPrecipitation());
event.setExtremeWindSpeedHourly(regionalWeatherData.getExtremeWindSpeedHourly());
}
// create_by/update_by 可根据当前登录用户等进行填充
return e;
return event;
}
}

View File

@ -1,411 +0,0 @@
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(stationIds, 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; }
}
}

View File

@ -4,5 +4,82 @@
<!-- namespace 必须 = Mapper 接口全类名 -->
<mapper namespace="com.southern.power.grid.dao.RegionalWeatherDataMapper">
<resultMap id="BaseResultMap" type="com.southern.power.grid.entity.RegionalWeatherData">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="org_code" property="orgCode" jdbcType="VARCHAR"/>
<result column="data_time" property="dataTime" jdbcType="VARCHAR"/>
<result column="hourly_precipitation" property="hourlyPrecipitation" jdbcType="VARCHAR"/>
<result column="daily_precipitation" property="dailyPrecipitation" jdbcType="VARCHAR"/>
<result column="temperature" property="temperature" jdbcType="VARCHAR"/>
<result column="hourly_max_temperature" property="hourlyMaxTemperature" jdbcType="VARCHAR"/>
<result column="hourly_min_temperature" property="hourlyMinTemperature" jdbcType="VARCHAR"/>
<result column="extreme_wind_speed_hourly" property="extremeWindSpeedHourly" jdbcType="VARCHAR"/>
<result column="create_by" property="createBy" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id, org_code, data_time, hourly_precipitation, daily_precipitation, temperature,
hourly_max_temperature, hourly_min_temperature, extreme_wind_speed_hourly,
create_by, create_time, update_by, update_time
</sql>
<!-- 插入语句忽略主键id自增create_time/update_time由数据库默认生成 -->
<insert id="insert" parameterType="com.southern.power.grid.entity.RegionalWeatherData" useGeneratedKeys="true" keyProperty="id">
INSERT INTO regional_weather_data (
org_code, data_time, hourly_precipitation, daily_precipitation,
temperature, hourly_max_temperature, hourly_min_temperature,
extreme_wind_speed_hourly, create_by, update_by
) VALUES (
#{orgCode, jdbcType=VARCHAR},
#{dataTime, jdbcType=VARCHAR},
#{hourlyPrecipitation, jdbcType=VARCHAR},
#{dailyPrecipitation, jdbcType=VARCHAR},
#{temperature, jdbcType=VARCHAR},
#{hourlyMaxTemperature, jdbcType=VARCHAR},
#{hourlyMinTemperature, jdbcType=VARCHAR},
#{extremeWindSpeedHourly, jdbcType=VARCHAR},
#{createBy, jdbcType=VARCHAR},
#{updateBy, jdbcType=VARCHAR}
)
</insert>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM regional_weather_data
WHERE id = #{id, jdbcType=BIGINT}
</select>
<!-- 根据地区编码和资料时次查询 -->
<select id="selectByOrgCodeAndDataTime" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM regional_weather_data
WHERE org_code = #{orgCode, jdbcType=VARCHAR}
AND data_time >= #{dataTime, jdbcType=VARCHAR}
ORDER BY data_time ASC
</select>
<!-- 动态条件查询 -->
<select id="selectByCondition" parameterType="java.util.Map" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM regional_weather_data
<where>
<if test="orgCode != null and orgCode != ''">
AND org_code = #{orgCode, jdbcType=VARCHAR}
</if>
<if test="dataTime != null and dataTime != ''">
AND data_time = #{dataTime, jdbcType=VARCHAR}
</if>
<if test="startCreateTime != null and startCreateTime != ''">
AND create_time >= #{startCreateTime, jdbcType=TIMESTAMP}
</if>
<if test="endCreateTime != null and endCreateTime != ''">
AND create_time &lt;= #{endCreateTime, jdbcType=TIMESTAMP}
</if>
</where>
ORDER BY create_time DESC
</select>
</mapper>