feiyu02
2025-07-16 8fc27dba6719041402e3e3c099e2f3e01d9d52c7
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
@@ -1,18 +1,145 @@
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 {
class PollutedArea() {
    var name: String? = null
    /**
     * 溯源角度可设置
     */
    // 污染范围区域(经纬度多边形)
    constructor(
        historyData: List<BaseRealTimeData>,
        exceptionData: List<BaseRealTimeData>,
        config: RTExcWindLevelConfig,
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition?,
    ) : this() {
        distanceType = windLevelCondition?.distanceType
        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
    /**
     * 反向溯源
     */
    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)
        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)
            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
    }
}