feiyu02
2025-06-03 0bfd1f3b422677ddfed45f13fc034f17f049b9d5
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
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(
        exceptionData: List<BaseRealTimeData>,
        config: RTExcWindLevelConfig,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
    ) : this() {
        distanceType = windLevelCondition.distanceType
        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.distanceType.disRange,
            config.sourceTraceDegOffset
        ).map {
            // 将坐标转换为gcj02(火星坐标系),因为污染源场景信息都为此坐标系
            MapUtil.wgs84ToGcj02(it)
        }
 
        try {
            val address = AMapService.reGeo(pair)
            this.address = address.district + 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
    }
}