forked from tsunghao-huang/Python-Ports-Distance-Calculator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplanner.py
executable file
·177 lines (145 loc) · 7.12 KB
/
planner.py
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
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
from osgeo import gdal
from skimage.graph import route_through_array
import numpy as np
from geopy.distance import great_circle
import matplotlib.pyplot as plt
from port import Port
class Planner:
"""
A class to plan sea routes on a cost surface array.
"""
def __init__(self, _map_file):
"""
Initialize the Planner class, load the map and port data.
Args:
map_file (str): Path to the map file (GeoTIFF format).
ports_file (str): Path to the CSV file containing port data.
"""
# Load map data
self.dataset = gdal.OpenEx(_map_file, gdal.GA_ReadOnly)
if self.dataset is None:
raise FileNotFoundError(f"Failed to load map file: {_map_file}")
self.band = self.dataset.GetRasterBand(1)
self.map = self.band.ReadAsArray()
# Raster dimensions and geotransform
self.origin_x, self.pixel_width, _, self.origin_y, _, self.pixel_height = self.dataset.GetGeoTransform()
def coord_to_pixel_index(self, coord, pixel_x_offset):
"""
Converts geographic coordinates to pixel indices based on the map's origin and pixel dimensions.
Args:
coord (tuple): The coordinate to convert in the format (latitude, longitude).
pixel_x_offset (int): The amount to shift the x offset by.
Returns:
tuple: The x and y pixel indices.
"""
pixel_x = int((coord[1] - self.origin_x) /
self.pixel_width) + pixel_x_offset
# If the x index falls off the map, wrap around
if pixel_x < 0:
pixel_x += self.map.shape[1]
pixel_y = int((coord[0] - self.origin_y) / self.pixel_height)
return pixel_x, pixel_y
def cal_map_pixel_x_offset(self, start_coord, stop_coord):
"""
Calculate the horizontal shift for the map to avoid paths crossing the map boundary.
Args:
start_coord (tuple): The starting coordinate (latitude, longitude).
stop_coord (tuple): The stopping coordinate (latitude, longitude).
Returns:
int: The calculated x offset for the map.
"""
if abs(stop_coord[1] - start_coord[1]) < 180:
return -int(((start_coord[1] + stop_coord[1])) / 2 / self.pixel_width)
else:
return -int(((start_coord[1] + stop_coord[1] + 360)) / 2 / self.pixel_width)
def create_path(self, start_coord, stop_coord, draw_path=None):
"""
Create a path between two coordinates on a cost surface array.
Args:
start_coord (tuple): The starting coordinate (latitude, longitude).
stop_coord (tuple): The stopping coordinate (latitude, longitude).
draw_path (str, optional): Output file to save the path plot. Defaults to None.
Returns:
numpy.ndarray: The path coordinates as an array of shape (2, n), where n is the number of points in the path.
"""
pixel_x_offset = self.cal_map_pixel_x_offset(start_coord, stop_coord)
cost_surface_array = np.roll(self.map, pixel_x_offset)
# Convert coordinates to pixel indices
start_pixel_x, start_pixel_y = self.coord_to_pixel_index(
start_coord, pixel_x_offset)
stop_pixel_x, stop_pixel_y = self.coord_to_pixel_index(
stop_coord, pixel_x_offset)
# Find the path using a cost surface algorithm
indices, _ = route_through_array(
cost_surface_array,
(start_pixel_y, start_pixel_x),
(stop_pixel_y, stop_pixel_x),
fully_connected=True,
geometric=True
)
indices = np.array(indices).T
# Optionally draw the path on the map
if draw_path is not None:
plt.figure(figsize=(18, 9))
plt.imshow(cost_surface_array)
plt.plot(indices[1], indices[0], color="red")
plt.savefig(draw_path)
print("The path has been saved to", draw_path)
# Convert pixel indices back to geographic coordinates
indices = indices.astype(float)
indices[1] = indices[1] * self.pixel_width + self.origin_x
indices[0] = indices[0] * self.pixel_height + self.origin_y
return indices
def cal_distance_by_coordinates(self, start_coord, stop_coord, draw_path=None):
"""
Calculate the distance between two coordinates using the Haversine formula.
Args:
start_coord (tuple): The starting coordinate (latitude, longitude).
stop_coord (tuple): The ending coordinate (latitude, longitude).
draw_path (str, optional): Output file to save the path plot. Defaults to None.
Returns:
float: The calculated distance in kilometers.
"""
path_indices = self.create_path(start_coord, stop_coord, draw_path)
lat1, lon1 = path_indices[0][:-1], path_indices[1][:-1]
lat2, lon2 = path_indices[0][1:], path_indices[1][1:]
distances = np.vectorize(lambda a, b, c, d: great_circle(
(a, b), (c, d)).km)(lat1, lon1, lat2, lon2)
total_distance = np.sum(distances)
print(f"Total distance: {total_distance} km")
return total_distance
def cal_distance(self, _from: Port, _to: Port, draw_path=None):
"""
Calculate the distance between two ports.
Args:
_from (Port): The starting port object.
_to (Port): The stopping port object.
draw_path (str, optional): Output file to save the path plot. Defaults to None.
Returns:
float: The calculated distance in kilometers.
"""
print(f"From: {_from.code}, {_from.name}, {_from.coordinates}")
print(f"To: {_to.code}, {_to.name}, {_to.coordinates}")
return self.cal_distance_by_coordinates(_from.coordinates, _to.coordinates, draw_path)
if __name__ == "__main__":
# Get file paths
location = os.path.realpath(os.path.join(
os.getcwd(), os.path.dirname(__file__)))
map_file = os.path.join(location, "raw-data/map.tif")
draw_file = os.path.join(location, "sea-route.png")
# Initialize the Planner
planner = Planner(map_file)
port_HKGOM = Port(code="HKGOM", name="Hung Hom", name_wo_diacritics="Hung Hom",
function="-----6--", longitude=114.18333333333334, latitude=22.3)
port_CNPDG = Port(code="CNPDG", name="Pudong/Shanghai", name_wo_diacritics="Pudong/Shanghai",
function="1-3-----", longitude=121.5, latitude=31.233333333333334)
port_USZJI = Port(code="USZJI", name="Alpine, Los Angeles", name_wo_diacritics="Alpine, Los Angeles",
function="-23--6--", longitude=-118.1, latitude=34.53333333333333)
port_NLAMS = Port(code="NLAMS", name="Amsterdam", name_wo_diacritics="Amsterdam",
function="12345---", longitude=4.816666666666666, latitude=52.4)
port_CNNHN = Port(code="CNNHN", name="Wuhan", name_wo_diacritics="Wuhan",
function="12-45---", longitude=114.28333333333333, latitude=30.583333333333332)
planner.cal_distance(port_HKGOM, port_USZJI, draw_file)