feiyu02
2025-09-01 29383149f7040d89ae00ad48dc48bbcf46587946
2025.9.1
1. 新增走航任务统计功能(待完成)
已修改18个文件
已删除1个文件
已添加2个文件
733 ■■■■ 文件已修改
src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt 105 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/entity/ExpandFun.kt 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/entity/Mission.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/repository/SceneInfoRep.kt 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/bean/DataVo.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/bean/FactorStatistics.kt 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/eunm/SceneType.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt 160 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/socket/eunm/AggregatedFactorType.kt 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/socket/eunm/FactorType.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/generator/generatorConfig.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/MissionMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/biz/report/MissionSummaryTest.kt 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt
ÎļþÒÑɾ³ý
src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt
@@ -1,11 +1,12 @@
package com.flightfeather.uav.biz.report
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.entity.SceneInfo
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.*
import com.flightfeather.uav.lightshare.bean.FactorStatistics
import com.flightfeather.uav.socket.eunm.FactorType
import com.flightfeather.uav.socket.sender.MsgType
import org.springframework.beans.BeanUtils
/**
 * èµ°èˆªæº¯æºæ¸…单
@@ -31,37 +32,105 @@
    class MissionDetail : Mission() {
        var keyScene: List<SceneInfo>? = null
        var dataStatistics: List<FactorStatistics>? = null
        var exceptionCount: Int = 0
    }
    /**
     * è¾“出走航清单
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ¸…å•
     * å¤„理走航任务与污染线索数据,统计每个任务的异常因子、首要污染物和场景数量
     * @param missionClues åŒ…含走航任务和对应污染线索的Pair列表
     * @return åŒ…含统计信息的MissionInfo列表,每个元素包含任务基本信息及统计数据
     */
    fun missionList(missionClues: List<Pair<Mission?, List<PollutedClue?>>>): List<MissionInfo> {
        val missionMap = mutableMapOf<String, MissionInfo>()
        missionClues.forEach { (mission, clue) ->
            mission ?: return@forEach
            clue ?: return@forEach
            val missionInfo = missionMap[mission.missionCode] ?: MissionInfo().apply {
                missionMap[mission.missionCode] = this
            }
    fun generateMissionList(missionClues: List<Pair<Mission, List<PollutedClue?>>>): List<MissionInfo> {
        val result = missionClues.map { (mission, clue) ->
            val factorMap = mutableMapOf<FactorType, Int>()
            val abnormalFactors = mutableListOf<FactorType>()
            var sceneCount = 0
            clue.forEach {
                if (it?.msgType == MsgType.PolClue.value) {
                    it.pollutedData?.statisticMap?.keys?.forEach { k->
                        // è®¡ç®—每个走航任务的所有异常因子
                        if (!abnormalFactors.contains(k)) {
                            abnormalFactors.add(k)
                        }
                        // è®¡ç®—每个走航任务的首要污染物
                        if (!factorMap.containsKey(k)) {
                            factorMap[k] = 0
                        }
                        factorMap[k] = factorMap[k]!! + 1
                    }
                    // è®¡ç®—每个走航任务的溯源场景数量
                    sceneCount += it.pollutedSource?.sceneList?.size ?: 0
                }
            }
            // è®¡ç®—每个走航任务的所有异常因子
            // è®¡ç®—每个走航任务的首要污染物
            // è®¡ç®—每个走航任务的溯源场景数量
            val missionInfo = MissionInfo()
            BeanUtils.copyProperties(mission, missionInfo)
            missionInfo.apply {
                mainFactor = factorMap.maxByOrNull { it.value }?.key?.name
                this.abnormalFactors = abnormalFactors
                this.sceneCount = sceneCount
            }
        }
        return mutableListOf()
        return result
    }
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡è¯¦ç»†ä¿¡æ¯
     * æ•´åˆèµ°èˆªä»»åŠ¡åŸºæœ¬ä¿¡æ¯ã€å…³é”®åœºæ™¯ã€æ•°æ®ç»Ÿè®¡å’Œå¼‚å¸¸æ•°é‡ï¼Œç”Ÿæˆå®Œæ•´çš„ä»»åŠ¡è¯¦æƒ…æŠ¥å‘Š
     * @param keyScenes å…³é”®åœºæ™¯åˆ—表,用于分析走航是否经过该区域
     * @param mission èµ°èˆªä»»åŠ¡åŸºæœ¬ä¿¡æ¯å¯¹è±¡ï¼ŒåŒ…å«ä»»åŠ¡ID、名称、时间等元数据
     * @param pollutedClues æ±¡æŸ“线索列表,用于提取关键场景信息
     * @param data å®žæ—¶ç›‘测数据列表,用于计算环境因子统计信息
     * @param minDis æœ€å°è·ç¦»ï¼Œç”¨äºŽåˆ¤æ–­èµ°èˆªæ˜¯å¦ç»è¿‡å…³é”®åœºæ™¯
     * @return åŒ…含详细信息的MissionDetail对象,包括:
     *         - ä»»åŠ¡åŸºæœ¬ä¿¡æ¯ï¼ˆç»§æ‰¿è‡ªMission类)
     *         - å…³é”®åœºæ™¯åˆ—表(TYPE19和TYPE20类型的场景)
     *         - çŽ¯å¢ƒå› å­ç»Ÿè®¡æ•°æ®ï¼ˆå¹³å‡å€¼ã€æœ€å°å€¼ã€æœ€å¤§å€¼ï¼‰
     *         - å¼‚常数据点数量
     */
    fun generateMissionDetail(
        keyScenes: List<SceneInfo?>,
        mission: Mission,
        pollutedClues: List<PollutedClue?>,
        data: List<BaseRealTimeData>,
        minDis:Double = 100.0
    ): MissionDetail {
        // åˆ›å»ºä»»åŠ¡è¯¦æƒ…å¯¹è±¡å¹¶å¤åˆ¶åŸºæœ¬ä¿¡æ¯
        val missionDetail = MissionDetail()
        BeanUtils.copyProperties(mission, missionDetail)
        // æå–途径关键场景信息(计算走航路线是否与关键场景距离较近)
        val relatedScenes = mutableListOf<SceneInfo>()
        data.forEach { d->
            // è·³è¿‡ç¼ºå°‘经纬度的数据点
            if (d.longitude == null || d.latitude == null) {
                return@forEach
            }
            // è½¬æ¢ä¸ºGCJ02坐标系
            val point = MapUtil.wgs84ToGcj02(d.longitude!!.toDouble() to d.latitude!!.toDouble())
            keyScenes.forEach ks@ { k->
                // è·³è¿‡ç¼ºå°‘经纬度的场景
                if (k?.longitude == null || k.latitude == null) {
                    return@ks
                }
                // è®¡ç®—距离
                val distance = MapUtil.getDistance(k.longitude!!.toDouble(), k.latitude!!.toDouble(), point.first, point.second)
                // æ£€æŸ¥æ˜¯å¦è·ç¦»å°äºŽé˜ˆå€¼ä¸”未添加过
                if (distance < minDis && !relatedScenes.contains(k)) {
                    relatedScenes.add(k)
                }
            }
        }
        // å­˜å‚¨ä¸Žä»»åŠ¡ç›¸å…³è”çš„å…³é”®åœºæ™¯ä¿¡æ¯
        missionDetail.keyScene = relatedScenes
        // è®¡ç®—环境因子统计数据(平均值、最小值、最大值)
        missionDetail.dataStatistics = data.calDataStatistics()
        // å¼‚常数据点数量统计
        val clues = pollutedClues.filter { it?.msgType == MsgType.PolClue.value }
        missionDetail.exceptionCount = clues.size
        return missionDetail
    }
}
src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.flightfeather.uav.biz.report
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
/**
 * èµ°èˆªå…¸åž‹é𐿂£åŒºåŸŸ
 * @date 2025/8/25 14:18
 * @author feiyu
 */
class MissionRiskArea {
    class ClueByArea {
        var sceneInfo: SceneInfo? = null
        var clueByFactorList: MutableList<ClueByFactor>? = null
    }
    class ClueByFactor {
        var factor: String? = null
        var clues: MutableList<PollutedClue>? = null
    }
    fun generateClueByRiskArea(keyScenes: List<SceneInfo?>, pollutedClues: List<PollutedClue?>): List<ClueByArea> {
        val result = mutableListOf<ClueByArea>()
        pollutedClues.forEach { pollutedClue ->
            if (pollutedClue == null) return@forEach
            val dataList = pollutedClue.pollutedData?.dataList ?: emptyList()
            if (dataList.isEmpty()) return@forEach
            // è®¡ç®—单个PollutedClue的均值经纬度
            val avgData = dataList.avg()
            val wgs84Lng = avgData.longitude?.toDouble() ?: return@forEach
            val wgs84Lat = avgData.latitude?.toDouble() ?: return@forEach
            // åæ ‡è½¬æ¢
            val gcj02Point = MapUtil.wgs84ToGcj02(wgs84Lng to wgs84Lat)
            // æŸ¥æ‰¾æœ€è¿‘场景
            var minDistance = Double.MAX_VALUE
            var closestScene: SceneInfo? = null
            keyScenes.forEach { scene ->
                scene?.let { s ->
                    val sceneLng = s.longitude?.toDouble() ?: return@let
                    val sceneLat = s.latitude?.toDouble() ?: return@let
                    val distance = MapUtil.getDistance(gcj02Point.first, gcj02Point.second, sceneLng, sceneLat)
                    if (distance < minDistance) {
                        minDistance = distance
                        closestScene = s
                    }
                }
            }
            // æŒ‰åœºæ™¯å’Œå› å­åˆ†ç»„线索
            closestScene?.let { scene ->
                var clueByArea = result.find { it.sceneInfo?.guid == scene.guid }
                if (clueByArea == null) {
                    clueByArea = ClueByArea().apply {
                        this.sceneInfo = scene
                        this.clueByFactorList = mutableListOf()
                    }
                    result.add(clueByArea)
                }
                val firstFactorType = pollutedClue.pollutedData?.statisticMap?.keys?.first()
                val afType =  AggregatedFactorType.getAFType(firstFactorType)
                val factorName = afType?.des ?: firstFactorType?.des
                var clueByFactor = clueByArea.clueByFactorList?.find { it.factor == factorName }
                if (clueByFactor == null) {
                    clueByFactor = ClueByFactor().apply {
                        this.factor = factorName
                        this.clues = mutableListOf()
                    }
                    clueByArea.clueByFactorList?.add(clueByFactor)
                }
                clueByFactor.clues?.add(pollutedClue)
            }
        }
        return result
    }
}
src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt
@@ -3,6 +3,7 @@
import com.flightfeather.uav.biz.dataprocess.AvgPair
import com.flightfeather.uav.common.utils.DateUtil
import com.flightfeather.uav.lightshare.bean.DataVo
import com.flightfeather.uav.lightshare.bean.FactorStatistics
import com.flightfeather.uav.socket.bean.AirData
import com.flightfeather.uav.socket.eunm.FactorType
import java.io.Serializable
@@ -146,7 +147,7 @@
}
fun List<BaseRealTimeData>.avg(): BaseRealTimeData {
fun List<BaseRealTimeData>.avg(onEach: (BaseRealTimeData) -> Unit = {  }): BaseRealTimeData {
    if (isEmpty()) {
        return BaseRealTimeData()
    }
@@ -162,8 +163,9 @@
    }
    forEach {
        onEach(it)
        //风向
        it.windDirection?.let {w ->
        it.windDirection?.let { w ->
            val r = Math.toRadians(w.toDouble())
            u += sin(r)
            v += cos(r)
@@ -325,4 +327,67 @@
            windDirection = round(a.toFloat())
        }
    }
}
/**
 * è®¡ç®—实时监测数据列表的统计信息
 * ä¸ºæ¯ç§çŽ¯å¢ƒå› å­è®¡ç®—æœ€å°å€¼ã€æœ€å¤§å€¼å’Œå¹³å‡å€¼
 *
 * @return åŒ…含各环境因子统计信息的FactorStatistics列表
 *         æ¯ä¸ªFactorStatistics对象包含因子类型、最小值、最大值和平均值
 */
fun List<BaseRealTimeData>.calDataStatistics(): List<FactorStatistics> {
    // åˆå§‹åŒ–各环境因子的统计对象列表
    val statistics = mutableListOf<FactorStatistics>()
    listOf(
        FactorType.NO2,
        FactorType.CO,
        FactorType.H2S,
        FactorType.SO2,
        FactorType.O3,
        FactorType.PM25,
        FactorType.PM10,
        FactorType.VOC,
        FactorType.NOI,
        FactorType.VELOCITY,
        FactorType.WIND_SPEED,
        FactorType.HEIGHT,
        FactorType.NO
    ).forEach { statistics.add(FactorStatistics(it)) }
    // è®¡ç®—平均值并同时更新各因子的最小值和最大值
    val avgData = avg { item ->
        // æ›´æ–°æ¯ä¸ªå› å­çš„æœ€å°å’Œæœ€å¤§å€¼
        statistics[0].updateMinAndMaxValue(item.no2)
        statistics[1].updateMinAndMaxValue(item.co)
        statistics[2].updateMinAndMaxValue(item.h2s)
        statistics[3].updateMinAndMaxValue(item.so2)
        statistics[4].updateMinAndMaxValue(item.o3)
        statistics[5].updateMinAndMaxValue(item.pm25)
        statistics[6].updateMinAndMaxValue(item.pm10)
        statistics[7].updateMinAndMaxValue(item.voc)
        statistics[8].updateMinAndMaxValue(item.noi)
        statistics[9].updateMinAndMaxValue(item.velocity)
        statistics[10].updateMinAndMaxValue(item.windSpeed)
        statistics[11].updateMinAndMaxValue(item.height)
        statistics[12].updateMinAndMaxValue(item.no)
    }
    // å°†è®¡ç®—得到的平均值设置到对应的统计对象中
    statistics[0].avgValue = avgData.no2 ?: 0f
    statistics[1].avgValue = avgData.co ?: 0f
    statistics[2].avgValue = avgData.h2s ?: 0f
    statistics[3].avgValue = avgData.so2 ?: 0f
    statistics[4].avgValue = avgData.o3 ?: 0f
    statistics[5].avgValue = avgData.pm25 ?: 0f
    statistics[6].avgValue = avgData.pm10 ?: 0f
    statistics[7].avgValue = avgData.voc ?: 0f
    statistics[8].avgValue = avgData.noi ?: 0f
    statistics[9].avgValue = avgData.velocity ?: 0f
    statistics[10].avgValue = avgData.windSpeed ?: 0f
    statistics[11].avgValue = avgData.height ?: 0f
    statistics[12].avgValue = avgData.no ?: 0f
    return statistics
}
src/main/kotlin/com/flightfeather/uav/domain/entity/ExpandFun.kt
@@ -7,9 +7,6 @@
import com.flightfeather.uav.socket.bean.AirData
import com.flightfeather.uav.socket.eunm.FactorType
import com.flightfeather.uav.socket.eunm.FactorType.*
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
import kotlin.math.atan
import kotlin.math.cos
import kotlin.math.round
@@ -26,7 +23,7 @@
    list.add("纬度")
    val values = GsonUtils.parserJsonToArrayBeans(factors, AirData::class.java)
    values.forEach {
        if (FactorType.outputFactor(it.factorName)) {
        if (FactorType.isOutputFactor(it.factorName)) {
            val name = it.factorName ?: ""
            list.add(name)
//            list.add("$name(物理量)")
@@ -51,7 +48,7 @@
    }
    val values = GsonUtils.parserJsonToArrayBeans(factors, AirData::class.java)
    values.forEach {
        if (FactorType.outputFactor(it.factorName)) {
        if (FactorType.isOutputFactor(it.factorName)) {
            row.add(it.factorData ?: -1.0)
//            row.add(it.physicalQuantity ?: -1.0)
        }
src/main/kotlin/com/flightfeather/uav/domain/entity/Mission.java
@@ -63,6 +63,8 @@
    @Column(name = "pollution_degree")
    private String pollutionDegree;
    private Integer aqi;
    /**
     * @return mission_code
     */
@@ -312,4 +314,18 @@
    public void setPollutionDegree(String pollutionDegree) {
        this.pollutionDegree = pollutionDegree == null ? null : pollutionDegree.trim();
    }
    /**
     * @return aqi
     */
    public Integer getAqi() {
        return aqi;
    }
    /**
     * @param aqi
     */
    public void setAqi(Integer aqi) {
        this.aqi = aqi;
    }
}
src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt
@@ -2,6 +2,7 @@
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.mapper.MissionMapper
import com.flightfeather.uav.lightshare.bean.AreaVo
import org.springframework.stereotype.Repository
import tk.mybatis.mapper.entity.Example
import java.util.*
@@ -44,4 +45,25 @@
                .andIsNotNull("endTime")
        })
    }
    /**
     * æ ¹æ®åŒºåŸŸå’Œæ—¶é—´èŒƒå›´æŸ¥è¯¢æœ‰æ•ˆèµ°èˆªä»»åŠ¡åˆ—è¡¨
     * ç­›é€‰æŒ‡å®šè¡Œæ”¿åŒºåˆ’内、特定时间段且已完成有效里程记录的走航任务
     * @param areaVo åŒºåŸŸå‚数对象,包含省、市、区三级行政区划编码
     * @param startTime æŸ¥è¯¢èµ·å§‹æ—¶é—´ï¼ˆåŒ…含)
     * @param endTime æŸ¥è¯¢ç»“束时间(包含)
     * @return ç¬¦åˆæ¡ä»¶çš„走航任务列表,若无可返回空列表
     * @see AreaVo åŒºåŸŸå‚数数据结构
     * @see Mission èµ°èˆªä»»åŠ¡å®žä½“ç±»
     */
    fun findByAreaAndTime(areaVo: AreaVo, startTime: Date, endTime: Date): List<Mission?> {
        return missionMapper.selectByExample(Example(Mission::class.java).apply {
            createCriteria().andBetween("startTime", startTime, endTime)
                .andEqualTo("provinceCode", areaVo.provinceCode)
                .andEqualTo("cityCode", areaVo.cityCode)
                .andEqualTo("districtCode", areaVo.districtCode)
                .andIsNotNull("kilometres")
                .andNotEqualTo("kilometres", 0)
        })
    }
}
src/main/kotlin/com/flightfeather/uav/domain/repository/SceneInfoRep.kt
@@ -41,4 +41,13 @@
                .andLessThanOrEqualTo("latitude", range[3])
        })
    }
    fun findBySceneTypes(sceneTypes: List<Int>): List<SceneInfo?> {
        if (sceneTypes.isEmpty()){
            return emptyList()
        }
        return sceneInfoMapper.selectByExample(Example(SceneInfo::class.java).apply {
            createCriteria().andIn("typeId", sceneTypes)
        })
    }
}
src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt
@@ -99,9 +99,18 @@
        return if (res.isEmpty()) null else res[0]
    }
    fun fetchList(deviceCode: String, startTime: Date, endTime: Date): List<BaseExceptionResult?> {
        return sourceTraceMsgBlobMapper.selectWithBlob(deviceCode, startTime, endTime)
            .map { stm ->
    fun fetchList(
        deviceCode: String,
        startTime: Date,
        endTime: Date,
        msgType: MsgType? = null,
    ): List<BaseExceptionResult?> {
        var res = sourceTraceMsgBlobMapper.selectWithBlob(deviceCode, startTime, endTime)
        if (msgType !== null) {
            res = res.filter { it?.msgType == msgType.value }
        }
        return res.map { stm ->
            when (stm?.msgType) {
                MsgType.PolClue.value,
                MsgType.DataChange.value,
src/main/kotlin/com/flightfeather/uav/lightshare/bean/DataVo.kt
@@ -42,7 +42,7 @@
        row.add(lng ?: -1.0)
        row.add(lat ?: -1.0)
        values?.forEach {
            if (FactorType.outputFactor(it.factorName)) {
            if (FactorType.isOutputFactor(it.factorName)) {
                row.add(it.factorData ?: -1.0)
//                                row.add(it.physicalQuantity ?: -1.0)
            }
@@ -57,7 +57,7 @@
        list.add("经度")
        list.add("纬度")
        values?.forEach {
            if (FactorType.outputFactor(it.factorName)) {
            if (FactorType.isOutputFactor(it.factorName)) {
                val name = it.factorName ?: ""
                list.add(name)
//                        list.add("$name(物理量)")
src/main/kotlin/com/flightfeather/uav/lightshare/bean/FactorStatistics.kt
@@ -9,11 +9,24 @@
 */
data class FactorStatistics(
    // ç›‘测因子类型
    val factor: FactorType,
    var factor: FactorType,
    // æœ€å°å€¼
    val minValue: Double,
    var minValue: Float = Float.NaN,
    // æœ€å¤§å€¼
    val maxValue: Double,
    var maxValue: Float = Float.NaN,
    // å‡å€¼
    val avgValue: Double
)
    var avgValue: Float = Float.NaN,
){
    fun updateMinAndMaxValue(value: Float?){
        minValue = if (minValue.isNaN()) {
            value ?: Float.NaN
        } else {
            minValue.coerceAtMost(value ?: minValue)
        }
        maxValue = if (maxValue.isNaN()) {
            value ?: Float.NaN
        } else {
            maxValue.coerceAtLeast(value ?: maxValue)
        }
    }
}
src/main/kotlin/com/flightfeather/uav/lightshare/eunm/SceneType.kt
@@ -26,4 +26,5 @@
    TYPE18(18, "商业体"),
    TYPE19(19, "国控点"),
    TYPE20(20, "市控点"),
    TYPE21(21, "小微站"),
}
src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt
@@ -1,14 +1,21 @@
package com.flightfeather.uav.lightshare.service
import com.flightfeather.uav.biz.dataanalysis.BaseExceptionResult
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
import com.flightfeather.uav.biz.report.MissionInventory
import com.flightfeather.uav.biz.report.MissionInventory.MissionDetail
import com.flightfeather.uav.biz.report.MissionRiskArea
import com.flightfeather.uav.biz.report.MissionSummary
import com.flightfeather.uav.biz.sourcetrace.model.BasePollutedMsg
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.entity.SceneInfo
import com.flightfeather.uav.lightshare.bean.AreaVo
import java.util.*
/**
 *
 * æ•°æ®åˆ†æžæœåŠ¡æŽ¥å£
 * æä¾›èµ°èˆªä»»åŠ¡æ•°æ®çš„ç»Ÿè®¡åˆ†æžã€æ±¡æŸ“æº¯æºã€ä»»åŠ¡æ¸…å•ç”Ÿæˆç­‰æ ¸å¿ƒåŠŸèƒ½
 * ç”¨äºŽæ•´åˆå’Œå¤„理无人机监测数据,生成各类分析报告和统计结果
 * @date 2025/5/8
 * @author feiyu02
 */
@@ -16,11 +23,68 @@
    /**
     * æ±¡æŸ“溯源分析
     * @param missionCode èµ°èˆªä»»åŠ¡ç¼–å·
     * æ ¹æ®èµ°èˆªä»»åŠ¡ç¼–å·åˆ†æžæ±¡æŸ“çº¿ç´¢ï¼Œè¯†åˆ«å¼‚å¸¸æ•°æ®ç‚¹å’Œæ½œåœ¨æ±¡æŸ“æº
     * @param missionCode èµ°èˆªä»»åŠ¡ç¼–å·ï¼Œç”¨äºŽå”¯ä¸€æ ‡è¯†ç‰¹å®šçš„èµ°èˆªä»»åŠ¡
     * @return å¼‚常结果列表,每个元素包含异常类型、位置和详细信息
     */
    fun pollutionTrace(missionCode: String): List<ExceptionResult>
    /**
     * èŽ·å–åŽ†å²æ±¡æŸ“æº¯æºç»“æžœ
     * æ ¹æ®ä»»åŠ¡ç¼–å·æŸ¥è¯¢å¹¶è¿”å›žåŽ†å²æ±¡æŸ“æº¯æºç»“æžœ
     * @param missionCode èµ°èˆªä»»åŠ¡ç¼–å·
     * @return åŽ†å²æ±¡æŸ“æº¯æºç»“æžœçš„å­—ç¬¦ä¸²è¡¨ç¤ºï¼ˆå…·ä½“æ ¼å¼éœ€å‚è€ƒå®žçŽ°ç±»ï¼‰
     */
    fun fetchHistory(missionCode: String): String
    fun missionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ±‡æ€»ç»Ÿè®¡
     * æŒ‰æ—¶é—´èŒƒå›´å’ŒåŒºåŸŸç»Ÿè®¡èµ°èˆªä»»åŠ¡çš„å…³é”®æŒ‡æ ‡å’Œæ€»ä½“æƒ…å†µ
     * @param startTime å¼€å§‹æ—¶é—´ï¼Œç”¨äºŽç­›é€‰æŒ‡å®šæ—¶é—´æ®µå†…的任务
     * @param endTime ç»“束时间,用于筛选指定时间段内的任务
     * @param areaVo åŒºåŸŸå‚数,包含经纬度范围等地理信息
     * @return æ±‡æ€»ç»Ÿè®¡å¯¹è±¡ï¼ŒåŒ…含任务总数、异常率、平均数据等指标
     */
    fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ¸…å•ï¼ˆæŒ‰æ—¶é—´å’ŒåŒºåŸŸç­›é€‰ï¼‰
     * æ ¹æ®æ—¶é—´èŒƒå›´å’Œåœ°ç†åŒºåŸŸæŸ¥è¯¢èµ°èˆªä»»åŠ¡ï¼Œå¹¶ç”ŸæˆåŒ…å«ç»Ÿè®¡ä¿¡æ¯çš„ä»»åŠ¡åˆ—è¡¨
     * @param startTime å¼€å§‹æ—¶é—´
     * @param endTime ç»“束时间
     * @param areaVo åŒºåŸŸå‚æ•°
     * @return èµ°èˆªä»»åŠ¡ä¿¡æ¯åˆ—è¡¨ï¼Œæ¯ä¸ªå…ƒç´ åŒ…å«ä»»åŠ¡åŸºæœ¬ä¿¡æ¯å’Œç»Ÿè®¡æ•°æ®
     */
    fun generateMissionList(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionInfo>
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ¸…å•ï¼ˆç›´æŽ¥å¤„ç†ä»»åŠ¡çº¿ç´¢ï¼‰
     * å¤„理已有的走航任务和污染线索数据,生成任务清单
     * @param missionClues åŒ…含走航任务和对应污染线索的Pair列表
     * @return èµ°èˆªä»»åŠ¡ä¿¡æ¯åˆ—è¡¨
     */
    fun generateMissionList(missionClues: List<Pair<Mission, List<PollutedClue?>>>): List<MissionInventory.MissionInfo>
    /**
     * èŽ·å–èµ°èˆªä»»åŠ¡è¯¦æƒ…ï¼ˆæŒ‰æ—¶é—´å’ŒåŒºåŸŸç­›é€‰ï¼‰
     * æ ¹æ®æ—¶é—´èŒƒå›´å’ŒåŒºåŸŸæŸ¥è¯¢å¹¶ç”Ÿæˆè¯¦ç»†çš„任务报告,包含关键场景和数据统计
     * @param startTime å¼€å§‹æ—¶é—´
     * @param endTime ç»“束时间
     * @param areaVo åŒºåŸŸå‚æ•°
     * @return ä»»åŠ¡è¯¦æƒ…åˆ—è¡¨ï¼Œæ¯ä¸ªå…ƒç´ åŒ…å«ä»»åŠ¡å®Œæ•´ä¿¡æ¯ã€åœºæ™¯æ•°æ®å’Œç»Ÿè®¡ç»“æžœ
     */
    fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionDetail>
    /**
     * èŽ·å–èµ°èˆªä»»åŠ¡è¯¦æƒ…ï¼ˆç›´æŽ¥å¤„ç†ä»»åŠ¡æ•°æ®ï¼‰
     * å¤„理已有的任务、污染线索和实时数据,生成详细任务报告
     * @param keyScenes å…³é”®åœºæ™¯åˆ—表,用于分析走航是否经过该区域
     * @param missionCluesData åŒ…含任务、污染线索和实时数据的Triple列表
     * @return ä»»åŠ¡è¯¦æƒ…åˆ—è¡¨
     */
    fun generateMissionDetail(keyScenes: List<SceneInfo?>, missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>): List<MissionDetail>
    fun generateClueByRiskArea(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionRiskArea.ClueByArea>
    fun generateClueByRiskArea(keyScenes: List<SceneInfo?>, pollutedClues: List<PollutedClue?>): List<MissionRiskArea.ClueByArea>
}
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
@@ -1,32 +1,33 @@
package com.flightfeather.uav.lightshare.service.impl
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.BaseExceptionResult
import com.flightfeather.uav.biz.dataanalysis.ExceptionAnalysisController
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
import com.flightfeather.uav.biz.report.MissionInventory
import com.flightfeather.uav.biz.report.MissionRiskArea
import com.flightfeather.uav.biz.report.MissionSummary
import com.flightfeather.uav.biz.sourcetrace.model.BasePollutedMsg
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.common.exception.BizException
import com.flightfeather.uav.common.location.LocationRoadNearby
import com.flightfeather.uav.common.utils.GsonUtils
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.entity.SceneInfo
import com.flightfeather.uav.domain.mapper.MissionMapper
import com.flightfeather.uav.domain.repository.MissionRep
import com.flightfeather.uav.domain.repository.RealTimeDataRep
import com.flightfeather.uav.domain.repository.SegmentInfoRep
import com.flightfeather.uav.domain.repository.SourceTraceRep
import com.flightfeather.uav.domain.repository.*
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.eunm.SceneType
import com.flightfeather.uav.lightshare.service.DataAnalysisService
import com.flightfeather.uav.socket.eunm.FactorType
import com.flightfeather.uav.socket.sender.MsgType
import org.springframework.stereotype.Service
import tk.mybatis.mapper.entity.Example
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
/**
 *
 * æ•°æ®åˆ†æžæœåŠ¡æŽ¥å£å®žçŽ°ç±»
 * æä¾›èµ°èˆªä»»åŠ¡æ•°æ®çš„ç»Ÿè®¡åˆ†æžã€æ±¡æŸ“æº¯æºã€ä»»åŠ¡æ¸…å•åŠè¯¦æƒ…ç”Ÿæˆç­‰æ ¸å¿ƒä¸šåŠ¡åŠŸèƒ½
 * æ•´åˆå¤šæ•°æ®æºå®Œæˆæ•°æ®èšåˆä¸Žåˆ†æžï¼Œä¸ºå‰ç«¯æä¾›æ ‡å‡†åŒ–的统计结果
 * @date 2025/5/8
 * @author feiyu02
 */
@@ -37,9 +38,18 @@
    private val realTimeDataRep: RealTimeDataRep,
    private val locationRoadNearby: LocationRoadNearby,
    private val segmentInfoRep: SegmentInfoRep,
    private val sourceTraceRep: SourceTraceRep
    private val sourceTraceRep: SourceTraceRep,
    private val sceneInfoRep: SceneInfoRep
) : DataAnalysisService {
    /**
     * æ±¡æŸ“溯源分析
     * å¯¹æŒ‡å®šèµ°èˆªä»»åŠ¡è¿›è¡Œå¤šå› å­æ±¡æŸ“æ•°æ®åˆ†æžï¼Œè¯†åˆ«å¼‚å¸¸æ•°æ®ç‚¹å’Œæ½œåœ¨æ±¡æŸ“æº
     * @param missionCode èµ°èˆªä»»åŠ¡ç¼–ç ï¼ˆä¸»é”®ï¼‰
     * @return å¼‚常结果列表,包含异常类型、位置、浓度值等详细信息
     * @throws BizException å½“走航任务不存在时抛出
     * @see ExceptionAnalysisController å¼‚常分析控制器,处理具体的数据分析逻辑
     */
    override fun pollutionTrace(missionCode: String): List<ExceptionResult> {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
@@ -60,6 +70,13 @@
        )
    }
    /**
     * èŽ·å–åŽ†å²æ±¡æŸ“æº¯æºç»“æžœ
     * æŸ¥è¯¢æŒ‡å®šä»»åŠ¡çš„åŽ†å²æ±¡æŸ“æº¯æºç»“æžœå¹¶åºåˆ—åŒ–ä¸ºJSON字符串
     * @param missionCode èµ°èˆªä»»åŠ¡ç¼–ç 
     * @return åŽ†å²æ±¡æŸ“æº¯æºç»“æžœçš„JSON字符串,具体格式由sourceTraceRep实现决定
     * @throws BizException å½“走航任务不存在时抛出
     */
    override fun fetchHistory(missionCode: String): String {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在")
@@ -67,21 +84,124 @@
        return GsonUtils.gson.toJson(res)
    }
    override fun missionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ±‡æ€»ç»Ÿè®¡
     * æŒ‰æ—¶é—´èŒƒå›´å’Œè¡Œæ”¿åŒºåˆ’统计走航任务的关键指标(任务数量、异常率、平均里程等)
     * @param startTime ç»Ÿè®¡èµ·å§‹æ—¶é—´ï¼ˆåŒ…含)
     * @param endTime ç»Ÿè®¡ç»“束时间(包含)
     * @param areaVo åŒºåŸŸå‚数,包含省、市、区三级行政区划编码
     * @return æ±‡æ€»ç»Ÿè®¡å¯¹è±¡ï¼ŒåŒ…含任务总数、异常点数量、平均里程等核心指标
     * @see MissionSummary æ±‡æ€»ç»Ÿè®¡å¤„理器,封装具体的统计逻辑实现
     */
    override fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
        val clues = mutableListOf<PollutedClue?>()
        val missions = missionMapper.selectByExample(Example(Mission::class.java).apply {
            createCriteria().andBetween("startTime", startTime, endTime)
                .andEqualTo("provinceCode", areaVo.provinceCode)
                .andEqualTo("cityCode", areaVo.cityCode)
                .andEqualTo("districtCode", areaVo.districtCode)
                .andIsNotNull("kilometres")
                .andNotEqualTo("kilometres", 0)
        }).onEach {
        val missions = missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
            it ?: return@onEach
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime).filterIsInstance<PollutedClue?>()
            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
            clues.addAll(clue)
        }
        val summary = MissionSummary().execute(startTime, endTime, missions, clues)
        return summary
    }
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ¸…å•ï¼ˆæŒ‰æ—¶é—´å’ŒåŒºåŸŸç­›é€‰ï¼‰
     * æ ¹æ®æ—¶é—´èŒƒå›´å’Œè¡Œæ”¿åŒºåˆ’查询走航任务,并关联污染线索数据生成任务列表
     * @param startTime æŸ¥è¯¢èµ·å§‹æ—¶é—´ï¼ˆåŒ…含)
     * @param endTime æŸ¥è¯¢ç»“束时间(包含)
     * @param areaVo åŒºåŸŸå‚数,包含省、市、区编码
     * @return èµ°èˆªä»»åŠ¡ä¿¡æ¯åˆ—è¡¨ï¼Œæ¯ä¸ªå…ƒç´ åŒ…å«ä»»åŠ¡åŸºæœ¬ä¿¡æ¯å’Œå…³è”çš„æ±¡æŸ“çº¿ç´¢
     * @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)
    }
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡æ¸…å•ï¼ˆç›´æŽ¥å¤„ç†ä»»åŠ¡æ•°æ®ï¼‰
     * æŽ¥æ”¶å·²å…³è”的任务-污染线索数据对,生成格式化的任务列表
     * @param missionClues ä»»åŠ¡-污染线索数据对列表,Pair.first为任务对象,Pair.second为对应污染线索
     * @return æ ‡å‡†åŒ–的走航任务信息列表,包含任务基本属性和污染统计信息
     * @see MissionInventory ä»»åŠ¡æ¸…å•ç”Ÿæˆå™¨ï¼Œå°è£…å…·ä½“æ ¼å¼åŒ–é€»è¾‘
     */
    override fun generateMissionList(missionClues: List<Pair<Mission, List<PollutedClue?>>>): List<MissionInventory.MissionInfo> {
        return MissionInventory().generateMissionList(missionClues)
    }
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡è¯¦æƒ…ï¼ˆæŒ‰æ—¶é—´å’ŒåŒºåŸŸç­›é€‰ï¼‰
     * æ ¹æ®æ—¶é—´èŒƒå›´å’Œè¡Œæ”¿åŒºåˆ’查询任务,整合实时监测数据生成详细任务报告
     * @param startTime æŸ¥è¯¢èµ·å§‹æ—¶é—´ï¼ˆåŒ…含)
     * @param endTime æŸ¥è¯¢ç»“束时间(包含)
     * @param areaVo åŒºåŸŸå‚数,包含省、市、区编码
     * @return ä»»åŠ¡è¯¦æƒ…åˆ—è¡¨ï¼Œæ¯ä¸ªå…ƒç´ åŒ…å«ä»»åŠ¡å®Œæ•´ä¿¡æ¯ã€æ±¡æŸ“çº¿ç´¢åŠå®žæ—¶ç›‘æµ‹æ•°æ®
     * @see MissionRep.findByAreaAndTime åŒºåŸŸæ—¶é—´ç­›é€‰æ•°æ®æº
     * @see realTimeDataRep.fetchData å®žæ—¶æ•°æ®èŽ·å–æŽ¥å£
     */
    override fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo): 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 keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return generateMissionDetail(keyScenes, missionCluesData)
    }
    /**
     * ç”Ÿæˆèµ°èˆªä»»åŠ¡è¯¦æƒ…ï¼ˆç›´æŽ¥å¤„ç†ä»»åŠ¡æ•°æ®ï¼‰
     * æŽ¥æ”¶å·²å…³è”的任务-污染线索-实时数据三元组,生成详细任务报告
     * @param keyScenes å…³é”®åœºæ™¯åˆ—表,用于分析走航是否经过该区域
     * @param missionCluesData ä»»åŠ¡æ•°æ®ä¸‰å…ƒç»„åˆ—è¡¨ï¼ŒTriple分别为任务对象、污染线索列表、实时数据列表
     * @return æ ‡å‡†åŒ–的任务详情列表,包含完整的任务属性、污染分析和监测数据统计
     * @see MissionInventory.generateMissionDetail è¯¦æƒ…生成核心逻辑
     */
    override fun generateMissionDetail(
        keyScenes: List<SceneInfo?>,
        missionCluesData: List<Triple<Mission, List<PollutedClue?>, List<BaseRealTimeData>>>,
    ): List<MissionInventory.MissionDetail> {
        return missionCluesData.map {
            MissionInventory().generateMissionDetail(keyScenes, it.first, it.second, it.third)
        }
    }
    override fun generateClueByRiskArea(
        startTime: Date,
        endTime: Date,
        areaVo: AreaVo,
    ): List<MissionRiskArea.ClueByArea> {
        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?>
            clues.addAll(clue)
        }
        val keyScenes = sceneInfoRep.findBySceneTypes(
            listOf(
                SceneType.TYPE19.value,
                SceneType.TYPE20.value,
                SceneType.TYPE21.value
            )
        )
        return generateClueByRiskArea(keyScenes, clues)
    }
    override fun generateClueByRiskArea(
        keyScenes: List<SceneInfo?>,
        pollutedClues: List<PollutedClue?>,
    ): List<MissionRiskArea.ClueByArea> {
        return MissionRiskArea().generateClueByRiskArea(keyScenes, pollutedClues)
    }
}
src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt
@@ -1,11 +1,14 @@
package com.flightfeather.uav.lightshare.web
import com.fasterxml.jackson.annotation.JsonFormat
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.service.DataAnalysisService
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.util.*
/**
 * èµ°èˆªæ•°æ®åˆ†æž
@@ -31,4 +34,59 @@
        @ApiParam("走航任务编号") @RequestParam missionCode: String,
    ) = resPack { dataAnalysisService.fetchHistory(missionCode) }
    @ApiOperation(value = "生成走航任务汇总统计")
    @PostMapping("/report/missionSummary")
    fun generateMissionSummary(
        @ApiParam("开始时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        startTime: Date,
        @ApiParam("结束时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        endTime: Date,
        @ApiParam("区域") @RequestBody areaVo: AreaVo,
    ) = resPack { dataAnalysisService.generateMissionSummary(startTime, endTime, areaVo) }
    @ApiOperation(value = "生成走航任务清单")
    @PostMapping("/report/missionList")
    fun generateMissionList(
        @ApiParam("开始时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        startTime: Date,
        @ApiParam("结束时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        endTime: Date,
        @ApiParam("区域") @RequestBody areaVo: AreaVo,
    ) = resPack { dataAnalysisService.generateMissionList(startTime, endTime, areaVo) }
    @ApiOperation(value = "生成走航任务详情")
    @PostMapping("/report/missionDetail")
    fun generateMissionDetail(
        @ApiParam("开始时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        startTime: Date,
        @ApiParam("结束时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        endTime: Date,
        @ApiParam("区域") @RequestBody areaVo: AreaVo,
    ) = resPack { dataAnalysisService.generateMissionDetail(startTime, endTime, areaVo) }
    @ApiOperation(value = "走航典型隐患区域统计")
    @PostMapping("/report/clueByRiskArea")
    fun generateClueByRiskArea(
        @ApiParam("开始时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        startTime: Date,
        @ApiParam("结束时间") @RequestParam
        @JsonFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        @DateTimeFormat(pattern = "YYYY-MM-DD HH:mm:ss")
        endTime: Date,
        @ApiParam("区域") @RequestBody areaVo: AreaVo,
    ) = resPack { dataAnalysisService.generateClueByRiskArea(startTime, endTime, areaVo) }
}
src/main/kotlin/com/flightfeather/uav/socket/eunm/AggregatedFactorType.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.flightfeather.uav.socket.eunm
/**
 * èšåˆç›‘测因子
 * @date 2025/8/29
 * @author feiyu02
 */
enum class AggregatedFactorType(val des: String) {
    PM_AF("颗粒物(PM)"),
    NO_AF("氮氧化物");
    companion object {
        /**
         * æ ¹æ®å› å­ç±»åž‹èŽ·å–èšåˆå› å­ç±»åž‹
         */
        fun getAFType(factorType: FactorType?): AggregatedFactorType? {
            return when (factorType) {
                FactorType.PM25, FactorType.PM10 -> PM_AF
                FactorType.NO, FactorType.NO2 -> NO_AF
                else -> null
            }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/socket/eunm/FactorType.kt
@@ -174,7 +174,7 @@
            else -> 10.0
        }
        fun outputFactor(factorName: String?): Boolean {
        fun isOutputFactor(factorName: String?): Boolean {
            return when (factorName) {
                NO2.des,
                CO.des,
src/main/resources/generator/generatorConfig.xml
@@ -4,8 +4,8 @@
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!-- æ•°æ®åº“驱动:选择你的本地硬盘上面的数据库驱动包-->
    <classPathEntry  location="C:\Users\feiyu\.m2\repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>
<!--    <classPathEntry  location="C:\Users\feiyu02\.m2\repository\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar"/>-->
<!--    <classPathEntry  location="C:\Users\feiyu\.m2\repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>-->
    <classPathEntry  location="C:\Users\feiyu02\.m2\repository\mysql\mysql-connector-java\8.0.21\mysql-connector-java-8.0.21.jar"/>
    <!--defaultModelType{
    conditional:这个模型与hierarchical模型相似,除了如果一个实体类只包含一个字段,则不会单独生成此实体类。因此,如果一个表的主键只有一个字段,那么不会为该字段生成单独的实体类,会将该字段合并到基本实体类中。
    flat:该模型为每一张表只生成一个实体类。这个实体类包含表中的所有字段。
src/main/resources/mapper/MissionMapper.xml
@@ -22,6 +22,7 @@
    <result column="kilometres" jdbcType="REAL" property="kilometres" />
    <result column="region" jdbcType="VARCHAR" property="region" />
    <result column="pollution_degree" jdbcType="VARCHAR" property="pollutionDegree" />
    <result column="aqi" jdbcType="INTEGER" property="aqi" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--
@@ -29,6 +30,6 @@
    -->
    mission_code, device_type, device_code, start_time, end_time, data_pulled, province_code, 
    province_name, city_code, city_name, district_code, district_name, town_code, town_name, 
    kilometres, region, pollution_degree
    kilometres, region, pollution_degree, aqi
  </sql>
</mapper>
src/test/kotlin/com/flightfeather/uav/biz/report/MissionSummaryTest.kt
@@ -1,7 +1,5 @@
package com.flightfeather.uav.biz.report
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.mapper.MissionMapper
import com.flightfeather.uav.domain.repository.SourceTraceRep
import com.flightfeather.uav.lightshare.bean.AreaVo
@@ -11,10 +9,8 @@
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
import tk.mybatis.mapper.entity.Example
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.util.Date
@RunWith(SpringRunner::class)
@@ -36,7 +32,7 @@
    fun testMissionSummary() {
        val startTime = Date.from(LocalDateTime.of(2025,7,1,0,0,0).atZone(ZoneId.systemDefault()).toInstant())
        val endTime = Date.from(LocalDateTime.of(2025, 9, 30, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant())
        val summary = dataAnalysisService.missionSummary(startTime, endTime, AreaVo().apply {
        val summary = dataAnalysisService.generateMissionSummary(startTime, endTime, AreaVo().apply {
            provinceCode = "31"
            cityCode = "3100"
            districtCode = "310106"
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt
@@ -1,5 +1,6 @@
package com.flightfeather.uav.lightshare.service.impl
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.lightshare.service.DataAnalysisService
import junit.framework.TestCase
import org.junit.Test
@@ -7,6 +8,9 @@
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
@RunWith(SpringRunner::class)
@SpringBootTest
@@ -14,6 +18,14 @@
    @Autowired
    lateinit var dataAnalysisService: DataAnalysisService
    val startTime = Date.from(LocalDateTime.of(2025,7,1,0,0,0).atZone(ZoneId.systemDefault()).toInstant())
    val endTime = Date.from(LocalDateTime.of(2025, 9, 30, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant())
    val areaVo = AreaVo().apply {
        provinceCode = "31"
        cityCode = "3100"
        districtCode = "310106"
    }
    @Test
    fun testPollutionTrace() {
@@ -24,4 +36,22 @@
    fun fetchHistory() {
        dataAnalysisService.fetchHistory("SH-CN-20250723(01)")
    }
    @Test
    fun generateMissionList() {
        val res = dataAnalysisService.generateMissionList(startTime, endTime, areaVo)
        println(res)
    }
    @Test
    fun generateMissionDetail() {
        val res = dataAnalysisService.generateMissionDetail(startTime, endTime, areaVo)
        println(res)
    }
    @Test
    fun generateClueByRiskArea() {
        val res = dataAnalysisService.generateClueByRiskArea(startTime, endTime, areaVo)
        println(res)
    }
}