Skip to content

Commit

Permalink
update find path to all the end points
Browse files Browse the repository at this point in the history
  • Loading branch information
Gooong committed May 2, 2020
1 parent 2739333 commit b97dcf7
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 101 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ This algorithm finds the least cost path with given cost raster and points.
![Interface](example/images/interface.png)
![Result](example/images/result.png)

**Parameters:**

Please ensure all the input layers have the same CRS.
Please ensure all the input layers have the same CRS.

- Cost raster layer: Numeric raster layer that represents the cost of each spatial unit. It should not contains negative value. Pixel with `NoData` value represent it is unreachable.

- Cost raster band: The input band of the cost raster.

- Start-point layer: Layer that contains just one start point.

- End-point(s) layer: Layer that contains the destination point(s). If more than one destination are provided, the least cost path will connect start point with the nearest one.
- End-point(s) layer: Layer that contains the destination point(s).

- Only connect with the nearest end points: If more than one destination are provided, it will find the least cost path to all the end points by default. If enabled, the least cost path will only connect start point with the nearest end point.

- \[Optional\] Include liner referencing (PolylineM type): If selected, this algorithm will output the least cost path in `PolylineM` type, with the accumulated cost as linear referencing value.


Expand Down
121 changes: 83 additions & 38 deletions dijkstra_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@

from math import sqrt
import queue
import random
import collections

sqrt2 = sqrt(2)

def dijkstra(start_row_col, end_row_cols, block, feedback=None):
sqrt2 = sqrt(2)

def dijkstra(start_tuple, end_tuples, block, find_nearest, feedback=None):

class Grid:
def __init__(self, matrix):
self.map = matrix
self.h = len(matrix)
self.w = len(matrix[0])
self.manhattan_boundry = None
self.curr_boundry = None

def _in_bounds(self, id):
x, y = id
Expand All @@ -59,7 +62,7 @@ def neighbors(self, id):
x, y = id
results = [(x + 1, y), (x, y - 1), (x - 1, y), (x, y + 1),
(x + 1, y - 1), (x + 1, y + 1), (x - 1, y - 1), (x - 1, y + 1)]
results = filter(self.is_valid, results)
results = list(filter(self.is_valid, results))
return results

@staticmethod
Expand All @@ -68,8 +71,17 @@ def manhattan_distance(id1, id2):
x2, y2 = id2
return abs(x1 - x2) + abs(y1 - y2)

def min_manhattan(self, curr_node, end_nodes):
return min(map(lambda node: self.manhattan_distance(curr_node, node), end_nodes))
@staticmethod
def min_manhattan(curr_node, end_nodes):
return min(map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes))

@staticmethod
def max_manhattan(curr_node, end_nodes):
return max(map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes))

@staticmethod
def all_manhattan(curr_node, end_nodes):
return {end_node: Grid.manhattan_distance(curr_node, end_node) for end_node in end_nodes}

def simple_cost(self, cur, nex):
cx, cy = cur
Expand All @@ -81,63 +93,96 @@ def simple_cost(self, cur, nex):
else:
return sqrt2 * (currV + offsetV) / 2

result = []
grid = Grid(block)
end_row_cols = set(end_row_cols)

end_dict = collections.defaultdict(list)
for end_tuple in end_tuples:
end_dict[end_tuple[0]].append(end_tuple)
end_row_cols = set(end_dict.keys())
end_row_col_list = list(end_row_cols)
start_row_col = start_tuple[0]


frontier = queue.PriorityQueue()
frontier.put((0, start_row_col))
came_from = {}
cost_so_far = {}
decided = set()

if not grid.is_valid(start_row_col):
return None, None, None
if start_row_col in end_row_cols:
return None, None, None
return result

# update the progress bar
total_manhattan = grid.min_manhattan(start_row_col, end_row_col_list)
min_manhattan = total_manhattan
feedback.setProgress(100 * (1 - min_manhattan / total_manhattan))
# init progress
index = 0
distance_dic = grid.all_manhattan(start_row_col, end_row_cols)
if find_nearest:
total_manhattan = min(distance_dic.values())
else:
total_manhattan = sum(distance_dic.values())

total_manhattan = total_manhattan + 1
bound = total_manhattan
feedback.setProgress(1 + 100 * (1 - bound / total_manhattan))

came_from[start_row_col] = None
cost_so_far[start_row_col] = 0

current_node = None
while not frontier.empty():
current_cost, current_node = frontier.get()
_, current_node = frontier.get()
if current_node in decided:
continue
decided.add(current_node)

# update the progress bar
if feedback:
if feedback.isCanceled():
return None, None, None
curr_manhattan = grid.manhattan_distance(current_node, random.choice(end_row_col_list))
if curr_manhattan < min_manhattan:
min_manhattan = curr_manhattan
feedback.setProgress(100 * (1 - min_manhattan / total_manhattan))
return None

if current_node in end_row_cols:
break
index = (index + 1) % len(end_row_col_list)
target_node = end_row_col_list[index]
new_manhattan = grid.manhattan_distance(current_node, target_node)
if new_manhattan < distance_dic[target_node]:
if find_nearest:
curr_bound = new_manhattan
else:
curr_bound = bound - (distance_dic[target_node] - new_manhattan)

distance_dic[target_node] = new_manhattan

if curr_bound < bound:
bound = curr_bound
feedback.setProgress(1 + 100 * (1 - bound / total_manhattan)*(1 - bound / total_manhattan))

# reacn destination
if current_node in end_row_cols:
path = []
costs = []
traverse_node = current_node
while traverse_node is not None:
path.append(traverse_node)
costs.append(cost_so_far[traverse_node])
traverse_node = came_from[traverse_node]

# start point and end point overlaps
if len(path) == 1:
path.append(start_row_col)
costs.append(0.0)
path.reverse()
costs.reverse()
result.append((path, costs, end_dict[current_node]))

end_row_cols.remove(current_node)
end_row_col_list.remove(current_node)
if len(end_row_cols) == 0 or find_nearest:
break

# relax distance
for nex in grid.neighbors(current_node):
new_cost = cost_so_far[current_node] + grid.simple_cost(current_node, nex)
if nex not in cost_so_far or new_cost < cost_so_far[nex]:
cost_so_far[nex] = new_cost
frontier.put((new_cost, nex))
came_from[nex] = current_node

if current_node in end_row_cols:
end_node = current_node
least_cost = cost_so_far[current_node]
path = []
costs = []
while current_node is not None:
path.append(current_node)
costs.append(cost_so_far[current_node])
current_node = came_from[current_node]

path.reverse()
costs.reverse()
return path, costs, end_node
else:
return None, None, None
return result
Binary file modified example/images/interface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified example/images/result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b97dcf7

Please sign in to comment.