From f7bdafb7cddd049bbb1bbf265fa006683b4ac693 Mon Sep 17 00:00:00 2001
From: feiyu02 <risaku@163.com>
Date: 星期三, 11 六月 2025 17:08:35 +0800
Subject: [PATCH] 1. 新增动态污染溯源新的判定逻辑(待完成)

---
 src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSourceTest.kt                     |   31 +-
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt        |    2 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt          |   10 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt                        |   21 +-
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt                           |    9 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt                         |  109 ++++++++--
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt                     |   22 +
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt         |    4 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt                          |   10 +
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt                  |   74 +++---
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt           |   12 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt |    2 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt  |    2 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcChangeRate.kt                |  114 +++++++++++
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt                           |   48 +++-
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionAnalysis.kt                       |   12 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt           |   10 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt             |   22 +
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt                           |   55 ++++-
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionType.kt                         |    1 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt                        |   30 +-
 21 files changed, 418 insertions(+), 182 deletions(-)

diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionAnalysis.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionAnalysis.kt
index d43f689..2a95915 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionAnalysis.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionAnalysis.kt
@@ -18,10 +18,10 @@
     /**
      * 鐢熸垚涓�鏉″紓甯稿垎鏋愮粨鏋�
      */
-    abstract fun newResult(
-        start: BaseRealTimeData,
-        end: BaseRealTimeData?,
-        factor: FactorFilter.SelectedFactor,
-        exceptionData: List<BaseRealTimeData>,
-    ): Y
+//    abstract fun newResult(
+//        start: BaseRealTimeData,
+//        end: BaseRealTimeData?,
+//        factor: FactorFilter.SelectedFactor,
+//        exceptionData: List<BaseRealTimeData>,
+//    ): Y
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
index ce55583..2bfe2be 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
@@ -37,7 +37,7 @@
     /**
      * 绔嬪嵆鍒ゆ柇锛氬綋鍑虹幇寮傚父鏃讹紝缂撳瓨寮傚父鏁版嵁鐨勫悓鏃讹紝绔嬪嵆瀵瑰凡鏈夊紓甯歌繘琛屽垽鏂槸鍚︽弧瓒冲紓甯哥粨鏋滆姹�
      */
-    open fun immeExcCheck(tag: T): Boolean {
+    open fun immeExcCheck(tag: T, factorType: FactorType): Boolean {
         return false
     }
 
@@ -61,11 +61,11 @@
      * 鍒ゆ柇寮傚父鍑虹幇鐨勮繛缁釜鏁版槸鍚︽弧瓒虫潯浠�
      * @param tag 寮傚父鏁版嵁瀵硅薄
      */
-    abstract fun judgeExceptionCount(tag: T): Boolean
+    abstract fun judgeExceptionCount(tag: T, factorType: FactorType?): Boolean
 
     /**
      * 寮傚父鏁版嵁鐨勬埅鍙栧垽鏂�
-     * @return 榛樿涓嶉渶瑕佹埅鍙�
+     * @return
      */
     open fun needCut(tag: T, hasException: Boolean?): Boolean {
         // 榛樿鍒ゆ柇鏉′欢涓� 褰撳紓甯镐笉鍐嶉噸澶嶅嚭鐜版椂锛屽舰鎴愬紓甯哥粨鏋�
@@ -87,6 +87,8 @@
         config.factorFilter.selectedList.forEach { s ->
             val f = s.main
             tagMap[f]?.let {
+                it.addHistoryData(data)
+
                 it.eIndex++
                 // 璧峰鏁版嵁
                 it.endData = data
@@ -105,7 +107,7 @@
                     // 鏈夊紓甯稿嚭鐜版椂锛岃褰曞紓甯告暟鎹�
                     it.addExceptionData(data)
                     // 褰撶珛鍗冲垽鏂�氳繃鏃讹紝褰㈡垚寮傚父缁撴灉
-                    if (immeExcCheck(it)) {
+                    if (immeExcCheck(it, f)) {
                         recordException(s, it, data)
                     }
                 }
@@ -141,13 +143,13 @@
     ) {
         val tag = tagMap[factor?.main]
         if (factor != null && tag != null) {
-            if (tag.exceptionExisted && judgeExceptionCount(tag)) {
+            if (tag.exceptionExisted && judgeExceptionCount(tag, factor.main)) {
                 onNewException(tag, factor, exceptionStatus)
             }
         } else {
             config.factorFilter.selectedList.forEach { f ->
                 val tag1 = tagMap[f.main] ?: return@forEach
-                if (tag1.exceptionExisted && judgeExceptionCount(tag1)) {
+                if (tag1.exceptionExisted && judgeExceptionCount(tag1, null)) {
                     onNewException(tag1, f, exceptionStatus)
                 }
             }
@@ -159,7 +161,8 @@
      */
     open fun onNewException(tag: T, factor: FactorFilter.SelectedFactor, exceptionStatus: ExceptionStatusType) {
         if (tag.startData == null) return
-        val ex = newResult(tag.startData!!, tag.endData, factor, tag.exceptionData)
+//        val ex = newResult(tag.startData!!, tag.endData, factor, tag.exceptionData)
+        val ex = newResult(tag, factor)
             .apply { status = exceptionStatus.value }
         // 寮傚父宸插垱寤烘椂锛屾洿鏂板紓甯镐俊鎭�
         if (tag.exceptionCreated) {
@@ -177,4 +180,9 @@
         }
     }
 
+    /**
+     * 鐢熸垚涓�鏉″紓甯稿垎鏋愮粨鏋�
+     */
+    abstract fun newResult(tag:T, factor: FactorFilter.SelectedFactor): Y
+
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt
index a2e4282..bbd22fa 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt
@@ -15,13 +15,9 @@
 abstract class ExceptionContinuous(config: DataAnalysisConfig) :
     BaseExceptionContinuous<ExceptionTag, DataAnalysisConfig, ExceptionResult>(config, ExceptionTag::class.java) {
 
-    override fun newResult(
-        start: BaseRealTimeData,
-        end: BaseRealTimeData?,
-        factor: FactorFilter.SelectedFactor,
-        exceptionData: List<BaseRealTimeData>,
-    ): ExceptionResult {
+    override fun newResult(tag:ExceptionTag, factor: FactorFilter.SelectedFactor): ExceptionResult {
         val eType = getExceptionType()
-        return ExceptionResult(start, end, factor, exceptionData, config.mission.missionCode, eType)
+        return ExceptionResult(tag.startData!!, tag.endData, factor, tag.exceptionData, config.mission.missionCode,
+            eType)
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
index 96c4d5e..05265fc 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
@@ -33,17 +33,13 @@
         return res
     }
 
-    override fun judgeExceptionCount(tag: ExceptionTag): Boolean {
+    override fun judgeExceptionCount(tag: ExceptionTag, factorType: FactorType?): Boolean {
         return true
     }
 
-    override fun newResult(
-        start: BaseRealTimeData,
-        end: BaseRealTimeData?,
-        factor: FactorFilter.SelectedFactor,
-        exceptionData: List<BaseRealTimeData>,
-    ): ExceptionResult {
+    override fun newResult(tag: ExceptionTag, factor: FactorFilter.SelectedFactor): ExceptionResult {
         val eType = getExceptionType()
-        return ExceptionResult(start, end, factor, exceptionData, config.mission.missionCode, eType)
+        return ExceptionResult(tag.startData!!, tag.endData, factor, tag.exceptionData, config.mission.missionCode,
+            eType)
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt
index 7cfcd1f..21bede2 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionSlideAverage.kt
@@ -201,7 +201,9 @@
 
     }
 
-    override fun newResult(
+
+
+    fun newResult(
         start: BaseRealTimeData,
         end: BaseRealTimeData?,
         factor: FactorFilter.SelectedFactor,
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
index 082db5e..bb399d7 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
@@ -40,7 +40,7 @@
         return res
     }
 
-    override fun judgeExceptionCount(tag: ExceptionTag): Boolean {
+    override fun judgeExceptionCount(tag: ExceptionTag, factorType: FactorType?): Boolean {
         // 棣栦釜鏁版嵁娌℃湁鍓嶄竴涓暟鎹弬鐓э紝涓嶇畻寮傚父鍊硷紝鏈�鍚庝竴涓暟鎹槸鍒ゆ柇缁撴潫鐨勬甯稿�硷紝鍥犳寮傚父鏁版嵁涓暟鐨勮绠椾笅鏍囦负sIndex鍜宔Index
         val sIndex = tag.sIndex
         val eIndex = tag.eIndex - 1
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
index 7886a5d..3124bc8 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
@@ -24,6 +24,9 @@
     // 寮傚父鏁版嵁娈�
     var exceptionData = mutableListOf<BaseRealTimeData>()
 
+    // 杩戞鏃堕棿鍐呯殑鍘嗗彶鏁版嵁
+    var historyData = mutableListOf<BaseRealTimeData>()
+
     // 鏄惁瀛樺湪寮傚父
     var exceptionExisted = false
 
@@ -32,6 +35,13 @@
 
     var exceptionResult = mutableListOf<BaseExceptionResult>()
 
+    fun addHistoryData(data: BaseRealTimeData) {
+        historyData.add(data)
+        if (historyData.size > 20) {
+            historyData.removeAt(0)
+        }
+    }
+
     fun addExceptionData(data: BaseRealTimeData){
         exceptionExisted = true
         exceptionData.add(data)
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionType.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionType.kt
index 4e8a1f6..dac7f89 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionType.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionType.kt
@@ -10,4 +10,5 @@
     TYPE6(6, "鍗曟棩瓒呮爣娆℃暟涓磋繎澶勭綒寮傚父"),
     TYPE7(7, "婊戝姩骞冲潎鍊肩獊鍙樺紓甯�"),
     TYPE8(8, "鏈夋晥鐜囧紓甯�"),
+    TYPE9(9, "鍙樺寲閫熺巼寮傚父"),
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt
index cefe129..662cd6b 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt
@@ -35,23 +35,23 @@
         else
             RTExcWindLevelConfig(
                 FactorFilter.builder()
-//                .withMain(FactorType.NO2)
-                    .withMain(FactorType.CO)
-//                .withMain(FactorType.H2S)
-//                .withMain(FactorType.SO2)
-//                .withMain(FactorType.O3)
+//                    .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()
             )
-        pollutedSummary = PollutedSummary(config){ summaryCallback(it)}
+        pollutedSummary = PollutedSummary(config) { summaryCallback(it) }
         newTask()
     }
 
     constructor(sceneInfoRep: SceneInfoRep) : this(sceneInfoRep, null)
 
-    private val pollutedSummary:PollutedSummary
+    private val pollutedSummary: PollutedSummary
 
     private val sceneInfoRep: SceneInfoRep
 
@@ -71,6 +71,7 @@
             add(RTExcWindLevel1_1(config) { exceptionCallback(it) }.also { it.init() })
             add(RTExcWindLevel4(config) { exceptionCallback(it) }.also { it.init() })
             add(RTExcWindLevel6(config) { exceptionCallback(it) }.also { it.init() })
+            add(RTExcChangeRate(config) { exceptionCallback(it) }.also { it.init() })
         }
     }
 
@@ -102,11 +103,11 @@
         // 婧簮姹℃煋婧愪俊鎭�
         ex.searchScenes(sceneInfoRep)
 
-        // 璁板綍姹℃煋绾跨储
-        pollutedSummary.addClue(ex)
-
         // 骞挎挱姹℃煋婧簮寮傚父缁撴灉
         UnderwayWebSocketSender.broadcast(MsgType.PolClue.value, ex)
+
+        // 璁板綍姹℃煋绾跨储
+        pollutedSummary.addClue(ex)
     }
 
     private fun summaryCallback(ex: PollutedSummary.AnalysisResult) {
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
index 271388a..ff5b96e 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
@@ -3,6 +3,7 @@
 import com.flightfeather.uav.biz.FactorFilter
 import com.flightfeather.uav.biz.dataanalysis.BaseAnalysisConfig
 import com.flightfeather.uav.biz.sourcetrace.model.DistanceType
+import com.flightfeather.uav.socket.eunm.FactorType
 
 /**
  *
@@ -22,7 +23,14 @@
     var distanceLimit = 3000
     // 闄愬畾鏃堕棿鍐咃紙鍗曚綅锛氬垎閽燂級
     var timeLimit = 3
+    // 婧簮鎵╂暎鍋忕Щ瑙掑害锛堝崟浣嶏細搴︼級
+    var sourceTraceDegOffset = 60.0
+    // 瀹氭椂绾跨储鍒嗘瀽鏃堕棿闂撮殧(鍗曚綅锛氬垎閽�)
+    var analysisPeriod = 15
+    // 瀹氭椂鍒嗘瀽闂撮殧涓紝绔嬪嵆杩涜绾跨储鍒嗘瀽鐨勬渶灏忕嚎绱㈤噺(鍗曚綅锛氫釜)
+    var analysisCount = 4
 
+    /****鏁版嵁绐佸彉*****************************************************************************/
     // 0 - 1绾ч
     var windLevelCondition1 = WindLevelCondition(
         .0 to 1.5,
@@ -56,42 +64,32 @@
         3
     )
 
-    // 婧簮鎵╂暎鍋忕Щ瑙掑害锛堝崟浣嶏細搴︼級
-    var sourceTraceDegOffset = 120.0
-
-    // 瀹氭椂绾跨储鍒嗘瀽鏃堕棿闂撮殧(鍗曚綅锛氬垎閽�)
-    var analysisPeriod = 15
-    // 瀹氭椂鍒嗘瀽闂撮殧涓紝绔嬪嵆杩涜绾跨储鍒嗘瀽鐨勬渶灏忕嚎绱㈤噺(鍗曚綅锛氫釜)
-    var analysisCount = 2
-
-
-
-//    // 0 - 1绾ч
-//    var windLevelCondition1 = WindLevelCondition(
-//        .0 to 1.5,
-//        listOf(0.5 to DistanceType.TYPE1, 0.2 to DistanceType.TYPE2,),
-//        listOf(1, 1)
-//    )
-//
-//    // 0 - 1绾ч
-//    var windLevelCondition1_1 = WindLevelCondition(
-//        .0 to 1.5,
-//        0.2 to DistanceType.TYPE2,
-//        1
-//    )
-//
-//    // 2 - 4绾ч
-//    var windLevelCondition2 = WindLevelCondition(
-//        1.6 to 7.9,
-//        listOf(0.2 to DistanceType.TYPE3),
-////        listOf(3)
-//        listOf(1)
-//    )
-//
-//    // 5 - 6绾ч
-//    var windLevelCondition3 = WindLevelCondition(
-//        8.0 to 13.8,
-//        listOf(0.1 to DistanceType.TYPE4),
-//        listOf(3)
-//    )
+    /****鏁版嵁鍙樺寲閫熺巼*****************************************************************************/
+    var changeRateCondition = WindLevelCondition(
+        .0 to Double.MAX_VALUE,
+        0.1 to Double.MAX_VALUE,
+        DistanceType.TYPE1,
+        3
+    )
+    // 鐩戞祴鍥犲瓙鍦ㄤ竴涓洃娴嬪懆鏈燂紙4绉掞級鍐呮甯稿彉鍖栫殑閲忕骇鑼冨洿
+    var changeRate = mutableMapOf(
+        FactorType.PM25 to WindLevelCondition(
+            .0 to Double.MAX_VALUE,
+            4.0 to Double.MAX_VALUE,
+            DistanceType.TYPE1,
+            3
+        ),
+        FactorType.PM10 to WindLevelCondition(
+            .0 to Double.MAX_VALUE,
+            4.0 to Double.MAX_VALUE,
+            DistanceType.TYPE1,
+            3
+        ),
+        FactorType.VOC to WindLevelCondition(
+            .0 to Double.MAX_VALUE,
+            6.0 to Double.MAX_VALUE,
+            DistanceType.TYPE1,
+            1
+        ),
+    )
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
index 779d121..f78fd15 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
@@ -66,7 +66,7 @@
         return res
     }
 
-    override fun judgeExceptionCount(tag: ExceptionTag): Boolean {
+    override fun judgeExceptionCount(tag: ExceptionTag, factorType: FactorType?): Boolean {
         return tag.exceptionData.size >= windLevelCondition.countLimit
     }
 
@@ -97,20 +97,24 @@
         return b1 || b2
     }
 
-    override fun immeExcCheck(tag: ExceptionTag): Boolean {
+    override fun immeExcCheck(tag: ExceptionTag, factorType: FactorType): Boolean {
         // 寮傚父鍑虹幇绛変簬闄愬畾娆℃暟鏃讹紝灏遍渶瑕佸舰鎴愭薄鏌撶嚎绱�
         return tag.exceptionData.size == windLevelCondition.countLimit
     }
 
-    override fun newResult(
-        start: BaseRealTimeData,
-        end: BaseRealTimeData?,
-        factor: FactorFilter.SelectedFactor,
-        exceptionData: List<BaseRealTimeData>,
-    ): PollutedClue {
-        return PollutedClue(start, end, factor, exceptionData, getExceptionType(), config, windLevelCondition)
+    override fun newResult(tag: ExceptionTag, factor: FactorFilter.SelectedFactor): PollutedClue {
+        return PollutedClue(tag, factor, getExceptionType(), config, windLevelCondition)
     }
 
+//    override fun newResult(
+//        start: BaseRealTimeData,
+//        end: BaseRealTimeData?,
+//        factor: FactorFilter.SelectedFactor,
+//        exceptionData: List<BaseRealTimeData>,
+//    ): PollutedClue {
+//        return PollutedClue(start, end, factor, exceptionData, getExceptionType(), config, windLevelCondition)
+//    }
+
     override fun onNewException(
         tag: ExceptionTag,
         factor: FactorFilter.SelectedFactor,
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt
index d4c0232..962969f 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt
@@ -55,14 +55,10 @@
 //        lastData = data
 //    }
 
-    override fun newResult(
-        start: BaseRealTimeData,
-        end: BaseRealTimeData?,
-        factor: FactorFilter.SelectedFactor,
-        exceptionData: List<BaseRealTimeData>,
-    ): RealTimeExceptionResult {
+
+    override fun newResult(tag:T, factor: FactorFilter.SelectedFactor): RealTimeExceptionResult {
         val eType = getExceptionType()
-        return RealTimeExceptionResult(start, end, factor, exceptionData, eType)
+        return RealTimeExceptionResult(tag.startData!!, tag.endData, factor, tag.exceptionData, eType)
     }
 
     override fun onNewException(tag: T, factor: FactorFilter.SelectedFactor, exceptionStatus: ExceptionStatusType) {
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcChangeRate.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcChangeRate.kt
new file mode 100644
index 0000000..c6360d3
--- /dev/null
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcChangeRate.kt
@@ -0,0 +1,114 @@
+package com.flightfeather.uav.biz.sourcetrace.exceptiontype
+
+import com.flightfeather.uav.biz.FactorFilter
+import com.flightfeather.uav.biz.dataanalysis.BaseExceptionContinuous
+import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
+import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
+import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
+import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
+import com.flightfeather.uav.common.utils.MapUtil
+import com.flightfeather.uav.domain.entity.BaseRealTimeData
+import com.flightfeather.uav.lightshare.eunm.ExceptionStatusType
+import com.flightfeather.uav.socket.eunm.FactorType
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.ZoneId
+
+/**
+ * 鏁版嵁鍙樺寲閫熺巼寮傚父
+ * @date 2025/6/10
+ * @author feiyu02
+ */
+class RTExcChangeRate(config: RTExcWindLevelConfig) :
+    BaseExceptionContinuous<ExceptionTag, RTExcWindLevelConfig, PollutedClue>(config, ExceptionTag::class.java) {
+
+    constructor(config: RTExcWindLevelConfig, callback: NewPolluteClueCallback) : this(config){
+        this.callback = callback
+    }
+
+    private var callback: NewPolluteClueCallback? = null
+
+    override fun getExceptionType(): ExceptionType {
+        return ExceptionType.TYPE9
+    }
+
+    override fun judgeException(p: BaseRealTimeData?, n: BaseRealTimeData): MutableMap<FactorType, Boolean> {
+        val res = mutableMapOf<FactorType, Boolean>()
+        config.factorFilter.mainList().forEach { f ->
+            if (p?.getByFactorType(f) == null || n.getByFactorType(f) == null || n.windSpeed == null) {
+                res[f] = (false)
+                return@forEach
+            }
+
+            val rate = config.changeRate[f]
+
+            val pValue = p.getByFactorType(f)!!
+            val nValue = n.getByFactorType(f)!!
+            // 璁$畻鍚庝竴涓暟鎹浉姣斾簬鍓嶄竴涓暟鎹殑鍙樺寲閫熺巼
+            val v = (nValue - pValue)
+
+            val b1 = if (rate != null) {
+                v >= rate.mutationRate.first
+            } else {
+                false
+            }
+//                val r = (nValue - pValue) / pValue
+//                val b1 = r >= con.mutationRate.first && r < con.mutationRate.second
+            println("鍥犲瓙锛�${f.des}锛岄�熺巼锛�${v}锛�${b1}")
+            res[f] = b1
+        }
+        return res
+    }
+
+    override fun judgeExceptionCount(tag: ExceptionTag, factorType: FactorType?): Boolean {
+        return tag.exceptionData.size >= (config.changeRate[factorType]?.countLimit ?: 1)
+    }
+
+    override fun needCut(tag: ExceptionTag, hasException: Boolean?): Boolean {
+        // 鎸夌収鏃堕暱鍜岃窛绂婚檺鍒跺皢寮傚父鎴彇
+        if (tag.exceptionData.isEmpty()) return false
+
+        val se = tag.exceptionData.first()
+        val ee = tag.exceptionData.last()
+
+        val sTime = LocalDateTime.ofInstant(se.dataTime?.toInstant(), ZoneId.systemDefault())
+        val eTime = LocalDateTime.ofInstant(ee.dataTime?.toInstant(), ZoneId.systemDefault())
+        val duration = Duration.between(sTime, eTime).toMinutes()
+        // 鏁版嵁閲囨牱鐨勬椂闀胯秴杩囬檺鍒舵椂锛岄渶瑕佹埅鍙�
+        val b1 = duration > config.timeLimit
+
+        // 璧拌埅鏁版嵁鐨勮窛绂昏秴杩囬檺鍒舵椂锛岄渶瑕佹埅鍙�
+        val b2 = if (se.longitude == null || se.latitude == null || ee.longitude == null || ee.latitude == null) {
+            false
+        } else {
+            val distance = MapUtil.getDistance(
+                se.longitude!!.toDouble(), se.latitude!!.toDouble(), ee.longitude!!
+                    .toDouble(), ee.latitude!!.toDouble()
+            )
+            distance > config.distanceLimit
+        }
+
+        return b1 || b2
+    }
+
+    override fun immeExcCheck(tag: ExceptionTag, factorType: FactorType): Boolean {
+        // 寮傚父鍑虹幇绛変簬闄愬畾娆℃暟鏃讹紝灏遍渶瑕佸舰鎴愭薄鏌撶嚎绱�
+        return judgeExceptionCount(tag, factorType)
+    }
+
+    override fun newResult(tag: ExceptionTag, factor: FactorFilter.SelectedFactor): PollutedClue {
+        return PollutedClue(tag, factor, getExceptionType(), config, config.changeRate[factor.main])
+    }
+
+    override fun onNewException(
+        tag: ExceptionTag,
+        factor: FactorFilter.SelectedFactor,
+        exceptionStatus: ExceptionStatusType,
+    ) {
+        super.onNewException(tag, factor, exceptionStatus)
+        callback?.let { func ->
+            val exc = tag.exceptionResult.last()
+            func.invoke(exc as PollutedClue)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt
index 5aa8052..f08fce0 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt
@@ -144,7 +144,7 @@
     }
 
 
-    override fun newResult(
+    fun newResult(
         start: BaseRealTimeData,
         end: BaseRealTimeData?,
         factor: FactorFilter.SelectedFactor,
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
index 81bc08a..4c36a09 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
@@ -53,7 +53,7 @@
         return res
     }
 
-    override fun judgeExceptionCount(tag: ExceptionTag): Boolean {
+    override fun judgeExceptionCount(tag: ExceptionTag, factorType: FactorType?): Boolean {
         val count = tag.exceptionData.size
 
         val b1 = special && count >= (config.mutationNum / 2)
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
index 2d0a968..563466a 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
@@ -20,18 +20,22 @@
      */
 
     constructor(
+        historyData: List<BaseRealTimeData>,
         exceptionData: List<BaseRealTimeData>,
         config: RTExcWindLevelConfig,
-        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
+        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
     ) : this() {
-        distanceType = windLevelCondition.distanceType
-        sourceTrace(exceptionData, config, windLevelCondition)
+        distanceType = windLevelCondition?.distanceType
+        windLevelCondition?.let { sourceTrace(historyData, exceptionData, config, it) }
     }
 
     var address: String? = null
 
-    // 姹℃煋鑼冨洿鍖哄煙(缁忕含搴﹀杈瑰舰)
+    // 姹℃煋鑼冨洿鎵囧舰鍖哄煙(缁忕含搴﹀杈瑰舰)
     var polygon: List<Pair<Double, Double>>? = null
+
+    // 杩戣窛绂绘薄鏌撳渾褰㈠尯鍩�
+    var closePolygon: List<Pair<Double, Double>>? = null
 
     // 姹℃煋鍙兘鐨勫彂鐢熻窛绂�
     var distanceType: DistanceType? = null
@@ -40,6 +44,7 @@
      * 鍙嶅悜婧簮
      */
     private fun sourceTrace(
+        historyData: List<BaseRealTimeData>,
         exceptionData: List<BaseRealTimeData>,
         config: RTExcWindLevelConfig,
         windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
@@ -53,7 +58,7 @@
         val pair = avgData.longitude!!.toDouble() to avgData.latitude!!.toDouble()
 
         polygon = calSector(
-            avgData.windSpeed!!.toDouble(),
+            avgData.windDirection!!.toDouble(),
             pair,
             windLevelCondition.distanceType.disRange,
             config.sourceTraceDegOffset
@@ -61,6 +66,8 @@
             // 灏嗗潗鏍囪浆鎹负gcj02锛堢伀鏄熷潗鏍囩郴锛夛紝鍥犱负姹℃煋婧愬満鏅俊鎭兘涓烘鍧愭爣绯�
             MapUtil.wgs84ToGcj02(it)
         }
+
+        closePolygon = closeSourceTrace(historyData, pair)
 
         try {
             val address = AMapService.reGeo(pair)
@@ -92,7 +99,13 @@
         val result = mutableListOf<Pair<Double, Double>>()
 
         if (distanceRange.first == .0) {
-            result.add(center)
+//            result.add(center)
+            var startDeg = 0
+            while (startDeg <= 360) {
+                val p = MapUtil.getPointByLen(center, 50.0, startDeg * PI / 180)
+                result.add(p)
+                startDeg++
+            }
         } else {
             // 浠庡紑濮嬭搴﹀惊鐜绠楀潗鏍囩偣鑷崇粨鏉熻搴︼紝姝ラ暱1掳
             var startDeg = sDeg
@@ -101,16 +114,30 @@
                 result.add(p)
                 startDeg++
             }
+            if (distanceRange.second > .0) {
+                // 姝ゅ闇�瑕佷粠缁撴潫瑙掑害寮�濮嬪弽鍚戝惊鐜绠楄嚦寮�濮嬭搴︼紝姝ラ暱1掳锛屼娇寰椾袱缁勫潗鏍囩偣鎸夐『搴忔帓鍒楋紝鍙粯鍒跺搴旂殑澶氳竟褰�
+                startDeg = eDeg
+                while (startDeg >= sDeg) {
+                    val p = MapUtil.getPointByLen(center, distanceRange.second, startDeg * PI / 180)
+                    result.add(p)
+                    startDeg--
+                }
+            }
         }
 
-        if (distanceRange.second > .0) {
-            // 姝ゅ闇�瑕佷粠缁撴潫瑙掑害寮�濮嬪弽鍚戝惊鐜绠楄嚦寮�濮嬭搴︼紝姝ラ暱1掳锛屼娇寰椾袱缁勫潗鏍囩偣鎸夐『搴忔帓鍒楋紝鍙粯鍒跺搴旂殑澶氳竟褰�
-            var startDeg = eDeg
-            while (startDeg >= sDeg) {
-                val p = MapUtil.getPointByLen(center, distanceRange.second, startDeg * PI / 180)
-                result.add(p)
-                startDeg--
-            }
+        return result
+    }
+
+    private fun closeSourceTrace(
+        historyData: List<BaseRealTimeData>,
+        center: Pair<Double, Double>,
+    ): List<Pair<Double, Double>> {
+        val result = mutableListOf<Pair<Double, Double>>()
+        var startDeg = 0
+        while (startDeg <= 360) {
+            val p = MapUtil.getPointByLen(center, 50.0, startDeg * PI / 180)
+            result.add(p)
+            startDeg++
         }
 
         return result
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
index 6775572..53fbd07 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
@@ -2,6 +2,7 @@
 
 import com.flightfeather.uav.biz.FactorFilter
 import com.flightfeather.uav.biz.dataanalysis.BaseExceptionResult
+import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
 import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
 import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
 import com.flightfeather.uav.common.utils.DateUtil
@@ -18,19 +19,34 @@
  */
 class PollutedClue() : BaseExceptionResult() {
 
+//    constructor(
+//        start: BaseRealTimeData,
+//        end: BaseRealTimeData?,
+//        factor: FactorFilter.SelectedFactor,
+//        exceptionData: List<BaseRealTimeData>,
+//        eType: ExceptionType,
+//        config: RTExcWindLevelConfig,
+//        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
+//    ) : this() {
+//        if (exceptionData.isEmpty()) return
+//        pollutedData = PollutedData(start, end, factor, exceptionData, eType, windLevelCondition)
+//        pollutedArea = PollutedArea(exceptionData, config, windLevelCondition)
+//    }
+
     constructor(
-        start: BaseRealTimeData,
-        end: BaseRealTimeData?,
-        factor: FactorFilter.SelectedFactor,
-        exceptionData: List<BaseRealTimeData>,
-        eType: ExceptionType,
-        config: RTExcWindLevelConfig,
-        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition
-    ) : this() {
-        this.factor = factor
-        if (exceptionData.isEmpty()) return
-        pollutedData = PollutedData(start, end, factor, exceptionData, eType, windLevelCondition)
-        pollutedArea = PollutedArea(exceptionData, config, windLevelCondition)
+        tag: ExceptionTag, factor: FactorFilter.SelectedFactor, eType: ExceptionType, config: RTExcWindLevelConfig,
+        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
+    ) :this()
+//            this(
+//        tag.startData!!, tag.endData, factor, tag.exceptionData, eType, config,
+//        windLevelCondition
+//    )
+    {
+        if (tag.exceptionData.isEmpty()) return
+        pollutedData = PollutedData(
+            tag.startData!!, tag.endData, factor, tag.exceptionData, tag.historyData, eType, windLevelCondition
+        )
+        pollutedArea = PollutedArea(tag.historyData, tag.exceptionData, config, windLevelCondition)
     }
 
     /**
@@ -43,13 +59,13 @@
 
     var pollutedSource: PollutedSource? = null
 
-    private var factor: FactorFilter.SelectedFactor? = null
-
     /**
      * 鏌ユ壘绯荤粺鍐呴儴婧簮鑼冨洿鍐呯殑姹℃煋浼佷笟
      */
     fun searchScenes(sceneInfoRep: SceneInfoRep) {
-        if (pollutedArea == null || factor == null) return
-        pollutedSource = PollutedSource().also { it.searchScenes(pollutedArea!!, sceneInfoRep, factor!!) }
+        if (pollutedArea == null || pollutedData == null) return
+        pollutedSource = PollutedSource().also {
+            it.searchScenes(pollutedArea!!, sceneInfoRep, pollutedData!!)
+        }
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
index 56190ed..4a599d7 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
@@ -28,8 +28,9 @@
         end: BaseRealTimeData?,
         factor: FactorFilter.SelectedFactor,
         exceptionData: List<BaseRealTimeData>,
+        historyData: List<BaseRealTimeData>,
         eType: ExceptionType,
-        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
+        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
     ) : this() {
         exception = eType.des
         exceptionType = eType.value
@@ -47,14 +48,15 @@
         endData = end
 
         windSpeed = exceptionData.first().windSpeed?.toDouble()
-        percentage = windLevelCondition.mutationRate.first
-        times = windLevelCondition.countLimit
+        percentage = windLevelCondition?.mutationRate?.first
+        times = windLevelCondition?.countLimit
 
         dataList.add(start)
         exceptionData.forEach {
             dataList.add(it)
         }
         dataVoList.addAll(dataList.map { it.toDataVo() })
+        historyDataList.addAll(historyData.map { it.toDataVo() })
 
         calPer()
     }
@@ -87,6 +89,7 @@
     // 鍙戠敓娆℃暟
     var times: Int? = null
 
+    var historyDataList = mutableListOf<DataVo>()
     // 寮傚父鐩戞祴鏁版嵁
     var dataList: MutableList<BaseRealTimeData> = mutableListOf()
     var dataVoList: MutableList<DataVo> = mutableListOf()
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
index 7817607..d74ceaa 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
@@ -1,6 +1,5 @@
 package com.flightfeather.uav.biz.sourcetrace.model
 
-import com.flightfeather.uav.biz.FactorFilter
 import com.flightfeather.uav.common.utils.MapUtil
 import com.flightfeather.uav.domain.entity.SceneInfo
 import com.flightfeather.uav.domain.repository.SceneInfoRep
@@ -10,7 +9,7 @@
 import com.flightfeather.uav.socket.eunm.FactorType
 import org.springframework.beans.BeanUtils
 import org.springframework.web.context.ContextLoader
-import kotlin.math.min
+import kotlin.math.round
 
 /**
  * 姹℃煋鏉ユ簮
@@ -26,33 +25,29 @@
      */
 
     // 婧簮浼佷笟
-    var sceneList:List<SceneInfoVo?>? = null
+    var sceneList: List<SceneInfoVo?>? = null
 
-    init {
+    // 婧簮鎺ㄧ悊缁撹
+    var conclusion: String? = null
 
-    }
-
-    fun searchScenes(pollutedArea: PollutedArea, factor: FactorFilter.SelectedFactor) {
+    fun searchScenes(pollutedArea: PollutedArea, pollutedData: PollutedData) {
         ContextLoader.getCurrentWebApplicationContext()?.getBean(SceneInfoRep::class.java)?.run {
-            searchScenes(pollutedArea, this, factor)
+            searchScenes(pollutedArea, this, pollutedData)
         }
     }
 
     /**
      * 鏌ユ壘绯荤粺鍐呴儴婧簮鑼冨洿鍐呯殑姹℃煋浼佷笟
      */
-    fun searchScenes(pollutedArea: PollutedArea, sceneInfoRep: SceneInfoRep, factor: FactorFilter.SelectedFactor) {
+    fun searchScenes(pollutedArea: PollutedArea, sceneInfoRep: SceneInfoRep, pollutedData: PollutedData) {
         // Fixme 2025.5.14: 姹℃煋婧愮殑鍧愭爣鏄珮寰峰湴鍥惧潗鏍囩郴锛堢伀鏄熷潗鏍囩郴锛夛紝鑰岃蛋鑸暟鎹槸WGS84鍧愭爣绯�
         // 鎸夌収鍖哄煙妫�绱㈠唴閮ㄦ薄鏌撴簮淇℃伅
-        // 1. 棣栧厛鎸夌収鍥涜嚦鑼冨洿浠庢暟鎹簱鍒濇绛涢�夋薄鏌撴簮
-//        val polygonTmp = pollutedArea.polygon!!.map {
-//            MapUtil.gcj02ToWgs84(it)
-//        }
+        var result = mutableListOf<SceneInfo>()
+        // 1. 棣栧厛鎸夌収鍥涜嚦鑼冨洿浠庢暟鎹簱鍒濇绛涢�夋薄鏌撴簮锛屾澶勭殑鍖哄煙鍧愭爣宸茶浆鎹负鐏槦鍧愭爣绯�
         val polygonTmp = pollutedArea.polygon!!
         val fb = MapUtil.calFourBoundaries(polygonTmp)
         val sceneList = sceneInfoRep.findByCoordinateRange(fb)
         // 2. 鍐嶇簿纭垽鏂槸鍚﹀湪鍙嶅悜婧簮鍖哄煙澶氳竟褰㈠唴閮�
-        val result = mutableListOf<SceneInfo>()
         sceneList.forEach {
             val point = it!!.longitude.toDouble() to it.latitude.toDouble()
             if (MapUtil.isPointInPolygon(point, polygonTmp)) {
@@ -60,27 +55,91 @@
             }
         }
 
-        findClosestStation(sceneInfoRep, result)
+        val closePolygonTmp = pollutedArea.closePolygon!!
+        val closeFb = MapUtil.calFourBoundaries(closePolygonTmp)
+        val closeSceneList = sceneInfoRep.findByCoordinateRange(closeFb)
+        // 2. 鍐嶇簿纭垽鏂槸鍚﹀湪鍙嶅悜婧簮鍖哄煙澶氳竟褰㈠唴閮�
+        closeSceneList.forEach {
+            val point = it!!.longitude.toDouble() to it.latitude.toDouble()
+            if (MapUtil.isPointInPolygon(point, closePolygonTmp)) {
+                result.add(it)
+            }
+        }
 
+        // 鏍规嵁姹℃煋鍥犲瓙鐨勯噺绾э紝璁$畻涓昏鐨勬薄鏌撳満鏅被鍨嬶紝绛涢�夌粨鏋�
+        val mainSceneType = calSceneType(pollutedData)
+        if (mainSceneType != null) {
+            this.conclusion = mainSceneType.first
+            result = result.filter {
+                val r = mainSceneType.second.find { s->
+                    s.value == it.typeId.toInt()
+                }
+                r != null
+            }.toMutableList()
+        }
 
-//        TODO("鎸夌収鎵�閫夌洃娴嬪洜瀛愮被鍨嬶紝鍖哄垎姹℃煋婧愮被鍨�")
+        this.sceneList = findClosestStation(sceneInfoRep, result)
 
     }
 
     /**
-     * 璁$畻鍙兘鐨勭浉鍏虫薄鏌撳満鏅被鍨�
+     * 璁$畻鍙兘鐨勭浉鍏虫薄鏌撳満鏅被鍨嬩互鍙婃帹鐞嗙粨璁�
      */
-    private fun calFactorType(factor: FactorFilter.SelectedFactor) {
-//        when (factor.main) {
-//            FactorType.PM25 -> {}
-//
-//        }
+    @Throws(Exception::class)
+    private fun calSceneType(pollutedData: PollutedData): Pair<String, List<SceneType>>? {
+        when (pollutedData.selectedFactor?.main) {
+            // 姘哀鍖栧悎鐗╋紝涓�鑸敱浜庢満鍔ㄨ溅灏炬皵锛屽悓姝ヨ绠桟O
+            FactorType.NO2 -> {
+                val coAvg = round(pollutedData.dataList.map { it.co!! }.average()) / 1000
+                return "姘哀鍖栧悎鐗╁亸楂橈紝CO鐨勯噺绾т负${coAvg}mg/m鲁锛屼竴鑸敱浜庢満鍔ㄨ溅灏炬皵閫犳垚锛屾薄鏌撴簮浠ユ苯淇�佸姞娌圭珯涓轰富" to
+                        listOf(SceneType.TYPE6, SceneType.TYPE10, SceneType.TYPE17)
+            }
+
+            FactorType.CO -> return null
+
+            FactorType.H2S -> return null
+
+            FactorType.SO2 -> return null
+
+            FactorType.O3 -> return null
+            // a) pm2.5銆乸m10鐗瑰埆楂橈紝涓よ�呭湪鍚勬儏鍐典笅鍚屾灞曠ず锛宲m2.5鍗爌m10鐨勬瘮閲嶅彉鍖栵紝姣旈噸瓒婇珮锛岃秺鏈夊彲鑳芥槸椁愰ギ
+            // b) pm10鐗瑰埆楂樸�乸m2.5杈冮珮锛屽ぇ棰楃矑鎵皹姹℃煋锛屽彧灞曠ずpm10锛宲m2.5鍗爌m10鐨勬瘮閲嶅彉鍖栵紝宸ュ湴涓轰富
+            FactorType.PM25,
+            FactorType.PM10,
+                -> {
+                // 璁$畻寮傚父鏁版嵁鐨刾m2.5鍗爌m10姣旈噸鐨勫潎鍊�
+                val percentageAvg = pollutedData.dataList.map {
+                    it.pm25!! / it.pm10!!
+                }.average()
+                return if (percentageAvg > 0.666) {
+                    "PM2.5鍗燩M10鐨勬瘮閲嶄负${round(percentageAvg * 100)}%锛屾瘮閲嶈緝澶э紝姹℃煋婧愪互椁愰ギ涓轰富锛屽伐鍦版涔�" to
+                            listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5)
+                } else if (percentageAvg < 0.333) {
+                    "PM2.5鍗燩M10鐨勬瘮閲嶄负${round(percentageAvg * 100)}%锛屾瘮閲嶈緝灏忥紝灞炰簬澶ч绮掓壃灏樻薄鏌擄紝姹℃煋婧愪互宸ュ湴涓轰富" to
+                            listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5)
+                } else {
+                    "PM2.5鍗燩M10鐨勬瘮閲嶄负${round(percentageAvg * 100)}%锛屾薄鏌撴簮浠ラ楗�佸伐鍦颁负涓�" to
+                            listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5)
+                }
+            }
+            // c) VOC杈冮珮锛屽悓姣旇绠梡m2.5鐨勯噺绾э紝鍙兘瀛樺湪鍚屾鍋忛珮锛堟苯淇�佸姞娌圭珯锛�, 鍚屾璁$畻O3鏄惁鏈夐珮鍊�
+            // d) VOC杈冮珮锛屽浜庡姞娌圭珯锛堣溅杈嗘嫢鍫垫儏鍐碉級锛孋O涓�鑸緝楂�, 鍚屾璁$畻O3鏄惁鏈夐珮鍊�
+            FactorType.VOC -> {
+                val pm25Avg = round(pollutedData.dataList.map { it.pm25!! }.average() * 10) / 10
+                val coAvg = round(pollutedData.dataList.map { it.co!! }.average()) / 1000
+                val o3Avg = round(pollutedData.dataList.map { it.o3!! }.average() * 10) / 10
+                return "VOC鍋忛珮锛屽悓鏃禤M2.5閲忕骇涓�${pm25Avg}渭g/m鲁锛孋O閲忕骇涓�${coAvg}mg/m鲁锛孫3閲忕骇涓�${o3Avg}渭g/m鲁锛屾薄鏌撴簮浠ユ苯淇�佸姞娌圭珯涓轰富" to
+                        listOf(SceneType.TYPE6, SceneType.TYPE17, SceneType.TYPE12)
+            }
+
+            else -> return null
+        }
     }
 
     /**
      * 璁$畻鏈�杩戠殑鐩戞祴绔欑偣
      */
-    private fun findClosestStation(sceneInfoRep: SceneInfoRep, sceneList: List<SceneInfo>) {
+    private fun findClosestStation(sceneInfoRep: SceneInfoRep, sceneList: List<SceneInfo>): List<SceneInfoVo> {
         val res1 = sceneInfoRep.findByArea(AreaVo().apply {
             sceneTypeId = SceneType.TYPE19.value.toString()
         })
@@ -90,10 +149,10 @@
         })
         val res = res1.toMutableList().apply { addAll(res2) }
 
-        this.sceneList = sceneList.map {
+        return sceneList.map {
             var minLen = -1.0
             var selectedRes: SceneInfo? = null
-            res.forEach { r->
+            res.forEach { r ->
                 val dis = MapUtil.getDistance(
                     it.longitude.toDouble(),
                     it.latitude.toDouble(),
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt
index 5b3c06a..140c499 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt
@@ -31,11 +31,11 @@
     /**
      * 鍒嗘瀽缁撴灉
      */
-    inner class AnalysisResult{
+    inner class AnalysisResult {
         // 鎸夌収琚壂鎻忔鏁伴檷搴忔帓鍒楃殑姹℃煋婧愬垪琛�
         var sortedSceneList: List<Pair<SceneInfo?, Int>>? = null
         var time: Date? = null
-        var advice:String?= null
+        var advice: String? = null
         var direction: AMapService.AMapDirection? = null
     }
 
@@ -60,7 +60,7 @@
     private var analysisTimer: Timer? = null
 
     // 瀹氭椂姹℃煋鍒嗘瀽浠诲姟
-    private var lastAnalysisOnTimeTask:TimerTask? = null
+    private var lastAnalysisOnTimeTask: TimerTask? = null
 
     // 瀹氭椂姹℃煋鍒嗘瀽浠诲姟杩愯鐘舵��
     private var analysisTaskIsRunning = false
@@ -128,8 +128,8 @@
         val statistic = AnalysisStatistic()
         // 鍏辨湁澶氬皯鐩稿叧姹℃煋婧愶紝鍝簺姹℃煋婧愯鎵弿娆℃暟杈冨
         val sceneMap = mutableMapOf<String?, Pair<SceneInfo?, Int>>()
-        clueList.forEach {c->
-            c.pollutedSource?.sceneList?.forEach { s->
+        clueList.forEach { c ->
+            c.pollutedSource?.sceneList?.forEach { s ->
                 if (!sceneMap.containsKey(s?.guid)) {
                     sceneMap[s?.guid] = s to 1
                 } else {
@@ -145,11 +145,12 @@
      * 绾跨储鍒嗘瀽
      */
     private fun analysis() {
+        if (clueList.isEmpty()) return
         val result = AnalysisResult()
         // 鍏辨湁澶氬皯鐩稿叧姹℃煋婧愶紝鍝簺姹℃煋婧愯鎵弿娆℃暟杈冨
         val sceneMap = mutableMapOf<String?, Pair<SceneInfo?, Int>>()
-        clueList.forEach {c->
-            c.pollutedSource?.sceneList?.forEach { s->
+        clueList.forEach { c ->
+            c.pollutedSource?.sceneList?.forEach { s ->
                 if (!sceneMap.containsKey(s?.guid)) {
                     sceneMap[s?.guid] = s to 1
                 } else {
@@ -157,31 +158,34 @@
                 }
             }
         }
-        val res = sceneMap.entries.sortedBy { it.value.second }
+        val res = sceneMap.entries.sortedByDescending { it.value.second }
         result.sortedSceneList = res.map { it.value }
 
         // 褰撳墠鐨勮蛋鑸暟鎹殑瀹氫綅鍜屾薄鏌撴簮璺濈鏄惁鏄�愭笎鎺ヨ繎锛岃嫢璧拌埅杩滅浜嗕富瑕佹薄鏌撴簮锛屾彁绀虹敤鎴疯皟鏁磋蛋鑸矾绾�
         if (!result.sortedSceneList.isNullOrEmpty()) {
             val sT = clueList.first().pollutedData?.startTime
             val closetScene = result.sortedSceneList?.first()
-            result.advice = "鏍规嵁${sT}璧风殑${clueList.size}鏉℃渶鏂版薄鏌撶嚎绱紝姹℃煋婧怺${closetScene?.first?.name}]琚娆℃函婧愶紝鍏锋湁杈冮珮姹℃煋椋庨櫓锛岀幇鎻愪緵鏂扮殑璧拌埅鎺ㄨ崘璺嚎锛屽彲缁忚繃璇ユ薄鏌撴簮銆�"
-
+            // 璧拌埅璺嚎璋冩暣寤鸿
+            result.advice =
+                "鏍规嵁${sT}璧风殑${clueList.size}鏉℃渶鏂版薄鏌撶嚎绱紝姹℃煋婧愩��${closetScene?.first?.name}銆戣澶氭婧簮锛屽叿鏈夎緝楂樻薄鏌撻闄╋紝鐜版彁渚涙柊鐨勮蛋鑸帹鑽愯矾绾匡紝鍙粡杩囪姹℃煋婧愩��"
 
             val lastP = realTimeDataList.last()
+            // 寤鸿瀵瑰簲鐨勬暟鎹噰鏍锋椂闂�
+            result.time = lastP.dataTime
             if (lastP.longitude != null && lastP.latitude != null &&
                 lastP.longitude!! > BigDecimal.ZERO && lastP.latitude!! > BigDecimal.ZERO
                 && closetScene?.first?.longitude != null && closetScene.first?.latitude != null &&
-                closetScene.first?.longitude!! > BigDecimal.ZERO && closetScene.first?.latitude!! > BigDecimal.ZERO) {
+                closetScene.first?.longitude!! > BigDecimal.ZERO && closetScene.first?.latitude!! > BigDecimal.ZERO
+            ) {
 
                 val origin = MapUtil.wgs84ToGcj02(lastP.longitude!!.toDouble() to lastP.latitude!!.toDouble())
                 val destination = closetScene.first!!.longitude.toDouble() to closetScene.first!!.latitude.toDouble()
 
+                // 寤鸿鐨勮蛋鑸矾绾�
                 result.direction = AMapService.directionDriving(origin, destination)
             }
         }
 
-
-        result.time = realTimeDataList.last().dataTime
         // 绾跨储鍒嗘瀽瀹屾垚鍚庯紝绉诲姩鑷冲巻鍙茬嚎绱㈠垪琛�
         historyClueList.addAll(clueList)
         clueList.clear()
diff --git a/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSourceTest.kt b/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSourceTest.kt
index f6ceaa0..a31858a 100644
--- a/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSourceTest.kt
+++ b/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSourceTest.kt
@@ -13,21 +13,22 @@
 @SpringBootTest
 class PollutedSourceTest {
 
- @Autowired
- lateinit var sceneInfoRep: SceneInfoRep
+    @Autowired
+    lateinit var sceneInfoRep: SceneInfoRep
 
- @Test
- fun foo1() {
-  val source = PollutedSource()
-  val pollutedArea = PollutedArea().apply {
-   polygon = listOf(
-    121.421521 to 31.195457,
-    121.421721 to 31.195457,
-    121.421521 to 31.195257,
-    121.421721 to 31.195257,
-   )
-  }
-  source.searchScenes(pollutedArea, sceneInfoRep, FactorFilter.SelectedFactor(FactorType.VOC))
+    @Test
+    fun foo1() {
+        val source = PollutedSource()
+        val pollutedData = PollutedData()
+        val pollutedArea = PollutedArea().apply {
+            polygon = listOf(
+                121.421521 to 31.195457,
+                121.421721 to 31.195457,
+                121.421521 to 31.195257,
+                121.421721 to 31.195257,
+            )
+        }
+        source.searchScenes(pollutedArea, sceneInfoRep, pollutedData)
 
- }
+    }
 }
\ No newline at end of file

--
Gitblit v1.9.3