Riku
2025-07-13 37d47c6a7ab0f454b948b68c987146b261117993
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package com.flightfeather.uav.biz.satellite
 
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.*
import com.flightfeather.uav.model.underwaygrid.GridCellAndData
import com.flightfeather.uav.model.underwaygrid.GridCellSop
import com.flightfeather.uav.model.underwaygrid.UnderwayGridModel
import com.flightfeather.uav.socket.eunm.FactorType
import org.springframework.beans.BeanUtils
import kotlin.math.PI
import kotlin.math.sqrt
 
/**
 * 卫星遥测网格管理
 * 根据网格中心点计算4个顶点坐标
 * 根据已有网格进行细分网格计算
 * @date 2025/1/8
 * @author feiyu02
 */
object SatelliteGridManage {
 
    /**
     * 根据正方形网格中心点坐标,计算4个顶点坐标
     * 网格中心点坐标按照从左到右、从上到下的顺序排列
     * @date 2025.1.8
     * @param points 网格中心坐标点数组
     * @return 网格4个顶点经纬度坐标
     */
    fun calGridVertex(points: List<Pair<Double, Double>>): List<GridVertex> {
        // 网格少于2个,则无法绘制
        if (points.size < 2) return emptyList()
        // 获取前两个网格
        // Fixme 2025.01.10: 目前先简化逻辑,按照前两个点是左右排布,且点p2在点p1的东边
        val p1 = points[0]
        val p2 = points[1]
        // 两中心点间的角度
        val angle = MapUtil.getAngle(p1.first, p1.second, p2.first, p2.second)
        // 两中心点间的距离
        val dis = MapUtil.getDistance(p1.first, p1.second, p2.first, p2.second)
        // 网格正方形对角线的一半长度
        val halfDiagonal = sqrt((dis / 2) * (dis / 2) * 2)
        // 计算首个正方形各顶点相对于中心点的角度,得到正方形各顶点的坐标
        // 4个顶点按照从左上角开始,顺时针方向的顺序进行排列
        val angle1 = MapUtil.plusAngle(angle, 45.0 + 180.0)
        val gp1 = MapUtil.getPointByLen(p1, halfDiagonal, angle1 * PI / 180)
        val angle2 = MapUtil.plusAngle(angle1, 90.0)
        val gp2 = MapUtil.getPointByLen(p1, halfDiagonal, angle2 * PI / 180)
        val angle3 = MapUtil.plusAngle(angle2, 90.0)
        val gp3 = MapUtil.getPointByLen(p1, halfDiagonal, angle3 * PI / 180)
        val angle4 = MapUtil.plusAngle(angle3, 90.0)
        val gp4 = MapUtil.getPointByLen(p1, halfDiagonal, angle4 * PI / 180)
        // 计算4个顶点分别与中心点的经纬度差值
        val dx1 = gp1.first - p1.first
        val dy1 = gp1.second - p1.second
        val dx2 = gp2.first - p1.first
        val dy2 = gp2.second - p1.second
        val dx3 = gp3.first - p1.first
        val dy3 = gp3.second - p1.second
        val dx4 = gp4.first - p1.first
        val dy4 = gp4.second - p1.second
 
        // 得到所有正方形网格的4个顶点信息
        return points.map { p ->
            GridVertex(
                p.first + dx1, p.second + dy1,
                p.first + dx2, p.second + dy2,
                p.first + dx3, p.second + dy3,
                p.first + dx4, p.second + dy4,
            )
        }
    }
 
    /**
     * 拆分网格为细分网格,所有网格应该是相同边长的正方形
     * 根据相似矩形的原理,可以分别按比例得到每个细分网格的经纬度
     * @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, groupId:Int): List<GridCell?> {
        if (scale <= 0) throw IllegalArgumentException("网格拆分的数量不能小于1")
        // 拆分系数为1,则表示不拆分
        if (scale == 1) return gridCellList
        if (gridCellList.isEmpty()) return emptyList()
 
        val newGridCellList = mutableListOf<GridCell>()
 
        // 根据函数[calGridVertex]生成的网格4个顶点坐标,以上北下南左西右东方向为标准
        // 计算首个网格中心坐标点分别和4个顶点的经纬度差值
        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、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()
        // 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()
 
        // 网格索引
        var cellIndex = 0
 
        // 对网格组内的所有网格进行网格细分
        gridCellList.forEach { g ->
            if (g == null) return@forEach
 
            // 网格行循环
            for (row in 0 until scale) {
                val newGridCell1 = GridCell()
 
                // 确定每一行首个细分网格的中心坐标和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
 
                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
                rank
            }
            gridDataDetailList.add(dataDetail)
        }
 
        gridDataDetailList.sortBy { it.pm25 }
        gridDataDetailList.forEachIndexed { index, d ->
            d.rank = index + 1
        }
        gridDataDetailList.sortBy { it.cellId }
 
        return gridDataDetailList
    }
 
    /**
     * 计算热力图网格,即网格周边扩散影响权重计算
     * @param gridDataDetail 网格监测数据
     * @param gridCellList 区域网格数组
     * @param option 区域网格参数信息
     * @param searchLength 计算周边八方向(上下左右及四个对角)网格的长度
     * @return 周边网格及对应的监测数据结果
     */
    fun heatMap(
        gridDataDetail: GridDataDetail, gridCellList: List<GridCell?>,
        option: GridGroupOption, searchLength: Int,
    ): List<GridDataDetail> {
        // 找到网格数据对应的网格信息
        val gridCell = gridCellList.find { it?.cellIndex == gridDataDetail.cellId }
            ?: throw IllegalArgumentException("网格数据和给定的区域网格不匹配")
 
        // 获取周边网格
        val surroundGridCellList =
            SatelliteGridUtil.searchDiffuseGrid(gridDataDetail.cellId, gridCellList, option, searchLength)
 
        // 使用走航网格权重模型,计算周边网格的监测数据值
        val underwayGridModel = UnderwayGridModel()
        val dataList = listOf(GridCellAndData(gridCell, gridDataDetail))
        val gridCellSopList = surroundGridCellList.map {
            GridCellSop(
                it,
                it.id.toString(),
                it.cellIndex.toString(),
                it.cellIndex.toString()
            ) }
        underwayGridModel.execute(dataList, gridCellSopList)
        val resMap = underwayGridModel.outputResult()
 
        // 格式化结果并返回
        val result = mutableListOf<GridDataDetail>()
        gridCellSopList.forEach {
            val resGridDataDetail = GridDataDetail().apply {
                dataId = gridDataDetail.dataId
                groupId = gridDataDetail.groupId
                cellId = it.gridCell.cellIndex
            }
 
            val key = "${it.sourceName};${it.index}"
            val d = resMap[key]
            d?.forEach { (t, u) ->
                val avg = u["综合(${t})"]?.average ?: .0
                resGridDataDetail.setFactorValue(FactorType.getByName(t), avg.toFloat())
            }
            result.add(resGridDataDetail)
        }
 
        return result
    }
 
}