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
|
}
|
}
|