feiyu02
2025-08-22 b315032d126a640758d4a6fccf297acbab057772
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package com.flightfeather.uav.biz.sourcetrace
 
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.BaseExceptionAnalysis
import com.flightfeather.uav.biz.sourcetrace.exceptiontype.RealTimeExceptionSlideAverage
import com.flightfeather.uav.biz.sourcetrace.exceptiontype.RealTimeExceptionValueMutation
import com.flightfeather.uav.biz.sourcetrace.model.RealTimeExceptionResult
import com.flightfeather.uav.common.utils.GsonUtils
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.entity.avg
import com.flightfeather.uav.domain.repository.SceneInfoRep
import com.flightfeather.uav.socket.eunm.FactorType
import com.flightfeather.uav.socket.handler.UnderwayWebSocketServerHandler
import com.flightfeather.uav.socket.sender.UnderwayWebSocketSender
import java.util.Timer
import java.util.TimerTask
import kotlin.math.PI
 
/**
 * 实时走航污染溯源
 * @date 2025/5/8
 * @author feiyu02
 */
@Deprecated("2025.5.29, 逻辑与业务不匹配,后续删除")
class RealTimeExceptionAnalysisController {
 
    constructor(sceneInfoRep: SceneInfoRep, factorFilter: FactorFilter?) {
        this.sceneInfoRep = sceneInfoRep
        this.config = if (factorFilter != null)
            RealTimeAnalysisConfig(factorFilter)
        else
            RealTimeAnalysisConfig(
                FactorFilter.builder()
//                .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()
            )
        initTask()
    }
 
    constructor(sceneInfoRep: SceneInfoRep) : this(sceneInfoRep, null)
 
 
    private val sceneInfoRep: SceneInfoRep
 
    private val config: RealTimeAnalysisConfig
 
    private val taskList = mutableListOf<BaseExceptionAnalysis<RealTimeAnalysisConfig, RealTimeExceptionResult>>()
 
    fun initTask() {
        taskList.clear()
        taskList.apply {
            add(
                RealTimeExceptionValueMutation(config) { exceptionCallback(it) }.also { it.init() }
            )
            add(
                RealTimeExceptionSlideAverage(config){ exceptionCallback(it)}.also { it.init() }
            )
        }
 
    }
 
    /**
     * 计算新的一条实时走航数据
     */
    fun addOneData(data: BaseRealTimeData) {
        // 计算异常
        taskList.forEach { it.onNextData(data) }
        // 限定时间内没有新数据传入,则结束当前的计算
    }
 
    /**
     * 超时处理,有两种超时情况
     * 1. 较短时间内,主动结束连续当前异常判断
     * 2. 较长时间内,进行初始化操作
     */
    private fun dealOnTimeout() {
        val timer = Timer(true)
        timer.schedule(object : TimerTask() {
            override fun run() {
                TODO("Not yet implemented")
            }
        }, 60 * 1000)
        timer.cancel()
    }
 
    // 数据突变异常回调
    private fun exceptionCallback(ex: RealTimeExceptionResult) {
        // 溯源污染源信息
        sourceTrace(ex, config)
        // 广播污染溯源异常结果
        UnderwayWebSocketSender.broadcast(GsonUtils.gson.toJson(ex))
    }
 
    /**
     * 污染反向溯源
     */
    private fun sourceTrace(ex: RealTimeExceptionResult, config: RealTimeAnalysisConfig) {
        // 计算异常数据均值
        val avgData = ex.dataList.avg()
        if (avgData.windSpeed!! > config.sourceTraceWindSpeedLimit) {
            return
        }
 
        // 取中间点作为反向溯源的起点
        val midData = ex.dataList[ex.dataList.size / 2]
 
        // 计算反向溯源区域
        val polygon = calSector(
            avgData.windSpeed!!.toDouble(),
            avgData.windDirection!!.toDouble(),
            midData.longitude!!.toDouble() to midData.latitude!!.toDouble(),
            config.sourceTraceTimeLimit,
            config.sourceTraceDegOffset
        )
 
        // 按照区域检索内部污染源信息
 
        // 1. 首先按照四至范围从数据库初步筛选污染源
        val fb = MapUtil.calFourBoundaries(polygon)
        val sceneList = sceneInfoRep.findByCoordinateRange(fb)
        // 2. 再精确判断是否在反向溯源区域多边形内部
        val result = mutableListOf<SceneInfo>()
        sceneList.forEach {
            // Fixme 2025.5.14: 污染源的坐标是高德地图坐标系(火星坐标系),而走航数据是WGS84坐标系
            val point = MapUtil.gcj02ToWgs84(it!!.longitude.toDouble() to it.latitude.toDouble())
            if (MapUtil.isPointInPolygon(point, polygon)) {
                result.add(it)
            }
        }
 
        // 更新中间点信息
        ex.midData = avgData.apply {
            dataTime = midData.dataTime
            createTime = midData.createTime
            longitude = midData.longitude
            latitude = midData.latitude
        }.toDataVo()
        // 更新溯源范围内的污染场景信息
        ex.relatedSceneList = result
    }
 
    /**
     * 根据中心点坐标、风向和风速,以及给定的夹角,计算以中心点按照风向风速和时长,向外扩散形成的扇形的点坐标
     * @param windSpeed 风速,单位:米/秒
     * @param windDir 风向,单位:度
     * @param center 中心点坐标经纬度
     * @param durationMin 时长,单位:分钟
     * @param defaultDegOffset 扩散偏移角度
     * @return 多边形顶点坐标集合
     */
    private fun calSector(
        windSpeed: Double, windDir: Double, center: Pair<Double, Double>, durationMin: Int,
        defaultDegOffset: Double = 30.0,
    ): List<Pair<Double, Double>> {
 
        val sDeg = windDir - defaultDegOffset
        val eDeg = windDir + defaultDegOffset
        val distance = windSpeed * durationMin * 60
 
        val result = mutableListOf(center)
        var startDeg = sDeg
        while (startDeg < eDeg) {
            val p = MapUtil.getPointByLen(center, distance, startDeg * PI / 180)
            result.add(p)
            startDeg++
        }
 
        return result
 
//        // 左侧(逆时针侧)顶点
//        val p1 = MapUtil.getPointByLen(center, distance, sDeg * PI / 180)
//        // 风向反向顶点
//        val p2 = MapUtil.getPointByLen(center, distance, windDir * PI / 180)
//        // 右侧(顺时针侧)顶点
//        val p3 = MapUtil.getPointByLen(center, distance, eDeg * PI / 180)
//
//        return listOf(center, p1, p2, p3)
    }
 
}