feiyu02
2025-07-16 8fc27dba6719041402e3e3c099e2f3e01d9d52c7
2025.7.16
1. 修改动态溯源异常判断逻辑
已修改16个文件
557 ■■■■■ 文件已修改
src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt 106 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/FactorFilter.kt
@@ -28,6 +28,11 @@
            return this
        }
        fun withCombination(com: List<List<FactorType>>):Builder{
            combination.addAll(com)
            return this
        }
        fun create(): FactorFilter {
            return this@FactorFilter
        }
@@ -85,6 +90,9 @@
    // 所选因子集合
    val selectedList = mutableListOf<SelectedFactor>()
    // 因子的关联关系
    val combination = mutableListOf<List<FactorType>>()
    fun mainList(): List<FactorType> {
        return selectedList.map { it.main }
    }
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt
@@ -8,14 +8,7 @@
 * @date 2025/5/13
 * @author feiyu02
 */
abstract class BaseAnalysisConfig{
    constructor(factorFilter: FactorFilter, combination: List<List<FactorType>>?){
        this.factorFilter = factorFilter
        this.combination = combination
    }
abstract class BaseAnalysisConfig(
    // 因子筛选
    constructor(factorFilter: FactorFilter):this(factorFilter, null)
    val factorFilter:FactorFilter
    val combination: List<List<FactorType>>?
}
    val factorFilter: FactorFilter,
)
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
@@ -5,13 +5,14 @@
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.lightshare.eunm.ExceptionStatusType
import com.flightfeather.uav.socket.eunm.FactorType
import org.springframework.beans.BeanUtils
import java.time.Duration
/**
 * 连续类型的异常分析基类,适用于当前数据与相邻数据之间有关联关系的情况
 */
abstract class BaseExceptionContinuous<T : ExceptionTag, V : BaseAnalysisConfig, Y : BaseExceptionResult>(
    config: V, private val tagClz: Class<T>
    config: V, private val tagClz: Class<T>,
) : BaseExceptionAnalysis<V, Y>(config) {
    companion object {
@@ -27,11 +28,18 @@
    // 末尾数据对象
    protected var lastData: BaseRealTimeData? = null
    // 最新的一组异常,根据设定参数,将相关联的因子产生的异常合并
    protected val latestExceptionResult = mutableListOf<BaseExceptionResult>()
    // 最新的一组异常,记录单因子异常
    protected val latestExceptions = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
    // 最新的一组合并异常
    protected val latestCombinedResult = mutableListOf<List<BaseExceptionResult>>()
    /**
     * 最新的一组合并异常,根据配置参数从[latestExceptions]单因子异常中,合并异常
     */
    protected val latestCombinedExc = mutableListOf<List<Pair<FactorFilter.SelectedFactor, T>>>()
    /**
     * 异常结果
     */
    protected val result = mutableListOf<Y>()
    /**
     * 立即判断:当出现异常时,缓存异常数据的同时,立即对已有异常进行判断是否满足异常结果要求
@@ -148,6 +156,7 @@
        lastData = data
        mergeExceptionResult()
        clearExceptions(data)
    }
    override fun onDone() {
@@ -160,7 +169,7 @@
     */
    fun recordException(factor: FactorFilter.SelectedFactor, tag: T, data: BaseRealTimeData) {
        checkResult(factor, ExceptionStatusType.Ended)
        tag.refreshWithNextException(data)
//        tag.refreshWithNextException(data)
    }
    /**
@@ -168,7 +177,7 @@
     */
    open fun checkResult(
        factor: FactorFilter.SelectedFactor? = null,
        exceptionStatus: ExceptionStatusType = ExceptionStatusType.InProgress
        exceptionStatus: ExceptionStatusType = ExceptionStatusType.InProgress,
    ) {
        val tag = tagMap[factor?.main]
        if (factor != null && tag != null) {
@@ -191,23 +200,24 @@
    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, factor)
            .apply { status = exceptionStatus.value }
        // 异常已创建时,更新异常信息
        if (tag.exceptionCreated) {
            // 将最新的异常的guid赋值给ex
            val lastEx = tag.exceptionResult.last()
            ex.guid = lastEx.guid
            tag.exceptionResult.removeLast()
            tag.exceptionResult.add(ex)
        }
        // 异常未创建时,新建异常信息
        else {
            tag.exceptionResult.add(ex)
            tag.exceptionCreated = true
        }
        latestExceptionResult.add(ex)
//        val ex = newResult(tag, factor)
//            .apply { status = exceptionStatus.value }
//        // 异常已创建时,更新异常信息
//        if (tag.exceptionCreated) {
//            // 将最新的异常的guid赋值给ex
//            val lastEx = tag.exceptionResult.last()
//            ex.guid = lastEx.guid
//            tag.exceptionResult.removeLast()
//            tag.exceptionResult.add(ex)
//        }
//        // 异常未创建时,新建异常信息
//        else {
//            tag.exceptionResult.add(ex)
//            tag.exceptionCreated = true
//        }
//        val tagClone = tagClz.newInstance()
//        BeanUtils.copyProperties(tag, tagClone)
        latestExceptions.add(factor to tag)
    }
    /**
@@ -215,13 +225,13 @@
     */
    open fun mergeExceptionResult() {
        // 遍历所有的因子组合
        config.combination?.forEach {c ->
            val res = mutableListOf<BaseExceptionResult>()
        config.factorFilter.combination.forEach { c ->
            val res = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
            var exist = true
            // 查看组合内的所有因子是否都同时出现异常
            c.forEach { f->
                val r = latestExceptionResult.find { e->
                    e.factorId == f.value
            c.forEach { f ->
                val r = latestExceptions.find { e ->
                    e.first.main == f
                }
                if (r != null) {
                    res.add(r)
@@ -232,18 +242,41 @@
            // 如果组合内的所有因子都存在异常,则存储为合并异常
            if (exist) {
                // 将合并异常从单个异常集合中去除
                res.forEach { r->
                    latestExceptionResult.removeIf { e-> e.factorId == r.factorId }
                res.forEach { r ->
                    latestExceptions.removeIf { e -> e.first.main == r.first.main }
                }
                // 将合并异常存储
                latestCombinedResult.add(res)
                latestCombinedExc.add(res)
            }
        }
        // 存储异常结果
        latestExceptions.forEach {
            result.add(newResult(listOf(it)))
        }
        latestCombinedExc.forEach {
            result.add(newResult(it))
        }
    }
    private fun clearExceptions(data: BaseRealTimeData) {
        latestExceptions.forEach {
            it.second.refreshWithNextException(data)
        }
        latestExceptions.clear()
        latestCombinedExc.forEach {
            it.forEach { e ->
                e.second.refreshWithNextException(data)
            }
        }
        latestCombinedExc.clear()
        result.clear()
    }
    /**
     * 生成一条异常分析结果
     */
    abstract fun newResult(tag:T, factor: FactorFilter.SelectedFactor): Y
    abstract fun newResult(tag: T, factor: FactorFilter.SelectedFactor): Y
    abstract fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): Y
}
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionContinuous.kt
@@ -20,4 +20,8 @@
        return ExceptionResult(tag.startData!!, tag.endData, factor, tag.exceptionData, config.mission.missionCode,
            eType)
    }
    override fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): ExceptionResult {
        return ExceptionResult()
    }
}
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
@@ -42,4 +42,8 @@
        return ExceptionResult(tag.startData!!, tag.endData, factor, tag.exceptionData, config.mission.missionCode,
            eType)
    }
    override fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): ExceptionResult {
        return ExceptionResult()
    }
}
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
@@ -37,7 +37,7 @@
    fun addHistoryData(data: BaseRealTimeData) {
        historyData.add(data)
        if (historyData.size > 20) {
        if (historyData.size > 15) {
            historyData.removeAt(0)
        }
    }
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceController.kt
@@ -32,7 +32,7 @@
        this.sceneInfoRep = sceneInfoRep
        this.sourceTraceRep = sourceTraceRep
        this.config = if (factorFilter != null) {
            RTExcWindLevelConfig(factorFilter, emptyList())
            RTExcWindLevelConfig(factorFilter)
        } else {
            RTExcWindLevelConfig(
                FactorFilter.builder()
@@ -44,11 +44,13 @@
                    .withMain(FactorType.PM25)
                    .withMain(FactorType.PM10)
                    .withMain(FactorType.VOC)
                    .create(),
                listOf(
                    listOf(FactorType.PM25, FactorType.PM10),
                    listOf(FactorType.VOC, FactorType.CO),
                )
                    .withCombination(
                        listOf(
                            listOf(FactorType.PM25, FactorType.PM10),
                            listOf(FactorType.VOC, FactorType.CO),
                        )
                    )
                    .create()
            )
        }
        pollutedSummary = PollutedSummary(config) { summaryCallback(it) }
@@ -110,32 +112,28 @@
    }
    // 数据突变异常回调
    private fun exceptionCallback(ex: List<PollutedClue>) {
        ex.forEach {
            // 溯源污染源信息
            it.searchScenes(sceneInfoRep)
            it.msgType = MsgType.PolClue.value
        }
    private fun exceptionCallback(ex: PollutedClue) {
        // 溯源污染源信息
        ex.searchScenes(sceneInfoRep)
        ex.msgType = MsgType.PolClue.value
        // 广播污染溯源异常结果
        UnderwayWebSocketSender.broadcast(MsgType.PolClue.value, ex)
        sourceTraceRep.insertList(MsgType.PolClue, ex)
        sourceTraceRep.insert(MsgType.PolClue, ex)
        // 记录污染线索
        pollutedSummary.addClueList(ex)
        pollutedSummary.addClue(ex)
    }
    // 数据变化提醒回调
    private fun dataChangeCallback(ex: List<PollutedClue>) {
        ex.forEach {
            // 溯源污染源信息
            it.searchScenes(sceneInfoRep)
            it.msgType = MsgType.DataChange.value
        }
    private fun dataChangeCallback(ex: PollutedClue) {
        // 溯源污染源信息
        ex.searchScenes(sceneInfoRep)
        ex.msgType = MsgType.DataChange.value
        // 广播数据变化提醒
        UnderwayWebSocketSender.broadcast(MsgType.DataChange.value, ex)
        sourceTraceRep.insertList(MsgType.DataChange, ex)
        sourceTraceRep.insert(MsgType.DataChange, ex)
    }
    private fun summaryCallback(ex: AnalysisResult) {
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
@@ -10,15 +10,15 @@
 * @date 2025/5/29
 * @author feiyu02
 */
class RTExcWindLevelConfig(factorFilter: FactorFilter, combination: List<List<FactorType>>?): BaseAnalysisConfig
    (factorFilter, combination) {
class RTExcWindLevelConfig(factorFilter: FactorFilter): BaseAnalysisConfig(factorFilter) {
    inner class WindLevelCondition(
        val windSpeed: Pair<Double, Double>,
        val mutationRate: Pair<Double, Double>,
        val distanceType: DistanceType,
        val countLimit: Int,
    )
    ){
}
    // 限定距离内(单位:米)
    var distanceLimit = 3000
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt
@@ -110,7 +110,14 @@
    }
    override fun newResult(tag: ExceptionTag, factor: FactorFilter.SelectedFactor): PollutedClue {
        return PollutedClue(tag, factor, getExceptionType(), config, changeRate[factor.main])
        return PollutedClue()
    }
    override fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): PollutedClue {
        return if (exceptions.isEmpty())
            PollutedClue()
        else
            PollutedClue(exceptions, getExceptionType(), config, changeRate[exceptions[0].first.main])
    }
    override fun onNewException(
@@ -127,15 +134,16 @@
    override fun mergeExceptionResult() {
        super.mergeExceptionResult()
        latestExceptionResult
        latestCombinedResult
        callback?.let { func ->
            latestExceptionResult.forEach {
                func.invoke(listOf(it as PollutedClue))
            result.forEach {
                func.invoke(it)
            }
            latestCombinedResult.forEach {
                func.invoke(it as List<PollutedClue>)
            }
//            latestExceptions.forEach {
//                func.invoke(listOf(it as PollutedClue))
//            }
//            latestCombinedExc.forEach {
//                func.invoke(it as List<PollutedClue>)
//            }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
@@ -16,7 +16,7 @@
// 异常数据生成回调类
typealias NewPolluteClueCallback = (ex: List<PollutedClue>) -> Unit
typealias NewPolluteClueCallback = (ex: PollutedClue) -> Unit
/**
 * 不同风速下,数据突变异常基类
 * @date 2025/5/29
@@ -116,10 +116,17 @@
    }
    override fun newResult(tag: ExceptionTag, factor: FactorFilter.SelectedFactor): PollutedClue {
        return PollutedClue(tag, factor, getExceptionType(), config, windLevelCondition)
        return PollutedClue()
    }
//    override fun newResult(
    override fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): PollutedClue {
        return if (exceptions.isEmpty())
            PollutedClue()
        else
            PollutedClue(exceptions, getExceptionType(), config, windLevelCondition)
    }
    //    override fun newResult(
//        start: BaseRealTimeData,
//        end: BaseRealTimeData?,
//        factor: FactorFilter.SelectedFactor,
@@ -142,15 +149,16 @@
    override fun mergeExceptionResult() {
        super.mergeExceptionResult()
        latestExceptionResult
        latestCombinedResult
        callback?.let { func ->
            latestExceptionResult.forEach {
                func.invoke(listOf(it as PollutedClue))
            result.forEach {
                func.invoke(it)
            }
            latestCombinedResult.forEach {
                func.invoke(it as List<PollutedClue>)
            }
//            latestExceptions.forEach {
//                func.invoke(listOf(it as PollutedClue))
//            }
//            latestCombinedExc.forEach {
//                func.invoke(it as List<PollutedClue>)
//            }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRealTimeException.kt
@@ -61,6 +61,10 @@
        return RealTimeExceptionResult(tag.startData!!, tag.endData, factor, tag.exceptionData, eType)
    }
    override fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): RealTimeExceptionResult {
        return RealTimeExceptionResult()
    }
    override fun onNewException(tag: T, factor: FactorFilter.SelectedFactor, exceptionStatus: ExceptionStatusType) {
        super.onNewException(tag, factor, exceptionStatus)
        callback?.let { func ->
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
@@ -102,7 +102,7 @@
//            result.add(center)
            var startDeg = 0
            while (startDeg <= 360) {
                val p = MapUtil.getPointByLen(center, 50.0, startDeg * PI / 180)
                val p = MapUtil.getPointByLen(center, distanceRange.second, startDeg * PI / 180)
                result.add(p)
                startDeg++
            }
@@ -135,7 +135,7 @@
        val result = mutableListOf<Pair<Double, Double>>()
        var startDeg = 0
        while (startDeg <= 360) {
            val p = MapUtil.getPointByLen(center, 50.0, startDeg * PI / 180)
            val p = MapUtil.getPointByLen(center, DistanceType.TYPE1.disRange.second, startDeg * PI / 180)
            result.add(p)
            startDeg++
        }
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
@@ -18,39 +18,81 @@
 * @date 2025/5/27
 * @author feiyu02
 */
class PollutedClue() : BaseExceptionResult(){
class PollutedClue() : BaseExceptionResult() {
//    constructor(
//        start: BaseRealTimeData,
//        end: BaseRealTimeData?,
//        factor: FactorFilter.SelectedFactor,
//        exceptionData: List<BaseRealTimeData>,
//        eType: ExceptionType,
//        config: RTExcWindLevelConfig,
//        tag: ExceptionTag, factor: FactorFilter.SelectedFactor, 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)
//        if (tag.exceptionData.isEmpty()) return
//        deviceCode = tag.startData?.deviceCode
//        pollutedData = PollutedData(
//            tag.startData!!, tag.endData, factor, tag.exceptionData, tag.historyData, eType, windLevelCondition
//        )
//        pollutedArea = PollutedArea(tag.historyData, tag.exceptionData, config, windLevelCondition)
//    }
    constructor(
        tag: ExceptionTag, factor: FactorFilter.SelectedFactor, eType: ExceptionType, config: RTExcWindLevelConfig,
        exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>,
        eType: ExceptionType,
        config: RTExcWindLevelConfig,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
    ) : this()
//            this(
//        tag.startData!!, tag.endData, factor, tag.exceptionData, eType, config,
//        windLevelCondition
//    )
    {
        if (tag.exceptionData.isEmpty()) return
        deviceCode = tag.startData?.deviceCode
    ) : this() {
        if (exceptions.isEmpty() || exceptions[0].second.exceptionData.isEmpty()) return
        deviceCode = exceptions[0].second.startData?.deviceCode
        var startData: BaseRealTimeData? = null
        var endData: BaseRealTimeData? = null
        var exceptionData = mutableListOf<BaseRealTimeData>()
        var historyData = mutableListOf<BaseRealTimeData>()
        exceptions.forEach { e ->
            if (startData == null) {
                startData = e.second.startData
            } else {
                if (e.second.startData?.dataTime!! < startData!!.dataTime) {
                    startData = e.second.startData
                }
            }
            if (endData == null) {
                endData = e.second.endData
            } else {
                if (e.second.endData?.dataTime!! > endData!!.dataTime) {
                    endData = e.second.endData
                }
            }
            if (exceptionData.isEmpty()) {
                exceptionData = e.second.exceptionData
            } else {
                e.second.exceptionData.forEach {
                    if (exceptionData.find { d -> d.dataTime == it.dataTime } == null) {
                        exceptionData.add(it)
                    }
                }
            }
            if (historyData.isEmpty()) {
                historyData = e.second.historyData
            } else {
                e.second.historyData.forEach {
                    if (historyData.find { d -> d.dataTime == it.dataTime } == null) {
                        historyData.add(it)
                    }
                }
            }
        }
        exceptionData.sortBy { it.dataTime }
        historyData.sortBy { it.dataTime }
        val factorList = exceptions.map { it.first }
        pollutedData = PollutedData(
            tag.startData!!, tag.endData, factor, tag.exceptionData, tag.historyData, eType, windLevelCondition
            startData!!, endData, factorList, exceptionData, historyData, eType, windLevelCondition
        )
        pollutedArea = PollutedArea(tag.historyData, tag.exceptionData, config, windLevelCondition)
        pollutedArea = PollutedArea(historyData, exceptionData, config, windLevelCondition)
    }
    /**
     * 6. 展示数据变化情况,上升速率等等
     */
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
@@ -17,6 +17,23 @@
 */
class PollutedData() {
    inner class Statistic(){
        var factorId: Int? = null
        var factorName: String? = null
        var subFactorId: List<Int>? = null
        var subFactorName: List<String>? = null
        var selectedFactor: FactorFilter.SelectedFactor? = null
        // 因子量级平均变化幅度
        var avgPer: Double? = null
        // 因子量级平均变化速率
        var avgRate: Double? = null
        var avg: Double? = null
        var min: Double? = null
        var max: Double? = null
    }
    /**
     * 9. 关联因子
     *     a) pm2.5、pm10特别高,两者在各情况下同步展示,pm2.5占pm10的比重变化,比重越高,越有可能是餐饮
@@ -29,7 +46,7 @@
    constructor(
        start: BaseRealTimeData,
        end: BaseRealTimeData?,
        factor: FactorFilter.SelectedFactor,
        factorList: List<FactorFilter.SelectedFactor>,
        exceptionData: List<BaseRealTimeData>,
        historyData: List<BaseRealTimeData>,
        eType: ExceptionType,
@@ -37,11 +54,6 @@
    ) : 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 = start.dataTime
        endTime = end?.dataTime
@@ -51,7 +63,6 @@
        endData = end
        windSpeed = exceptionData.first().windSpeed?.toDouble()
        percentage = windLevelCondition?.mutationRate?.first
        times = windLevelCondition?.countLimit
        dataList.add(start)
@@ -61,25 +72,30 @@
        dataVoList.addAll(dataList.map { it.toDataVo() })
        historyDataList.addAll(historyData.map { it.toDataVo() })
        calPer()
        calRate()
        val s = dataSummary(exceptionData, factor.main)
        avg = s.first
        min = s.second
        max = s.third
        factorList.forEach { f->
            statisticMap[f.main] = Statistic().apply {
                factorId = f.main.value
                factorName = f.main.des
                subFactorId = f.subs.map { it.value }
                subFactorName = f.subs.map { it.des }
                selectedFactor = f
                avgPer = calPer(f.main)
                avgRate = calRate(f.main)
                val s = dataSummary(exceptionData, f.main)
                avg = s.first
                min = s.second
                max = s.third
            }
        }
    }
    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: Date? = null
    var endTime: Date? = null
@@ -90,17 +106,6 @@
    // 风速
    var windSpeed: Double? = null
    // 因子量级变化幅度
    var percentage: Double? = null
    // 因子量级平均变化幅度
    var avgPer: Double? = null
    // 因子量级平均变化速率
    var avgRate: Double? = null
    var avg: Double? = null
    var min: Double? = null
    var max: Double? = null
    // 发生次数
    var times: Int? = null
@@ -109,34 +114,37 @@
    var dataList: MutableList<BaseRealTimeData> = mutableListOf()
    var dataVoList: MutableList<DataVo> = mutableListOf()
    private fun calPer() {
        val list = dataList
//        list.add(startData)
//        list.addAll(dataList)
        if (list.size < 2) return
    var statisticMap = mutableMapOf<FactorType, Statistic>()
        var total = .0
        for (i in 0 until list.size - 1) {
            val p = list[i]?.getByFactorType(selectedFactor!!.main)!!
            val n = list[i + 1]?.getByFactorType(selectedFactor!!.main)!!
            total += (n - p) / p
        }
        avgPer = total / (list.size - 1)
    fun toFactorNames(): String {
        val factors = statisticMap.entries.map { it.key }.sortedBy { it.value }.joinToString(";") { it.des }
        return factors
    }
    private fun calRate() {
    private fun calPer(factorType: FactorType): Double? {
        val list = dataList
//        list.add(startData)
//        list.addAll(dataList)
        if (list.size < 2) return
        if (list.size < 2) return null
        var total = .0
        for (i in 0 until list.size - 1) {
            val p = list[i]?.getByFactorType(selectedFactor!!.main)!!
            val n = list[i + 1]?.getByFactorType(selectedFactor!!.main)!!
            val p = list[i].getByFactorType(factorType)!!
            val n = list[i + 1].getByFactorType(factorType)!!
            total += (n - p) / p
        }
        return total / (list.size - 1)
    }
    private fun calRate(factorType: FactorType): Double? {
        val list = dataList
        if (list.size < 2) return null
        var total = .0
        for (i in 0 until list.size - 1) {
            val p = list[i].getByFactorType(factorType)!!
            val n = list[i + 1].getByFactorType(factorType)!!
            total += (n - p) / 4
        }
        avgRate = total / (list.size - 1)
        return total / (list.size - 1)
    }
    private fun dataSummary(exceptionData: List<BaseRealTimeData?>, factorType: FactorType): Triple<Double, Double,
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
@@ -90,57 +90,82 @@
     */
    @Throws(Exception::class)
    private fun calSceneType(pollutedData: PollutedData): Pair<String, List<SceneType>>? {
        when (pollutedData.selectedFactor?.main) {
            // 氮氧化合物,一般由于机动车尾气,同步计算CO
            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、pm10特别高,两者在各情况下同步展示,pm2.5占pm10的比重变化,比重越高,越有可能是餐饮
            // b) pm10特别高、pm2.5较高,大颗粒扬尘污染,只展示pm10,pm2.5占pm10的比重变化,工地为主
            FactorType.PM25,
            FactorType.PM10,
                -> {
                val pm25Avg = round(pollutedData.dataList.map { it.pm25!! }.average() * 10) / 10
                val pm10Avg = round(pollutedData.dataList.map { it.pm10!! }.average() * 10) / 10
                // 计算异常数据的pm2.5占pm10比重的均值
                val percentageAvg = pollutedData.dataList.map {
                    it.pm25!! / it.pm10!!
                }.average()
                val str =
                    "PM2.5量级为${pm25Avg}μg/m³,PM10量级为${pm10Avg}μg/m³,PM2.5占PM10的比重为${round(percentageAvg * 100)}%"
                return if (percentageAvg > 0.666) {
                    "${str},比重较大,污染源以餐饮为主,工地次之" to
                            listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5)
                } else if (percentageAvg < 0.333) {
                    "${str},比重较小,属于大颗粒扬尘污染,污染源以工地为主" to
                            listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5)
                } else {
                    "${str},污染源以餐饮、工地为主" to
                            listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5)
        var des: String? = null
        val sceneTypes = mutableListOf<SceneType>()
        pollutedData.statisticMap.entries.forEach { s ->
            val res = when (s.key) {
                // 氮氧化合物,一般由于机动车尾气,同步计算CO
                FactorType.NO2 -> {
                    val coAvg = round(pollutedData.dataList.map { it.co!! }.average()) / 1000
                     "氮氧化合物偏高,CO的量级为${coAvg}mg/m³,一般由于机动车尾气造成,污染源以汽修、加油站为主" to
                            listOf(SceneType.TYPE6, SceneType.TYPE10, SceneType.TYPE17)
                }
            }
            // c) VOC较高,同比计算pm2.5的量级,可能存在同步偏高(汽修、加油站), 同步计算O3是否有高值
            // d) VOC较高,处于加油站(车辆拥堵情况),CO一般较高, 同步计算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偏高,同时PM2.5量级为${pm25Avg}μg/m³,CO量级为${coAvg}mg/m³,O3量级为${o3Avg}μg/m³,污染源以汽修、加油站为主" to
                        listOf(SceneType.TYPE6, SceneType.TYPE17, SceneType.TYPE12)
            }
            else -> return null
                FactorType.CO ->  null
                FactorType.H2S ->  null
                FactorType.SO2 ->  null
                FactorType.O3 ->  null
                // a) pm2.5、pm10特别高,两者在各情况下同步展示,pm2.5占pm10的比重变化,比重越高,越有可能是餐饮
                // b) pm10特别高、pm2.5较高,大颗粒扬尘污染,只展示pm10,pm2.5占pm10的比重变化,工地为主
                FactorType.PM25,
                FactorType.PM10,
                    -> {
                    val pm25Avg = round(pollutedData.dataList.map { it.pm25!! }.average() * 10) / 10
                    val pm10Avg = round(pollutedData.dataList.map { it.pm10!! }.average() * 10) / 10
                    // 计算异常数据的pm2.5占pm10比重的均值
                    val percentageAvg = pollutedData.dataList.map {
                        it.pm25!! / it.pm10!!
                    }.average()
                    val str =
                        "PM2.5量级为${pm25Avg}μg/m³,PM10量级为${pm10Avg}μg/m³,PM2.5占PM10的比重为${round(percentageAvg * 100)}%"
                     if (percentageAvg > 0.666) {
                        "${str},比重较大,污染源以餐饮为主,工地次之" to
                                listOf(
                                    SceneType.TYPE1,
                                    SceneType.TYPE2,
                                    SceneType.TYPE3,
                                    SceneType.TYPE14,
                                    SceneType.TYPE5
                                )
                    } else if (percentageAvg < 0.333) {
                        "${str},比重较小,属于大颗粒扬尘污染,污染源以工地为主" to
                                listOf(
                                    SceneType.TYPE1,
                                    SceneType.TYPE2,
                                    SceneType.TYPE3,
                                    SceneType.TYPE14,
                                    SceneType.TYPE5
                                )
                    } else {
                        "${str},污染源以餐饮、工地为主" to
                                listOf(
                                    SceneType.TYPE1,
                                    SceneType.TYPE2,
                                    SceneType.TYPE3,
                                    SceneType.TYPE14,
                                    SceneType.TYPE5
                                )
                    }
                }
                // c) VOC较高,同比计算pm2.5的量级,可能存在同步偏高(汽修、加油站), 同步计算O3是否有高值
                // d) VOC较高,处于加油站(车辆拥堵情况),CO一般较高, 同步计算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
                     "VOC偏高,同时PM2.5量级为${pm25Avg}μg/m³,CO量级为${coAvg}mg/m³,O3量级为${o3Avg}μg/m³,污染源以汽修、加油站为主" to
                            listOf(SceneType.TYPE6, SceneType.TYPE17, SceneType.TYPE12)
                }
                else ->  null
            }
            des = res?.first
            res?.second?.let { sceneTypes.addAll(it) }
        }
        return (des ?: "") to sceneTypes
    }
    /**
@@ -186,10 +211,10 @@
        val st = DateUtil.instance.getTime(pollutedData.startTime)
        val et = DateUtil.instance.getTime(pollutedData.endTime)
        var txt =
            "${pollutedData.selectedFactor?.main?.des}在${st}至${et}之间,出现${pollutedData.exception},最低值为${
                pollutedData
                    .min
            },最高值为${pollutedData.max}"
            "在${st}至${et}之间,出现${pollutedData.exception}"
        pollutedData.statisticMap.entries.forEach {s ->
            txt += ",${s.key.des}最低值为${s.value.min}μg/m³,最高值为${s.value.max}μg/m³,均值为${s.value.avg}μg/m³"
        }
        if (sceneList.isEmpty()) {
            txt += (",可能存在隐藏风险源。")
        } else {
src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt
@@ -27,7 +27,7 @@
    fun insert(msgType: MsgType, obj: PollutedClue): Int {
        val stm = SourceTraceMsg().apply {
            deviceCode = obj.deviceCode
            factorName = obj.pollutedData?.factorName
            factorName = obj.pollutedData?.toFactorNames()
            exceptionType = obj.pollutedData?.exceptionType
            startTime = obj.pollutedData?.startTime
            endTime = obj.pollutedData?.endTime