| | |
| | | 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 |
| | | |
| | | /** |
| | |
| | | * @date 2025/1/8 |
| | | * @author feiyu02 |
| | | */ |
| | | class SatelliteGridManage { |
| | | object SatelliteGridManage { |
| | | |
| | | /** |
| | | * 根据正方形网格中心点坐标,计算4个顶点坐标 |
| | | * 网格中心点坐标按照从左到右、从上到下的顺序排列 |
| | | * @date 2025.1.8 |
| | | * @param points 网格中心坐标点数组 |
| | | * @return 网格4个顶点经纬度坐标 |
| | | */ |
| | | fun calGridVertex(points:List<Pair<Double, Double>>):List<Array<Pair<Double, Double>>> { |
| | | fun calGridVertex(points: List<Pair<Double, Double>>): List<GridVertex> { |
| | | // 网格少于2个,则无法绘制 |
| | | if (points.size < 2) return emptyList() |
| | | val p1 = points[0]; |
| | | val p2 = points[1]; |
| | | // 获取前两个网格 |
| | | // 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 angle = MapUtil.getAngle(p1.first, p1.second, p2.first, p2.second) |
| | | // 两中心点间的距离 |
| | | val dis = MapUtil.getDistance(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); |
| | | val halfDiagonal = sqrt((dis / 2) * (dis / 2) * 2) |
| | | // 计算首个正方形各顶点相对于中心点的角度,得到正方形各顶点的坐标 |
| | | val angle1 = MapUtil.plusAngle(angle, 45.0); |
| | | val gp1 = MapUtil.getPointByLen(p1, halfDiagonal, angle1); |
| | | val angle2 = MapUtil.plusAngle(angle1, 90.0); |
| | | val gp2 = MapUtil.getPointByLen(p1, halfDiagonal, angle2); |
| | | val angle3 = MapUtil.plusAngle(angle2, 90.0); |
| | | val gp3 = MapUtil.getPointByLen(p1, halfDiagonal, angle3); |
| | | val angle4 = MapUtil.plusAngle(angle3, 90.0); |
| | | val gp4 = MapUtil.getPointByLen(p1, halfDiagonal, angle4); |
| | | // 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 dy4 = gp4.second - p1.second |
| | | |
| | | // 得到所有正方形网格的4个顶点信息 |
| | | return points.map { p-> |
| | | arrayOf( |
| | | p.first + dx1 to p.second + dy1, |
| | | p.first + dx2 to p.second + dy2, |
| | | p.first + dx3 to p.second + dy3, |
| | | p.first + dx4 to p.second + dy4, |
| | | 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() { |
| | | 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 |
| | | } |
| | | |
| | | } |