package com.flightfeather.uav.biz.satellite
|
|
import com.flightfeather.uav.common.utils.MapUtil
|
import com.flightfeather.uav.domain.entity.*
|
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
|
// 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
|
}
|
|
|
|
}
|