feiyu02
2025-12-25 d649f734c44541641158aec2d6b10d630f5a0827
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
@@ -3,6 +3,7 @@
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
@@ -10,18 +11,18 @@
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.BaseRealTimeData
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.entity.SceneInfo
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.*
/**
@@ -39,7 +40,9 @@
    private val locationRoadNearby: LocationRoadNearby,
    private val segmentInfoRep: SegmentInfoRep,
    private val sourceTraceRep: SourceTraceRep,
    private val sceneInfoRep: SceneInfoRep
    private val sceneInfoRep: SceneInfoRep,
    private val satelliteGridRep: SatelliteGridRep,
    private val satelliteDataCalculateService: SatelliteDataCalculateService,
) : DataAnalysisService {
    /**
@@ -74,13 +77,14 @@
     * 获取历史污染溯源结果
     * 查询指定任务的历史污染溯源结果并序列化为JSON字符串
     * @param missionCode 走航任务编码
     * @param minPer 最小污染百分比,用于筛选异常数据点(可选)
     * @return 历史污染溯源结果的JSON字符串,具体格式由sourceTraceRep实现决定
     * @throws BizException 当走航任务不存在时抛出
     */
    override fun fetchHistory(missionCode: String): String {
    override fun fetchHistory(missionCode: String, minPer: Double?): String {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime)
        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, minPer = minPer ?: 0.5)
        return GsonUtils.gson.toJson(res)
    }
@@ -91,16 +95,35 @@
     * @param endTime 统计结束时间(包含)
     * @param areaVo 区域参数,包含省、市、区三级行政区划编码
     * @return 汇总统计对象,包含任务总数、异常点数量、平均里程等核心指标
     * @see MissionSummary 汇总统计处理器,封装具体的统计逻辑实现
     * @see MissionSummary 汇总统计处理器,封装具体统计逻辑的实现
     */
    override fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
    override fun generateMissionSummary(
        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean, minPer: Double?,
    ): 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?>
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue, minPer ?: 0.5) as List<PollutedClue?>
            clues.addAll(clue)
        }
        filterClue(areaVo, clues, removeOtherDistrict, removeNoPollutedSource)
        val summary = MissionSummary().execute(startTime, endTime, missions, clues)
        return summary
    }
    override fun generateMissionSummary(
        missionCode: String, minPer: Double?,
    ): MissionSummary.Summary {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
        val clues = sourceTraceRep.fetchList(
            mission.deviceCode,
            mission.startTime,
            mission.endTime,
            MsgType.PolClue,
            minPer ?: 0.5,
        ) as List<PollutedClue?>
        val summary = MissionSummary().execute(mission.startTime, mission.endTime, listOf(mission), clues)
        return summary
    }
@@ -114,11 +137,29 @@
     * @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?>
    override fun generateMissionList(
        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean, minPer: Double?,
    ): List<MissionInventory.MissionInfo> {
        val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
            val clues = sourceTraceRep.fetchList(
                it.deviceCode,
                it.startTime,
                it.endTime,
                MsgType.PolClue,
                minPer ?: 0.5
            ) as List<PollutedClue?>
            filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
            Triple(it, clues, realTimeDataRep.fetchData(it))
        }
        return generateMissionList(missionClues)
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return generateMissionInfo(keyScenes, missionCluesData)
    }
    /**
@@ -132,23 +173,45 @@
        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 区域参数,包含省、市、区编码
     * @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?,
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        minPer: Double?,
    ): 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 clues = sourceTraceRep.fetchList(
                it.deviceCode,
                it.startTime,
                it.endTime,
                MsgType.PolClue,
                minPer ?: 0.5
            ) as List<PollutedClue?>
            filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
            Triple(it, clues, realTimeDataRep.fetchData(it))
        }
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
@@ -157,7 +220,31 @@
                SceneType.TYPE21.value
            )
        )
        return generateMissionDetail(keyScenes, missionCluesData)
        return generateMissionDetail(keyScenes, missionCluesData, granularity ?: "MINUTE")
    }
    override fun generateMissionDetail(
        missionCode: String,
        granularity: String?,
        minPer: Double?,
    ): MissionInventory.MissionDetail {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val missionClues = sourceTraceRep.fetchList(
            mission.deviceCode,
            mission.startTime,
            mission.endTime,
            MsgType.PolClue,
            minPer ?: 0.5
        ) 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, granularity ?: "MINUTE")
    }
    /**
@@ -171,9 +258,10 @@
    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)
        }
    }
@@ -181,13 +269,45 @@
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
    ): List<MissionRiskArea.ClueByArea> {
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        minPer: Double?,
    ): List<MissionRiskArea.ClassifyClue> {
        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?>
            val clue = sourceTraceRep.fetchList(
                it.deviceCode,
                it.startTime,
                it.endTime,
                MsgType.PolClue,
                minPer ?: 0.5
            ) as List<PollutedClue?>
            clues.addAll(clue)
        }
//        if (removeOtherDistrict) {
//            clues.removeIf {
//                !areaVo.districtName.isNullOrBlank() &&
//                        (it?.pollutedArea?.address.isNullOrBlank()
//                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
//            }
//        }
//        if (removeNoPollutedSource) {
//            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
//        }
        filterClue(areaVo, clues, removeOtherDistrict, removeNoPollutedSource)
        return MissionRiskArea().generateClueByRiskArea(clues)
    }
    override fun generateClueByRiskArea(missionCode: String, minPer: Double?): List<MissionRiskArea.ClueByArea> {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val pollutedClues = sourceTraceRep.fetchList(
            mission.deviceCode,
            mission.startTime,
            mission.endTime,
            MsgType.PolClue,
            minPer ?: 0.5
        ) as List<PollutedClue?>
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
@@ -195,13 +315,107 @@
                SceneType.TYPE21.value
            )
        )
        return generateClueByRiskArea(keyScenes, clues)
        return generateClueByRiskArea(keyScenes, pollutedClues)
    }
    override fun generateClueByRiskArea(
        keyScenes: List<SceneInfo?>,
        pollutedClues: List<PollutedClue?>,
    ): List<MissionRiskArea.ClueByArea> {
        return MissionRiskArea().generateClueByRiskArea(keyScenes, pollutedClues)
        return MissionRiskArea().generateClueByKeyRiskScene(keyScenes, pollutedClues)
    }
    override fun generateGridFusion(
        factorTypes: List<FactorType>,
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        minPer: Double?,
    ): 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 {
                val clues = sourceTraceRep.fetchList(
                    it.deviceCode,
                    it.startTime,
                    it.endTime,
                    MsgType.PolClue,
                    minPer ?: 0.5
                ) as List<PollutedClue?>
                filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
                Triple(it, clues, 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)
    }
    private fun filterClue(
        areaVo: AreaVo, clues: MutableList<PollutedClue?>, removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
    ) {
        if (removeOtherDistrict) {
            clues.removeIf {
                !areaVo.districtName.isNullOrBlank() &&
                        (it?.pollutedArea?.address.isNullOrBlank()
                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
            }
            clues.forEach {
                it?.pollutedSource?.sceneList = it?.pollutedSource?.sceneList?.filter { s->
                    s.districtCode == areaVo.districtCode
                }
            }
        }
        if (removeNoPollutedSource) {
            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
        }
    }
}