2025.10.14
1. 新增数据统计颗粒度选项,可选秒级数据、分钟数据进行数据统计
2. 典型隐患区域统计新增按照污染溯源区域进行分类统计的功能
| | |
| | | mission: Mission, |
| | | pollutedClues: List<PollutedClue?>, |
| | | data: List<BaseRealTimeData>, |
| | | granularity: String, |
| | | minDis: Double = 100.0, |
| | | ): MissionDetail { |
| | | // 创建任务详情对象并复制基本信息 |
| | |
| | | // missionDetail.keyScene = relatedScenes |
| | | |
| | | // 计算环境因子统计数据(平均值、最小值、最大值) |
| | | missionDetail.dataStatistics = data.calDataStatistics() |
| | | missionDetail.dataStatistics = data.calDataStatistics(granularity) |
| | | |
| | | // 异常数据点数量统计 |
| | | // val clues = pollutedClues.filter { it?.msgType == MsgType.PolClue.value } |
| | |
| | | |
| | | class ClueByArea { |
| | | var sceneInfo: SceneInfo? = null |
| | | var address: String? = null |
| | | var clueByFactorList: MutableList<ClueByFactor>? = null |
| | | } |
| | | |
| | |
| | | var clues: MutableList<PollutedClue>? = null |
| | | } |
| | | |
| | | fun generateClueByRiskArea(keyScenes: List<SceneInfo?>, pollutedClues: List<PollutedClue?>): List<ClueByArea> { |
| | | /** |
| | | * 生成走航典型隐患区域,根据关键场景列表进行分组 |
| | | * @param keyScenes 关键场景列表 |
| | | * @param pollutedClues 污染线索列表 |
| | | * @return 按区域和因子分组的污染线索 |
| | | */ |
| | | fun generateClueByKeyRiskScene(keyScenes: List<SceneInfo?>, pollutedClues: List<PollutedClue?>): List<ClueByArea> { |
| | | val result = mutableListOf<ClueByArea>() |
| | | |
| | | pollutedClues.forEach { pollutedClue -> |
| | |
| | | |
| | | return result |
| | | } |
| | | |
| | | /** |
| | | * 生成走航典型隐患区域,根据污染线索溯源地址进行分组 |
| | | * @param pollutedClues 污染线索列表 |
| | | * @return 按区域和因子分组的污染线索 |
| | | */ |
| | | fun generateClueByRiskArea(pollutedClues: List<PollutedClue?>): List<ClueByArea> { |
| | | val result = mutableListOf<ClueByArea>() |
| | | |
| | | pollutedClues.forEach { pollutedClue -> |
| | | if (pollutedClue == null) return@forEach |
| | | val dataList = pollutedClue.pollutedData?.dataList ?: emptyList() |
| | | if (dataList.isEmpty()) return@forEach |
| | | |
| | | // 按污染溯源地址和因子分组线索 |
| | | pollutedClue.pollutedArea?.address?.let { address -> |
| | | var clueByArea = result.find { it.address == address } |
| | | if (clueByArea == null) { |
| | | clueByArea = ClueByArea().apply { |
| | | this.address = address |
| | | this.clueByFactorList = mutableListOf() |
| | | } |
| | | result.add(clueByArea) |
| | | } |
| | | |
| | | val firstFactorType = pollutedClue.pollutedData?.statisticMap?.keys?.first() |
| | | val afType = AggregatedFactorType.getAFType(firstFactorType) |
| | | val factorName = afType?.des ?: firstFactorType?.des |
| | | var clueByFactor = clueByArea.clueByFactorList?.find { it.factor == factorName } |
| | | if (clueByFactor == null) { |
| | | clueByFactor = ClueByFactor().apply { |
| | | this.factor = factorName |
| | | this.clues = mutableListOf() |
| | | } |
| | | clueByArea.clueByFactorList?.add(clueByFactor) |
| | | } |
| | | |
| | | clueByFactor.clues?.add(pollutedClue) |
| | | } |
| | | } |
| | | |
| | | return result |
| | | } |
| | | } |
| | |
| | | val probByFactor = clueRes.third |
| | | |
| | | // 7. 从异常所在地区和溯源的场景中统计聚焦区域 |
| | | val focusRegion = calFocusRegion(clues) |
| | | val focusRegion = calFocusRegion(clues, area) |
| | | |
| | | // 8. 构建并返回统计结果 |
| | | return Summary( |
| | |
| | | } |
| | | } |
| | | val probByFactor = probByFactorMap.entries.map { |
| | | val per = if(probCount == 0) .0 else round(it.value.toDouble() / probCount * 100) / 100 |
| | | val per = if (probCount == 0) .0 else round(it.value.toDouble() / probCount * 100) / 100 |
| | | Triple(it.key.des, it.value, per) |
| | | } |
| | | return Triple(probCount, highRiskSceneCount, probByFactor) |
| | | } |
| | | |
| | | private fun calFocusRegion(clues: List<PollutedClue?>): List<String> { |
| | | private fun calFocusRegion(clues: List<PollutedClue?>, area: AreaVo): List<String> { |
| | | // 统计每个区域或场景出现的次数 |
| | | val focusArea = mutableMapOf<String, Int>() |
| | | val focusScene = mutableMapOf<String, Int>() |
| | | clues.forEach { c-> |
| | | clues.forEach { c -> |
| | | if (c?.msgType == MsgType.PolClue.value) { |
| | | if (!c.pollutedArea?.address.isNullOrBlank()) { |
| | | if (focusArea.containsKey(c.pollutedArea?.address)) { |
| | |
| | | focusArea[c.pollutedArea?.address!!] = 1 |
| | | } |
| | | } |
| | | c.pollutedSource?.sceneList?.forEach { s-> |
| | | c.pollutedSource?.sceneList?.forEach { s -> |
| | | if (s.name != null) { |
| | | if (focusScene.containsKey(s.name!!)) { |
| | | focusScene[s.name!!] = focusScene[s.name!!]!! + 1 |
| | |
| | | } |
| | | } |
| | | } |
| | | return focusArea.entries.sortedByDescending { it.value }.map { it.key }.take(FOCUS_AREA_COUNT) + |
| | | focusScene.entries.sortedByDescending { it.value }.map { it.key }.take(FOCUS_AREA_COUNT) |
| | | return focusArea.entries.sortedByDescending { it.value } |
| | | .filter { |
| | | // 仅统计包含区县名称的区域 |
| | | area.districtName ?: return@filter true |
| | | return@filter it.key.contains(area.districtName!!) |
| | | } |
| | | .map { it.key }.take(FOCUS_AREA_COUNT) + |
| | | focusScene.entries.sortedByDescending { it.value } |
| | | .filter { |
| | | // 仅统计包含区县名称的区域 |
| | | area.districtName ?: return@filter true |
| | | return@filter it.key.contains(area.districtName!!) |
| | | }.map { it.key }.take(FOCUS_AREA_COUNT) |
| | | } |
| | | } |
| | |
| | | import java.math.BigDecimal |
| | | import java.time.LocalDateTime |
| | | import java.time.ZoneId |
| | | import java.time.temporal.ChronoUnit |
| | | import java.util.* |
| | | import javax.persistence.Column |
| | | import javax.persistence.GeneratedValue |
| | |
| | | /** |
| | | * 计算实时监测数据列表的统计信息 |
| | | * 为每种环境因子计算最小值、最大值和平均值 |
| | | * |
| | | * @param granularity 数据颗粒度,可选值为SECOND, MINUTE, HOUR, 默认MINUTE |
| | | * @return 包含各环境因子统计信息的FactorStatistics列表 |
| | | * 每个FactorStatistics对象包含因子类型、最小值、最大值和平均值 |
| | | */ |
| | | fun List<BaseRealTimeData>.calDataStatistics(): List<FactorStatistics> { |
| | | fun List<BaseRealTimeData>.calDataStatistics(granularity: String): List<FactorStatistics> { |
| | | |
| | | // 检查颗粒度是否有效 |
| | | if (granularity !in listOf("SECOND", "MINUTE", "HOUR")) { |
| | | throw IllegalArgumentException("无效的颗粒度参数,可选值为SECOND, MINUTE, HOUR") |
| | | } |
| | | |
| | | val groupedData = when (granularity) { |
| | | "SECOND" -> this |
| | | "MINUTE" -> groupBy { it.dataTime?.toInstant()?.truncatedTo(ChronoUnit.MINUTES) }.mapValues { |
| | | it.value.avg().apply { |
| | | dataTime = Date.from(it.key) |
| | | createTime = dataTime |
| | | } |
| | | }.values.toList() |
| | | "HOUR" -> groupBy { it.dataTime?.toInstant()?.truncatedTo(ChronoUnit.HOURS) }.mapValues { |
| | | it.value.avg().apply { |
| | | dataTime = Date.from(it.key) |
| | | createTime = dataTime |
| | | } |
| | | }.values.toList() |
| | | else -> throw IllegalArgumentException("无效的颗粒度参数,可选值为SECOND, MINUTE, HOUR") |
| | | } |
| | | |
| | | // 初始化各环境因子的统计对象列表 |
| | | val statistics = mutableListOf<FactorStatistics>() |
| | |
| | | ).forEach { statistics.add(FactorStatistics(it)) } |
| | | |
| | | // 计算平均值并同时更新各因子的最小值和最大值 |
| | | val avgData = avg { item -> |
| | | val avgData = groupedData.avg { item -> |
| | | // 更新每个因子的最小和最大值 |
| | | statistics[0].updateMinAndMaxValue(item.no2) |
| | | statistics[1].updateMinAndMaxValue(item.co) |
| | |
| | | * @param areaVo 区域参数 |
| | | * @return 任务详情列表,每个元素包含任务完整信息、场景数据和统计结果 |
| | | */ |
| | | fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionDetail> |
| | | fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo,granularity: String?): List<MissionDetail> |
| | | |
| | | /** |
| | | * 生成走航任务详情(按任务编号筛选) |
| | | * 根据任务编号查询并生成详细的任务报告,包含任务完整信息、场景数据和统计结果 |
| | | * @param missionCode 任务编号,用于唯一标识特定的走航任务 |
| | | * @param granularity 数据颗粒度,可选值为SECOND, MINUTE, HOUR, 默认MINUTE |
| | | * @return 任务详情对象,包含任务完整信息、场景数据和统计结果 |
| | | */ |
| | | fun generateMissionDetail(missionCode: String): MissionDetail |
| | | fun generateMissionDetail(missionCode: String, granularity: String?): MissionDetail |
| | | |
| | | /** |
| | | * 获取走航任务详情(直接处理任务数据) |
| | | * 处理已有的任务、污染线索和实时数据,生成详细任务报告 |
| | | * @param keyScenes 关键场景列表,用于分析走航是否经过该区域 |
| | | * @param missionCluesData 包含任务、污染线索和实时数据的Triple列表 |
| | | * @param granularity 数据颗粒度,可选值为SECOND, MINUTE, HOUR, 默认MINUTE |
| | | * @return 任务详情列表 |
| | | */ |
| | | fun generateMissionDetail(keyScenes: List<SceneInfo?>, missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>): List<MissionDetail> |
| | | fun generateMissionDetail( |
| | | keyScenes: List<SceneInfo?>, |
| | | missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>, |
| | | granularity: String, |
| | | ): List<MissionDetail> |
| | | |
| | | fun generateClueByRiskArea(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionRiskArea.ClueByArea> |
| | | |
| | |
| | | import com.flightfeather.uav.socket.eunm.FactorType |
| | | import com.flightfeather.uav.socket.sender.MsgType |
| | | import org.springframework.stereotype.Service |
| | | import tk.mybatis.mapper.entity.Example |
| | | import java.util.* |
| | | |
| | | /** |
| | |
| | | * @param startTime 查询起始时间(包含) |
| | | * @param endTime 查询结束时间(包含) |
| | | * @param areaVo 区域参数,包含省、市、区编码 |
| | | * @param granularity 数据颗粒度,可选值为SECOND, MINUTE, HOUR, 默认MINUTE |
| | | * @return 任务详情列表,每个元素包含任务完整信息、污染线索及实时监测数据 |
| | | * @see MissionRep.findByAreaAndTime 区域时间筛选数据源 |
| | | * @see realTimeDataRep.fetchData 实时数据获取接口 |
| | | */ |
| | | override fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionDetail> { |
| | | override fun generateMissionDetail( |
| | | startTime: Date, |
| | | endTime: Date, |
| | | areaVo: AreaVo, |
| | | granularity: String?, |
| | | ): List<MissionInventory.MissionDetail> { |
| | | val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map { |
| | | Triple( |
| | | it, |
| | |
| | | SceneType.TYPE21.value |
| | | ) |
| | | ) |
| | | return generateMissionDetail(keyScenes, missionCluesData) |
| | | return generateMissionDetail(keyScenes, missionCluesData, granularity ?: "MINUTE") |
| | | } |
| | | |
| | | override fun generateMissionDetail(missionCode: String): MissionInventory.MissionDetail { |
| | | override fun generateMissionDetail(missionCode: String, granularity: String?): MissionInventory.MissionDetail { |
| | | val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在") |
| | | val missionClues = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, MsgType.PolClue) as List<PollutedClue?> |
| | | val realTimeData = realTimeDataRep.fetchData(mission) |
| | |
| | | SceneType.TYPE21.value |
| | | ) |
| | | ) |
| | | return MissionInventory().generateMissionDetail(keyScenes, mission, missionClues, realTimeData) |
| | | return MissionInventory().generateMissionDetail(keyScenes, mission, missionClues, realTimeData, granularity ?: "MINUTE") |
| | | } |
| | | |
| | | /** |
| | |
| | | override fun generateMissionDetail( |
| | | keyScenes: List<SceneInfo?>, |
| | | missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>, |
| | | granularity: String, |
| | | ): List<MissionInventory.MissionDetail> { |
| | | return missionCluesData.map { |
| | | MissionInventory().generateMissionDetail(keyScenes, it.first, it.second, it.third) |
| | | MissionInventory().generateMissionDetail(keyScenes, it.first, it.second, it.third, granularity) |
| | | } |
| | | } |
| | | |
| | |
| | | val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?> |
| | | clues.addAll(clue) |
| | | } |
| | | val keyScenes = sceneInfoRep.findBySceneTypes( |
| | | listOf( |
| | | SceneType.TYPE19.value, |
| | | SceneType.TYPE20.value, |
| | | SceneType.TYPE21.value |
| | | ) |
| | | ) |
| | | return generateClueByRiskArea(keyScenes, clues) |
| | | // val keyScenes = sceneInfoRep.findBySceneTypes( |
| | | // listOf( |
| | | // SceneType.TYPE19.value, |
| | | // SceneType.TYPE20.value, |
| | | // SceneType.TYPE21.value |
| | | // ) |
| | | // ) |
| | | return MissionRiskArea().generateClueByRiskArea(clues) |
| | | } |
| | | |
| | | override fun generateClueByRiskArea(missionCode: String): List<MissionRiskArea.ClueByArea> { |
| | |
| | | keyScenes: List<SceneInfo?>, |
| | | pollutedClues: List<PollutedClue?>, |
| | | ): List<MissionRiskArea.ClueByArea> { |
| | | return MissionRiskArea().generateClueByRiskArea(keyScenes, pollutedClues) |
| | | return MissionRiskArea().generateClueByKeyRiskScene(keyScenes, pollutedClues) |
| | | } |
| | | |
| | | override fun generateGridFusion( |
| | |
| | | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | endTime: LocalDateTime, |
| | | @ApiParam("数据颗粒度", allowableValues = "SECOND, MINUTE, HOUR") @RequestParam(required = false) |
| | | granularity: String?, |
| | | @ApiParam("区域") @RequestBody areaVo: AreaVo, |
| | | ) = resPack { |
| | | dataAnalysisService.generateMissionDetail( |
| | | Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()), |
| | | Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()), |
| | | areaVo |
| | | areaVo,granularity |
| | | ) |
| | | } |
| | | |
| | |
| | | @GetMapping("/report/missionDetail/one") |
| | | fun generateOneMissionDetail( |
| | | @ApiParam("任务编号") @RequestParam missionCode: String, |
| | | ) = resPack { dataAnalysisService.generateMissionDetail(missionCode) } |
| | | @ApiParam("数据颗粒度", allowableValues = "SECOND, MINUTE, HOUR") @RequestParam(required = false) |
| | | granularity: String?, |
| | | ) = resPack { dataAnalysisService.generateMissionDetail(missionCode, granularity) } |
| | | |
| | | @ApiOperation(value = "走航典型隐患区域统计") |
| | | @PostMapping("/report/clueByRiskArea") |
| | |
| | | |
| | | @Test |
| | | fun generateMissionDetail() { |
| | | val res = dataAnalysisService.generateMissionDetail(startTime, endTime, areaVo) |
| | | val res = dataAnalysisService.generateMissionDetail(startTime, endTime, areaVo, "MINUTE") |
| | | println(res) |
| | | } |
| | | |