feiyu02
2025-01-03 0ddfab15b32dc054464d75c695999fa76c3b9b78
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/SatelliteTelemetryServiceImpl.kt
@@ -1,5 +1,9 @@
package com.flightfeather.uav.lightshare.service.impl
import com.flightfeather.uav.biz.satellite.SatelliteDataMix
import com.flightfeather.uav.common.exception.BizException
import com.flightfeather.uav.common.utils.FileExchange
import com.flightfeather.uav.domain.entity.GridAod
import com.flightfeather.uav.domain.entity.GridCell
import com.flightfeather.uav.domain.entity.GridData
import com.flightfeather.uav.domain.entity.GridDataDetail
@@ -7,10 +11,19 @@
import com.flightfeather.uav.domain.repository.SatelliteGridRep
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.bean.DataHead
import com.flightfeather.uav.lightshare.bean.GridDataImportResult
import com.flightfeather.uav.lightshare.service.SatelliteTelemetryService
import com.github.pagehelper.PageHelper
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import java.io.ByteArrayInputStream
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
import javax.servlet.http.HttpServletResponse
import kotlin.math.round
/**
 *
@@ -18,8 +31,12 @@
 * @author feiyu02
 */
@Service
class SatelliteTelemetryServiceImpl(private val satelliteGridRep: SatelliteGridRep) : SatelliteTelemetryService {
class SatelliteTelemetryServiceImpl(
    private val satelliteGridRep: SatelliteGridRep,
    private val satelliteDataMix: SatelliteDataMix,
) : SatelliteTelemetryService {
    private val fileExchange = FileExchange()
    override fun fetchGridGroup(areaVo: AreaVo, page: Int?, perPage: Int?): Pair<DataHead, List<GridGroup?>> {
        val pageInfo = PageHelper.startPage<GridGroup>(page ?: 1, perPage ?: 100)
        val res = satelliteGridRep.fetchGridGroup(areaVo)
@@ -34,7 +51,157 @@
        return satelliteGridRep.fetchGridData(groupId, dataTime, type)
    }
    override fun fetchGridAod(groupId: Int, dataTime: LocalDateTime?): List<GridAod?> {
        return satelliteGridRep.fetchGridAod(groupId, dataTime)
    }
    override fun fetchGridDataDetail(dataId: Int, groupId: Int?, cellId: Int?): List<GridDataDetail?> {
        return satelliteGridRep.fetchGridDataDetail(dataId, groupId, cellId)
        val res =  satelliteGridRep.fetchGridDataDetail(dataId, groupId, cellId)
        res.forEach {
            if (it?.pm25 != null) {
                it.pm25 = round(it.pm25 * 100) / 100
            }
        }
        return res
    }
    @Transactional
    override fun mixGridData(dataIdList: List<Int>): List<GridData?> {
        if (dataIdList.isEmpty()) throw BizException("融合所需数据id不能为空")
        // 1. 根据数据主键id数组,查询该组合下是否已有数据融合记录
        val exist = satelliteGridRep.fetchGridData(GridData().apply {
            type = 1
            mixDataId = dataIdList.joinToString(",")
        })
        // 2. 若融合数据已存在,直接返回
        return exist.ifEmpty {
            listOf(satelliteDataMix.mixData(dataIdList).first)
        }
    }
    override fun importGridData(groupId: Int, dataTime: LocalDateTime?, update: Boolean, file: MultipartFile): GridDataImportResult? {
        // 因为是导入卫星遥测数据 type始终为0
        val type = 0
        // 首先判断文件类型,文件类型不是xlsx直接报错
        if (!file.contentType!!.startsWith("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
            throw BizException("文件类型错误,请上传xlsx类型文件")
        }
        // 校验 用户更新指令 和 数据库中网格数据存在与否 是否匹配,不匹配抛出错误
        // 这个过程需要查询数据库中网格组数据
        val gridData = satelliteGridRep.fetchGridData(groupId, dataTime, type)
        // 将用户导入的文件转换为数据
        // 转换的过程需要网格单元格数据 以判断网格单元格是否在正确的范围内
        val gridCellsInDBSet = satelliteGridRep.fetchGridCell(groupId).asSequence().map { it?.id ?: -1 }.toSet()
        val importData
                = fileExchange.exchangeGridData(ByteArrayInputStream(file.bytes), gridCellsInDBSet)
        if (update) {
            // 用户需要执行更新
            if (gridData.isEmpty()) {
                throw BizException("指令错误,数据库对应网格组和日期下不存在遥测数据,请执行插入操作")
            }
            importData.forEach {
                it.dataId = gridData[0]?.id
                it.groupId = gridData[0]?.groupId
            }
            satelliteGridRep.updatePM25Batch(importData)
            return GridDataImportResult(true, "覆盖成功")
        }else {
            // 用户需要执行插入
            if (gridData.isNotEmpty()) {
                throw BizException("指令错误,数据库对应网格组和日期下已存在遥测数据,请执行更新操作")
            }
            val gridDataEntity = GridData()
            gridDataEntity.groupId = groupId
            gridDataEntity.dataTime = dataTime?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
                ?.let { Date(it) }
            gridDataEntity.type = type.toByte()
            satelliteGridRep.insertGridDataAndDetail(gridDataEntity, importData)
            return GridDataImportResult(true, "导入成功")
        }
    }
    override fun downloadTemplate(response: HttpServletResponse): Boolean {
        val fileName = "GridData-PM2.5-Template.xlsx"
        val path = (Thread.currentThread().contextClassLoader?.getResource("/")?.path
            ?: "src/main/resources") + "/templates/" + fileName
        val file = File(path)
        if (file.exists()) {
            val fName = Base64.getEncoder().encodeToString(fileName.toByteArray())
            response.apply {
                setHeader("Content-Disposition", "attachment;filename=$fName")
                setHeader("fileName", fName)
                addHeader("Access-Control-Expose-Headers", "fileName")
                contentType = "application/vnd.ms-excel;charset=UTF-8"
                setHeader("Pragma", "no-cache")
                setHeader("Cache-Control", "no-cache")
                setDateHeader("Expires", 0)
            }
            response.outputStream.write(file.readBytes())
        }
        return true
    }
    override fun importGridAOD(
        groupId: Int,
        dataTime: LocalDateTime?,
        update: Boolean,
        file: MultipartFile
    ): GridDataImportResult? {
        // 首先判断文件类型,文件类型不是xlsx直接报错
        if (!file.contentType!!.startsWith("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
            throw BizException("文件类型错误,请上传xlsx类型文件")
        }
        // 校验 用户更新指令 和 数据库中网格数据存在与否 是否匹配,不匹配抛出错误
        // 这个过程需要查询数据库中网格组数据
        val gridAod = satelliteGridRep.fetchGridAod(groupId, dataTime)
        // 将用户导入的文件转换为数据
        // 转换的过程需要网格单元格数据 以判断网格单元格是否在正确的范围内
        val gridCellsInDBSet = satelliteGridRep.fetchGridCell(groupId).asSequence().map { it?.id ?: -1 }.toSet()
        val importAod
                = fileExchange.exchangeGridAod(ByteArrayInputStream(file.bytes), gridCellsInDBSet)
        if (update) {
            // 用户需要执行更新
            if (gridAod.isEmpty()) {
                throw BizException("指令错误,数据库对应网格组和日期下不存在AOD数据,请执行插入操作")
            }
            importAod.forEach {
                it.aodId = gridAod[0]?.id
                it.groupId = gridAod[0]?.groupId
            }
            satelliteGridRep.updateGridAodBatch(importAod)
            return GridDataImportResult(true, "覆盖成功")
        }else {
            // 用户需要执行插入
            if (gridAod.isNotEmpty()) {
                throw BizException("指令错误,数据库对应网格组和日期下已存在AOD数据,请执行更新操作")
            }
            val gridAodEntity = GridAod()
            gridAodEntity.groupId = groupId
            gridAodEntity.dataTime = dataTime?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
                ?.let { Date(it) }
            satelliteGridRep.insertGridAodAndDetail(gridAodEntity, importAod)
            return GridDataImportResult(true, "导入成功")
        }
    }
    override fun downloadAODTemplate(response: HttpServletResponse): Boolean {
        val fileName = "GridData-AOD-Template.xlsx"
        val path = (Thread.currentThread().contextClassLoader?.getResource("/")?.path
            ?: "src/main/resources") + "/templates/" + fileName
        val file = File(path)
        if (file.exists()) {
            val fName = Base64.getEncoder().encodeToString(fileName.toByteArray())
            response.apply {
                setHeader("Content-Disposition", "attachment;filename=$fName")
                setHeader("fileName", fName)
                addHeader("Access-Control-Expose-Headers", "fileName")
                contentType = "application/vnd.ms-excel;charset=UTF-8"
                setHeader("Pragma", "no-cache")
                setHeader("Cache-Control", "no-cache")
                setDateHeader("Expires", 0)
            }
            response.outputStream.write(file.readBytes())
        }
        return true
    }
}