-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathairport_finder.py
135 lines (101 loc) · 5.27 KB
/
airport_finder.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
import argparse
import json
import math
from functools import lru_cache
from urllib.parse import urlencode
from urllib.request import urlopen
class AirportService:
def __init__(self, longitude, latitude):
self.location = MapCoordinate(longitude, latitude)
def get_nearest_airports_in_radius(self, radius):
offset_coo1 = self.location.get_offset_coordinate_by_distance(radius)
offset_coo2 = self.location.get_offset_coordinate_by_distance(-1 * radius)
airport_data = CloudantService.get_airports_in_coordinate_box(offset_coo2.longitude, offset_coo1.longitude,
offset_coo2.latitude, offset_coo1.latitude)
airports = []
for airport_dict in airport_data['rows']:
airports.append(
Airport(airport_dict['fields']['lon'], airport_dict['fields']['lat'], airport_dict['fields']['name']))
return sorted(airports, key=lambda airport: self.location.distance_from_coordinate(airport.location))
class Airport:
def __init__(self, longitude, latitude, name):
self.location = MapCoordinate(longitude, latitude)
self.name = name
def __str__(self):
return f'{self.name} - {self.location}'
class MapCoordinate:
EARTH_RADIUS = 6378137
_RADIAN = 180 / math.pi
def __init__(self, longitude, latitude):
self.longitude = longitude
self.latitude = latitude
def __str__(self):
return f'(longitude: {self.longitude}, latitude: {self.latitude})'
def get_offset_coordinate_by_distance(self, longitudinal_distance, latitudinal_distance=None):
if latitudinal_distance is None:
latitudinal_distance = longitudinal_distance
return MapCoordinate(self._calculate_longitude_offset_by_distance(longitudinal_distance),
self._calculate_latitude_offset_by_distance(latitudinal_distance))
@lru_cache(maxsize=128)
def distance_from_coordinate(self, other):
"""Haversine formula"""
longitudinal_distance = math.radians(other.longitude - self.longitude)
latitudinal_distance = math.radians(other.latitude - self.latitude)
a = math.sin(latitudinal_distance / 2) ** 2 + math.cos(math.radians(self.latitude)) \
* math.cos(math.radians(other.latitude)) * math.sin(longitudinal_distance / 2) ** 2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return self.EARTH_RADIUS * c
def _calculate_longitude_offset_by_distance(self, distance):
return self.longitude + (distance / self.EARTH_RADIUS) * self._RADIAN / math.cos(math.pi * self.latitude / 180)
def _calculate_latitude_offset_by_distance(self, distance):
return self.latitude + (distance / self.EARTH_RADIUS) * self._RADIAN
class CloudantService:
CLOUDANT_REST_API_URL = 'https://mikerhodes.cloudant.com/airportdb/_design/view1/_search/geo'
@staticmethod
def get_airports_in_coordinate_box(lon1, lon2, lat1, lat2):
cloudant_response = urlopen(CloudantService._build_url(lon1, lon2, lat1, lat2))
data = cloudant_response.read()
data_encoding = cloudant_response.info().get_content_charset('utf-8')
return json.loads(data.decode(data_encoding))
@staticmethod
def _build_url(lon1, lon2, lat1, lat2):
if lon1 > lon2:
lon1, lon2 = lon2, lon1
if lat1 > lat2:
lat1, lat2 = lat2, lat1
values = {'q': f'lon:[{lon1} TO {lon2}] AND lat:[{lat1} TO {lat2}]'}
data = urlencode(values)
return f'{CloudantService.CLOUDANT_REST_API_URL}?{data}'
def get_input_from_user(text):
while True:
try:
value = float(input(f'{text}: '))
except ValueError:
print(f'{text} must be an float value!')
else:
return value
def get_input_data():
print('Please provide the following information (radius in meters):')
longitude = get_input_from_user('Longitude')
latitude = get_input_from_user('Latitude')
radius = get_input_from_user('Radius')
return longitude, latitude, radius
def main():
parser = argparse.ArgumentParser(description='Find the closest airports in a radius of a coordinate.')
parser.add_argument('--longitude', type=float, help='a float value for the longitudinal part of the coordinate')
parser.add_argument('--latitude', type=float, help='a float value for the latitudinal part of the coordinate')
parser.add_argument('--radius', type=float,
help='a float value that represents the radius of the search area in meters')
args = parser.parse_args()
if args.longitude is None or args.latitude is None or args.radius is None:
longitude, latitude, radius = get_input_data()
else:
longitude, latitude, radius = args.longitude, args.latitude, args.radius
print(f'Finding airports sorted by distance in a radius of {radius:,.0f} meters from the coordinate '
f'longitude: {longitude}, latitude: {latitude}')
airport_service = AirportService(longitude, latitude)
airports = airport_service.get_nearest_airports_in_radius(radius)
for airport in airports:
print(f'{airport}: {airport_service.location.distance_from_coordinate(airport.location):,.0f} meter')
if __name__ == "__main__":
main()