部分暴雨灾害链报告产出,部分内容待完善

This commit is contained in:
wzy-warehouse
2026-06-28 15:57:59 +08:00
parent 2ef96f812f
commit 0c59afa490
12 changed files with 586 additions and 163 deletions
@@ -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();
}
}
+2
View File
@@ -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/**
+15 -1
View File
@@ -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"