feiyu02
2025-07-29 2e024c986c14943a41f7bfe913cfef0cede64198
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
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() {
 
    /**
     * 溯源角度可设置
     */
 
    constructor(
        historyData: List<BaseRealTimeData>,
        exceptionData: List<BaseRealTimeData>,
        config: RTExcWindLevelConfig,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
    ) : this() {
        distanceType = windLevelCondition?.distanceType
        distanceRange = distanceType?.disRange
        distanceDes = distanceType?.des
        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
 
    var distanceRange: Pair<Double, Double>? = null
    var distanceDes: String? = null
 
    /**
     * 反向溯源
     */
    private fun sourceTrace(
        historyData: List<BaseRealTimeData>,
        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.windDirection!!.toDouble(),
            pair,
            windLevelCondition.distanceType.disRange,
            config.sourceTraceDegOffset
        ).map {
            // 将坐标转换为gcj02(火星坐标系),因为污染源场景信息都为此坐标系
            MapUtil.wgs84ToGcj02(it)
        }
 
        closePolygon = closeSourceTrace(historyData, pair).map {
            // 将坐标转换为gcj02(火星坐标系),因为污染源场景信息都为此坐标系
            MapUtil.wgs84ToGcj02(it)
        }
 
        if (config.isSearchAddress) {
            try {
                val address = AMapService.reGeo(MapUtil.wgs84ToGcj02(pair))
                this.address = address.district + address.township + address.street
//                Thread.sleep(100)
            } 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)
            var startDeg = 0
            while (startDeg <= 360) {
                val p = MapUtil.getPointByLen(center, distanceRange.second, startDeg * PI / 180)
                result.add(p)
                startDeg++
            }
        } 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°,使得两组坐标点按顺序排列,可绘制对应的多边形
                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, DistanceType.TYPE1.disRange.second, startDeg * PI / 180)
            result.add(p)
            startDeg++
        }
 
        return result
    }
}