部分暴雨灾害链报告产出,部分内容待完善
This commit is contained in:
@@ -4,13 +4,25 @@ import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "report")
|
||||
public class ReportProperties {
|
||||
private DisasterCausingFactors disasterCausingFactors;
|
||||
private ReportFiles files;
|
||||
private Map<String, String> disasterTypes = new LinkedHashMap<>();
|
||||
|
||||
@Data
|
||||
public static class ReportFiles {
|
||||
/**
|
||||
* 文件服务器前缀
|
||||
*/
|
||||
private String serverUrl;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DisasterCausingFactors {
|
||||
@@ -22,5 +34,9 @@ public class ReportProperties {
|
||||
public static class FactorConfig {
|
||||
private List<String> type;
|
||||
private Integer number;
|
||||
private String templatePath;
|
||||
private String outputPath;
|
||||
private String outputName;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.gis.xian.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 灾害风险数据——按灾害类型聚合,用于填充报告模板
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DisasterRiskData {
|
||||
|
||||
/** 灾害类型中文名(滑坡、泥石流、山洪、内涝、崩塌) */
|
||||
private String disasterType;
|
||||
|
||||
/** 模板条件变量后缀(Landslide、DebrisFlow、TorrentialFlood、WaterLogging、Collapse) */
|
||||
private String conditionSuffix;
|
||||
|
||||
/** 中高风险街道(逗号分隔) */
|
||||
private String highRiskStreets;
|
||||
|
||||
/** 影响人数下限(暂空,待公式计算) */
|
||||
private String influenceLower = "";
|
||||
|
||||
/** 影响人数上限(暂空,待公式计算) */
|
||||
private String influenceUpper = "";
|
||||
|
||||
/** 隐患点人口分布图完整 URL */
|
||||
private String imageUrl;
|
||||
|
||||
/** 风险明细列表 */
|
||||
private List<SpotRisk> spots = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 是否有该类型的数据
|
||||
*/
|
||||
public boolean hasData() {
|
||||
return spots != null && !spots.isEmpty();
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class SpotRisk {
|
||||
/** 序号 */
|
||||
private int index;
|
||||
/** 位置 */
|
||||
private String position;
|
||||
/** 发生概率(小数 0-1) */
|
||||
private Double probability;
|
||||
/** 灾害等级:高(>=70%) / 中(50%-70%) */
|
||||
private String riskLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gis.xian.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 推理结果文件表
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "xian_inference_result_file", autoResultMap = true)
|
||||
public class XianInferenceResultFile {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long inferenceId;
|
||||
|
||||
private String filePath;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private Integer isDelete;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.gis.xian.mapper;
|
||||
|
||||
import com.gis.xian.entity.XianInferenceResultFile;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 针对表【xian_inference_result_file(推理结果文件表)】的数据库操作Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface XianInferenceResultFileMapper {
|
||||
|
||||
/**
|
||||
* 根据推理结果id获取文件列表
|
||||
*/
|
||||
List<XianInferenceResultFile> selectByInferenceId(@Param("inferenceId") Long inferenceId);
|
||||
}
|
||||
@@ -2,237 +2,445 @@ package com.gis.xian.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.data.PictureRenderData;
|
||||
import com.deepoove.poi.data.Pictures;
|
||||
import com.deepoove.poi.policy.TableRenderPolicy;
|
||||
import com.gis.xian.config.AlgorithmClient;
|
||||
import com.gis.xian.config.ReportProperties;
|
||||
import com.gis.xian.domain.ApiResponse;
|
||||
import com.gis.xian.dto.BaseCondition;
|
||||
import com.gis.xian.dto.DisasterRiskData;
|
||||
import com.gis.xian.dto.RainfallDistrictSummaryResponseDTO;
|
||||
import com.gis.xian.dto.RiskAndHiddenSpotDTO;
|
||||
import com.gis.xian.entity.XianHiddenDangerSpots;
|
||||
import com.gis.xian.entity.XianInferenceResult;
|
||||
import com.gis.xian.entity.XianInferenceResultFile;
|
||||
import com.gis.xian.mapper.XianHiddenDangerSpotsMapper;
|
||||
import com.gis.xian.mapper.XianInferenceResultFileMapper;
|
||||
import com.gis.xian.mapper.XianInferenceResultMapper;
|
||||
import com.gis.xian.mapper.XianRiskSpotsMapper;
|
||||
import com.gis.xian.service.ReportOutputService;
|
||||
import com.gis.xian.service.ex.ReportParameterException;
|
||||
import com.gis.xian.utils.DateTimeUtils;
|
||||
import com.gis.xian.utils.ReportTableBuilder;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class IReportOutputServiceImpl implements ReportOutputService {
|
||||
|
||||
/**
|
||||
* 暴雨灾害类型 → 模板变量后缀
|
||||
*/
|
||||
private static final Map<String, String> DISASTER_CONDITION_MAP = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
DISASTER_CONDITION_MAP.put("滑坡", "Landslide");
|
||||
DISASTER_CONDITION_MAP.put("泥石流", "DebrisFlow");
|
||||
DISASTER_CONDITION_MAP.put("山洪", "TorrentialFlood");
|
||||
DISASTER_CONDITION_MAP.put("内涝", "WaterLogging");
|
||||
DISASTER_CONDITION_MAP.put("崩塌", "Collapse");
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片命名模式
|
||||
*/
|
||||
private static final String IMAGE_NAME_PATTERN = "暴雨%s潜在隐患点及人口分布图.jpg";
|
||||
|
||||
@Value("${files.local.path}")
|
||||
private String localPath;
|
||||
|
||||
@Value("${files.server.url}")
|
||||
private String fileServerPath;
|
||||
|
||||
@Resource
|
||||
private XianInferenceResultMapper xianInferenceResultMapper;
|
||||
private XianInferenceResultMapper inferenceResultMapper;
|
||||
|
||||
@Resource
|
||||
private XianHiddenDangerSpotsMapper hiddenDangerSpotsMapper;
|
||||
|
||||
@Resource
|
||||
private XianRiskSpotsMapper riskSpotsMapper;
|
||||
|
||||
@Resource
|
||||
private XianInferenceResultFileMapper inferenceResultFileMapper;
|
||||
|
||||
@Resource
|
||||
private AlgorithmClient algorithmClient;
|
||||
|
||||
@Resource
|
||||
private XianRiskSpotsMapper xianRiskSpotsMapper;
|
||||
|
||||
@Resource
|
||||
private XianHiddenDangerSpotsMapper xianHiddenDangerSpotsMapper;
|
||||
|
||||
@Resource
|
||||
private ReportProperties reportProperties;
|
||||
|
||||
@Override
|
||||
public String outputRainReport(Long id) {
|
||||
// 存储替换模版的map
|
||||
Map<String, Object> replaceTemplateMap = new HashMap<>();
|
||||
|
||||
// 中高风险隐患点id
|
||||
Map<String, List<Long>> hiddenDangerSpotsIds = null;
|
||||
|
||||
// 获取模拟信息
|
||||
XianInferenceResult simulationInfo = xianInferenceResultMapper.selectById(id);
|
||||
|
||||
// 隐患点id
|
||||
hiddenDangerSpotsIds = getHiddenDangerSpotsIds(simulationInfo);
|
||||
|
||||
// 获取高风险隐患点信息
|
||||
List<XianHiddenDangerSpots> highHiddenDangerSpots = xianHiddenDangerSpotsMapper.getPointDetailByIds(hiddenDangerSpotsIds.get("high"));
|
||||
|
||||
// 获取中风险隐患点信息
|
||||
List<XianHiddenDangerSpots> middleHiddenDangerSpots = xianHiddenDangerSpotsMapper.getPointDetailByIds(hiddenDangerSpotsIds.get("middle"));
|
||||
|
||||
// 获取降雨信息
|
||||
ApiResponse<List<RainfallDistrictSummaryResponseDTO>> rainData = algorithmClient.post("/rainfall/district-summary", Map.of("inference_id", 1),
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
|
||||
// 获取行政区划名称
|
||||
List<String> districtNames = getDistrictNames(rainData.getData());
|
||||
|
||||
// 获取风险区和隐患点数量
|
||||
List<RiskAndHiddenSpotDTO> riskNumbers = xianRiskSpotsMapper.queryRiskNumberByDistrictName(districtNames);
|
||||
List<RiskAndHiddenSpotDTO> hiddenDangerNumbers = xianHiddenDangerSpotsMapper.queryHiddenDangerNumberByDistrictName(districtNames);
|
||||
|
||||
// 获取预测点数量
|
||||
Integer predictPointNum = xianInferenceResultMapper.getTheNumberOfPredictedPoints();
|
||||
|
||||
// 生成降雨报告模版
|
||||
generateRainReportTemplate(replaceTemplateMap, simulationInfo, highHiddenDangerSpots, middleHiddenDangerSpots, rainData.getData(), districtNames, riskNumbers, hiddenDangerNumbers, predictPointNum);
|
||||
System.out.println(replaceTemplateMap);
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隐患点id
|
||||
*
|
||||
* @param simulationInfo 模拟信息
|
||||
* @return 隐患点id
|
||||
*/
|
||||
private Map<String, List<Long>> getHiddenDangerSpotsIds(XianInferenceResult simulationInfo) {
|
||||
// 预测结果
|
||||
Map<String, Double> probabilities = JSON.parseObject(simulationInfo.getResult(), new TypeReference<>() {
|
||||
// 获取模拟数据
|
||||
XianInferenceResult sim = inferenceResultMapper.selectById(id);
|
||||
Map<String, Double> probabilities = JSON.parseObject(sim.getResult(), new TypeReference<>() {
|
||||
});
|
||||
|
||||
// id
|
||||
Map<String, List<Long>> ids = new HashMap<>();
|
||||
ids.put("high", new ArrayList<>());
|
||||
ids.put("middle", new ArrayList<>());
|
||||
// 获取所有隐患点详情
|
||||
List<Long> pointIds = extractPointIds(probabilities.keySet());
|
||||
List<XianHiddenDangerSpots> allSpots = pointIds.isEmpty()
|
||||
? Collections.emptyList()
|
||||
: hiddenDangerSpotsMapper.getPointDetailByIds(pointIds);
|
||||
|
||||
for (String point : probabilities.keySet()) {
|
||||
Long id = Long.parseLong(point.split("_")[0]);
|
||||
if (probabilities.get(point) >= 70) {
|
||||
ids.get("high").add(id);
|
||||
} else if (probabilities.get(point) >= 50) {
|
||||
ids.get("middle").add(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
// 按灾害类型分组并构建风险数据
|
||||
List<XianInferenceResultFile> files = inferenceResultFileMapper.selectByInferenceId(id);
|
||||
List<DisasterRiskData> disasterDataList = buildDisasterDataList(allSpots, probabilities, files);
|
||||
|
||||
// 获取降雨概况
|
||||
List<RainfallDistrictSummaryResponseDTO> rainData = fetchRainData();
|
||||
List<String> districtNames = top3DistrictNames(rainData);
|
||||
|
||||
// 获取风险统计
|
||||
int riskCount = sumCount(riskSpotsMapper.queryRiskNumberByDistrictName(districtNames));
|
||||
int hiddenCount = sumCount(hiddenDangerSpotsMapper.queryHiddenDangerNumberByDistrictName(districtNames));
|
||||
int predictPointNum = inferenceResultMapper.getTheNumberOfPredictedPoints();
|
||||
|
||||
// 构建模板数据模型
|
||||
Map<String, Object> model = buildTemplateModel(sim, disasterDataList, rainData,
|
||||
districtNames, riskCount, hiddenCount, predictPointNum);
|
||||
|
||||
// 渲染输出
|
||||
return renderReport(
|
||||
reportProperties.getDisasterCausingFactors().getRainfall().getTemplatePath(),
|
||||
Path.of(localPath, reportProperties.getDisasterCausingFactors().getRainfall().getOutputPath().replace("{id}", id.toString())).toString(),
|
||||
reportProperties.getDisasterCausingFactors().getRainfall().getOutputName().replace("{currentTime}", System.currentTimeMillis() + ""),
|
||||
model
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前3个风险地区名称
|
||||
*
|
||||
* @param rainData 降雨数据
|
||||
* @return 行政区名称
|
||||
* 从 JSONB key 集合中提取隐患点 ID
|
||||
*/
|
||||
private List<String> getDistrictNames(List<RainfallDistrictSummaryResponseDTO> rainData) {
|
||||
private List<Long> extractPointIds(Set<String> keys) {
|
||||
return keys.stream()
|
||||
.map(k -> Long.parseLong(k.split("_")[0]))
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用算法服务获取各区降雨概况
|
||||
*/
|
||||
private List<RainfallDistrictSummaryResponseDTO> fetchRainData() {
|
||||
ApiResponse<List<RainfallDistrictSummaryResponseDTO>> resp = algorithmClient.post(
|
||||
"/rainfall/district-summary",
|
||||
Map.of("inference_id", 1),
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
return resp != null ? resp.getData() : Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 降雨量前3行政区
|
||||
*/
|
||||
private List<String> top3DistrictNames(List<RainfallDistrictSummaryResponseDTO> rainData) {
|
||||
return rainData.stream()
|
||||
.filter(dto -> dto.getRainfall() != null && !dto.getRainfall().isEmpty())
|
||||
// 按降雨量降序排序
|
||||
.sorted((dto1, dto2) -> {
|
||||
double r1 = Double.parseDouble(dto1.getRainfall());
|
||||
double r2 = Double.parseDouble(dto2.getRainfall());
|
||||
return Double.compare(r2, r1); // 降序
|
||||
})
|
||||
.filter(d -> d.getRainfall() != null && !d.getRainfall().isEmpty())
|
||||
.sorted((a, b) -> Double.compare(
|
||||
Double.parseDouble(b.getRainfall()),
|
||||
Double.parseDouble(a.getRainfall())))
|
||||
.limit(3)
|
||||
.map(RainfallDistrictSummaryResponseDTO::getDistrictName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void generateRainReportTemplate(
|
||||
Map<String, Object> m,
|
||||
XianInferenceResult simulationInfo,
|
||||
List<XianHiddenDangerSpots> highHiddenDangerSpots,
|
||||
List<XianHiddenDangerSpots> middleHiddenDangerSpots,
|
||||
List<RainfallDistrictSummaryResponseDTO> rainData,
|
||||
List<String> districtNames,
|
||||
List<RiskAndHiddenSpotDTO> riskNumbers,
|
||||
List<RiskAndHiddenSpotDTO> hiddenDangerNumbers,
|
||||
Integer predictPointNum
|
||||
) {
|
||||
|
||||
try {
|
||||
BaseCondition condition = JSON.parseObject(simulationInfo.getCondition(), BaseCondition.class);
|
||||
List<XianHiddenDangerSpots> hiddenDangerSpots = Stream.concat(highHiddenDangerSpots.stream(), middleHiddenDangerSpots.stream()).collect(Collectors.toList());
|
||||
|
||||
m.put("报告时间", DateTimeUtils.datetimeFormat(simulationInfo.getOccurredTime(), "MM月dd日HH时mm分"));
|
||||
m.put("降雨时间", DateTimeUtils.datetimeFormat(simulationInfo.getOccurredTime(), "yyyy年MM月dd日HH时"));
|
||||
m.put("降雨地区", String.join("、", districtNames));
|
||||
m.put("持续时间", condition.getDuration() == null ? 72 : condition.getDuration());
|
||||
m.put("降雨量", getRainfall(rainData, districtNames));
|
||||
m.put("降雨集中区域", districtNames.isEmpty() ? "" : districtNames.get(0));
|
||||
m.put("风险区数量", riskNumbers.size());
|
||||
m.put("隐患点数量", hiddenDangerNumbers.size());
|
||||
m.put("致灾因子", String.join("、", reportProperties.getDisasterCausingFactors().getRainfall().getType()) + "等");
|
||||
m.put("致灾因子数量", reportProperties.getDisasterCausingFactors().getRainfall().getNumber());
|
||||
m.put("预测点数量", predictPointNum);
|
||||
m.put("灾害链", getRainfallDisasterChain(hiddenDangerSpots));
|
||||
m.put("具体风险地区", getRainfallDisasterChainPosition(hiddenDangerSpots));
|
||||
m.put("高风险区域数量", highHiddenDangerSpots.size());
|
||||
m.put("次生灾害类型", getSecondaryDisasterType(highHiddenDangerSpots));
|
||||
m.put("hasDisasters", !hiddenDangerSpots.isEmpty());
|
||||
m.put("hasLandslide", highHiddenDangerSpots.stream().anyMatch(d -> "滑坡".equals(d.getDisasterType())));
|
||||
m.put("滑坡中高风险街道", highHiddenDangerSpots.stream().filter(d -> "滑坡".equals(d.getDisasterType())).map(XianHiddenDangerSpots::getVillage).collect(Collectors.joining("、")));
|
||||
} catch (Exception e) {
|
||||
log.error("生成暴雨报告出错:{}", e.getMessage());
|
||||
throw new ReportParameterException("生成暴雨报告出错:" + e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对DTO列表中的number字段求和
|
||||
*/
|
||||
private int sumCount(List<RiskAndHiddenSpotDTO> list) {
|
||||
return list.stream().mapToInt(d -> Integer.parseInt(d.getNumber())).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取降雨量
|
||||
*
|
||||
* @param rainData 降雨数据
|
||||
* @param districtNames 行政区名称
|
||||
* @return 降雨量
|
||||
* 将隐患点按disasterType分组,构建各类型的DisasterRiskData
|
||||
*/
|
||||
private String getRainfall(List<RainfallDistrictSummaryResponseDTO> rainData, List<String> districtNames) {
|
||||
Map<String, String> map = rainData.stream()
|
||||
.collect(Collectors.toMap(
|
||||
RainfallDistrictSummaryResponseDTO::getDistrictName,
|
||||
RainfallDistrictSummaryResponseDTO::getRainfall,
|
||||
(v1, v2) -> v1
|
||||
));
|
||||
return districtNames.stream()
|
||||
.map(map::get)
|
||||
.filter(Objects::nonNull)
|
||||
private List<DisasterRiskData> buildDisasterDataList(
|
||||
List<XianHiddenDangerSpots> allSpots,
|
||||
Map<String, Double> probabilities,
|
||||
List<XianInferenceResultFile> files) {
|
||||
|
||||
// 按灾害类型分组
|
||||
Map<String, List<XianHiddenDangerSpots>> grouped = allSpots.stream()
|
||||
.filter(s -> s.getDisasterType() != null)
|
||||
.collect(Collectors.groupingBy(XianHiddenDangerSpots::getDisasterType, LinkedHashMap::new, Collectors.toList()));
|
||||
|
||||
List<DisasterRiskData> result = new ArrayList<>();
|
||||
for (Map.Entry<String, List<XianHiddenDangerSpots>> entry : grouped.entrySet()) {
|
||||
String disasterType = entry.getKey();
|
||||
String suffix = DISASTER_CONDITION_MAP.get(disasterType);
|
||||
if (suffix == null) continue;
|
||||
|
||||
DisasterRiskData data = new DisasterRiskData()
|
||||
.setDisasterType(disasterType)
|
||||
.setConditionSuffix(suffix)
|
||||
.setInfluenceLower("")
|
||||
.setInfluenceUpper("");
|
||||
|
||||
// 构建风险明细(按概率降序)
|
||||
List<DisasterRiskData.SpotRisk> spots = entry.getValue().stream()
|
||||
.map(spot -> {
|
||||
Double prob = probabilities.get(spot.getId() + "_1");
|
||||
if (prob == null) return null;
|
||||
String level = prob >= 0.70 ? "高" : "中";
|
||||
return new DisasterRiskData.SpotRisk()
|
||||
.setPosition(spot.getPosition())
|
||||
.setProbability(prob)
|
||||
.setRiskLevel(level);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(DisasterRiskData.SpotRisk::getProbability).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 重新编号
|
||||
AtomicInteger reIdx = new AtomicInteger(1);
|
||||
spots.forEach(s -> s.setIndex(reIdx.getAndIncrement()));
|
||||
|
||||
data.setSpots(spots);
|
||||
|
||||
// 中高风险街道
|
||||
data.setHighRiskStreets(spots.stream()
|
||||
.map(DisasterRiskData.SpotRisk::getPosition)
|
||||
.collect(Collectors.joining("、")));
|
||||
|
||||
// 图片
|
||||
data.setImageUrl(findImage(files, disasterType));
|
||||
|
||||
result.add(data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件列表中匹配图片
|
||||
*/
|
||||
private String findImage(List<XianInferenceResultFile> files, String disasterType) {
|
||||
String expectedName = String.format(IMAGE_NAME_PATTERN, disasterType);
|
||||
return files.stream()
|
||||
.filter(f -> expectedName.equals(f.getFileName()))
|
||||
.findFirst()
|
||||
.map(f -> (reportProperties.getFiles().getServerUrl() + "/" + f.getFilePath()).replace("\\", "/").replace("//", "/"))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建模板数据模型
|
||||
*
|
||||
* @param sim 模拟结果
|
||||
* @param disasterDataList 灾害数据
|
||||
* @param rainData 降雨数据
|
||||
* @param districtNames 灾害名称
|
||||
* @param riskCount 灾害数量
|
||||
* @param hiddenCount 隐患数量
|
||||
* @param predictPointNum 预测点数
|
||||
* @return 模板数据模型
|
||||
*/
|
||||
private Map<String, Object> buildTemplateModel(
|
||||
XianInferenceResult sim,
|
||||
List<DisasterRiskData> disasterDataList,
|
||||
List<RainfallDistrictSummaryResponseDTO> rainData,
|
||||
List<String> districtNames,
|
||||
int riskCount,
|
||||
int hiddenCount,
|
||||
int predictPointNum) {
|
||||
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
BaseCondition cond = JSON.parseObject(sim.getCondition(), BaseCondition.class);
|
||||
|
||||
// 基础文本
|
||||
m.put("报告时间", DateTimeUtils.datetimeFormat(sim.getOccurredTime(), "MM月dd日HH时mm分"));
|
||||
m.put("降雨时间", DateTimeUtils.datetimeFormat(sim.getOccurredTime(), "yyyy年MM月dd日HH时"));
|
||||
m.put("降雨地区", String.join("、", districtNames));
|
||||
m.put("持续时间", cond.getDuration() == null ? 72 : cond.getDuration());
|
||||
m.put("降雨量", buildRainfallStr(rainData, districtNames));
|
||||
m.put("降雨集中区域", districtNames.isEmpty() ? "" : districtNames.get(0));
|
||||
m.put("风险区数量", riskCount);
|
||||
m.put("隐患点数量", hiddenCount);
|
||||
m.put("致灾因子", String.join("、", reportProperties.getDisasterCausingFactors().getRainfall().getType()) + "等");
|
||||
m.put("致灾因子数量", reportProperties.getDisasterCausingFactors().getRainfall().getNumber());
|
||||
m.put("预测点数量", predictPointNum);
|
||||
m.put("灾害链", buildDisasterChain("暴雨", disasterDataList));
|
||||
m.put("具体风险地区", buildRiskPosition(disasterDataList));
|
||||
m.put("高风险区域数量", countHighRisk(disasterDataList));
|
||||
m.put("次生灾害类型", buildSecondaryTypes(disasterDataList));
|
||||
|
||||
// 所有灾害类型默认值
|
||||
for (String typeName : DISASTER_CONDITION_MAP.keySet()) {
|
||||
String suffix = DISASTER_CONDITION_MAP.get(typeName);
|
||||
m.put("has" + suffix, false);
|
||||
}
|
||||
|
||||
// 各灾害类型(覆盖有数据的)
|
||||
for (DisasterRiskData data : disasterDataList) {
|
||||
String suffix = data.getConditionSuffix();
|
||||
boolean has = data.hasData();
|
||||
m.put("has" + suffix, has);
|
||||
|
||||
if (has) {
|
||||
m.put(data.getDisasterType() + "中高风险街道", data.getHighRiskStreets());
|
||||
m.put(data.getDisasterType() + "风险表",
|
||||
ReportTableBuilder.buildRiskTable(data.getDisasterType(), data.getSpots()));
|
||||
m.put(data.getDisasterType() + "隐患点人口分布图",
|
||||
downloadImage(data.getImageUrl()));
|
||||
m.put(data.getDisasterType() + "影响人数下限", data.getInfluenceLower());
|
||||
m.put(data.getDisasterType() + "影响人数上限", data.getInfluenceUpper());
|
||||
}
|
||||
}
|
||||
|
||||
// 全局条件
|
||||
boolean hasAny = disasterDataList.stream().anyMatch(DisasterRiskData::hasData);
|
||||
m.put("hasDisasters", hasAny);
|
||||
|
||||
// 应急处置建议
|
||||
m.put("高风险街道", buildAllHighRiskPosition(disasterDataList));
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建降雨量字符串
|
||||
*
|
||||
* @param rainData 降雨数据
|
||||
* @param names 位置
|
||||
* @return 降雨量字符串
|
||||
*/
|
||||
private String buildRainfallStr(List<RainfallDistrictSummaryResponseDTO> rainData, List<String> names) {
|
||||
Map<String, String> map = rainData.stream().collect(Collectors.toMap(
|
||||
RainfallDistrictSummaryResponseDTO::getDistrictName,
|
||||
RainfallDistrictSummaryResponseDTO::getRainfall,
|
||||
(a, b) -> a));
|
||||
return names.stream().map(map::get).filter(Objects::nonNull)
|
||||
.collect(Collectors.joining("mm、")) + "mm";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取暴雨灾害链
|
||||
* 构建灾害链字符串
|
||||
*
|
||||
* @param hiddenDangerSpots 隐藏点信息
|
||||
* @return 暴雨Chain
|
||||
* @param list 灾害数据
|
||||
* @return 灾害链
|
||||
*/
|
||||
private String getRainfallDisasterChain(List<XianHiddenDangerSpots> hiddenDangerSpots) {
|
||||
return hiddenDangerSpots.stream()
|
||||
.map(XianHiddenDangerSpots::getDisasterType)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.map(name -> "暴雨-" + name)
|
||||
private String buildDisasterChain(String type, List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.map(d -> type + "-" + d.getDisasterType())
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取暴雨灾害链位置
|
||||
* 构建高风险位置字符串
|
||||
*
|
||||
* @param hiddenDangerSpots 隐藏点信息
|
||||
* @return 暴雨灾害链位置
|
||||
* @param list 灾害数据
|
||||
* @return 高风险位置
|
||||
*/
|
||||
private String getRainfallDisasterChainPosition(List<XianHiddenDangerSpots> hiddenDangerSpots) {
|
||||
return hiddenDangerSpots.stream()
|
||||
.map(XianHiddenDangerSpots::getVillage)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
private String buildRiskPosition(List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.map(DisasterRiskData::getHighRiskStreets)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取次生灾害类型
|
||||
* 统计高风险数量
|
||||
*
|
||||
* @param hiddenDangerSpots 隐藏点信息
|
||||
* @param list 灾害数据
|
||||
* @return 高风险数量
|
||||
*/
|
||||
private int countHighRisk(List<DisasterRiskData> list) {
|
||||
return (int) list.stream().filter(DisasterRiskData::hasData)
|
||||
.flatMap(d -> d.getSpots().stream())
|
||||
.filter(s -> "高".equals(s.getRiskLevel()))
|
||||
.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建次生灾害类型字符串
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 次生灾害类型
|
||||
*/
|
||||
private String getSecondaryDisasterType(List<XianHiddenDangerSpots> hiddenDangerSpots) {
|
||||
return hiddenDangerSpots.stream()
|
||||
.map(XianHiddenDangerSpots::getDisasterType)
|
||||
.filter(Objects::nonNull)
|
||||
private String buildSecondaryTypes(List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.map(DisasterRiskData::getDisasterType)
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建所有高风险位置字符串
|
||||
*
|
||||
* @param list 灾害数据
|
||||
* @return 所有高风险位置
|
||||
*/
|
||||
private String buildAllHighRiskPosition(List<DisasterRiskData> list) {
|
||||
return list.stream().filter(DisasterRiskData::hasData)
|
||||
.flatMap(d -> d.getSpots().stream())
|
||||
.map(DisasterRiskData.SpotRisk::getPosition)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染报告
|
||||
*
|
||||
* @param templatePath 模板路径
|
||||
* @param outputPath 输出路径
|
||||
* @param outputName 输出文件名
|
||||
* @param model 模板数据
|
||||
* @return 输出文件路径
|
||||
*/
|
||||
private String renderReport(String templatePath, String outputPath, String outputName, Map<String, Object> model) {
|
||||
|
||||
// 注册表格策略
|
||||
Configure config = Configure.builder()
|
||||
.bind("滑坡风险表", new TableRenderPolicy())
|
||||
.bind("泥石流风险表", new TableRenderPolicy())
|
||||
.bind("山洪风险表", new TableRenderPolicy())
|
||||
.bind("内涝风险表", new TableRenderPolicy())
|
||||
.bind("崩塌风险表", new TableRenderPolicy())
|
||||
.build();
|
||||
|
||||
try {
|
||||
Path dir = Path.of(outputPath);
|
||||
if (!Files.exists(dir)) Files.createDirectories(dir);
|
||||
Path output = dir.resolve(outputName);
|
||||
|
||||
XWPFTemplate template = XWPFTemplate.compile(Path.of(templatePath).toFile(), config)
|
||||
.render(model);
|
||||
try (FileOutputStream fos = new FileOutputStream(output.toFile())) {
|
||||
template.write(fos);
|
||||
}
|
||||
template.close();
|
||||
|
||||
log.info("报告生成成功: {}", Path.of(outputPath, outputName).toString());
|
||||
return output.toString();
|
||||
} catch (Exception e) {
|
||||
log.error("生成暴雨报告失败", e);
|
||||
throw new RuntimeException("生成暴雨报告失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 URL 下载图片
|
||||
*/
|
||||
private PictureRenderData downloadImage(String imageUrl) {
|
||||
if (imageUrl == null || imageUrl.isEmpty()) return null;
|
||||
try {
|
||||
return Pictures.ofUrl(imageUrl).size(500, 375).create();
|
||||
} catch (Exception e) {
|
||||
log.warn("下载图片失败, 使用占位: {}", imageUrl, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.gis.xian.utils;
|
||||
|
||||
import com.deepoove.poi.data.RowRenderData;
|
||||
import com.deepoove.poi.data.Rows;
|
||||
import com.deepoove.poi.data.TableRenderData;
|
||||
import com.deepoove.poi.data.Tables;
|
||||
import com.gis.xian.dto.DisasterRiskData;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* poi-tl 报告表格构建工具
|
||||
*/
|
||||
public class ReportTableBuilder {
|
||||
|
||||
private static final DecimalFormat PCT_FMT = new DecimalFormat("0.00%");
|
||||
|
||||
/**
|
||||
* 构建灾害风险表(四列:序号、位置、发生概率、灾害等级)
|
||||
*/
|
||||
public static TableRenderData buildRiskTable(String disasterType, List<DisasterRiskData.SpotRisk> spots) {
|
||||
RowRenderData header = Rows.of(
|
||||
"序号",
|
||||
"位置",
|
||||
disasterType + "发生概率",
|
||||
"灾害等级"
|
||||
).textBold().center().create();
|
||||
|
||||
Tables.TableBuilder builder = Tables.of(header);
|
||||
AtomicInteger idx = new AtomicInteger(1);
|
||||
for (DisasterRiskData.SpotRisk spot : spots) {
|
||||
builder.addRow(Rows.of(
|
||||
String.valueOf(idx.getAndIncrement()),
|
||||
spot.getPosition(),
|
||||
PCT_FMT.format(spot.getProbability()),
|
||||
spot.getRiskLevel()
|
||||
).center().create());
|
||||
}
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ safety:
|
||||
- /druid
|
||||
- /websocket/info
|
||||
- /websocket/**
|
||||
- /report/output/**
|
||||
|
||||
# 请求无需解密的路径
|
||||
no-decrypt-paths:
|
||||
@@ -48,4 +49,5 @@ safety:
|
||||
- /druid
|
||||
- /websocket/info
|
||||
- /websocket/**
|
||||
- /report/output/**
|
||||
|
||||
|
||||
@@ -16,10 +16,24 @@ mybatis-plus:
|
||||
|
||||
# 报告配置
|
||||
report:
|
||||
files:
|
||||
server-url: ${files.server.url:http://localhost:8082}
|
||||
disaster-causing-factors:
|
||||
rainfall:
|
||||
type: [ "降雨强度", "持续时间", "累计降雨量", "高程", "坡度", "坡向", "土壤分类", "岩性", "土地利用类型", "不透水率", "植被覆盖指数", "距河道距离", "管网密度" ]
|
||||
number: 19
|
||||
template-path: "src/main/resources/template/rainfall/rainfall-template.docx"
|
||||
output-path: "/xian/report/rainfall/{id}"
|
||||
output-name: "暴雨应急预评估报告_{currentTime}.docx"
|
||||
earthquake:
|
||||
type: [ "震度", "震源深度", "距离震中位置", "高程", "坡度", "坡向", "土壤分类", "岩性", "土地利用类型", "植被覆盖指数", "距离断裂带距离" ]
|
||||
number: 17
|
||||
number: 17
|
||||
template-path: "src/main/resources/template/earthquake/earthquake-template.docx"
|
||||
output-path: "/xian/report/earthquake/{id}"
|
||||
output-name: "地震应急预评估报告_{currentTime}.docx"
|
||||
disaster-types:
|
||||
landslide: 滑坡
|
||||
debrisFlow: 泥石流
|
||||
torrentialFlood: 山洪
|
||||
waterLogging: 内涝
|
||||
collapse: 崩塌
|
||||
@@ -84,7 +84,7 @@
|
||||
</select>
|
||||
|
||||
<select id="getPointDetailByIds" resultMap="XianHiddenDangerSpotsResultMap">
|
||||
SELECT disaster_name, disaster_type, village, position, risk_grade FROM
|
||||
SELECT id, disaster_name, disaster_type, village, position, risk_grade FROM
|
||||
xian_hidden_danger_spots
|
||||
<where>
|
||||
<if test="ids != null and ids.size() > 0">
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gis.xian.mapper.XianInferenceResultFileMapper">
|
||||
|
||||
<resultMap id="XianInferenceResultFileResultMap" type="com.gis.xian.entity.XianInferenceResultFile">
|
||||
<id property="id" column="id"/>
|
||||
<result property="inferenceId" column="inference_id"/>
|
||||
<result property="filePath" column="file_path"/>
|
||||
<result property="fileName" column="file_name"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="isDelete" column="is_delete"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByInferenceId" resultMap="XianInferenceResultFileResultMap">
|
||||
SELECT file_path, file_name
|
||||
FROM xian_inference_result_file
|
||||
WHERE inference_id = #{inferenceId}
|
||||
AND is_delete = 0
|
||||
ORDER BY create_time
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -6,4 +6,11 @@ algorithm:
|
||||
server:
|
||||
url: "http://localhost:8082"
|
||||
connect-timeout: 5
|
||||
read-timeout: 120
|
||||
read-timeout: 120
|
||||
|
||||
# 文件配置
|
||||
files:
|
||||
server:
|
||||
url: "http://localhost:8082"
|
||||
local:
|
||||
path: "G:/files"
|
||||
@@ -2,8 +2,16 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
# 算法服务器配置
|
||||
algorithm:
|
||||
server:
|
||||
url: "http://localhost:8081"
|
||||
connect-timeout: 5
|
||||
read-timeout: 120
|
||||
read-timeout: 120
|
||||
|
||||
# 文件配置
|
||||
files:
|
||||
server:
|
||||
url: "http://localhost:8082"
|
||||
local:
|
||||
path: "/data/files"
|
||||
Reference in New Issue
Block a user