Last active
July 30, 2019 01:04
-
-
Save pcchin/5b9a99e7d6afdebe0bdd064437f59c9b to your computer and use it in GitHub Desktop.
A simple Python program designed to calculate the distance between your hub airport and the destination airport and outputs them into the existing demand Excel file from am3.info. Openpyxl is required and you can use -h to see all the possible modifiers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import argparse | |
import traceback | |
import math | |
from openpyxl import load_workbook | |
A_UPPERCASE = ord('A') | |
ALPHABET_SIZE = 26 | |
def main(): | |
parser = argparse.ArgumentParser(description="Adds a column to an AM3 database that calculates the distance between" | |
" your base airport and the target airport. It is designed to work " | |
"with the default Demand.xlsx file from am3.info and uses openpyxl.") | |
parser.add_argument("airports", help="Custom location for the a CSV file containing the info of airports.") | |
parser.add_argument("excel", help="The location of the demand excel file.") | |
parser.add_argument("base", help="The base airport for your operations.") | |
parser.add_argument("-s", "--sheet", help="The sheet name to use to read and write the info. " | |
"Defaults to first sheet.", default="") | |
parser.add_argument("-sp", "--separator", help="The separator used to separate the values in the txt file. " | |
"Defaults to comma.", default=",") | |
parser.add_argument("-c", "--col", help="The column number to insert into the file. Starts from 1, defaults to 11.", | |
default=11, type=int) | |
parser.add_argument("-ix", "--icao_xlsx", help="The column in the xlsx file that shows the ICAO of the airports. " | |
"Starts with 1, defaults to 4.", default=4, type=int) | |
parser.add_argument("-it", "--icao_txt", help="The column in the txt file that shows the ICAO of the airports. " | |
"Starts with 0, defaults to 5.", default=5, type=int) | |
parser.add_argument("-p", "--pos", help="The element number for the list for the longitude of the points, which " | |
"should be followed by its latitude. Starts with 0, defaults to 6.", default=6, type=int) | |
args = parser.parse_args() | |
try: | |
wb = load_workbook(args.excel) | |
wb.template = False | |
try: | |
txt = open(args.airports, "r", encoding="utf8") | |
base_coords = [None, None] | |
# Get coordinates of target airport | |
for line in txt: | |
airport_data = line.split(args.separator) | |
if len(airport_data) > args.pos + 1: | |
# The start and end of each ICAO has to be stripped of " as it was downloaded as this way from the txt file. | |
icao = airport_data[args.icao_txt].lstrip("\"").rstrip("\"") | |
if icao == args.base: | |
try: | |
base_coords.insert(0, float(airport_data[args.pos])) | |
base_coords.insert(1, float(airport_data[args.pos + 1])) | |
except ValueError: | |
base_coords.insert(0, float(airport_data[args.pos + 1])) | |
base_coords.insert(1, float(airport_data[args.pos + 2])) | |
break | |
# Starts dict | |
if base_coords[0] is None or base_coords[1] is None: | |
print("Error: The coordinates for the target airport could not be found.") | |
else: | |
print("Coordinates of " + args.base + " is " + str(base_coords[0]) + ", " + str(base_coords[1])) | |
distance_dict = {} | |
earth_radius = 6378.1 | |
current_coords = [None, None] | |
coords_diff = [None, None] | |
txt.seek(0, 0) | |
for line in txt: | |
airport_data = line.split(",") | |
if len(airport_data) > args.pos + 1: | |
icao = airport_data[args.icao_txt].lstrip("\"").rstrip("\"") | |
print("Current airport in txt: " + icao, end="\r") | |
# Add each item to the dict | |
try: | |
current_coords.insert(0, float(airport_data[args.pos])) | |
current_coords.insert(1, float(airport_data[args.pos + 1])) | |
except ValueError: | |
current_coords.insert(0, float(airport_data[args.pos + 1])) | |
current_coords.insert(1, float(airport_data[args.pos + 2])) | |
# Calculates distance based on Haversine | |
coords_diff.insert(0, math.radians(current_coords[0] - base_coords[0])) | |
coords_diff.insert(1, math.radians(current_coords[1] - base_coords[1])) | |
a = (math.sin(coords_diff[0] / 2) ** 2) + ((math.sin(coords_diff[1] / 2) ** 2) * | |
math.cos(math.radians(current_coords[0])) * | |
math.cos(math.radians(base_coords[0]))) | |
dist = 2 * earth_radius * math.atan2(math.sqrt(a), math.sqrt(1 - a)) | |
distance_dict[icao] = dist | |
# Updates distance to xlsx file | |
if args.sheet == "": | |
target_ws = wb[wb.sheetnames[0]] | |
else: | |
target_ws = wb[args.sheet] | |
target_ws[base_10_to_alphabet(args.col) + "1"] = "Distance to " + args.base | |
row = 2 | |
current_icao_ref = base_10_to_alphabet(args.icao_xlsx) + str(row) | |
while target_ws[current_icao_ref].value is not None and target_ws[current_icao_ref].value != "": | |
# Stores values to xlsx file | |
print("Current airport in xlsx: ", target_ws[current_icao_ref].value, end="\r") | |
if target_ws[current_icao_ref].value in distance_dict: | |
target_ws[base_10_to_alphabet(args.col) + str(row)] = \ | |
distance_dict[target_ws[current_icao_ref].value] | |
row += 1 | |
current_icao_ref = base_10_to_alphabet(args.icao_xlsx) + str(row) | |
print("\nSaving xlsx file...") | |
wb.save(args.excel) | |
txt.close() | |
except FileNotFoundError: | |
print("") | |
traceback.print_exc() | |
print("Error: The txt file is not found.") | |
except FileNotFoundError: | |
print("") | |
traceback.print_exc() | |
print("Error: The excel file is not found.") | |
except TypeError: | |
print("") | |
traceback.print_exc() | |
print("The txt file provided is in an invalid format.") | |
raise SystemExit | |
def _decompose(number): | |
"""Generate digits from `number` in base alphabet, most significants | |
bits first. | |
""" | |
number -= 1 # Account for A in base alphabet being 1 in decimal rather than 0 | |
if number < ALPHABET_SIZE: | |
yield number | |
else: | |
number, remainder = divmod(number, ALPHABET_SIZE) | |
yield from _decompose(number) | |
yield remainder | |
# Base 10 to base 26 converter from StackOverflow | |
def base_10_to_alphabet(number): | |
"""Convert a decimal number to its base alphabet representation""" | |
return ''.join( | |
chr(A_UPPERCASE + part) | |
for part in _decompose(number) | |
) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment