feiyu02
2025-05-29 4d065a305b997bfb66f41b33a31d59de63b1958d
1. 新增动态污染溯源新的判定逻辑(待完成)
已修改10个文件
已添加6个文件
664 ■■■■ 文件已修改
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel1.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel1_1.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel4.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel6.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/DistanceType.kt 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
@@ -19,42 +19,6 @@
        private const val OFFSET = 10
    }
    inner class Tag {
        // èµ·å§‹æ•°æ®ä¸‹æ ‡
        var sIndex = 0
        // èµ·å§‹æ•°æ®å¯¹è±¡
        var startData: BaseRealTimeData? = null
        // æœ«å°¾æ•°æ®ä¸‹æ ‡
        var eIndex = -1
        // æœ«å°¾æ•°æ®å¯¹è±¡
        var endData: BaseRealTimeData? = null
        // å¼‚常数据段
        var exceptionData = mutableListOf<BaseRealTimeData>()
        // æ˜¯å¦å­˜åœ¨å¼‚常
        var exceptionExisted = false
        // å¼‚常结果是否创建
        var exceptionCreated = false
        fun addExceptionData(data: BaseRealTimeData) {
            exceptionExisted = true
            exceptionData.add(data)
        }
        fun refreshWithNextException(data: BaseRealTimeData) {
            sIndex = eIndex
            startData = data
            exceptionData.clear()
            exceptionExisted = false
            exceptionCreated = false
        }
    }
    protected val tagMap = mutableMapOf<FactorType, T>()
    // èµ·å§‹æ•°æ®ä¸Žæœ«å°¾æ•°æ®é—´éš”
@@ -62,6 +26,20 @@
    // æœ«å°¾æ•°æ®å¯¹è±¡
    protected var lastData: BaseRealTimeData? = null
    /**
     * åŽç½®åˆ¤æ–­ï¼šå½“相邻数据时间不连续时,或者满足自定义条件时,对之前已有的异常进行记录
     */
    open fun afterExcCheck(isContinue: Boolean, tag: T, hasException: Boolean?): Boolean {
        return !isContinue || needCut(tag, hasException)
    }
    /**
     * ç«‹å³åˆ¤æ–­ï¼šå½“出现异常时,缓存异常数据的同时,立即对已有异常进行判断是否满足异常结果要求
     */
    open fun immeExcCheck(tag: T): Boolean {
        return false
    }
    /**
     * åˆ¤æ–­ç›¸é‚»æ•°æ®æ˜¯å¦è¿žç»­
@@ -75,7 +53,7 @@
    }
    /**
     * åˆ¤æ–­æ˜¯å¦æ»¡è¶³å¼‚常条件
     * åˆ¤æ–­å‰åŽæ•°æ®æ˜¯å¦æ»¡è¶³å¼‚常条件
     */
    abstract fun judgeException(p: BaseRealTimeData?, n: BaseRealTimeData): MutableMap<FactorType, Boolean>
@@ -87,11 +65,11 @@
    /**
     * å¼‚常数据的截取判断
     * æ˜¯å¦éœ€è¦é™åˆ¶ä¸€ç»„异常数据的长度
     * @return é»˜è®¤ä¸éœ€è¦æˆªå–
     */
    open fun needCut(tag: T): Boolean {
        return false
    open fun needCut(tag: T, hasException: Boolean?): Boolean {
        // é»˜è®¤åˆ¤æ–­æ¡ä»¶ä¸º å½“异常不再重复出现时,形成异常结果
        return tag.exceptionExisted && hasException == false
    }
    override fun init() {
@@ -115,15 +93,19 @@
                if (it.startData == null) {
                    it.refreshWithNextException(data)
                }
                // åˆ¤æ–­ç›¸é‚»æ•°æ®æ˜¯å¦è¿žç»­å¹¶ä¸”是否满足异常判断
                if (!isContinue || needCut(it)) {
                    // æ•°æ®ä¸è¿žç»­æ—¶ï¼Œè®°å½•异常情况
                // å¯¹äºŽå¼‚常的生成分别执行后置判断、和立即判断
                // 1. åŽç½®åˆ¤æ–­ï¼šå½“相邻数据时间不连续时,或者满足自定义条件时,对之前已有的异常进行记录,形成异常结果
                if (afterExcCheck(isContinue, it, hasException[f])) {
                    // æ•°æ®ä¸è¿žç»­æ—¶æˆ–者满足主动截断条件时,记录异常情况
                    recordException(s, it, data)
                } else {
                    if (hasException[f] == true) {
                        it.addExceptionData(data)
                    } else {
                        // å¼‚常不再重复出现时,记录异常情况
                }
                // 2. ç«‹å³åˆ¤æ–­ï¼šå½“出现异常时,缓存异常数据的同时,立即对已有异常进行判断是否满足异常结果要求
                else if (hasException[f] == true) {
                    // æœ‰å¼‚常出现时,记录异常数据
                    it.addExceptionData(data)
                    // å½“立即判断通过时,形成异常结果
                    if (immeExcCheck(it)) {
                        recordException(s, it, data)
                    }
                }
@@ -138,12 +120,11 @@
    /**
     * å¼‚常结束,记录异常
     * åˆ¤æ–­å·²æœ‰çš„异常数据是否满足异常条件,满足则记录,不满足则略过
     */
    fun recordException(factor: FactorFilter.SelectedFactor, tag: T, data: BaseRealTimeData) {
        checkResult(factor, ExceptionStatusType.Ended)
//        if (tag.eIndex - tag.sIndex >= durationCount) {
        tag.refreshWithNextException(data)
//        }
    }
    /**
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package com.flightfeather.uav.biz.sourcetrace.config
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.BaseAnalysisConfig
import com.flightfeather.uav.biz.sourcetrace.model.DistanceType
/**
 *
 * @date 2025/5/29
 * @author feiyu02
 */
class RTExcWindLevelConfig(factorFilter: FactorFilter): BaseAnalysisConfig(factorFilter) {
    inner class WindLevelCondition(
        val windSpeed: Pair<Double, Double>,
        val mutationRate: Pair<Double, DistanceType>,
        val countLimit: Int,
    )
    // é™å®šè·ç¦»å†…(单位:米)
    var distanceLimit = 1000
    // é™å®šæ—¶é—´å†…(单位:分钟)
    var timeLimit = 2
    // 0 - 1级风
    var windLevelCondition1 = WindLevelCondition(
        .0 to 1.5,
        0.5 to DistanceType.TYPE1,
        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,
        0.2 to DistanceType.TYPE3,
        3
    )
    // 5 - 6级风
    var windLevelCondition3 = WindLevelCondition(
        8.0 to 13.8,
        0.1 to DistanceType.TYPE4,
        3
    )
    // æº¯æºæ‰©æ•£åç§»è§’度(单位:度)
    var sourceTraceDegOffset = 120.0
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
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
// å¼‚常数据生成回调类
typealias NewPolluteClueCallback = (ex: PollutedClue) -> Unit
/**
 * ä¸åŒé£Žé€Ÿä¸‹ï¼Œæ•°æ®çªå˜å¼‚常基类
 * @date 2025/5/29
 * @author feiyu02
 */
abstract class BaseRTExcWindLevel(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
    abstract var windLevelCondition: RTExcWindLevelConfig.WindLevelCondition
    override fun getExceptionType(): ExceptionType {
        return ExceptionType.TYPE4
    }
    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 con = windLevelCondition
            if (n.windSpeed!! in con.windSpeed.first..con.windSpeed.second) {
                val pValue = p.getByFactorType(f)!!
                val nValue = n.getByFactorType(f)!!
                // è®¡ç®—后一个数据相比于前一个数据的变化率
                val r = (nValue - pValue) / pValue
                val b1 = r >= con.mutationRate.first
                res[f] = b1
            } else {
                res[f] = false
            }
        }
        return res
    }
    override fun judgeExceptionCount(tag: ExceptionTag): Boolean {
        return tag.exceptionData.size >= windLevelCondition.countLimit
    }
    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): 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 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)
        }
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt
@@ -17,6 +17,7 @@
 * @date 2025/5/13
 * @author feiyu02
 */
@Deprecated("2025.5.29, é€»è¾‘与业务不匹配,后续删除")
abstract class BaseRealTimeException<T : ExceptionTag>(config: RealTimeAnalysisConfig, tagClz: Class<T>) :
    BaseExceptionContinuous<T, RealTimeAnalysisConfig, RealTimeExceptionResult>(config, tagClz) {
@@ -26,33 +27,33 @@
    var callback: NewExceptionCallback? = null
    override fun onNextData(data: BaseRealTimeData) {
        val isContinue = isContinuous(lastData, data)
        val hasException = judgeException(lastData, data)
        config.factorFilter.selectedList.forEach { s ->
            val f = s.main
            tagMap[f]?.let {
                it.eIndex++
                // èµ·å§‹æ•°æ®
                it.endData = data
                if (it.startData == null) {
                    it.refreshWithNextException(data)
                }
                // åˆ¤æ–­ç›¸é‚»æ•°æ®æ˜¯å¦è¿žç»­æˆ–者是否满足自定义截取条件
                if (!isContinue || needCut(it)) {
                    // è®°å½•异常,结束异常的实时状态播报
                    recordException(s, it, data)
                } else {
                    // ç§»é™¤äº†çˆ¶ç±»åŽŸæœ‰é€»è¾‘ï¼Œæ”¹ä¸ºå½“æ»¡è¶³å¼‚å¸¸æ¡ä»¶æ—¶ï¼Œéœ€è¦å®žæ—¶æŽ¨é€æ’­æŠ¥å¼‚å¸¸çš„çŠ¶æ€å˜åŒ–ï¼Œä½†ä¸æˆªå–å¼‚å¸¸
                    if (hasException[f] == true) {
                        it.addExceptionData(data)
                        checkResult(s)
                    }
                }
            }
        }
        lastData = data
    }
//    override fun onNextData(data: BaseRealTimeData) {
//        val isContinue = isContinuous(lastData, data)
//        val hasException = judgeException(lastData, data)
//        config.factorFilter.selectedList.forEach { s ->
//            val f = s.main
//            tagMap[f]?.let {
//                it.eIndex++
//                // èµ·å§‹æ•°æ®
//                it.endData = data
//                if (it.startData == null) {
//                    it.refreshWithNextException(data)
//                }
//                // åˆ¤æ–­ç›¸é‚»æ•°æ®æ˜¯å¦è¿žç»­æˆ–者是否满足自定义截取条件
//                if (!isContinue || needCut(it)) {
//                    // è®°å½•异常,结束异常的实时状态播报
//                    recordException(s, it, data)
//                } else {
//                    // ç§»é™¤äº†çˆ¶ç±»åŽŸæœ‰é€»è¾‘ï¼Œæ”¹ä¸ºå½“æ»¡è¶³å¼‚å¸¸æ¡ä»¶æ—¶ï¼Œéœ€è¦å®žæ—¶æŽ¨é€æ’­æŠ¥å¼‚å¸¸çš„çŠ¶æ€å˜åŒ–ï¼Œä½†ä¸æˆªå–å¼‚å¸¸
//                    if (hasException[f] == true) {
//                        it.addExceptionData(data)
//                        checkResult(s)
//                    }
//                }
//            }
//        }
//        lastData = data
//    }
    override fun newResult(
        start: BaseRealTimeData,
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel1.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.flightfeather.uav.biz.sourcetrace.exceptiontype
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
/**
 * 0 - 1级风,软风(风速1.5m/s及以下)状态下,数据突变异常
 * @date 2025/5/29
 * @author feiyu02
 */
class RTExcWindLevel1 : BaseRTExcWindLevel {
    constructor(config: RTExcWindLevelConfig) : super(config)
    constructor(config: RTExcWindLevelConfig, callback: NewPolluteClueCallback) : super(config, callback)
    override var windLevelCondition: RTExcWindLevelConfig.WindLevelCondition = config.windLevelCondition1
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel1_1.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.flightfeather.uav.biz.sourcetrace.exceptiontype
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
/**
 * 0 - 1级风,软风(风速1.5m/s及以下)状态下,数据突变异常
 * @date 2025/5/29
 * @author feiyu02
 */
class RTExcWindLevel1_1 : BaseRTExcWindLevel {
    constructor(config: RTExcWindLevelConfig) : super(config)
    constructor(config: RTExcWindLevelConfig, callback: NewPolluteClueCallback) : super(config, callback)
    override var windLevelCondition: RTExcWindLevelConfig.WindLevelCondition = config.windLevelCondition1_1
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel4.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.flightfeather.uav.biz.sourcetrace.exceptiontype
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
/**
 * 2 - 4级风,和风(风速1.6 - 7.9m/s及以下)状态下,数据突变异常
 * @date 2025/5/29
 * @author feiyu02
 */
class RTExcWindLevel4:BaseRTExcWindLevel {
    constructor(config: RTExcWindLevelConfig) : super(config)
    constructor(config: RTExcWindLevelConfig, callback: NewPolluteClueCallback) : super(config, callback)
    override var windLevelCondition: RTExcWindLevelConfig.WindLevelCondition = config.windLevelCondition2
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcWindLevel6.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.flightfeather.uav.biz.sourcetrace.exceptiontype
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
/**
 * 4 - 6级风,强风(风速8 - 13.8m/s)状态下,数据突变异常
 * @date 2025/5/29
 * @author feiyu02
 */
class RTExcWindLevel6:BaseRTExcWindLevel {
    constructor(config: RTExcWindLevelConfig) : super(config)
    constructor(config: RTExcWindLevelConfig, callback: NewPolluteClueCallback) : super(config, callback)
    override var windLevelCondition: RTExcWindLevelConfig.WindLevelCondition = config.windLevelCondition3
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionSlideAverage.kt
@@ -19,6 +19,7 @@
 * @date 2025/5/13
 * @author feiyu02
 */
@Deprecated("2025.5.29, é€»è¾‘与业务不匹配,后续删除")
class RealTimeExceptionSlideAverage : BaseExceptionAnalysis<RealTimeAnalysisConfig, RealTimeExceptionResult> {
    constructor(config: RealTimeAnalysisConfig) : super(config)
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
@@ -15,6 +15,7 @@
 * @date 2025/5/13
 * @author feiyu02
 */
@Deprecated("2025.5.29, é€»è¾‘与业务不匹配,后续删除")
class RealTimeExceptionValueMutation : BaseRealTimeException<ExceptionTag> {
    constructor(config: RealTimeAnalysisConfig) : super(config, ExceptionTag::class.java)
@@ -61,7 +62,7 @@
        return b1 || b2
    }
    override fun needCut(tag: ExceptionTag): Boolean {
    override fun needCut(tag: ExceptionTag, hasException: Boolean?): Boolean {
        // æŒ‰ç…§æ—¶é•¿å’Œè·ç¦»é™åˆ¶å°†å¼‚常截取
        if (tag.exceptionData.isEmpty()) return false
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/DistanceType.kt
@@ -5,9 +5,13 @@
 * @date 2025/5/28
 * @author feiyu02
 */
enum class DistanceType(val des: String) {
    TYPE1("50ç±³"),
    TYPE2("50ç±³ - 500ç±³"),
    TYPE3("50ç±³ - 1公里"),
    TYPE4("50ç±³ - 2公里")
enum class DistanceType(val des: String, val disRange: Pair<Double, Double>) {
    TYPE1("50ç±³", .0 to 50.0),
    TYPE2("50ç±³ - 500ç±³", 50.0 to 500.0),
    TYPE3("50ç±³ - 1公里", 50.0 to 1000.0),
    TYPE4("50ç±³ - 2公里", 50.0 to 2000.0);
    override fun toString(): String {
        return this.des
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
@@ -1,18 +1,115 @@
package com.flightfeather.uav.biz.sourcetrace.model
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
import com.flightfeather.uav.common.net.AMapService
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.domain.entity.avg
import kotlin.math.PI
/**
 * åŠ¨æ€æº¯æºæ±¡æŸ“åŒºåŸŸ
 * é€šè¿‡åœ°å›¾åæ ‡ç‚¹å½¢æˆå¤šè¾¹å½¢æ¥æè¿°ä¸€å—污染区域
 * @date 2025/5/27
 * @author feiyu02
 */
class PollutedArea {
class PollutedArea() {
    var name: String? = null
    /**
     * æº¯æºè§’度可设置
     */
    constructor(
        exceptionData: List<BaseRealTimeData>,
        config: RTExcWindLevelConfig,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
    ) : this() {
        distanceType = windLevelCondition.mutationRate.second
        sourceTrace(exceptionData, config, windLevelCondition)
    }
    var address: String? = null
    // æ±¡æŸ“范围区域(经纬度多边形)
    var polygon: List<Pair<Double, Double>>? = null
    // æ±¡æŸ“可能的发生距离
    var distanceType: DistanceType? = null
    /**
     * åå‘溯源
     */
    private fun sourceTrace(
        exceptionData: List<BaseRealTimeData>,
        config: RTExcWindLevelConfig,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
    ) {
        val avgData = if (exceptionData.size == 1) {
            exceptionData.first()
        } else {
            exceptionData.avg()
        }
        val pair = avgData.longitude!!.toDouble() to avgData.latitude!!.toDouble()
        polygon = calSector(
            avgData.windSpeed!!.toDouble(),
            pair,
            windLevelCondition.mutationRate.second.disRange,
            config.sourceTraceDegOffset
        )
        try {
            val address = AMapService.reGeo(pair)
            this.address = address.township + address.street
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    /**
     * æ ¹æ®ä¸­å¿ƒç‚¹åæ ‡ã€é£Žå‘和溯源距离,以及给定的夹角,计算以中心点按照风向向外扩散形成的扇形的点坐标
     * @param windDir é£Žå‘,单位:度
     * @param center ä¸­å¿ƒç‚¹åæ ‡ç»çº¬åº¦
     * @param distanceRange æº¯æºè·ç¦»èŒƒå›´ï¼Œå•位:米
     * @param defaultDegOffset æ‰©æ•£åç§»è§’度
     * @return å¤šè¾¹å½¢é¡¶ç‚¹åæ ‡é›†åˆ
     */
    private fun calSector(
        windDir: Double,
        center: Pair<Double, Double>,
        distanceRange: Pair<Double, Double>,
        defaultDegOffset: Double = 60.0,
    ): List<Pair<Double, Double>> {
        val sDeg = windDir - defaultDegOffset / 2
        val eDeg = windDir + defaultDegOffset / 2
        val result = mutableListOf<Pair<Double, Double>>()
        if (distanceRange.first == .0) {
            result.add(center)
        } else {
            // ä»Žå¼€å§‹è§’度循环计算坐标点值结束角度,步长1°
            var startDeg = sDeg
            while (startDeg <= eDeg) {
                val p = MapUtil.getPointByLen(center, distanceRange.first, 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
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
@@ -1,10 +1,55 @@
package com.flightfeather.uav.biz.sourcetrace.model
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.BaseExceptionResult
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
import com.flightfeather.uav.common.utils.DateUtil
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.domain.entity.SceneInfo
import com.flightfeather.uav.domain.repository.SceneInfoRep
/**
 * æ±¡æŸ“线索
 * æ ¹æ®
 * é€šè¿‡æ±¡æŸ“数据[PollutedData],污染区域[PollutedArea],污染来源[PollutedSource],形成一条污染溯源线索
 * @date 2025/5/27
 * @author feiyu02
 */
class PollutedClue {
class PollutedClue() : BaseExceptionResult() {
    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)
    }
    /**
     * 6. å±•示数据变化情况,上升速率等等
     */
    var pollutedData: PollutedData? = null
    var pollutedArea: PollutedArea? = null
    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!!) }
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
@@ -1,5 +1,9 @@
package com.flightfeather.uav.biz.sourcetrace.model
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
import com.flightfeather.uav.common.utils.DateUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.lightshare.bean.DataVo
@@ -8,7 +12,7 @@
 * @date 2025/5/27
 * @author feiyu02
 */
class PollutedData {
class PollutedData() {
    /**
     *
@@ -20,11 +24,68 @@
     * 3. 8 - 13.8 m/s ä»¥ä¸Šï¼Œå‰åŽå€¼ä¸Šå‡å¹…度在10%以上3次,认为是远距离发生(50ç±³ - 2公里)
     */
    /**
     * 9. å…³è”因子
     *     a) pm2.5、pm10特别高,两者在各情况下同步展示,pm2.5占pm10的比重变化,比重越高,越有可能是餐饮
     *     b) pm10特别高、pm2.5较高,大颗粒扬尘污染,只展示pm10,pm2.5占pm10的比重变化,工地为主
     *     c) VOC较高,同比计算pm2.5的量级,可能存在同步偏高(汽修、加油站), åŒæ­¥è®¡ç®—O3是否有高值
     *     d) VOC较高,处于加油站(车辆拥堵情况),CO一般较高, åŒæ­¥è®¡ç®—O3是否有高值
     *     e) æ°®æ°§åŒ–合物,一般由于机动车尾气,同步计算CO
     */
    constructor(
        start: BaseRealTimeData,
        end: BaseRealTimeData?,
        factor: FactorFilter.SelectedFactor,
        exceptionData: List<BaseRealTimeData>,
        eType: ExceptionType,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
    ) : this() {
        exception = eType.des
        exceptionType = eType.value
        factorId = factor.main.value
        factorName = factor.main.des
        subFactorId = factor.subs.map { it.value }
        subFactorName = factor.subs.map { it.des }
        selectedFactor = factor
        startTime = DateUtil.instance.dateToString(start.dataTime, DateUtil.DateStyle.HH_MM_SS)
        endTime = DateUtil.instance.dateToString(end?.dataTime, DateUtil.DateStyle.HH_MM_SS) ?: startTime
        startData = start.getByFactorType(factor.main)
        endData = end?.getByFactorType(factor.main) ?: startData
        windSpeed = exceptionData.first().windSpeed?.toDouble()
        percentage = windLevelCondition.mutationRate.first
        times = windLevelCondition.countLimit
        exceptionData.forEach {
            dataList.add(it)
            dataVoList.add(it.toDataVo())
        }
    }
    var deviceCode: String? = null
    var exception: String? = null
    var exceptionType: Int? = null
    var factorId: Int? = null
    var factorName: String? = null
    var subFactorId: List<Int>? = null
    var subFactorName: List<String>? = null
    var selectedFactor: FactorFilter.SelectedFactor? = null
    var startTime: String? = null
    var endTime: String? = null
    var startData: Float? = null
    var endData: Float? = null
    // é£Žé€Ÿ
    var windSpeed: Float? = null
    var windSpeed: Double? = null
    // å› å­é‡çº§å˜åŒ–幅度
    var percentage: Float? = null
    var percentage: Double? = null
    // å‘生次数
    var times: Int? = null
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
@@ -1,5 +1,10 @@
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
/**
 * æ±¡æŸ“来源
 * ç³»ç»Ÿå†…部的污染场景、电子地图搜索得到的实际路段路口等标志信息
@@ -8,5 +13,38 @@
 */
class PollutedSource {
    /**
     * æº¯æºæ¸…单显示与临近监测站点的距离(国控、市控、网格化监测点)
     *
     */
    // æº¯æºä¼ä¸š
    var sceneList:List<SceneInfo?>? = null
    /**
     * æŸ¥æ‰¾ç³»ç»Ÿå†…部溯源范围内的污染企业
     */
    fun searchScenes(pollutedArea: PollutedArea, sceneInfoRep: SceneInfoRep, factor: FactorFilter.SelectedFactor) {
        // Fixme 2025.5.14: æ±¡æŸ“源的坐标是高德地图坐标系(火星坐标系),而走航数据是WGS84坐标系
        // æŒ‰ç…§åŒºåŸŸæ£€ç´¢å†…部污染源信息
        // 1. é¦–先按照四至范围从数据库初步筛选污染源,需要先将坐标转换为gcj02(火星坐标系),因为污染源场景信息都为此坐标系
        val polygonTmp = pollutedArea.polygon!!.map {
            MapUtil.gcj02ToWgs84(it)
        }
        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)) {
                result.add(it)
            }
        }
        this.sceneList = result
        TODO("按照所选监测因子类型,区分污染源类型")
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSummary.kt
@@ -2,9 +2,18 @@
/**
 * æ±¡æŸ“情况汇总
 * é’ˆå¯¹å•次走航,定时统计已有污染线索,按照策略给出走航建议
 * é’ˆå¯¹å•次走航,定时统计已有污染线索[PollutedClue],按照策略给出走航建议
 * @date 2025/5/27
 * @author feiyu02
 */
class PollutedSummary {
    /**
     * 5. æ±¡æŸ“源的被扫描次数
     * æ¯ä¸€åˆ»é’Ÿå¯¹åŽ†å²çº¿ç´¢è¿›è¡Œç»Ÿè®¡ï¼Œæå‡ºä¼šå•†å»ºè®®ï¼ˆç¦»æ±¡æŸ“æºè¾ƒè¿œã€æ±¡æŸ“æºæ•°é‡ã€å‡ºçŽ°æ¬¡æ•°ï¼‰ã€èµ°èˆªè·¯çº¿è°ƒæ•´å»ºè®®ï¼ˆç¦»æ±¡æŸ“æºè¾ƒè¿‘ã€èµ°èˆªè½¨è¿¹æœªæŽ¥è¿‘æº¯æºåœºæ™¯ï¼‰
     */
    // æ±¡æŸ“线索
    var clueList = mutableListOf<PollutedClue>()
}