业务场景
通过无人机不断上报的数据(其中包含经纬度和其他业务数据),根据经纬度生成网格,这个网格有范围,当无人机的位置超出这个网格则再次创建一个相同的网格,以此类推…,这些网格可以利用无人机上报的数据中的业务数据显示不同的信息。
实现思路
- 创建两个数组,一个作为无人机所经过的网格,另一个作为无人机可能到达的网格 ;
- 以第一个数据作为基准,并创建网格,这个网格存储在无人机经过的网格中;
- 根据网格的长度计算紧邻当前网格的八个格子的四个角坐标,形成九宫格的模式,当然,计算出来的这八个格子作为备选并不直接显示,存储在备选网格中;
- 每一条上报的数据都需要判断,判断是否在已经经过的网格或者备选的网格中,如果在备选的网格中,则将这个网格“转正”,然后再以这个网格计算它的备选网格,逐次类推;
示例Demo
最终实现功能
- 网格根据飞行数据不断自增
- 网格可以显示最新的业务数据
- 可以从接口返回的历史数据进行加载网格
- 可以根据自己的业务需求对表格的长宽做修改
grid.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- <link rel="stylesheet" href="./Cesium-1.85.0-fix-18//Widgets/widgets.css">
<script src="./Cesium-1.85.0-fix-18/Cesium.js"></script> -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.104/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.104/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<title>Document</title>
<script src="./grid.js"></script>
<script>
Cesium.Ion.defaultAccessToken = '替换成自己的Cesium Token'
</script>
<style>
html,
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
const viewer = new Cesium.Viewer('container', {})
let gridInstance = new Grid({
viewer,
})
let flightDataArr = []
const start = () => {
// gridInstance.reloadGrid(flightDataArr)
const flightData = {
longitude: 118.98468275078057,
latitude: 28.947040637215718,
gas: 30
}
const cl = setInterval(() => {
if (flightDataArr.length < 100) {
flightData.latitude += 0.00004
flightData.longitude += 0.00004
gridInstance.watchFlightData(flightData)
flightDataArr.push({
longitude: flightData.longitude + 0.00004,
latitude: flightData.latitude + 0.00004,
gas: 30
})
} else {
clearInterval(cl)
gridInstance.destory()
gridInstance = undefined
}
}, 600)
}
start()
</script>
</body>
</html>
grid.js
class Grid {
constructor(options) {
this.#viewer = options.viewer;
this.#gridWidth = options.gridWidth ?? 30;
this.#gridHeight = options.gridHeight ?? 30;
}
#viewer;
#preGridsScope = []; // 无人机经过的格子
#reserveGridsScope = []; // 无人机可能经过的格子
#gridWidth = 30; // 格子宽,注意:只需修改这里即可
#gridHeight = 30; // 格子长,注意:只需修改这里即可
#pointEntity // 模拟显示无人机的位置
#isReloadTimeing = false // 重载Timing
getPreGridsScope() {
return this.#preGridsScope
}
/* 绘制数据库已有数据的表格 */
reloadGrid(list) {
this.#isReloadTimeing = true
for (let i = 0; i < list.length; i++) {
this.#startWorking(list[i])
}
this.#isReloadTimeing = false
}
/* 监听飞行数据变化 */
watchFlightData(flightData) {
if (!this.#isReloadTimeing) {
this.#startWorking(flightData)
this.#mockAirMove(flightData)
}
}
/* 开始 */
#startWorking(flightData) {
if (this.#preGridsScope.length > 0) {
this.#judgeIsNotFirstGrid(flightData)
} else {
this.#calcFirstGrid(flightData);
}
}
#mockAirMove(flightData) {
const c = this.#wgs842Dkr(flightData)
if (!this.#pointEntity) {
this.#pointEntity = this.#viewer.entities.add({
position: c,
point: {
pixelSize: 10,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
}
})
this.#viewer.trackedEntity = this.#pointEntity
} else {
this.#pointEntity.position = c
}
}
/* 计算第一个表格的位置 */
#calcFirstGrid(flightData) {
const center = this.#wgs842Dkr(flightData)
const positions = this.#calcGridPeaksWidthCenter(center)
const scope = this.#calcScopeWithPositions(positions)
this.#updatePreGrids({
scope,
positions,
center,
}, flightData)
}
/* 如果当前已经有表格显示了 */
#judgeIsNotFirstGrid(flightData) {
const firstGrid = this.#preGridsScope[this.#preGridsScope.length - 1]
// 判断是否是在最后一个表格
const isInLastGrids = this.#judgeIsInGridScope(firstGrid.scope, flightData)
if (isInLastGrids === false) {
// 判断是否在前面的表格中
const isInPreGrids = this.#judgeFlightDataIsInPreGrids(flightData)
if (isInPreGrids === false) {
if (this.#reserveGridsScope.length > 0) {
// 判断是否在备选表格中
const isInReserveGrids = this.#judegeFlightDataIsInReserveGrids(flightData)
if (isInReserveGrids === false) {
// console.log('不在任何格子中');
this.#calcFirstGrid(flightData)
return
} else {
// console.log(`在第${isInReserveGrids + 1}个备选格中`);
const findReserveGrid = this.#reserveGridsScope[isInReserveGrids]
this.#updatePreGrids(findReserveGrid, flightData)
}
}
} else {
// console.log(`在第${isInPreGrids + 1}个格中`);
const findGrid = this.#preGridsScope[isInPreGrids]
this.#updateGridEntity(flightData, findGrid.id)
}
} else {
// console.log('在最后一个格中');
this.#updateGridEntity(flightData, firstGrid.id)
}
}
/* 更新表格列表 */
#updatePreGrids(grid, flightData) {
// this.#reserveGridsScope = [] // 这里可以清空备选表格,也可以不清空。清空的话可能会导致无人机在以一个小角度折返时形成的网格与之前的重叠。具体依据业务而定
const entity = this.#createGridEntity(grid.scope, flightData)
this.#preGridsScope.push({
...grid,
entity,
data: flightData,
})
this.#calcReserveGrids({
...grid,
data: flightData,
})
}
/* 计算备选表格的位置(九宫格) */
#calcReserveGrids(data) {
const { positions: P, center: C } = data
const calcReserveGrid = (cente, start, end, dis) => {
const lineC = this.#calcLineCenter(this.#wgs842Dkr(start), this.#wgs842Dkr(end))
const gridC = this.#translateByDirection(lineC, cente, dis)
const gridP = this.#calcGridPeaksWidthCenter(gridC)
const gridM = this.#calcScopeWithPositions(gridP)
return {
scope: gridM,
positions: gridP,
center: gridC
}
}
// 正上
const T = calcReserveGrid(C, P.LT, P.RT, this.#gridHeight)
// 正右
const R = calcReserveGrid(C, P.RT, P.RB, this.#gridWidth)
// 正下
const B = calcReserveGrid(C, P.LB, P.RB, this.#gridHeight)
// 正左
const L = calcReserveGrid(C, P.LB, P.LT, this.#gridWidth)
// 左上
const LT = calcReserveGrid(T.center, T.positions.LT, T.positions.LB, this.#gridWidth)
// 右上
const RT = calcReserveGrid(T.center, T.positions.RT, T.positions.RB, this.#gridWidth)
// 左下
const LB = calcReserveGrid(B.center, B.positions.LT, B.positions.LB, this.#gridWidth)
// 右下
const RB = calcReserveGrid(B.center, B.positions.RT, B.positions.RB, this.#gridWidth)
this.#reserveGridsScope = [T, RT, R, RB, B, LB, L, LT]
}
/* 根据中心点计算表格的四个顶点 */
#calcGridPeaksWidthCenter(center) {
const L2W = Cesium.Transforms.localFrameToFixedFrameGenerator('east', 'up')(center);
const computedposition = (L2W, x, y, z) => Cesium.Matrix4.multiplyByPoint(L2W, Cesium.Cartesian3.fromElements(x, y, z), new Cesium.Cartesian3())
return {
LT: this.#dkr2Wgs84(computedposition(L2W, -this.#gridWidth, 0, -this.#gridHeight)),
LB: this.#dkr2Wgs84(computedposition(L2W, -this.#gridWidth, 0, this.#gridHeight)),
RT: this.#dkr2Wgs84(computedposition(L2W, this.#gridWidth, 0, -this.#gridHeight)),
RB: this.#dkr2Wgs84(computedposition(L2W, this.#gridWidth, 0, this.#gridHeight))
}
}
/* 根据宫格顶点坐标数据筛选经纬度极值(指最大、最小的经、纬度,共四个数字) */
#calcScopeWithPositions(positions) {
const { LT, LB, RB, RT } = positions
const lons = [LT.longitude, LB.longitude, RB.longitude, RT.longitude]
const lats = [LT.latitude, LB.latitude, RB.latitude, RT.latitude]
return {
maxX: Math.max(...lons),
maxY: Math.max(...lats),
minX: Math.min(...lons),
minY: Math.min(...lats)
}
}
/* 判断飞行数据是否在已经展示的表格中(除去最后一个,因为已经判断过了) */
#judgeFlightDataIsInPreGrids(position) {
for (let i = 0; i < this.#preGridsScope.length - 1; i++) {
const isIn = this.#judgeIsInGridScope(this.#preGridsScope[i].scope, position)
if (isIn) {
return i
}
}
return false
}
/* 判断飞行数据是否在备选的表格中 */
#judegeFlightDataIsInReserveGrids(position) {
for (let i = 0; i < this.#reserveGridsScope.length; i++) {
const isIn = this.#judgeIsInGridScope(this.#reserveGridsScope[i].scope, position)
if (isIn) {
return i
}
}
return false
}
/* 判断经纬度是否在格子范围内 */
#judgeIsInGridScope(scope, position) {
const { minX, minY, maxX, maxY } = scope
if (position.longitude < minX || position.longitude > maxX || position.latitude < minY || position.latitude > maxY) {
return false
} else {
return true
}
}
/* 地图上创建表格entity */
#createGridEntity(scope, flightData, color = '#ffab00') {
return this.#viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees((scope.minX + scope.maxX) / 2, (scope.minY + scope.maxY) / 2, 0.5),
rectangle: {
coordinates: Cesium.Rectangle.fromDegrees(scope.minX, scope.minY, scope.maxX, scope.maxY),
outline: true,
extrudedHeight: 0,
material: Cesium.Color.fromCssColorString(color).withAlpha(0.8),
outlineColor: Cesium.Color.fromCssColorString(color),
outlineWidth: 100,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
label: {
font: "40px",
text: `${flightData.gas}`,
fillColor: Cesium.Color.BLACK,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
flightData,
scope
})
}
/* 修改地图上表格的entity信息 */
#updateGridEntity(flightData, id) {
}
/* 销毁 */
destory() {
for (let i = 0; i < this.#preGridsScope.length; i++) {
this.#viewer.entities.remove(this.#preGridsScope[i].entity)
}
this.#preGridsScope = []
this.#reserveGridsScope = []
this.#isReloadTimeing = []
}
/* wgs84转笛卡尔 */
#dkr2Wgs84(c3) {
let cartographic = Cesium.Cartographic.fromCartesian(c3)
let x = Cesium.Math.toDegrees(cartographic.longitude)
let y = Cesium.Math.toDegrees(cartographic.latitude)
let z = Cesium.Math.toDegrees(0)
return {
longitude: x,
latitude: y,
height: z,
}
}
/* 笛卡尔转wgs84 */
#wgs842Dkr(wgs84) {
return Cesium.Cartesian3.fromDegrees(wgs84.longitude, wgs84.latitude, 0)
}
/* 计算一个点向一个方向平移一段距离的点 */
#translateByDirection(end, start, offset) {
const direction = Cesium.Cartesian3.subtract(
end,
start,
new Cesium.Cartesian3()
);
let normalize = Cesium.Cartesian3.normalize(
direction,
new Cesium.Cartesian3()
);
let scalerNormalize = Cesium.Cartesian3.multiplyByScalar(
normalize,
offset,
new Cesium.Cartesian3()
);
return Cesium.Cartesian3.add(
end,
scalerNormalize,
new Cesium.Cartesian3()
);
}
/* 计算线段中点 */
#calcLineCenter(startPosition, endPosition) {
return Cesium.Cartesian3.midpoint(startPosition, endPosition, new Cesium.Cartesian3())
}
}
```![请添加图片描述](https://img-blog.csdnimg.cn/direct/20f6dae4caf24f1bb52dcf2958db1c0a.gif)
## 结语
>以上功能可能会存在一些数据上的重复计算,但无伤大雅,如果特别要求,可以自行更改优化;且目前来说应该足以应对大部分应用场景;各位有更好的实现方案也十分欢迎讨论;