| | |
| | | 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.socket.eunm.FactorType |
| | | import org.springframework.beans.BeanUtils |
| | | import org.springframework.web.context.ContextLoader |
| | | import kotlin.math.round |
| | | |
| | | /** |
| | | * 污染来源 |
| | |
| | | */ |
| | | |
| | | // 溯源企业 |
| | | var sceneList: List<SceneInfoVo?>? = null |
| | | var sceneList: List<SceneInfoVo>? = null |
| | | |
| | | // 溯源推理结论 |
| | | var conclusion: String? = null |
| | |
| | | // Fixme 2025.5.14: 污染源的坐标是高德地图坐标系(火星坐标系),而走航数据是WGS84坐标系 |
| | | // 按照区域检索内部污染源信息 |
| | | var result = mutableListOf<SceneInfo>() |
| | | // 1. 首先按照四至范围从数据库初步筛选污染源,此处的区域坐标已转换为火星坐标系 |
| | | val polygonTmp = pollutedArea.polygon!! |
| | | val fb = MapUtil.calFourBoundaries(polygonTmp) |
| | | 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) |
| | | } |
| | | } |
| | | val polygonTmp = pollutedArea.polygon |
| | | this.sceneList = emptyList() |
| | | |
| | | val closePolygonTmp = pollutedArea.closePolygon!! |
| | | val closeFb = MapUtil.calFourBoundaries(closePolygonTmp) |
| | | val closeSceneList = sceneInfoRep.findByCoordinateRange(closeFb) |
| | | // 2. 再精确判断是否在反向溯源区域多边形内部 |
| | | closeSceneList.forEach { |
| | | val point = it!!.longitude.toDouble() to it.latitude.toDouble() |
| | | if (MapUtil.isPointInPolygon(point, closePolygonTmp)) { |
| | | result.add(it) |
| | | 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) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 根据污染因子的量级,计算主要的污染场景类型,筛选结果 |
| | | val mainSceneType = calSceneType(pollutedData) |
| | | if (mainSceneType != null) { |
| | | this.conclusion = mainSceneType.first |
| | | // 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.second.find { s-> |
| | | val r = mainSceneType.find { s -> |
| | | s.value == it.typeId.toInt() |
| | | } |
| | | r != null |
| | | }.toMutableList() |
| | | this.sceneList = findClosestStation(sceneInfoRep, result) |
| | | } |
| | | |
| | | this.sceneList = findClosestStation(sceneInfoRep, result) |
| | | |
| | | val txt = summaryTxt(pollutedData, this.sceneList!!) |
| | | this.conclusion = txt |
| | | } |
| | | |
| | | /** |
| | | * 计算可能的相关污染场景类型以及推理结论 |
| | | */ |
| | | @Throws(Exception::class) |
| | | private fun calSceneType(pollutedData: PollutedData): Pair<String, List<SceneType>>? { |
| | | when (pollutedData.selectedFactor?.main) { |
| | | // 氮氧化合物,一般由于机动车尾气,同步计算CO |
| | | FactorType.NO2 -> { |
| | | val coAvg = round(pollutedData.dataList.map { it.co!! }.average()) / 1000 |
| | | return "氮氧化合物偏高,CO的量级为${coAvg}mg/m³,一般由于机动车尾气造成,污染源以汽修、加油站为主" to |
| | | listOf(SceneType.TYPE6, SceneType.TYPE10, SceneType.TYPE17) |
| | | } |
| | | |
| | | FactorType.CO -> return null |
| | | |
| | | FactorType.H2S -> return null |
| | | |
| | | FactorType.SO2 -> return null |
| | | |
| | | FactorType.O3 -> return null |
| | | // a) pm2.5、pm10特别高,两者在各情况下同步展示,pm2.5占pm10的比重变化,比重越高,越有可能是餐饮 |
| | | // b) pm10特别高、pm2.5较高,大颗粒扬尘污染,只展示pm10,pm2.5占pm10的比重变化,工地为主 |
| | | FactorType.PM25, |
| | | FactorType.PM10, |
| | | -> { |
| | | // 计算异常数据的pm2.5占pm10比重的均值 |
| | | val percentageAvg = pollutedData.dataList.map { |
| | | it.pm25!! / it.pm10!! |
| | | }.average() |
| | | return if (percentageAvg > 0.666) { |
| | | "PM2.5占PM10的比重为${round(percentageAvg * 100)}%,比重较大,污染源以餐饮为主,工地次之" to |
| | | listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5) |
| | | } else if (percentageAvg < 0.333) { |
| | | "PM2.5占PM10的比重为${round(percentageAvg * 100)}%,比重较小,属于大颗粒扬尘污染,污染源以工地为主" to |
| | | listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5) |
| | | } else { |
| | | "PM2.5占PM10的比重为${round(percentageAvg * 100)}%,污染源以餐饮、工地为主" to |
| | | listOf(SceneType.TYPE1, SceneType.TYPE2, SceneType.TYPE3, SceneType.TYPE14, SceneType.TYPE5) |
| | | 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) |
| | | } |
| | | } |
| | | // c) VOC较高,同比计算pm2.5的量级,可能存在同步偏高(汽修、加油站), 同步计算O3是否有高值 |
| | | // d) VOC较高,处于加油站(车辆拥堵情况),CO一般较高, 同步计算O3是否有高值 |
| | | FactorType.VOC -> { |
| | | val pm25Avg = round(pollutedData.dataList.map { it.pm25!! }.average() * 10) / 10 |
| | | val coAvg = round(pollutedData.dataList.map { it.co!! }.average()) / 1000 |
| | | val o3Avg = round(pollutedData.dataList.map { it.o3!! }.average() * 10) / 10 |
| | | return "VOC偏高,同时PM2.5量级为${pm25Avg}μg/m³,CO量级为${coAvg}mg/m³,O3量级为${o3Avg}μg/m³,污染源以汽修、加油站为主" to |
| | | listOf(SceneType.TYPE6, SceneType.TYPE17, SceneType.TYPE12) |
| | | } |
| | | |
| | | else -> return null |
| | | 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() |
| | | } |
| | | |
| | | /** |
| | |
| | | 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 |
| | | } |
| | | } |