feiyu02
23 小时以前 8eb584869b4fd4de0f51c93f2616f12e51df9193
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
@@ -1,9 +1,17 @@
package com.flightfeather.uav.biz.sourcetrace.model
import com.flightfeather.uav.biz.FactorFilter
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
/**
 * 污染来源
@@ -19,32 +27,204 @@
     */
    // 溯源企业
    var sceneList:List<SceneInfo?>? = null
    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, factor: FactorFilter.SelectedFactor) {
    fun searchScenes(pollutedArea: PollutedArea, sceneInfoRep: SceneInfoRep, pollutedData: PollutedData) {
        // Fixme 2025.5.14: 污染源的坐标是高德地图坐标系(火星坐标系),而走航数据是WGS84坐标系
        // 按照区域检索内部污染源信息
        // 1. 首先按照四至范围从数据库初步筛选污染源,需要先将坐标转换为gcj02(火星坐标系),因为污染源场景信息都为此坐标系
        val polygonTmp = pollutedArea.polygon!!.map {
            MapUtil.gcj02ToWgs84(it)
        }
        val fb = MapUtil.calFourBoundaries(polygonTmp)
        val sceneList = sceneInfoRep.findByCoordinateRange(fb)
        // 2. 再精确判断是否在反向溯源区域多边形内部
        val result = mutableListOf<SceneInfo>()
        sceneList.forEach {
            val point = it!!.longitude.toDouble() to it.latitude.toDouble()
            if (MapUtil.isPointInPolygon(point, polygonTmp)) {
                result.add(it)
        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)
        }
        this.sceneList = result
        val txt = summaryTxt(pollutedData, this.sceneList!!)
        this.conclusion = txt
    }
        TODO("按照所选监测因子类型,区分污染源类型")
    /**
     * 计算可能的相关污染场景类型以及推理结论
     */
    @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
    }
}