feiyu02
2025-05-08 9a9a27f185bc0cf9dc0001cfc6839e6d13dbccd9
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/SatelliteDataCalculateServiceImpl.kt
@@ -1,12 +1,22 @@
package com.flightfeather.uav.lightshare.service.impl
import com.flightfeather.uav.biz.satellite.GridGroupOption
import com.flightfeather.uav.biz.satellite.SatelliteGridManage
import com.flightfeather.uav.common.exception.BizException
import com.flightfeather.uav.domain.entity.GridCell
import com.flightfeather.uav.domain.entity.GridGroup
import com.flightfeather.uav.common.utils.TimeUtil
import com.flightfeather.uav.domain.entity.*
import com.flightfeather.uav.domain.repository.MissionRep
import com.flightfeather.uav.domain.repository.RealTimeDataRep
import com.flightfeather.uav.domain.repository.SatelliteGridRep
import com.flightfeather.uav.lightshare.bean.GridDataDetailMixVo
import com.flightfeather.uav.lightshare.eunm.GridType
import com.flightfeather.uav.lightshare.eunm.SatelliteDataType
import com.flightfeather.uav.lightshare.service.SatelliteDataCalculateService
import org.springframework.beans.BeanUtils
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.Date
import kotlin.math.round
/**
 * 卫星网格坐标及监测数据二次计算
@@ -14,7 +24,11 @@
 * @author feiyu02
 */
@Service
class SatelliteDataCalculateServiceImpl(private val satelliteGridRep: SatelliteGridRep) : SatelliteDataCalculateService {
class SatelliteDataCalculateServiceImpl(
    private val satelliteGridRep: SatelliteGridRep,
    private val realTimeDataRep: RealTimeDataRep,
    private val missionRep: MissionRep,
) : SatelliteDataCalculateService {
    override fun calGridVertex(groupId: Int): List<GridCell?> {
@@ -46,14 +60,228 @@
        return cellList
    }
    @Transactional
    override fun splitGrid(groupId: Int, scale: Int): List<GridCell?> {
        // 检查该网格属性是否合规
        val gridGroup =
            satelliteGridRep.fetchGridGroup(groupId) ?: throw BizException("该网格组不存在,无法进行网格细分")
        if (gridGroup.length == null) throw BizException("该网格组没有设定网格边长,无法进行网格细分")
        // 检查该网格下该种类的细分网格是否存在,若不存在,则新建
        satelliteGridRep.fetchGridGroup(groupId)
        val searchGridGroup = GridGroup().apply {
            type = GridType.Sub.name.lowercase()
            fatherGroupId = gridGroup.id
            length = round(gridGroup.length / scale)
        }
        val subGridGroupList = satelliteGridRep.fetchGridGroup(searchGridGroup)
        // 若细分网格记录超过1个,说明业务逻辑存在问题,相同边长的细分网格应该只有1个
        if (subGridGroupList.size > 1) {
            throw BizException("该网格组下${searchGridGroup.length}米边长的网格记录超过1个,无法再进行网格细分,并且请检查生成逻辑是否问题")
        }
        // 若细分网格记录有且只有1个,则无需再次细分,直接返回已有结果
        else if (subGridGroupList.size == 1) {
            val g = subGridGroupList.first()
                ?: throw BizException("该网格组下的细分网格记录已损坏,无法使用,请检查数据库记录")
            return satelliteGridRep.fetchGridCell(g.id)
        }
        // 当没有记录时,执行生成逻辑
        // 生成新的细分网格组记录
        val newGridGroup = GridGroup()
        BeanUtils.copyProperties(gridGroup, newGridGroup)
        newGridGroup.apply {
            id = null
            name += "${searchGridGroup.length.toInt()}米细分"
            createTime = Date()
            length = searchGridGroup.length
            type = GridType.Sub.name.lowercase()
            fatherGroupId = groupId
        }
        satelliteGridRep.insertGridGroup(newGridGroup)
        // 获取具体网格信息
        val cellList = satelliteGridRep.fetchGridCell(groupId)
        // 按照给定的拆分系数进行拆分
//        val subCellList = SatelliteGridManage.splitGrid(cellList, scale)
        val subCellList = SatelliteGridManage.splitGrid(cellList, scale, newGridGroup.id)
        satelliteGridRep.insertGridCell(subCellList)
        return emptyList()
        return subCellList
    }
    @Transactional
    override fun splitData(groupId: Int, dataId: Int): List<GridDataDetail?> {
        // 检查是否是细分网格类型
        val gridGroup =
            satelliteGridRep.fetchGridGroup(groupId) ?: throw BizException("该网格组不存在,无法进行细分网格数据映射")
        if (gridGroup.type != GridType.Sub.name.lowercase()) throw BizException("该网格组不是细分网格类型存在,无法进行细分网格数据映射")
        val subGridCellList = satelliteGridRep.fetchGridCell(groupId)
        val originGridData = satelliteGridRep.fetchGridData(dataId)
        val originGridDataDetailList = satelliteGridRep.fetchGridDataDetail(dataId)
        val subGridData = GridData().apply {
            this.groupId = groupId
            dataTime = originGridData?.dataTime
            type = SatelliteDataType.Sub.value.toByte()
        }
        satelliteGridRep.insertGridData(subGridData)
        val subGridDataDetailList =
            SatelliteGridManage.splitData(subGridCellList, subGridData, originGridDataDetailList)
        satelliteGridRep.insertGridDataDetail(subGridDataDetailList)
        return subGridDataDetailList
    }
    @Transactional
    override fun dataFusion(gridData: GridData): List<GridDataDetail?> {
        val missionCode = gridData.missionCode ?: throw BizException("缺少任务编号")
        val groupId = gridData.groupId ?: throw BizException("缺少网格组id")
        // 查询走航任务及对应走航监测数据
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val data = realTimeDataRep.fetchData(mission)
        // 查找是否已有走航融合记录
        val oldGridDataList = satelliteGridRep.fetchGridData(GridData().apply {
            this.groupId = groupId
            this.missionCode = missionCode
            this.type = SatelliteDataType.Monitor.value.toByte()
        })
        if (oldGridDataList.isEmpty()) {
            // 创建融合数据索引对象
            val newGridData = gridData.apply {
                this.groupId = groupId
                dataTime = mission.startTime
                type = SatelliteDataType.Monitor.value.toByte()
                this.missionCode = mission.missionCode
                provinceCode = mission.provinceCode
                provinceName = mission.provinceName
                cityCode = mission.cityCode
                cityName = mission.cityName
                districtCode = mission.districtCode
                districtName = mission.districtName
                // 2025.3.27: 所属监测点区域需要通过用户选择确定
                zone
                pollutionDegreeIndex
                pollutionDegree
                val period = TimeUtil.getDayTimeTag(mission.startTime, mission.endTime)
                dayTimePeriod = period?.first
                dayTimePeriodStart = period?.second
                dayTimePeriodEnd = period?.third
            }
            satelliteGridRep.insertGridData(newGridData)
            // 查询网格单元格信息
            val gridCellList = satelliteGridRep.fetchGridCell(groupId)
            // 将走航数据和卫星网格进行融合计算
            val gridDataDetailList = SatelliteGridManage.dataFusion(data, newGridData, gridCellList)
            satelliteGridRep.insertGridDataDetail(gridDataDetailList)
            return gridDataDetailList
        } else {
            val oldGridData = oldGridDataList.first()
            val oldGridDataDetailList = satelliteGridRep.fetchGridDataDetail(oldGridData?.id, oldGridData?.groupId)
            // 查询网格单元格信息
            val gridCellList = satelliteGridRep.fetchGridCell(groupId)
            // 将走航数据和卫星网格进行融合计算
            val gridDataDetailList = SatelliteGridManage.dataFusion(data, oldGridData, gridCellList)
            // 将已有的数据id赋值给新的融合结果,两组结果均以根据cellId顺序排列,所以直接循环赋值
            gridDataDetailList.forEachIndexed { index, gridDataDetail ->
                gridDataDetail.id = oldGridDataDetailList[index]?.id
            }
            satelliteGridRep.updateGridDataDetail(gridDataDetailList)
            return gridDataDetailList
        }
    }
    override fun mixUnderwayGridData(groupId: Int, dataIdList: List<Int>): List<GridDataDetailMixVo> {
        // 获取所使用的网格组网格信息
        val gridCellList = satelliteGridRep.fetchGridCell(groupId)
        val gridDataDetailMap = mutableMapOf<Int?, MutableList<GridDataDetail>>()
        // 归集所有相同单元网格的数据
        dataIdList.forEach { id ->
            satelliteGridRep.fetchGridDataDetail(id, groupId).forEach gdd@{ gdd ->
                if (gdd == null) return@gdd
                if (!gridDataDetailMap.containsKey(gdd.cellId)) {
                    gridDataDetailMap[gdd.cellId] = mutableListOf()
                }
                gridDataDetailMap[gdd.cellId]?.add(gdd)
            }
        }
        // 将所有结果格式化,同时重叠网格进行监测数据均值计算
        val result = mutableListOf<GridDataDetailMixVo>()
        gridDataDetailMap.forEach { (_, v) ->
            val target = GridDataDetailMixVo()
            if (v.size == 1) {
                BeanUtils.copyProperties(v[0], target)
            } else {
                BeanUtils.copyProperties(v.avg(), target)
                target.mixData = true
                target.originDataList = v
            }
            target.apply {
                this.dataId = v.first().dataId
                this.groupId = v.first().groupId
                this.cellId = v.first().cellId
            }
            result.add(target)
        }
        result.sortBy { it.pm25 }
        result.forEachIndexed { index, d ->
            d.rank = index + 1
        }
        result.sortBy { it.cellId }
        return result
    }
    override fun buildHeatmap(
        groupId: Int,
        gridDataDetailList: List<GridDataDetail>,
        searchLength: Int,
    ): List<GridDataDetail> {
        val gridGroup =satelliteGridRep.fetchGridGroup(groupId)
        val gridCellList = satelliteGridRep.fetchGridCell(groupId)
        val originCellIdList = gridDataDetailList.map { it.cellId }
        // Fixme 2025.3.24: 此处根据现有的网格信息设计方式,使用临时的参数,后续将网格通过二维坐标形式表示,此处参数去除
        val option = GridGroupOption(gridGroup?.maxXaxis ?: 120, gridGroup?.maxYaxis ?: 90, 10, 10)
        val resMap = mutableMapOf<Int, MutableList<GridDataDetail>>()
        // 循环计算每个网格的周边扩散网格结果
        gridDataDetailList.forEach { gdd ->
            SatelliteGridManage.heatMap(gdd, gridCellList, option, searchLength).forEach { r ->
                if (!originCellIdList.contains(r.cellId)) {
                    if (!resMap.containsKey(r.cellId)) {
                        resMap[r.cellId] = mutableListOf()
                    }
                    resMap[r.cellId]?.add(r)
                }
            }
        }
        // 将所有结果格式化,同时重叠网格进行监测数据均值计算
        val result = mutableListOf<GridDataDetail>()
        resMap.forEach { (_, v) ->
            result.add(v.avg().apply {
                this.dataId = v.first().dataId
                this.groupId = v.first().groupId
                this.cellId = v.first().cellId
            })
        }
        result.addAll(gridDataDetailList)
        return result
    }
}