feiyu02
2025-12-25 d649f734c44541641158aec2d6b10d630f5a0827
2025.12.19
1. 动态溯源相关分析逻辑调整;
2. 走航报告接口参数调整;
已修改13个文件
327 ■■■■■ 文件已修改
src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt 145 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/Test.kt 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt
@@ -3,7 +3,6 @@
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.SceneInfo
import com.flightfeather.uav.domain.entity.avg
import com.flightfeather.uav.socket.eunm.AggregatedFactorType
import com.flightfeather.uav.socket.eunm.FactorType
@@ -57,7 +56,7 @@
        pollutedClues.forEach { pollutedClue ->
            if (pollutedClue == null) return@forEach
            // 计算单个PollutedClue的均值经纬度
            val wgs84Center = pollutedClue.pollutedData?.getExceptionCenter() ?: return@forEach
            val wgs84Center = pollutedClue.pollutedData?.exceptionCenter() ?: return@forEach
            // 坐标转换
            val gcj02Point = MapUtil.wgs84ToGcj02(wgs84Center)
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
@@ -1,12 +1,8 @@
package com.flightfeather.uav.biz.sourcetrace.model
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
import com.flightfeather.uav.common.net.AMapService
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.domain.entity.avg
import kotlin.math.PI
/**
@@ -70,7 +66,7 @@
        windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
    ) {
        val avgData = pollutedData.getExceptionAvgData()
        val avgData = pollutedData.exceptionAvgData()
        val pair = avgData.longitude!!.toDouble() to avgData.latitude!!.toDouble()
        polygon = calSector(
            avgData.windDirection?.toDouble() ?: .0,
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
@@ -3,7 +3,6 @@
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
import com.flightfeather.uav.common.utils.DateUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.domain.entity.avg
@@ -31,8 +30,8 @@
        constructor(dataIndexList: List<Int>, factorType: FactorType){
            this.dataIndexList = dataIndexList
            this.factorType = factorType
            val first = getFirstDataValue()?.toDouble()
            val last = getLastDataValue()?.toDouble()
            val first = firstDataValue()?.toDouble()
            val last = lastDataValue()?.toDouble()
            if (first != null && last != null) {
                per = round((last - first) / first * 100) / 100
                rate = round((last - first) / DEFAULT_PERIOD * 100) / 100
@@ -52,26 +51,26 @@
         * 获取异常数据的第一个数据
         * !!!!第一个数据其实是首个异常数据的前一个数据值!!!!
         */
        fun getFirstData(): BaseRealTimeData? {
        fun firstData(): BaseRealTimeData? {
            return dataIndexList?.firstOrNull()?.let {
                val i = if (it > 0) it - 1 else it
                historyDataList[i].toBaseRealTimeData(BaseRealTimeData::class.java)
            }
        }
        fun getFirstDataValue(): Float? {
            return getFirstData()?.getByFactorType(factorType)
        fun firstDataValue(): Float? {
            return firstData()?.getByFactorType(factorType)
        }
        /**
         * 获取异常数据的最后一个数据
         */
        fun getLastData(): BaseRealTimeData? {
        fun lastData(): BaseRealTimeData? {
            return dataIndexList?.lastOrNull()?.let {
                historyDataList[it].toBaseRealTimeData(BaseRealTimeData::class.java)
            }
        }
        fun getLastDataValue(): Float? {
            return getLastData()?.getByFactorType(factorType)
        fun lastDataValue(): Float? {
            return lastData()?.getByFactorType(factorType)
        }
    }
@@ -105,7 +104,7 @@
        /**
         * 获取异常数据
         */
        fun getExceptionData(): List<BaseRealTimeData>? {
        fun exceptionData(): List<BaseRealTimeData>? {
            return dataIndexList?.map { historyDataList[it].toBaseRealTimeData(BaseRealTimeData::class.java) }
        }
@@ -113,7 +112,7 @@
         * 获取异常数据分段情况
         * 将连续的异常数据分为一组
         */
        fun getExceptionDataGroup(): List<List<Int>> {
        fun exceptionDataGroup(): List<List<Int>> {
            val res = mutableListOf<MutableList<Int>>()
            var curGroup = mutableListOf<Int>()
            var lastIndex = -2
@@ -202,7 +201,7 @@
                min = s.second
                max = s.third
                excGroup = getExceptionDataGroup().map { ExcGroup(it, e.first.main) }
                excGroup = exceptionDataGroup().map { ExcGroup(it, e.first.main) }
                avgPer = excGroup?.mapNotNull { it.per }?.average()
                avgRate = excGroup?.mapNotNull { it.rate }?.average()
            }
@@ -232,16 +231,16 @@
        return factors
    }
    fun getExceptionAvgData(): BaseRealTimeData {
        val exceptionDataList = statisticMap.flatMap { it.value.getExceptionData() ?: emptyList() }
    fun exceptionAvgData(): BaseRealTimeData {
        val exceptionDataList = statisticMap.flatMap { it.value.exceptionData() ?: emptyList() }
        val avgData = exceptionDataList.avg()
        return avgData
    }
    /**
     * 获取异常数据中心坐标(异常数据中经度纬度的平均值)
     */
    fun getExceptionCenter(): Pair<Double, Double>? {
        val avgData = getExceptionAvgData()
    fun exceptionCenter(): Pair<Double, Double>? {
        val avgData = exceptionAvgData()
        val wgs84Lng = avgData.longitude?.toDouble()
        val wgs84Lat = avgData.latitude?.toDouble()
        return if (wgs84Lng == null || wgs84Lat == null) null else Pair(wgs84Lng, wgs84Lat)
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
@@ -3,7 +3,6 @@
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
@@ -12,6 +11,7 @@
import com.flightfeather.uav.socket.eunm.FactorType
import org.springframework.beans.BeanUtils
import org.springframework.web.context.ContextLoader
import kotlin.math.round
/**
 * 污染来源
@@ -186,9 +186,9 @@
        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 preValue = p.firstDataValue()
                val curValue = p.lastDataValue()
                val per = round(p.per?.times(100) ?: .0)
                val rate = p.rate
                if (preValue == null || curValue == null || per == null) return@exception
                when (pollutedData.exceptionType) {
src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt
@@ -99,18 +99,26 @@
        return if (res.isEmpty()) null else res[0]
    }
    val stMsgCache = mutableMapOf<String, List<BaseExceptionResult?>>()
    fun fetchList(
        deviceCode: String,
        startTime: Date,
        endTime: Date,
        msgType: MsgType? = null,
        minPer: Double? = 0.5,
    ): List<BaseExceptionResult?> {
        var stMsgList = listOf<BaseExceptionResult?>()
        val key = "${deviceCode}_${startTime.time}_${endTime.time}_${msgType?.value}"
        if (stMsgCache.containsKey(key)) {
            stMsgList = stMsgCache[key]!!
        }
        if (stMsgList.isEmpty()) {
        var res = sourceTraceMsgBlobMapper.selectWithBlob(deviceCode, startTime, endTime)
        if (msgType !== null) {
            res = res.filter { it?.msgType == msgType.value }
        }
        return res.map { stm ->
            stMsgList = res.map { stm ->
            when (stm?.msgType) {
                MsgType.PolClue.value,
                MsgType.DataChange.value,
@@ -125,6 +133,20 @@
                else -> null
            }
        }
            stMsgCache[key] = stMsgList
        }
        // 筛选出异常数据PollutedClue中异常百分比大于minPer的
        return stMsgList.filter {
            if (it is PollutedClue) {
                var valid = false
                (it as PollutedClue?)?.pollutedData?.statisticMap?.entries?.forEach {sta->
                    if (!valid) valid = (sta.value.avgPer?:.0) > minPer!!
                }
                valid
            } else {
                true
            }
        }
    }
    @Transactional
src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt
@@ -44,4 +44,8 @@
    @ApiParam("需要统计的监测因子", example = "NO2, CO")
    var factorTypes: List<String>? = null
    /** 最小污染百分比,用于筛选异常数据点(可选) */
    @ApiModelProperty(value = "最小污染百分比,用于筛选异常数据点(可选)")
    var minPer: Double? = 0.5
}
src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt
@@ -39,9 +39,10 @@
     * 获取历史污染溯源结果
     * 根据任务编号查询并返回历史污染溯源结果
     * @param missionCode 走航任务编号
     * @param minPer 最小污染百分比,用于筛选异常数据点(可选)
     * @return 历史污染溯源结果的字符串表示(具体格式需参考实现类)
     */
    fun fetchHistory(missionCode: String): String
    fun fetchHistory(missionCode: String, minPer: Double?): String
    /**
     * 生成走航任务汇总统计
@@ -51,9 +52,14 @@
     * @param areaVo 区域参数,包含经纬度范围等地理信息
     * @return 汇总统计对象,包含任务总数、异常率、平均数据等指标
     */
    fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary
    fun generateMissionSummary(
        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean = false,
        removeNoPollutedSource: Boolean = false, minPer: Double? = 0.5,
    ): MissionSummary.Summary
    fun generateMissionSummary(missionCode: String): MissionSummary.Summary
    fun generateMissionSummary(
        missionCode: String, minPer: Double? = 0.5,
    ): MissionSummary.Summary
    /**
     * 生成走航任务清单(按时间和区域筛选)
@@ -63,7 +69,10 @@
     * @param areaVo 区域参数
     * @return 走航任务信息列表,每个元素包含任务基本信息和统计数据
     */
    fun generateMissionList(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionInfo>
    fun generateMissionList(
        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean = false,
        removeNoPollutedSource: Boolean = false, minPer: Double? = 0.5,
    ): List<MissionInventory.MissionInfo>
    /**
     * 生成走航任务清单(直接处理任务线索)
@@ -86,7 +95,12 @@
     * @param areaVo 区域参数
     * @return 任务详情列表,每个元素包含任务完整信息、场景数据和统计结果
     */
    fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo,granularity: String?): List<MissionDetail>
    fun generateMissionDetail(
        startTime: Date, endTime: Date, areaVo: AreaVo, granularity: String?,
        removeOtherDistrict: Boolean = false,
        removeNoPollutedSource: Boolean = false,
        minPer: Double? = 0.5,
    ): List<MissionDetail>
    /**
     * 生成走航任务详情(按任务编号筛选)
@@ -95,7 +109,9 @@
     * @param granularity 数据颗粒度,可选值为SECOND, MINUTE, HOUR, 默认MINUTE
     * @return 任务详情对象,包含任务完整信息、场景数据和统计结果
     */
    fun generateMissionDetail(missionCode: String, granularity: String?): MissionDetail
    fun generateMissionDetail(
        missionCode: String, granularity: String?, minPer: Double? = 0.5,
    ): MissionDetail
    /**
     * 获取走航任务详情(直接处理任务数据)
@@ -115,16 +131,24 @@
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        removeOtherDistrict: Boolean = false,
        removeNoPollutedSource: Boolean = false,
        minPer: Double? = 0.5,
    ): List<MissionRiskArea.ClassifyClue>
    fun generateClueByRiskArea(missionCode: String): List<MissionRiskArea.ClueByArea>
    fun generateClueByRiskArea(missionCode: String, minPer: Double? = 0.5): List<MissionRiskArea.ClueByArea>
    fun generateClueByRiskArea(keyScenes: List<SceneInfo?>, pollutedClues: List<PollutedClue?>): List<MissionRiskArea.ClueByArea>
    fun generateGridFusion(factorTypes: List<FactorType>, startTime: Date, endTime: Date, areaVo: AreaVo):
            List<MissionGridFusion.GridFusionByAQI>
    fun generateGridFusion(
        factorTypes: List<FactorType>,
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
        removeOtherDistrict: Boolean = false,
        removeNoPollutedSource: Boolean = false,
        minPer: Double? = 0.5,
    ): List<MissionGridFusion.GridFusionByAQI>
    fun generateGridFusion(
        factorTypes: List<FactorType>,
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
@@ -77,13 +77,14 @@
     * 获取历史污染溯源结果
     * 查询指定任务的历史污染溯源结果并序列化为JSON字符串
     * @param missionCode 走航任务编码
     * @param minPer 最小污染百分比,用于筛选异常数据点(可选)
     * @return 历史污染溯源结果的JSON字符串,具体格式由sourceTraceRep实现决定
     * @throws BizException 当走航任务不存在时抛出
     */
    override fun fetchHistory(missionCode: String): String {
    override fun fetchHistory(missionCode: String, minPer: Double?): String {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime)
        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, minPer = minPer ?: 0.5)
        return GsonUtils.gson.toJson(res)
    }
@@ -94,26 +95,33 @@
     * @param endTime 统计结束时间(包含)
     * @param areaVo 区域参数,包含省、市、区三级行政区划编码
     * @return 汇总统计对象,包含任务总数、异常点数量、平均里程等核心指标
     * @see MissionSummary 汇总统计处理器,封装具体的统计逻辑实现
     * @see MissionSummary 汇总统计处理器,封装具体统计逻辑的实现
     */
    override fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
    override fun generateMissionSummary(
        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean, minPer: Double?,
    ): MissionSummary.Summary {
        val clues = mutableListOf<PollutedClue?>()
        val missions = missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
            it ?: return@onEach
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue, minPer ?: 0.5) as List<PollutedClue?>
            clues.addAll(clue)
        }
        filterClue(areaVo, clues, removeOtherDistrict, removeNoPollutedSource)
        val summary = MissionSummary().execute(startTime, endTime, missions, clues)
        return summary
    }
    override fun generateMissionSummary(missionCode: String): MissionSummary.Summary {
    override fun generateMissionSummary(
        missionCode: String, minPer: Double?,
    ): MissionSummary.Summary {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
        val clues = sourceTraceRep.fetchList(
            mission.deviceCode,
            mission.startTime,
            mission.endTime,
            MsgType.PolClue
            MsgType.PolClue,
            minPer ?: 0.5,
        ) as List<PollutedClue?>
        val summary = MissionSummary().execute(mission.startTime, mission.endTime, listOf(mission), clues)
        return summary
@@ -129,17 +137,20 @@
     * @see MissionRep.findByAreaAndTime 区域时间筛选数据源
     * @see generateMissionList 重载方法,处理已关联的数据对
     */
    override fun generateMissionList(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionInfo> {
//        val missionClues = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
//            it to sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
//        }
//        return generateMissionList(missionClues)
    override fun generateMissionList(
        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean, minPer: Double?,
    ): List<MissionInventory.MissionInfo> {
        val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
            Triple(
                it,
                sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
                realTimeDataRep.fetchData(it)
            )
            val clues = sourceTraceRep.fetchList(
                it.deviceCode,
                it.startTime,
                it.endTime,
                MsgType.PolClue,
                minPer ?: 0.5
            ) as List<PollutedClue?>
            filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
            Triple(it, clues, realTimeDataRep.fetchData(it))
        }
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
@@ -187,13 +198,20 @@
        endTime: Date,
        areaVo: AreaVo,
        granularity: String?,
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        minPer: Double?,
    ): List<MissionInventory.MissionDetail> {
        val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
            Triple(
                it,
                sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
                realTimeDataRep.fetchData(it)
            )
            val clues = sourceTraceRep.fetchList(
                it.deviceCode,
                it.startTime,
                it.endTime,
                MsgType.PolClue,
                minPer ?: 0.5
            ) as List<PollutedClue?>
            filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
            Triple(it, clues, realTimeDataRep.fetchData(it))
        }
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
@@ -205,13 +223,18 @@
        return generateMissionDetail(keyScenes, missionCluesData, granularity ?: "MINUTE")
    }
    override fun generateMissionDetail(missionCode: String, granularity: String?): MissionInventory.MissionDetail {
    override fun generateMissionDetail(
        missionCode: String,
        granularity: String?,
        minPer: Double?,
    ): MissionInventory.MissionDetail {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val missionClues = sourceTraceRep.fetchList(
            mission.deviceCode,
            mission.startTime,
            mission.endTime,
            MsgType.PolClue
            MsgType.PolClue,
            minPer ?: 0.5
        ) as List<PollutedClue?>
        val realTimeData = realTimeDataRep.fetchData(mission)
        val keyScenes = sceneInfoRep.findBySceneTypes(
@@ -248,33 +271,42 @@
        areaVo: AreaVo,
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        minPer: Double?,
    ): List<MissionRiskArea.ClassifyClue> {
        val clues = mutableListOf<PollutedClue?>()
        missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
            it ?: return@onEach
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
            val clue = sourceTraceRep.fetchList(
                it.deviceCode,
                it.startTime,
                it.endTime,
                MsgType.PolClue,
                minPer ?: 0.5
            ) as List<PollutedClue?>
            clues.addAll(clue)
        }
        if (removeOtherDistrict) {
            clues.removeIf {
                !areaVo.districtName.isNullOrBlank() &&
                        (it?.pollutedArea?.address.isNullOrBlank()
                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
            }
        }
        if (removeNoPollutedSource) {
            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
        }
//        if (removeOtherDistrict) {
//            clues.removeIf {
//                !areaVo.districtName.isNullOrBlank() &&
//                        (it?.pollutedArea?.address.isNullOrBlank()
//                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
//            }
//        }
//        if (removeNoPollutedSource) {
//            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
//        }
        filterClue(areaVo, clues, removeOtherDistrict, removeNoPollutedSource)
        return MissionRiskArea().generateClueByRiskArea(clues)
    }
    override fun generateClueByRiskArea(missionCode: String): List<MissionRiskArea.ClueByArea> {
    override fun generateClueByRiskArea(missionCode: String, minPer: Double?): List<MissionRiskArea.ClueByArea> {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val pollutedClues = sourceTraceRep.fetchList(
            mission.deviceCode,
            mission.startTime,
            mission.endTime,
            MsgType.PolClue
            MsgType.PolClue,
            minPer ?: 0.5
        ) as List<PollutedClue?>
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
@@ -298,6 +330,9 @@
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
        removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
        minPer: Double?,
    ): List<MissionGridFusion.GridFusionByAQI> {
        val gridLen = 100
        // 查询100米网格的具体网格数据
@@ -328,11 +363,15 @@
            val gridDataDetailMixVos = satelliteDataCalculateService.mixUnderwayGridData(gridGroup.id, gridDataIds)
            // 统计每个走航任务的走航详情信息
            val missionCluesData = validMissions.filterNotNull().map {
                Triple(
                    it,
                    sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
                    realTimeDataRep.fetchData(it)
                )
                val clues = sourceTraceRep.fetchList(
                    it.deviceCode,
                    it.startTime,
                    it.endTime,
                    MsgType.PolClue,
                    minPer ?: 0.5
                ) as List<PollutedClue?>
                filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
                Triple(it, clues, realTimeDataRep.fetchData(it))
            }
            val keyScenes = sceneInfoRep.findBySceneTypes(
                listOf(
@@ -357,4 +396,26 @@
    ): List<MissionGridFusion.GridFusionByAQI> {
        return MissionGridFusion(sceneInfoRep).generateGridFusion(factorTypes, gridLen, gridCells, dataList)
    }
    private fun filterClue(
        areaVo: AreaVo, clues: MutableList<PollutedClue?>, removeOtherDistrict: Boolean,
        removeNoPollutedSource: Boolean,
    ) {
        if (removeOtherDistrict) {
            clues.removeIf {
                !areaVo.districtName.isNullOrBlank() &&
                        (it?.pollutedArea?.address.isNullOrBlank()
                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
            }
            clues.forEach {
                it?.pollutedSource?.sceneList = it?.pollutedSource?.sceneList?.filter { s->
                    s.districtCode == areaVo.districtCode
                }
            }
        }
        if (removeNoPollutedSource) {
            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt
@@ -1,17 +1,13 @@
package com.flightfeather.uav.lightshare.web
import com.fasterxml.jackson.annotation.JsonFormat
import com.flightfeather.uav.common.exception.BizException
import com.flightfeather.uav.lightshare.bean.AnalysisOption
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.service.DataAnalysisService
import com.flightfeather.uav.socket.eunm.FactorType
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.ApiParam
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.web.bind.annotation.*
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
@@ -36,7 +32,8 @@
    @GetMapping("/pollution/trace/history")
    fun fetchHistory(
        @ApiParam("走航任务编号") @RequestParam missionCode: String,
    ) = resPack { dataAnalysisService.fetchHistory(missionCode) }
        @ApiParam("最小污染百分比,用于筛选异常数据点(可选)") @RequestParam(required = false) minPer: Double? = null,
    ) = resPack { dataAnalysisService.fetchHistory(missionCode, minPer) }
    @ApiOperation(value = "生成走航任务汇总统计")
    @PostMapping("/report/missionSummary")
@@ -52,7 +49,10 @@
        dataAnalysisService.generateMissionSummary(
            Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
            Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
            areaVo
            areaVo,
            analysisOption.removeOtherDistrict,
            analysisOption.removeNoPollutedSource,
            analysisOption.minPer,
        )
    }
@@ -60,7 +60,8 @@
    @GetMapping("/report/missionSummary/one")
    fun generateOneMissionSummary(
        @ApiParam("任务编号") @RequestParam missionCode: String,
    ) = resPack { dataAnalysisService.generateMissionSummary(missionCode) }
        @ApiParam("最小污染百分比,用于筛选异常数据点(可选)") @RequestParam(required = false) minPer: Double? = null,
    ) = resPack { dataAnalysisService.generateMissionSummary(missionCode, minPer) }
    @ApiOperation(value = "生成走航任务清单")
    @PostMapping("/report/missionList")
@@ -76,7 +77,10 @@
        dataAnalysisService.generateMissionList(
            Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
            Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
            areaVo
            areaVo,
            analysisOption.removeOtherDistrict,
            analysisOption.removeNoPollutedSource,
            analysisOption.minPer,
        )
    }
@@ -95,7 +99,10 @@
        dataAnalysisService.generateMissionDetail(
            Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
            Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
            areaVo,granularity
            areaVo,granularity,
            analysisOption.removeOtherDistrict,
            analysisOption.removeNoPollutedSource,
            analysisOption.minPer,
        )
    }
@@ -105,7 +112,8 @@
        @ApiParam("任务编号") @RequestParam missionCode: String,
        @ApiParam("数据颗粒度", allowableValues = "SECOND, MINUTE, HOUR") @RequestParam(required = false)
        granularity: String?,
    ) = resPack { dataAnalysisService.generateMissionDetail(missionCode, granularity) }
        @ApiParam("最小污染百分比,用于筛选异常数据点(可选)") @RequestParam(required = false) minPer: Double? = null,
    ) = resPack { dataAnalysisService.generateMissionDetail(missionCode, granularity, minPer) }
    @ApiOperation(value = "走航典型隐患区域统计")
    @PostMapping("/report/clueByRiskArea")
@@ -122,7 +130,8 @@
            Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
            areaVo,
            analysisOption.removeOtherDistrict,
            analysisOption.removeNoPollutedSource
            analysisOption.removeNoPollutedSource,
            analysisOption.minPer,
        )
    }
@@ -130,7 +139,8 @@
    @GetMapping("/report/clueByRiskArea/one")
    fun generateOneClueByRiskArea(
        @ApiParam("任务编号") @RequestParam missionCode: String,
    ) = resPack { dataAnalysisService.generateClueByRiskArea(missionCode) }
        @ApiParam("最小污染百分比,用于筛选异常数据点(可选)") @RequestParam(required = false) minPer: Double? = null,
    ) = resPack { dataAnalysisService.generateClueByRiskArea(missionCode, minPer) }
    @ApiOperation(value = "叠加融合分析")
    @PostMapping("/report/gridFusion")
@@ -148,7 +158,10 @@
            factorTypes,
            Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
            Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
            areaVo
            areaVo,
            analysisOption.removeOtherDistrict,
            analysisOption.removeNoPollutedSource,
            analysisOption.minPer,
        )
    }
}
src/test/kotlin/com/flightfeather/uav/Test.kt
@@ -16,6 +16,7 @@
import java.io.OutputStreamWriter
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.round
/**
 * @author riku
@@ -202,8 +203,8 @@
    @Test
    fun reGeo() {
        val a = AMapService.reGeo(MapUtil.wgs84ToGcj02(121.45017 to 31.274426))
        println(a)
        val a = 0.7000000000000001
        println(round(a * 100))
    }
    @Test
src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt
@@ -39,8 +39,10 @@
    @Test
    fun autoSourceTrace() {
        val sourceTraceController = SourceTraceController(sceneInfoRep, sourceTraceRep, true)
        val startTime = LocalDateTime.of(2025, 12, 11, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
        val endTime = LocalDateTime.of(2025, 12, 11, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
        val startTime = LocalDateTime.of(2025, 11, 19, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
//        val endTime = LocalDateTime.of(2025, 11, 19, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
//        val startTime = LocalDateTime.of(2025, 11, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
        val endTime = LocalDateTime.of(2025, 12, 31, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
        val missions = missionMapper.selectByExample(Example(Mission::class.java).apply {
            createCriteria().andBetween("startTime", startTime, endTime)
        })
@@ -65,8 +67,10 @@
    @Test
    fun deleteSourceTrace() {
        val startTime = LocalDateTime.of(2025, 12, 11, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
        val endTime = LocalDateTime.of(2025, 12, 11, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
        val startTime = LocalDateTime.of(2025, 11, 19, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
//        val endTime = LocalDateTime.of(2025, 11, 19, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
//        val startTime = LocalDateTime.of(2025, 11, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
        val endTime = LocalDateTime.of(2025, 12, 31, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
        val missions = missionMapper.selectByExample(Example(Mission::class.java).apply {
            createCriteria().andBetween("startTime", startTime, endTime)
        })
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt
@@ -34,7 +34,7 @@
    @Test
    fun fetchHistory() {
        dataAnalysisService.fetchHistory("SH-CN-20250723(01)")
        dataAnalysisService.fetchHistory("SH-CN-20250723(01)", .0)
    }
    @Test
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
@@ -34,7 +34,7 @@
    @Test
    fun calMissionInfo() {
        missionMapper.selectByExample(Example(Mission::class.java).apply {
            createCriteria().andBetween("startTime", "2025-12-05 00:00:00", "2025-12-31 23:59:59")
            createCriteria().andBetween("startTime", "2025-12-11 00:00:00", "2025-12-11 23:59:59")
        }).forEach {mission ->
            mission?.let { missionService.calMissionInfo(it.missionCode) }
            Thread.sleep(1000)