前言

本文讨论的是如何根据起点终点规划出一条避开大于指定坡度最短路线,目的是研究A*算法的应用场景,加深对其的理解。

前置知识:理解A*算法https://juejin.cn/post/7452166964440907817

注:实现过程中,非重点研究的算法直接使用了turfjs库计算,它是一个地理空间计算库。

实现思路

  1. 确认起点和终点位置。
  2. 根据起点和终点位置来确认出一片包含两者的矩形区域。
  3. 将矩形区域划分成均匀的网格。
  4. 计算每个网格的坡度。
  5. 设定一个坡度阈值,将坡度大于该阈值的网格视为障碍物。
  6. 基于上述结果,使用A*算法在可通行的网格中寻找最短路径。

1.确认起点和终点

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
let start = [108.99746771415697, 34.00421419748617];
let end = [109.00550820307224, 33.99905943761618];
//绘制起点
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(...start),
point: {
pixelSize: 10,
color: Cesium.Color.GREEN,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 3,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: 20000,
},
});
//绘制终点
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(...end),
point: {
pixelSize: 10,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 3,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: 20000,
},
});

image.png

2.确认矩形搜索区域

为了尽可能找到可通路径,我们创建的矩形区域并非局限于能包含起点和终点的最小范围,而是将该区域适当放大几倍。这样做的目的是扩展搜索空间,从而发现可能位于最小矩形区域外部的可行路线,提高路径规划的灵活性。

如下图,红色区域为原始区域,绿色区域为放大后的区域。如果在最小区域无合适路径,我们仍有冗余的可搜索空间。
image.png

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
let minLng = Math.min(start[0], end[0]);
let maxLng = Math.max(start[0], end[0]);
let maxLat = Math.max(start[1], end[1]);
let minLat = Math.min(start[1], end[1]);
// 创建矩形
let poly = turf.polygon([
[
[minLng, minLat],//西南
[minLng, maxLat],//西北
[maxLng, maxLat],//东北
[maxLng, minLat],//东南
[minLng, minLat],//回到起点
],
]);
//将矩形扩大5倍(借助turf.js)
let scaledPoly = turf.transformScale(poly, 5);
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray(scaledPoly.geometry.coordinates[0].flat()),
material: Cesium.Color.GREEN,
//贴地
clampToGround: true,
width:5,
}
})

image.png

3.划分为网格

根据扩大后的矩形边界创建网格,注意此时需要指定网格的大小,网格定义的越小在后面的计算中性能开销越大。还有一种定义网格大小的方案是将网格大小动态计算,与边界大小成倍数关系,这样就能根据区域大小合理的控制计算精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//计算扩大后的边界
let enveloped = turf.envelope(scaledPoly).bbox;
//指定网格大小
const gridSize = 0.03;
//划分为网格
let squareGrid = turf.squareGrid(enveloped, gridSize);
squareGrid.features.forEach(item => {
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray(item.geometry.coordinates[0].flat()),
material: Cesium.Color.GREEN,
clampToGround: true,
width: 1,
}
})
})

image.png

4.计算网格坡度

在路径规划前,首先要计算出每个网格的坡度,将超出阈值坡度的网格指定为障碍物。

计算单个网格的坡度的过程:

  • 沿着网格边缘插值点并计算每个点位的高程
  • 找出高程差距最大的两点
  • 使用三角函数计算坡度值

image.png

计算网格坡度,将超出阈值的网格指定为障碍:

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
// 每个网格轮廓上进行插值12个,将所有插值点存入interpolatingPoints
// ----------------------------------------------------------------↓
let interpolatingPoints = [];
const splitCount = 12;
let line = turf.lineString(squareGrid.features[0].geometry.coordinates[0]);
let lineLength = turf.length(line);
squareGrid.features.forEach((item, index) => {
let positions = item.geometry.coordinates[0];
let line = turf.lineString(positions);
//在每个网格轮廓上进行插值12个
for (let j = 1; j <= splitCount; j++) {
let along = turf.along(line, lineLength * (j / splitCount)).geometry
.coordinates;
interpolatingPoints.push(Cesium.Cartographic.fromDegrees(...along));
}
});
// ----------------------------------------------------------------↑

//为每个网格添加序号、坡角、中心点属性,并找出起点和终点的网格序号
// ----------------------------------------------------------------↓
const maxSlope = 25;//可以通过的最大坡度
let startXy = [];//起点所在的网格序号
let endXy = [];//终点所在的网格序号
Cesium.sampleTerrainMostDetailed(
viewer.scene.terrainProvider,
interpolatingPoints
).then((updatePositions) => {
//计算每列有多少个网格(为了计算每个网格在整体网格中的序号(供A*算法使用))
let columnCount = Math.floor(
turf.distance(
turf.point([enveloped[0], enveloped[1]]),
turf.point([enveloped[0], enveloped[3]])
) / gridSize
);

let count = 0;
for (let i = 0; i < updatePositions.length; i += splitCount) {
let group = updatePositions.slice(i, i + splitCount);
let sort = group.sort((a, b) => a.height - b.height);
let [minHeightPos, maxHeigthPos] = [sort.at(0), sort.at(-1)];
//获取中心点
let polygon = squareGrid.features[count];
let center = turf.centroid(polygon);
//计算斜坡角度
let line = turf.lineString([
[Cesium.Math.toDegrees(maxHeigthPos.longitude), Cesium.Math.toDegrees(maxHeigthPos.latitude)],
[Cesium.Math.toDegrees(minHeightPos.longitude), Cesium.Math.toDegrees(minHeightPos.latitude)],
]);
let edge1 = turf.length(line);
let edge2 = (maxHeigthPos.height - minHeightPos.height) / 1000;
//根据两条直角边的反正切值计算坡度
let slope = Cesium.Math.toDegrees(Math.atan(edge2 / edge1));
//计算每个网格的横纵向序号(供A*算法使用)
let x = Math.floor(count / columnCount);
let y = (x + 1) * columnCount - count - 1;
let currentGrid = squareGrid.features[count];
currentGrid.properties = {
slope: slope, //坡角
center: center.geometry.coordinates,//中心点
id: `${x}-${y}`,
x: x,//横向序号
y: y,//纵向序号
};
//计算起点和终点的二维xy坐标(turf.booleanPointInPolygon计算点是否在多边形内)
if (!startXy.length && turf.booleanPointInPolygon(turf.point(start), polygon)) {
startXy = { x, y };
}
if (!endXy.length && turf.booleanPointInPolygon(turf.point(end), polygon)) {
endXy = { x, y };
}
count += 1;//count+1开始计算下一个网格的相关属性
if (currentGrid.properties.slope > maxSlope) {
viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray(currentGrid.geometry.coordinates[0].flat()),
material: Cesium.Color.RED.withAlpha(0.5),
}
})
}
}
})
// ----------------------------------------------------------------↑

image.png

5.使用 A* 算法寻路

至此我们已具备使用A*算法的所有前置条件。

再阅读下面代码前必须先理解A*算法,否则会一头雾水:https://juejin.cn/post/7452166964440907817

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
let allList = []; //所有网格
let openList = []; //待计算的网格
squareGrid.features.forEach(({ geometry: { coordinates }, properties }) => {
let obj = {
x: properties.x,
y: properties.y,
id: properties.id,
center: properties.center,
slope: properties.slope,
h: Math.sqrt(
Math.pow(properties.x - endXy.x, 2) +
Math.pow(properties.y - endXy.y, 2)
), //当前网格和终点距为未来预期代价
g: null, //当前网格和起点距离为历史代价
f: null,
parentId: null,
coordinates: coordinates[0].map((item) => [item[0], item[1]]),
};

if (properties.slope > maxSlope) {
obj.isInCloseList = 1; //障碍物就关闭,后面不再对该网格进行计算
} else {
obj.isInOpenList = 0; //该网格为待计算网格
}
allList.push(obj);
});
let startNode = allList.find(
(item) => item.x == startXy.x && item.y == startXy.y
);
startNode.g = 0;
startNode.isInOpenList = 1;
//计算好起点的代价后将它插入到openList中
openList.push(startNode);
let endNode = {};

while (openList.length) {
//根据代价逆序排序,从openList中获取到代价最小的网格(如果有多个代价相同的点,优先选择 g 值(历史代价)较小的网格,因为这更有可能导向最短路径。)
let sortedByF = openList.sort((a, b) => a.f - b.f);
let minFNodes = sortedByF.filter((item) => item.f == sortedByF[0].f);
let nodeCurrent = minFNodes.sort((a, b) => a.g - b.g)[0];
//获取代价最小的网格周围的网格
let childUp = allList.find(
(item) => item.x == nodeCurrent.x && item.y == nodeCurrent.y - 1
);
let childRight = allList.find(
(item) => item.x == nodeCurrent.x + 1 && item.y == nodeCurrent.y
);
let childDown = allList.find(
(item) => item.x == nodeCurrent.x && item.y == nodeCurrent.y + 1
);
let childLeft = allList.find(
(item) => item.x == nodeCurrent.x - 1 && item.y == nodeCurrent.y
);
let childList = [childUp, childRight, childDown, childLeft];
//只有当左边和上边不全是障碍物,才能走左上的网格
if (!childUp?.isInCloseList || !childLeft?.isInCloseList) {
let childLeftUp = allList.find(
(item) => item.x == nodeCurrent.x - 1 && item.y == nodeCurrent.y - 1
);
childList.push(childLeftUp);
}
//只有当右边和上边不全是障碍物,才能走右上的网格
if (!childUp?.isInCloseList || !childRight?.isInCloseList) {
let childRightUp = allList.find(
(item) => item.x == nodeCurrent.x + 1 && item.y == nodeCurrent.y - 1
);
childList.push(childRightUp);
}
//只有当右边和下边不全是障碍物,才能走右下的网格
if (!childDown?.isInCloseList || !childRight?.isInCloseList) {
let childRightDown = allList.find(
(item) => item.x == nodeCurrent.x + 1 && item.y == nodeCurrent.y + 1
);
childList.push(childRightDown);
}
//只有当左边和下边不全是障碍物,才能走左下的网格
if (!childDown?.isInCloseList || !childLeft?.isInCloseList) {
let childLeftDown = allList.find(
(item) => item.x == nodeCurrent.x - 1 && item.y == nodeCurrent.y + 1
);
childList.push(childLeftDown);
}
//遍历周围网格
for (let i = 0; i < childList.length; i++) {
let child = childList[i];
if (!child || child.isInCloseList == 1) {
//已经关闭,后面不再计算
continue;
}
//计算当前网格到它子网格的距离
let currentToChild = Math.sqrt(
Math.pow(nodeCurrent.x - child.x, 2) +
Math.pow(nodeCurrent.y - child.y, 2)
);

if (child.isInOpenList == 0) {
//从来没有被计算过,现在计算它的代价
child.parentId = nodeCurrent.id;
//子网格的历史代价是当前网格历史代价加上当前网格到子网格的距离
child.g = nodeCurrent.g + currentToChild;
//子网格的未来期望代价是子网格到终点的距离
child.h = Math.sqrt(
Math.pow(child.x - endXy.x, 2) + Math.pow(child.y - endXy.y, 2)
);
//得出最终代价
child.f = child.g + child.h;
//设置标记,表明这个子网格已经被计算过至少一次了
child.isInOpenList = 1;
openList.push(child); //将这个子网格加入到待计算列表中
} else if (child.isInOpenList == 1) {
//这个子网格被计算过
// 将子网格的父级替换为当前网格重新计算代价
let g = nodeCurrent.g + currentToChild;
//如果更换为新父级后代价比以前小,就更新一下
if (g < child.g) {
child.g = g;
child.f = child.g + child.h;
child.parentId = nodeCurrent.id;
}
}
//找到终点了,赋值后直接跳出
if (child.x == endXy.x && child.y == endXy.y) {
endNode = child;

let roadPath = [];
//溯源出路线
let currentNode = endNode;
while (currentNode) {
roadPath.push(currentNode.center);
currentNode = allList.find(({ id }) => id == currentNode.parentId);
}
let line = turf.lineString(roadPath);
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray(roadPath.flat()),
width: 5,
clampToGround: true,
material: Cesium.Color.fromCssColorString("red"),
},
});
break;
}
}
if (endNode.id) {
break;
}

//将当前网格从待计算列表中移除并将它关闭
let index = openList.findIndex(
({ x, y }) => x == nodeCurrent.x && y == nodeCurrent.y
);
openList[index].isInCloseList = 1;
openList.splice(index, 1);
}
if (!openList.length && !endNode.id) {
alert("无路可走");
}

大功到成
image.png

总结

文章主要探讨了A*算法的应用场景。如对文中提到的功能实现有更优的想法,或是掌握其他更好的解决方案,欢迎在评论区积极分享与交流,共同促进与提升!

完整代码

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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./Cesium/Widgets/widgets.css">
<style>
* {
padding: 0;
margin: 0;
}

html,
body {
height: 100%;
}

#map {
width: 100%;
height: 100%;
}
</style>
</head>

<body>
<div id="map"></div>
</body>
<script src="./Cesium/Cesium.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/Turf.js/6.5.0/turf.min.js"></script>
<script>
Cesium.Ion.defaultAccessToken = "xxxxx";
let viewer = new Cesium.Viewer('map', {
terrain: Cesium.Terrain.fromWorldTerrain({
requestVertexNormals: true, //Needed to visualize slope
}),
fullscreenButton: false,
baseLayerPicker: false,
timeline: true,
geocoder: false,
selectionIndicator: false,
sceneModePicker: false,
shouldAnimate: true,
infoBox: false,
homeButton: false,
navigationHelpButton: false,
animation: false,
});
viewer.camera.setView({
destination: Cesium.Cartesian3.fromRadians(1.9025916109699217, 0.5935674511615108, 3395.0586965429834),
orientation: {
heading: 4.018977641484386,
pitch: -1.2137508326515745,
roll: 0,
},
});

let handler = viewer.scene.globe.terrainProviderChanged.addEventListener(e=>{
viewer.scene.globe.terrainProviderChanged.removeEventListener(handler);
analysis();
})
function analysis() {
let start = [108.99746771415697, 34.00421419748617];
let end = [109.00611576089523, 34.00031304168087];
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(...start),
point: {
pixelSize: 10,
color: Cesium.Color.GREEN,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 3,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: 20000,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(...end),
point: {
pixelSize: 10,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 3,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: 20000,
},
});
return;
//将矩形范围扩大
let minLng = Math.min(start[0], end[0]);
let maxLng = Math.max(start[0], end[0]);
let maxLat = Math.max(start[1], end[1]);
let minLat = Math.min(start[1], end[1]);

// 创建矩形
let poly = turf.polygon([
[
[minLng, minLat],
[minLng, maxLat],
[maxLng, maxLat],
[maxLng, minLat],
[minLng, minLat],
],
]);
//将矩形扩大5倍(借助turf.js)
let scaledPoly = turf.transformScale(poly, 5);



//计算扩大后的边界
let enveloped = turf.envelope(scaledPoly).bbox;
//指定网格大小
const gridSize = 0.03;
//划分为网格
let squareGrid = turf.squareGrid(enveloped, gridSize);
let interpolatingPoints = [];
const splitCount = 12;
let line = turf.lineString(squareGrid.features[0].geometry.coordinates[0]);
let lineLength = turf.length(line);
squareGrid.features.forEach((item, index) => {
let positions = item.geometry.coordinates[0];
let line = turf.lineString(positions);
//在每个网格轮廓上进行插值12个
for (let j = 1; j <= splitCount; j++) {
let along = turf.along(line, lineLength * (j / splitCount)).geometry
.coordinates;
interpolatingPoints.push(Cesium.Cartographic.fromDegrees(...along));
}
});



const maxSlope = 20;//可以通过的最大坡度
let startXy = [];//起点所在的网格序号
let endXy = [];//终点所在的网格序号
Cesium.sampleTerrainMostDetailed(
viewer.scene.terrainProvider,
interpolatingPoints
).then((updatePositions) => {
//计算每列有多少个网格(为了计算每个网格在整体网格中的序号(供A*算法使用))
let columnCount = Math.floor(
turf.distance(
turf.point([enveloped[0], enveloped[1]]),
turf.point([enveloped[0], enveloped[3]])
) / gridSize
);

let count = 0;
//为每个网格添加序号、坡角、中心点属性,并找出起点和终点的网格序号
for (let i = 0; i < updatePositions.length; i += splitCount) {
let group = updatePositions.slice(i, i + splitCount);
let sort = group.sort((a, b) => a.height - b.height);
let [minHeightPos, maxHeigthPos] = [sort.at(0), sort.at(-1)];
//获取中心点
let polygon = squareGrid.features[count];
let center = turf.centroid(polygon);
//计算斜坡角度
let line = turf.lineString([
[Cesium.Math.toDegrees(maxHeigthPos.longitude), Cesium.Math.toDegrees(maxHeigthPos.latitude)],
[Cesium.Math.toDegrees(minHeightPos.longitude), Cesium.Math.toDegrees(minHeightPos.latitude)],
]);
let edge1 = turf.length(line);
let edge2 = (maxHeigthPos.height - minHeightPos.height) / 1000;
//根据两条直角边的反正切值计算坡度
let slope = Cesium.Math.toDegrees(Math.atan(edge2 / edge1));
//计算每个网格的横纵向序号(供A*算法使用)
let x = Math.floor(count / columnCount);
let y = (x + 1) * columnCount - count - 1;
let currentGrid = squareGrid.features[count];
currentGrid.properties = {
slope: slope, //坡角
center: center.geometry.coordinates,//中心点
id: `${x}-${y}`,
x: x,//横向序号
y: y,//纵向序号
};
//计算起点和终点的二维xy坐标(turf.booleanPointInPolygon计算点是否在多边形内)
if (!startXy.length && turf.booleanPointInPolygon(turf.point(start), polygon)) {
startXy = { x, y };
}
if (!endXy.length && turf.booleanPointInPolygon(turf.point(end), polygon)) {
endXy = { x, y };
}
count += 1;//count+1开始计算下一个网格的相关属性
}


let allList = []; //所有网格
let openList = []; //待计算的网格
squareGrid.features.forEach(({ geometry: { coordinates }, properties }) => {
let obj = {
x: properties.x,
y: properties.y,
id: properties.id,
center: properties.center,
slope: properties.slope,
h: Math.sqrt(
Math.pow(properties.x - endXy.x, 2) +
Math.pow(properties.y - endXy.y, 2)
), //当前网格和终点距为未来预期代价
g: null, //当前网格和起点距离为历史代价
f: null,
parentId: null,
coordinates: coordinates[0].map((item) => [item[0], item[1]]),
};

if (properties.slope > maxSlope) {
obj.isInCloseList = 1; //障碍物就关闭,后面不再对该网格进行计算
} else {
obj.isInOpenList = 0; //该网格为待计算网格
}
allList.push(obj);
});
let startNode = allList.find(
(item) => item.x == startXy.x && item.y == startXy.y
);
startNode.g = 0;
startNode.isInOpenList = 1;
//计算好起点的代价后将它插入到openList中
openList.push(startNode);
let endNode = {};

while (openList.length) {
//根据代价逆序排序,从openList中获取到代价最小的网格(如果有多个代价相同的点,优先选择 g 值(历史代价)较小的网格,因为这更有可能导向最短路径。)
let sortedByF = openList.sort((a, b) => a.f - b.f);
let minFNodes = sortedByF.filter((item) => item.f == sortedByF[0].f);
let nodeCurrent = minFNodes.sort((a, b) => a.g - b.g)[0];
//获取代价最小的网格周围的网格
let childUp = allList.find(
(item) => item.x == nodeCurrent.x && item.y == nodeCurrent.y - 1
);
let childRight = allList.find(
(item) => item.x == nodeCurrent.x + 1 && item.y == nodeCurrent.y
);
let childDown = allList.find(
(item) => item.x == nodeCurrent.x && item.y == nodeCurrent.y + 1
);
let childLeft = allList.find(
(item) => item.x == nodeCurrent.x - 1 && item.y == nodeCurrent.y
);
let childList = [childUp, childRight, childDown, childLeft];
//只有当左边和上边不全是障碍物,才能走左上的网格
if (!childUp?.isInCloseList || !childLeft?.isInCloseList) {
let childLeftUp = allList.find(
(item) => item.x == nodeCurrent.x - 1 && item.y == nodeCurrent.y - 1
);
childList.push(childLeftUp);
}
//只有当右边和上边不全是障碍物,才能走右上的网格
if (!childUp?.isInCloseList || !childRight?.isInCloseList) {
let childRightUp = allList.find(
(item) => item.x == nodeCurrent.x + 1 && item.y == nodeCurrent.y - 1
);
childList.push(childRightUp);
}
//只有当右边和下边不全是障碍物,才能走右下的网格
if (!childDown?.isInCloseList || !childRight?.isInCloseList) {
let childRightDown = allList.find(
(item) => item.x == nodeCurrent.x + 1 && item.y == nodeCurrent.y + 1
);
childList.push(childRightDown);
}
//只有当左边和下边不全是障碍物,才能走左下的网格
if (!childDown?.isInCloseList || !childLeft?.isInCloseList) {
let childLeftDown = allList.find(
(item) => item.x == nodeCurrent.x - 1 && item.y == nodeCurrent.y + 1
);
childList.push(childLeftDown);
}
//遍历周围网格
for (let i = 0; i < childList.length; i++) {
let child = childList[i];
if (!child || child.isInCloseList == 1) {
//已经关闭,后面不再计算
continue;
}
//计算当前网格到它子网格的距离
let currentToChild = Math.sqrt(
Math.pow(nodeCurrent.x - child.x, 2) +
Math.pow(nodeCurrent.y - child.y, 2)
);
if (child.isInOpenList == 0) {
//从来没有被计算过,现在计算它的代价
child.parentId = nodeCurrent.id;
//子网格的历史代价是当前网格历史代价加上当前网格到子网格的距离
child.g = nodeCurrent.g + currentToChild;
//子网格的未来期望代价是子网格到终点的距离
child.h = Math.sqrt(
Math.pow(child.x - endXy.x, 2) + Math.pow(child.y - endXy.y, 2)
);
//得出最终代价
child.f = child.g + child.h;
//设置标记,表明这个子网格已经被计算过至少一次了
child.isInOpenList = 1;
openList.push(child); //将这个子网格加入到待计算列表中
} else if (child.isInOpenList == 1) {
//这个子网格被计算过
// 将子网格的父级替换为当前网格重新计算代价
let g = nodeCurrent.g + currentToChild;
//如果更换为新父级后代价比以前小,就更新一下
if (g < child.g) {
child.g = g;
child.f = child.g + child.h;
child.parentId = nodeCurrent.id;
}
}
//找到终点了,赋值后直接跳出
if (child.x == endXy.x && child.y == endXy.y) {
endNode = child;

let roadPath = [];
//溯源出路线
let currentNode = endNode;
while (currentNode) {
roadPath.push(currentNode.center);
currentNode = allList.find(({ id }) => id == currentNode.parentId);
}
let line = turf.lineString(roadPath);
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray(roadPath.flat()),
width: 5,
clampToGround: true,
material: Cesium.Color.fromCssColorString("red"),
},
});
break;
}
}
if (endNode.id) {
break;
}

//将当前网格从待计算列表中移除并将它关闭
let index = openList.findIndex(
({ x, y }) => x == nodeCurrent.x && y == nodeCurrent.y
);
openList[index].isInCloseList = 1;
openList.splice(index, 1);
}
if (!openList.length && !endNode.id) {
alert("无路可走");
}
})
}


</script>

</html>