I work for an airport, and my task is to calculate the taxiing routes for airplanes on taxiways, ensuring they follow the prescribed road sequence. Here, I'll give an example and would like to ask if there are any high-performance algorithms for calculating the shortest path while adhering to the specified route sequence, using Python.
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
G.add_edge("A1", "A2", weight=1, name="A")
G.add_edge("A2", "A3", weight=1, name="A")
G.add_edge("A3", "A4", weight=1, name="A")
G.add_edge("A4", "A5", weight=1, name="A")
G.add_edge("A5", "A6", weight=1, name="A")
G.add_edge("A2", "B1", weight=1, name="B")
G.add_edge("B1", "B2", weight=1, name="B")
G.add_edge("B2", "B3", weight=1, name="B")
G.add_edge("A6", "C1", weight=1, name="C")
G.add_edge("C1", "C2", weight=1, name="C")
G.add_edge("A6", "C3", weight=1, name="C")
G.add_edge("C3", "C4", weight=1, name="C")
G.add_edge("C4", "C2", weight=1, name="C")
G.add_edge("B2", "D1", weight=1, name="D")
G.add_edge("D1", "D2", weight=1, name="D")
G.add_edge("D2", "D3", weight=1, name="D")
G.add_edge("D3", "C2", weight=1, name="D")
G.add_edge("D1", "E1", weight=1, name="E")
G.add_edge("D2", "E1", weight=1, name="E")
G.add_edge("E1", "E2", weight=1, name="E")
# draw
pos = nx.spring_layout(G, seed=22)
nx.draw_networkx_nodes(G, pos, node_size=300)
nx.draw_networkx_edges(G, pos, width=1)
nx.draw_networkx_labels(G, pos, font_size=10, font_family="sans-serif")
edge_labels = nx.get_edge_attributes(G, "name")
nx.draw_networkx_edge_labels(G, pos, edge_labels)
ax = plt.gca()
ax.margins(0.0001)
plt.axis("off")
plt.tight_layout()
plt.show()
For example:
If source = "B3" and target = "E2", and the route sequence order = ["B", "A", "C", "D", "E"], then the calculated result should be: ["B3", "B2", "B1", "A2", "A3", "A4", "A5", "A6", "C1", "C2", "D3", "D2", "E1", "E2"].
If source = "A1" and target = "B3", and the route sequence order = ["A", "B", "D", "E", "D", "B"], then the calculated result should be: ["A1", "A2", "B1", "B2", "D1", "D2", "E1", "D1", "B2", "B3"].
I have written a function based on my own ideas, and it is currently working as expected. However, I think its performance can still be optimized. The current approach involves sequentially searching for feasible paths in the specified order and then calculating the weight of each feasible path to determine the optimal one.
import networkx as nx
def sequential_shortest_path(G, start_node, end_node, order, order_label, weight_label):
# Initialize the pathfinding paths index
pindex = 0
# Initialize the pathfinding paths
paths = {
pindex: {
"prev": "",
"node": start_node,
"npath": [start_node],
"visited": []
}
}
# Find the path in stages according to the sequence order
for k in range(1, len(order)):
# Current road name
now_way = order[k-1]
# Target road name
target_way = order[k]
# The paths to the target road has been reached
reach_paths = {}
# Infinite loop pathfinding until the paths reaches the target road
while True:
for i in list(paths.keys()):
prev = paths[i]["prev"]
node = paths[i]["node"]
neighbors = [neighbor for neighbor in G.neighbors(node) if neighbor != prev and neighbor not in paths[i]["visited"] and (G[node][neighbor][order_label] == now_way or G[node][neighbor][order_label] == target_way)]
if len(neighbors) == 0:
del paths[i]
elif len(neighbors) == 1:
neighbor = neighbors[0]
if G[node][neighbor][order_label] == target_way:
reach_paths[i] = {
"prev": node,
"node": neighbor,
"npath": paths[i]["npath"].copy() + [neighbor],
"visited": []
}
del paths[i]
else:
paths[i]["prev"] = node
paths[i]["node"] = neighbor
paths[i]["npath"].append(neighbor)
paths[i]["visited"].append(neighbor)
elif len(neighbors) > 1:
for neighbor in neighbors:
pindex += 1
if G[node][neighbor][order_label] == target_way:
reach_paths[pindex] = {
"prev": node,
"node": neighbor,
"npath": paths[i]["npath"].copy() + [neighbor],
"visited": []
}
else:
paths[pindex] = {
"prev": node,
"node": neighbor,
"npath": paths[i]["npath"].copy() + [neighbor],
"visited": paths[i]["visited"].copy() + [neighbor]
}
del paths[i]
if len(paths) == 0:
break
paths.update(reach_paths)
# Find the last section of the road to the end node
reach_paths = {}
while True:
for i in list(paths.keys()):
prev = paths[i]["prev"]
node = paths[i]["node"]
if node == end_node:
reach_paths[i] = {
"prev": prev,
"node": node,
"npath": paths[i]["npath"].copy()
}
del paths[i]
continue
neighbors = [neighbor for neighbor in G.neighbors(node) if neighbor != prev and neighbor not in paths[i]["visited"] and G[node][neighbor][order_label] == target_way]
if len(neighbors) == 0:
del paths[i]
elif len(neighbors) == 1:
neighbor = neighbors[0]
paths[i]["prev"] = node
paths[i]["node"] = neighbor
paths[i]["npath"].append(neighbor)
paths[i]["visited"].append(neighbor)
elif len(neighbors) > 1:
for neighbor in neighbors:
pindex += 1
paths[pindex] = {
"prev": node,
"node": neighbor,
"npath": paths[i]["npath"].copy() + [neighbor],
"visited": paths[i]["visited"].copy() + [neighbor],
}
del paths[i]
if len(paths) == 0:
break
# Best path
best_path = None
# Weight of the best path
best_weight = None
# Calculate the best path among the feasible paths
for i, path in reach_paths.items():
weight = nx.path_weight(G, path["npath"], weight_label)
if best_weight is None or weight < best_weight:
best_weight = weight
best_path = path["npath"]
return best_path, best_weight

