feiyu02
2025-09-12 61871594dfa0a5ac2c4d895d9ec4034feba57094
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
@@ -1,32 +1,35 @@
package com.flightfeather.uav.lightshare.service.impl
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.BaseExceptionResult
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.BasePollutedMsg
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.Mission
import com.flightfeather.uav.domain.entity.*
import com.flightfeather.uav.domain.mapper.MissionMapper
import com.flightfeather.uav.domain.repository.MissionRep
import com.flightfeather.uav.domain.repository.RealTimeDataRep
import com.flightfeather.uav.domain.repository.SegmentInfoRep
import com.flightfeather.uav.domain.repository.SourceTraceRep
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.time.LocalDateTime
import java.time.ZoneId
import java.util.*
/**
 *
 * 数据分析服务接口实现类
 * 提供走航任务数据的统计分析、污染溯源、任务清单及详情生成等核心业务功能
 * 整合多数据源完成数据聚合与分析,为前端提供标准化的统计结果
 * @date 2025/5/8
 * @author feiyu02
 */
@@ -37,9 +40,20 @@
    private val realTimeDataRep: RealTimeDataRep,
    private val locationRoadNearby: LocationRoadNearby,
    private val segmentInfoRep: SegmentInfoRep,
    private val sourceTraceRep: SourceTraceRep
    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("走航任务不存在")
@@ -60,6 +74,13 @@
        )
    }
    /**
     * 获取历史污染溯源结果
     * 查询指定任务的历史污染溯源结果并序列化为JSON字符串
     * @param missionCode 走航任务编码
     * @return 历史污染溯源结果的JSON字符串,具体格式由sourceTraceRep实现决定
     * @throws BizException 当走航任务不存在时抛出
     */
    override fun fetchHistory(missionCode: String): String {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
@@ -67,21 +88,212 @@
        return GsonUtils.gson.toJson(res)
    }
    override fun missionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
    /**
     * 生成走航任务汇总统计
     * 按时间范围和行政区划统计走航任务的关键指标(任务数量、异常率、平均里程等)
     * @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 = missionMapper.selectByExample(Example(Mission::class.java).apply {
            createCriteria().andBetween("startTime", startTime, endTime)
                .andEqualTo("provinceCode", areaVo.provinceCode)
                .andEqualTo("cityCode", areaVo.cityCode)
                .andEqualTo("districtCode", areaVo.districtCode)
                .andIsNotNull("kilometres")
                .andNotEqualTo("kilometres", 0)
        }).onEach {
        val missions = missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
            it ?: return@onEach
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime).filterIsInstance<PollutedClue?>()
            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
    }
    /**
     * 生成走航任务清单(按时间和区域筛选)
     * 根据时间范围和行政区划查询走航任务,并关联污染线索数据生成任务列表
     * @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)
    }
    /**
     * 生成走航任务详情(直接处理任务数据)
     * 接收已关联的任务-污染线索-实时数据三元组,生成详细任务报告
     * @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(
        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)
    }
}