feiyu02
21 小时以前 8eb584869b4fd4de0f51c93f2616f12e51df9193
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package com.flightfeather.uav.biz.sourcetrace.model
 
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
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
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.bean.SceneInfoVo
import com.flightfeather.uav.lightshare.eunm.SceneType
import com.flightfeather.uav.socket.eunm.FactorType
import org.springframework.beans.BeanUtils
import org.springframework.web.context.ContextLoader
 
/**
 * 污染来源
 * 系统内部的污染场景、电子地图搜索得到的实际路段路口等标志信息
 * @date 2025/5/27
 * @author feiyu02
 */
class PollutedSource {
 
    /**
     * 溯源清单显示与临近监测站点的距离(国控、市控、网格化监测点)
     *
     */
 
    // 溯源企业
    var sceneList: List<SceneInfoVo>? = null
 
    // 溯源推理结论
    var conclusion: String? = null
 
    fun searchScenes(pollutedArea: PollutedArea, pollutedData: PollutedData) {
        ContextLoader.getCurrentWebApplicationContext()?.getBean(SceneInfoRep::class.java)?.run {
            searchScenes(pollutedArea, this, pollutedData)
        }
    }
 
    /**
     * 查找系统内部溯源范围内的污染企业
     */
    fun searchScenes(pollutedArea: PollutedArea, sceneInfoRep: SceneInfoRep, pollutedData: PollutedData) {
        // Fixme 2025.5.14: 污染源的坐标是高德地图坐标系(火星坐标系),而走航数据是WGS84坐标系
        // 按照区域检索内部污染源信息
        var result = mutableListOf<SceneInfo>()
        val polygonTmp = pollutedArea.polygon
        this.sceneList = emptyList()
 
        if (polygonTmp != null) {
            val fb = MapUtil.calFourBoundaries(polygonTmp)
            // 1. 首先按照四至范围从数据库初步筛选污染源,此处的区域坐标已转换为火星坐标系
            val sceneList = sceneInfoRep.findByCoordinateRange(fb)
            // 2. 再精确判断是否在反向溯源区域多边形内部
            sceneList.forEach {
                val point = it!!.longitude.toDouble() to it.latitude.toDouble()
                if (MapUtil.isPointInPolygon(point, polygonTmp)) {
                    result.add(it)
                }
            }
 
            // 3. 再统一检索近距离污染圆形区域内部的污染源
            val closePolygonTmp = pollutedArea.closePolygon!!
            val closeFb = MapUtil.calFourBoundaries(closePolygonTmp)
            val closeSceneList = sceneInfoRep.findByCoordinateRange(closeFb)
            closeSceneList.forEach {
                val point = it!!.longitude.toDouble() to it.latitude.toDouble()
                if (MapUtil.isPointInPolygon(point, closePolygonTmp)) {
                    result.add(it)
                }
            }
            // 4. 去重
            result = result.distinctBy { it.guid }.toMutableList()
 
            // 5. 根据污染因子的量级,计算主要的污染场景类型,筛选结果
            val mainSceneType = calSceneType(pollutedData)
            result = result.filter {
                val r = mainSceneType.find { s ->
                    s.value == it.typeId.toInt()
                }
                r != null
            }.toMutableList()
            this.sceneList = findClosestStation(sceneInfoRep, result)
        }
 
        val txt = summaryTxt(pollutedData, this.sceneList!!)
        this.conclusion = txt
    }
 
    /**
     * 计算可能的相关污染场景类型以及推理结论
     */
    @Throws(Exception::class)
    private fun calSceneType(pollutedData: PollutedData): List<SceneType> {
        val sceneTypes = mutableListOf<SceneType>()
        pollutedData.statisticMap.entries.forEach { s ->
            val res = when (s.key) {
                // 氮氧化合物,一般由于机动车尾气,同步计算CO
                FactorType.NO,
                FactorType.NO2,
                    -> {
                    listOf(SceneType.TYPE1, SceneType.TYPE6, SceneType.TYPE10, SceneType.TYPE17)
                }
 
                FactorType.CO -> listOf(SceneType.TYPE6, SceneType.TYPE10, SceneType.TYPE17)
                FactorType.H2S -> null
                FactorType.SO2 -> null
                FactorType.O3 -> null
                FactorType.PM25,
                FactorType.PM10,
                    -> {
                    listOf(
                        SceneType.TYPE1,
                        SceneType.TYPE2,
                        SceneType.TYPE3,
                        SceneType.TYPE14,
                        SceneType.TYPE5,
                        SceneType.TYPE18
                    )
                }
 
                FactorType.VOC -> {
                    listOf(SceneType.TYPE5, SceneType.TYPE6, SceneType.TYPE17, SceneType.TYPE12, SceneType.TYPE18)
                }
 
                else -> null
            }
            res?.let { sceneTypes.addAll(it) }
        }
        return sceneTypes.distinct()
    }
 
    /**
     * 计算最近的监测站点
     */
    private fun findClosestStation(sceneInfoRep: SceneInfoRep, sceneList: List<SceneInfo>): List<SceneInfoVo> {
        val res1 = sceneInfoRep.findByArea(AreaVo().apply {
            sceneTypeId = SceneType.TYPE19.value.toString()
        })
 
        val res2 = sceneInfoRep.findByArea(AreaVo().apply {
            sceneTypeId = SceneType.TYPE20.value.toString()
        })
        val res = res1.toMutableList().apply { addAll(res2) }
 
        return sceneList.map {
            var minLen = -1.0
            var selectedRes: SceneInfo? = null
            res.forEach { r ->
                val dis = MapUtil.getDistance(
                    it.longitude.toDouble(),
                    it.latitude.toDouble(),
                    r!!.longitude.toDouble(),
                    r.latitude.toDouble()
                )
                if (minLen < 0 || dis < minLen) {
                    minLen = dis
                    selectedRes = r
                }
            }
            val vo = SceneInfoVo()
            BeanUtils.copyProperties(it, vo)
            vo.closestStation = selectedRes
            vo.length = minLen
 
            return@map vo
        }
    }
 
    /**
     * 溯源解析
     * @param pollutedData 污染数据
     * @param sceneList 风险源列表
     * @return 溯源描述
     */
    private fun summaryTxt(pollutedData: PollutedData, sceneList: List<SceneInfoVo>): String {
        val st = DateUtil.instance.getTime(pollutedData.startTime)
        val et = DateUtil.instance.getTime(pollutedData.endTime)
 
        // 1. 描述异常发生的时间和异常类型
        var txt = "在${st}至${et}之间,出现${pollutedData.exception}${pollutedData.times}次"
 
        // 2. 描述异常数据的变化情况
        val statArr = mutableListOf<String>()
        pollutedData.statisticMap.entries.forEach { s ->
            val txtArr = mutableListOf<String>()
            s.value.excGroup?.forEach exception@{ p ->
                val preValue = p.getFirstDataValue()
                val curValue = p.getLastDataValue()
                val per = p.per?.times(100)
                val rate = p.rate
                if (preValue == null || curValue == null || per == null) return@exception
                when (pollutedData.exceptionType) {
                    // 量级突变
                    ExceptionType.TYPE4.value -> {
                        txtArr.add("从${preValue}μg/m³突变至${curValue}μg/m³,变化率为${per}%")
                    }
                    // 快速上升
                    ExceptionType.TYPE9.value -> {
                        txtArr.add("从${preValue}μg/m³快速上升至${curValue}μg/m³,变化速率为${rate}μg/m³/秒,变化率为${per}%")
                    }
                }
            }
            statArr.add("${s.key.getTxt()}量级${txtArr.joinToString(",")}")
        }
        txt += ",${statArr.joinToString(";")}"
 
        // 3. 描述发现的风险源情况
        if (sceneList.isEmpty()) {
            txt += (",可能存在隐藏风险源。")
        } else {
            txt += (",发现${sceneList.size}个风险源,包含")
 
            val sizeMap = mutableMapOf<String, Int>()
            sceneList.forEach {
                if (!sizeMap.containsKey(it.type)) {
                    sizeMap[it.type] = 0
                }
                sizeMap[it.type] = sizeMap[it.type]!! + 1
            }
            sizeMap.forEach { (t, u) ->
                txt += ("${u}个${t},")
            }
            txt = txt.replaceRange(txt.length - 1, txt.length, "。")
        }
 
        return txt
    }
}