feiyu02
2025-03-21 e5bdf2e02090357cbd580d54e6cd2406dd541760
src/main/kotlin/com/flightfeather/uav/biz/satellite/SatelliteGridManage.kt
@@ -1,7 +1,8 @@
package com.flightfeather.uav.biz.satellite
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.GridCell
import com.flightfeather.uav.domain.entity.*
import org.springframework.beans.BeanUtils
import kotlin.math.PI
import kotlin.math.sqrt
@@ -17,7 +18,9 @@
    /**
     * 根据正方形网格中心点坐标,计算4个顶点坐标
     * 网格中心点坐标按照从左到右、从上到下的顺序排列
     * @date 2025.1.8
     * @param points 网格中心坐标点数组
     * @return 网格4个顶点经纬度坐标
     */
    fun calGridVertex(points: List<Pair<Double, Double>>): List<GridVertex> {
        // 网格少于2个,则无法绘制
@@ -64,12 +67,15 @@
    }
    /**
     * 拆分网格为细分网格
     * 拆分网格为细分网格,所有网格应该是相同边长的正方形
     * 根据相似矩形的原理,可以分别按比例得到每个细分网格的经纬度
     * @date 2025.1.17
     * @param gridCellList 原始网格数组
     * @param scale 拆分的系数,例如 2,表示将原有网格按边长的 1/2 拆分成 2 * 2 的4个网格
     * @param groupId 细分后的网格所属的网格组id
     * @return 细分网格
     */
    fun splitGrid(gridCellList: List<GridCell>, scale: Int): List<GridCell> {
    fun splitGrid(gridCellList: List<GridCell?>, scale: Int, groupId:Int): List<GridCell?> {
        if (scale <= 0) throw IllegalArgumentException("网格拆分的数量不能小于1")
        // 拆分系数为1,则表示不拆分
        if (scale == 1) return gridCellList
@@ -79,68 +85,207 @@
        // 根据函数[calGridVertex]生成的网格4个顶点坐标,以上北下南左西右东方向为标准
        // 计算首个网格中心坐标点分别和4个顶点的经纬度差值
        val p = gridCellList[0]
        val p = gridCellList.find { it != null }!!
        // (通过近似平面坐标系的方式)根据单个原始网格的4个顶点坐标,分别确定以下三组坐标点之间的经纬度单位偏移量
        val p1 = p.point1Lon to p.point1Lat
        val p2 = p.point2Lon to p.point2Lat
        val p3 = p.point3Lon to p.point3Lat
        val p4 = p.point4Lon to p.point4Lat
        // p1、p3的经纬度单位差值
        val dx1 = (p3.first - p1.first) / scale.toBigDecimal()
        val dy1 = (p3.second - p1.second) / scale.toBigDecimal()
        // p1、p4的经纬度单位差值
        val dx1 = (p4.first - p1.first) / scale.toBigDecimal()
        val dy1 = (p4.second - p1.second) / scale.toBigDecimal()
        // p1、p2的经纬度单位差值
        val dx2 = (p2.first - p1.first) / scale.toBigDecimal()
        val dy2 = (p2.second - p1.second) / scale.toBigDecimal()
        // p3、p4的经纬度单位差值
        val dx3 = (p4.first - p3.first) / scale.toBigDecimal()
        val dy3 = (p4.second - p3.second) / scale.toBigDecimal()
        // p4、p3的经纬度单位差值
        val dx3 = (p3.first - p4.first) / scale.toBigDecimal()
        val dy3 = (p3.second - p4.second) / scale.toBigDecimal()
        // 中心点和p1的经纬度单位差值
        val dxC = (p.longitude - p1.first) / scale.toBigDecimal()
        val dyC = (p.latitude - p1.second) / scale.toBigDecimal()
        // 网格行循环
        for (row in 0 until scale) {
            val newGridCell1 = GridCell()
        // 网格索引
        var cellIndex = 0
            // 确定每一行首个细分网格的中心坐标和4个顶点坐标
            // 左上角顶点根据所在行数在原始网格顶点基础上增加偏移量
            newGridCell1.point1Lon = p1.first + dx1 * row.toBigDecimal()
            newGridCell1.point1Lat = p1.second + dy1 * row.toBigDecimal()
            // 左下角顶点根据所在行数在原始网格顶点基础上增加偏移量(比左上角顶点多一个偏移)
            newGridCell1.point3Lon = p1.first + dx1 * (row + 1).toBigDecimal()
            newGridCell1.point3Lat = p1.second + dy1 * (row + 1).toBigDecimal()
            // 右上角顶点在细分网格左上角的基础上增加相应的偏移量
            newGridCell1.point2Lon = newGridCell1.point1Lon + dx2
            newGridCell1.point2Lat = newGridCell1.point1Lat + dy2
            // 右下角顶点在细分网格左下角的基础上增加相应的偏移量
            newGridCell1.point4Lon = newGridCell1.point3Lon + dx3
            newGridCell1.point4Lat = newGridCell1.point3Lat + dy3
            // 中心点在细分网格左上角的基础上增加固定偏移量
            newGridCell1.longitude = newGridCell1.point1Lon + dxC
            newGridCell1.latitude = newGridCell1.point1Lat + dyC
        // 对网格组内的所有网格进行网格细分
        gridCellList.forEach { g ->
            if (g == null) return@forEach
            // 加入结果集合
            newGridCellList.add(newGridCell1)
            // 网格行循环
            for (row in 0 until scale) {
                val newGridCell1 = GridCell()
            // 网格列循环(从第2列开始)
            for (col in 1 until scale) {
                val newGridCell = GridCell()
                newGridCell.point1Lon = newGridCell1.point1Lon + dx2 * col.toBigDecimal()
                newGridCell.point1Lat = newGridCell1.point1Lat + dy2 * col.toBigDecimal()
                newGridCell.point2Lon = newGridCell1.point2Lon + dx2 * col.toBigDecimal()
                newGridCell.point2Lat = newGridCell1.point2Lat + dy2 * col.toBigDecimal()
                newGridCell.point3Lon = newGridCell1.point3Lon + dx3 * col.toBigDecimal()
                newGridCell.point3Lat = newGridCell1.point3Lat + dy3 * col.toBigDecimal()
                newGridCell.point4Lon = newGridCell1.point4Lon + dx3 * col.toBigDecimal()
                newGridCell.point4Lat = newGridCell1.point4Lat + dy3 * col.toBigDecimal()
                newGridCell.longitude = newGridCell.point1Lon + dxC
                newGridCell.latitude = newGridCell.point1Lat + dyC
                // 确定每一行首个细分网格的中心坐标和4个顶点坐标
                // 左上角顶点根据所在行数在原始网格顶点基础上增加偏移量
                newGridCell1.point1Lon = g.point1Lon + dx1 * row.toBigDecimal()
                newGridCell1.point1Lat = g.point1Lat + dy1 * row.toBigDecimal()
                // 左下角顶点根据所在行数在原始网格顶点基础上增加偏移量(比左上角顶点多一个偏移)
                newGridCell1.point4Lon = g.point1Lon + dx1 * (row + 1).toBigDecimal()
                newGridCell1.point4Lat = g.point1Lat + dy1 * (row + 1).toBigDecimal()
                // 右上角顶点在细分网格左上角的基础上增加相应的偏移量
                newGridCell1.point2Lon = newGridCell1.point1Lon + dx2
                newGridCell1.point2Lat = newGridCell1.point1Lat + dy2
                // 右下角顶点在细分网格左下角的基础上增加相应的偏移量
                newGridCell1.point3Lon = newGridCell1.point4Lon + dx3
                newGridCell1.point3Lat = newGridCell1.point4Lat + dy3
                // 中心点在细分网格左上角的基础上增加固定偏移量
                newGridCell1.longitude = newGridCell1.point1Lon + dxC
                newGridCell1.latitude = newGridCell1.point1Lat + dyC
                newGridCellList.add(newGridCell)
                newGridCell1.groupId = groupId
                newGridCell1.cellIndex = ++cellIndex
                newGridCell1.fatherCellIndex = g.cellIndex
                // 加入结果集合
                newGridCellList.add(newGridCell1)
                // 网格列循环(从第2列开始)
                for (col in 1 until scale) {
                    val newGridCell = GridCell()
                    newGridCell.point1Lon = newGridCell1.point1Lon + dx2 * col.toBigDecimal()
                    newGridCell.point1Lat = newGridCell1.point1Lat + dy2 * col.toBigDecimal()
                    newGridCell.point2Lon = newGridCell1.point2Lon + dx2 * col.toBigDecimal()
                    newGridCell.point2Lat = newGridCell1.point2Lat + dy2 * col.toBigDecimal()
                    newGridCell.point3Lon = newGridCell1.point3Lon + dx3 * col.toBigDecimal()
                    newGridCell.point3Lat = newGridCell1.point3Lat + dy3 * col.toBigDecimal()
                    newGridCell.point4Lon = newGridCell1.point4Lon + dx3 * col.toBigDecimal()
                    newGridCell.point4Lat = newGridCell1.point4Lat + dy3 * col.toBigDecimal()
                    newGridCell.longitude = newGridCell.point1Lon + dxC
                    newGridCell.latitude = newGridCell.point1Lat + dyC
                    newGridCell.groupId = groupId
                    newGridCell.cellIndex = ++cellIndex
                    newGridCell.fatherCellIndex = g.cellIndex
                    newGridCellList.add(newGridCell)
                }
            }
        }
        return newGridCellList
    }
    /**
     * 拆分数据,将原始卫星网格遥测数据映射到对应细分网格上
     * @date 2025.2.7
     * @param subGridCellList 细分网格
     * @param subGridData 细分网格对应的数据索引
     * @param originGridDataDetailList 细分网格所属网格的原始网格数据
     * @return 映射后的细分网格遥测数据
     */
    fun splitData(
        subGridCellList: List<GridCell?>, subGridData: GridData, originGridDataDetailList: List<GridDataDetail?>
    ): List<GridDataDetail> {
        if (subGridCellList.isEmpty() || originGridDataDetailList.isEmpty()) return emptyList()
        val result = mutableListOf<GridDataDetail>()
        // 将细分网格按照父网格id和自身网格id进行升序排列
        val _subGridCellList = subGridCellList.sortedWith(Comparator { o1, o2 ->
            if (o1 == null && o2 == null) {
                return@Comparator 0
            } else if (o1 == null) {
                return@Comparator -1
            } else if (o2 == null) {
                return@Comparator 1
            } else {
                if (o1.fatherCellIndex == o2.fatherCellIndex) {
                    return@Comparator o1.cellIndex - o2.cellIndex
                } else {
                    return@Comparator o1.fatherCellIndex - o2.fatherCellIndex
                }
            }
        })
        // 将原始网格数据按照网格id升序排列
        val _originGridDataDetailIterator = originGridDataDetailList.sortedBy { it?.cellId }.iterator()
        var fatherGridData = _originGridDataDetailIterator.next()
        // 遍历细分网格,为每个细分网格生成一条网格数据
        _subGridCellList.forEach {
            while (fatherGridData?.cellId != it?.fatherCellIndex && _originGridDataDetailIterator.hasNext()) {
                fatherGridData = _originGridDataDetailIterator.next()
            }
            val subGridDataDetail = GridDataDetail().apply {
                dataId = subGridData.id
                groupId = it?.groupId
                cellId = it?.cellIndex
                pm25 = fatherGridData?.pm25
                rank = fatherGridData?.rank
            }
            result.add(subGridDataDetail)
        }
        return result
    }
    /**
     * 走航数据和卫星网格融合
     * 数据融合采用均值方式统计(暂时)
     * @date 2025.2.7
     * @param realTimeDataList 待融合的走航监测数据
     * @param gridData 融合后的数据组索引
     * @param gridCellList 待融合的卫星网格
     * @return 融合后的网格监测数据
     */
    fun dataFusion(
        realTimeDataList: List<BaseRealTimeData>,
        gridData: GridData?,
        gridCellList: List<GridCell?>,
    ): List<GridDataDetail> {
        // 遍历走航监测数据,计算每个点所在网格
        val dataMap = mutableMapOf<GridCell, MutableList<BaseRealTimeData>>()
        realTimeDataList.forEach {
            if (it.longitude == null || it.latitude == null) return@forEach
            SatelliteGridUtil.searchGirdIn(it.longitude!!.toDouble() to it.latitude!!.toDouble(), gridCellList)
                ?.let { cell ->
                    if (!dataMap.containsKey(cell)) {
                        dataMap[cell] = mutableListOf()
                    }
                    dataMap[cell]?.add(it)
                }
        }
        // 统计每个网格中的均值
        // Fixme 2025.2.20 暂时默认以均值方式统计,后续调整为多种方式并支持用户选择
        val gridDataDetailList = mutableListOf<GridDataDetail>()
        dataMap.forEach { (k, v) ->
            val avgData = v.avg()
            val dataDetail = GridDataDetail()
            BeanUtils.copyProperties(avgData, dataDetail)
            dataDetail.apply {
                dataId = gridData?.id
                groupId = k.groupId
                cellId = k.cellIndex
//                no2 = avgData.no2
//                co = avgData.co
//                h2s = avgData.h2s
//                so2 = avgData.so2
//                o3 = avgData.o3
//                pm25 = avgData.pm25
//                pm10 = avgData.pm10
//                temperature = avgData.temperature
//                humidity = avgData.humidity
//                voc = avgData.voc
//                noi = avgData.noi
//                no = avgData.no
//                windSpeed
//                windDirection
                rank
            }
            gridDataDetailList.add(dataDetail)
        }
        gridDataDetailList.sortBy { it.pm25 }
        gridDataDetailList.forEachIndexed { index, d ->
            d.rank = index + 1
        }
        gridDataDetailList.sortBy { it.cellId }
        return gridDataDetailList
    }
}