From d649f734c44541641158aec2d6b10d630f5a0827 Mon Sep 17 00:00:00 2001
From: feiyu02 <risaku@163.com>
Date: 星期四, 25 十二月 2025 17:25:16 +0800
Subject: [PATCH] 2025.12.19 1. 动态溯源相关分析逻辑调整; 2. 走航报告接口参数调整;

---
 src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt |  266 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 240 insertions(+), 26 deletions(-)

diff --git a/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt b/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
index 9826661..ad2cc21 100644
--- a/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
+++ b/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 鍘嗗彶姹℃煋婧簮缁撴灉鐨凧SON瀛楃涓诧紝鍏蜂綋鏍煎紡鐢眘ourceTraceRep瀹炵幇鍐冲畾
      * @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) ->
+            // 绛涢�夊嚭鏈夌綉鏍艰瀺鍚堟暟鎹殑璧拌埅浠诲姟(鍚屾椂鑾峰彇瀵瑰簲鐨勮瀺鍚堟暟鎹甶d鍒楄〃)
+            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() }
+        }
+
     }
 }
\ No newline at end of file

--
Gitblit v1.9.3