feiyu02
2025-09-30 94fee0b511279679b43e210878d3d36e5a14384b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package com.flightfeather.uav.lightshare.service.impl
 
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.ExceptionAnalysisController
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
import com.flightfeather.uav.biz.report.MissionGridFusion
import com.flightfeather.uav.biz.report.MissionInventory
import com.flightfeather.uav.biz.report.MissionRiskArea
import com.flightfeather.uav.biz.report.MissionSummary
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.common.exception.BizException
import com.flightfeather.uav.common.location.LocationRoadNearby
import com.flightfeather.uav.common.utils.GsonUtils
import com.flightfeather.uav.domain.entity.*
import com.flightfeather.uav.domain.mapper.MissionMapper
import com.flightfeather.uav.domain.repository.*
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.bean.GridDataDetailMixVo
import com.flightfeather.uav.lightshare.eunm.PollutionDegree
import com.flightfeather.uav.lightshare.eunm.SceneType
import com.flightfeather.uav.lightshare.service.DataAnalysisService
import com.flightfeather.uav.lightshare.service.SatelliteDataCalculateService
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.*
 
/**
 * 数据分析服务接口实现类
 * 提供走航任务数据的统计分析、污染溯源、任务清单及详情生成等核心业务功能
 * 整合多数据源完成数据聚合与分析,为前端提供标准化的统计结果
 * @date 2025/5/8
 * @author feiyu02
 */
@Service
class DataAnalysisServiceImpl(
    private val missionRep: MissionRep,
    private val missionMapper: MissionMapper,
    private val realTimeDataRep: RealTimeDataRep,
    private val locationRoadNearby: LocationRoadNearby,
    private val segmentInfoRep: SegmentInfoRep,
    private val sourceTraceRep: SourceTraceRep,
    private val sceneInfoRep: SceneInfoRep,
    private val satelliteGridRep: SatelliteGridRep,
    private val satelliteDataCalculateService: SatelliteDataCalculateService
) : DataAnalysisService {
 
    /**
     * 污染溯源分析
     * 对指定走航任务进行多因子污染数据分析,识别异常数据点和潜在污染源
     * @param missionCode 走航任务编码(主键)
     * @return 异常结果列表,包含异常类型、位置、浓度值等详细信息
     * @throws BizException 当走航任务不存在时抛出
     * @see ExceptionAnalysisController 异常分析控制器,处理具体的数据分析逻辑
     */
    override fun pollutionTrace(missionCode: String): List<ExceptionResult> {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
 
        val exceptionAnalysisController =
            ExceptionAnalysisController(realTimeDataRep, locationRoadNearby, segmentInfoRep)
 
        return exceptionAnalysisController.execute(
            mission, FactorFilter.builder()
//                .withMain(FactorType.NO2)
                .withMain(FactorType.CO)
//                .withMain(FactorType.H2S)
//                .withMain(FactorType.SO2)
//                .withMain(FactorType.O3)
                .withMain(FactorType.PM25)
                .withMain(FactorType.PM10)
                .withMain(FactorType.VOC)
                .create()
        )
    }
 
    /**
     * 获取历史污染溯源结果
     * 查询指定任务的历史污染溯源结果并序列化为JSON字符串
     * @param missionCode 走航任务编码
     * @return 历史污染溯源结果的JSON字符串,具体格式由sourceTraceRep实现决定
     * @throws BizException 当走航任务不存在时抛出
     */
    override fun fetchHistory(missionCode: String): String {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
 
        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime)
        return GsonUtils.gson.toJson(res)
    }
 
    /**
     * 生成走航任务汇总统计
     * 按时间范围和行政区划统计走航任务的关键指标(任务数量、异常率、平均里程等)
     * @param startTime 统计起始时间(包含)
     * @param endTime 统计结束时间(包含)
     * @param areaVo 区域参数,包含省、市、区三级行政区划编码
     * @return 汇总统计对象,包含任务总数、异常点数量、平均里程等核心指标
     * @see MissionSummary 汇总统计处理器,封装具体的统计逻辑实现
     */
    override fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
        val clues = mutableListOf<PollutedClue?>()
        val missions = missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
            it ?: return@onEach
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
            clues.addAll(clue)
        }
        val summary = MissionSummary().execute(startTime, endTime, missions, clues)
        return summary
    }
 
    override fun generateMissionSummary(missionCode: String): MissionSummary.Summary {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
        val clues = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, MsgType.PolClue) as List<PollutedClue?>
        val summary = MissionSummary().execute(mission.startTime, mission.endTime, listOf(mission), clues)
        return summary
    }
 
    /**
     * 生成走航任务清单(按时间和区域筛选)
     * 根据时间范围和行政区划查询走航任务,并关联污染线索数据生成任务列表
     * @param startTime 查询起始时间(包含)
     * @param endTime 查询结束时间(包含)
     * @param areaVo 区域参数,包含省、市、区编码
     * @return 走航任务信息列表,每个元素包含任务基本信息和关联的污染线索
     * @see MissionRep.findByAreaAndTime 区域时间筛选数据源
     * @see generateMissionList 重载方法,处理已关联的数据对
     */
    override fun generateMissionList(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionInfo> {
//        val missionClues = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
//            it to sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
//        }
//        return generateMissionList(missionClues)
        val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
            Triple(
                it,
                sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
                realTimeDataRep.fetchData(it)
            )
        }
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return generateMissionInfo(keyScenes, missionCluesData)
    }
 
    /**
     * 生成走航任务清单(直接处理任务数据)
     * 接收已关联的任务-污染线索数据对,生成格式化的任务列表
     * @param missionClues 任务-污染线索数据对列表,Pair.first为任务对象,Pair.second为对应污染线索
     * @return 标准化的走航任务信息列表,包含任务基本属性和污染统计信息
     * @see MissionInventory 任务清单生成器,封装具体格式化逻辑
     */
    override fun generateMissionList(missionClues: List<Pair<Mission, List<PollutedClue?>>>): List<MissionInventory.MissionInfo> {
        return MissionInventory().generateMissionList(missionClues)
    }
 
    override fun generateMissionInfo(
        keyScenes: List<SceneInfo?>,
        missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>,
    ): List<MissionInventory.MissionInfo> {
        return missionCluesData.map {
            MissionInventory().generateMissionInfo(keyScenes, it.first, it.second, it.third)
        }
    }
 
    /**
     * 生成走航任务详情(按时间和区域筛选)
     * 根据时间范围和行政区划查询任务,整合实时监测数据生成详细任务报告
     * @param startTime 查询起始时间(包含)
     * @param endTime 查询结束时间(包含)
     * @param areaVo 区域参数,包含省、市、区编码
     * @return 任务详情列表,每个元素包含任务完整信息、污染线索及实时监测数据
     * @see MissionRep.findByAreaAndTime 区域时间筛选数据源
     * @see realTimeDataRep.fetchData 实时数据获取接口
     */
    override fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionDetail> {
        val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
            Triple(
                it,
                sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
                realTimeDataRep.fetchData(it)
            )
        }
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return generateMissionDetail(keyScenes, missionCluesData)
    }
 
    override fun generateMissionDetail(missionCode: 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)
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return MissionInventory().generateMissionDetail(keyScenes, mission, missionClues, realTimeData)
    }
 
    /**
     * 生成走航任务详情(直接处理任务数据)
     * 接收已关联的任务-污染线索-实时数据三元组,生成详细任务报告
     * @param keyScenes 关键场景列表,用于分析走航是否经过该区域
     * @param missionCluesData 任务数据三元组列表,Triple分别为任务对象、污染线索列表、实时数据列表
     * @return 标准化的任务详情列表,包含完整的任务属性、污染分析和监测数据统计
     * @see MissionInventory.generateMissionDetail 详情生成核心逻辑
     */
    override fun generateMissionDetail(
        keyScenes: List<SceneInfo?>,
        missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>,
    ): List<MissionInventory.MissionDetail> {
        return missionCluesData.map {
            MissionInventory().generateMissionDetail(keyScenes, it.first, it.second, it.third)
        }
    }
 
    override fun generateClueByRiskArea(
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
    ): List<MissionRiskArea.ClueByArea> {
        val clues = mutableListOf<PollutedClue?>()
        missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
            it ?: return@onEach
            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)
    }
 
    override fun generateClueByRiskArea(missionCode: String): List<MissionRiskArea.ClueByArea> {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val pollutedClues = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, MsgType.PolClue) as List<PollutedClue?>
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return generateClueByRiskArea(keyScenes, pollutedClues)
    }
 
    override fun generateClueByRiskArea(
        keyScenes: List<SceneInfo?>,
        pollutedClues: List<PollutedClue?>,
    ): List<MissionRiskArea.ClueByArea> {
        return MissionRiskArea().generateClueByRiskArea(keyScenes, pollutedClues)
    }
 
    override fun generateGridFusion(
        factorTypes: List<FactorType>,
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
    ): List<MissionGridFusion.GridFusionByAQI> {
        val gridLen = 100
        // 查询100米网格的具体网格数据
        val gridGroup = satelliteGridRep.fetchGridGroup(GridGroup().apply {
            type = "sub"
            length = gridLen.toDouble()
            provinceCode = areaVo.provinceCode
            cityCode = areaVo.cityCode
            districtCode = areaVo.districtCode
        }).firstOrNull() ?: throw BizException("未查询到100米网格")
        val gridCells = satelliteGridRep.fetchGridCell(gridGroup.id).filterNotNull()
        // 查询范围内的所有走航任务
        val missions = missionRep.findByAreaAndTime(areaVo, startTime, endTime)
        // 根据空气质量等级分类
        val missionGroups = missions.groupBy { PollutionDegree.getByDes(it?.pollutionDegree ?: "") }
        // 查询每个等级下的走航任务对应的网格数据(如果没有数据则剔除该任务)
        val gridDataDetailList = missionGroups.mapNotNull { (degree, missionList) ->
            // 筛选出有网格融合数据的走航任务(同时获取对应的融合数据id列表)
            val gridDataIds = mutableListOf<Int>()
            val validMissions = missionList.filter {mission ->
                val gridData = satelliteGridRep.fetchGridData(GridData().apply { missionCode = mission?.missionCode }).firstOrNull()
                val res = gridData != null
                if (res) gridDataIds.add(gridData?.id ?: 0)
                res
            }
            // 合并每个等级下的网格数据
            val gridDataDetailMixVos = satelliteDataCalculateService.mixUnderwayGridData(gridGroup.id, gridDataIds)
            // 统计每个走航任务的走航详情信息
            val missionCluesData = validMissions.filterNotNull().map {
                Triple(
                    it,
                    sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
                    realTimeDataRep.fetchData(it)
                )
            }
            val keyScenes = sceneInfoRep.findBySceneTypes(
                listOf(
                    SceneType.TYPE19.value,
                    SceneType.TYPE20.value,
                    SceneType.TYPE21.value
                )
            )
            val missionInfos = generateMissionInfo(keyScenes, missionCluesData)
 
            return@mapNotNull Triple(degree, missionInfos, gridDataDetailMixVos)
        }.filter { it.second.isNotEmpty() }
 
        return generateGridFusion(factorTypes, gridLen, gridCells, gridDataDetailList)
    }
 
    override fun generateGridFusion(
        factorTypes: List<FactorType>,
        gridLen: Int,
        gridCells: List<GridCell>,
        dataList: List<Triple<PollutionDegree, List<MissionInventory.MissionInfo>, List<GridDataDetailMixVo>>>,
    ): List<MissionGridFusion.GridFusionByAQI> {
        return MissionGridFusion(sceneInfoRep).generateGridFusion(factorTypes, gridLen, gridCells, dataList)
    }
}