commit 0b29bdab3dd04d9224af7d846a860029f442868f Author: Marco Thomas Date: Wed Jul 27 12:12:05 2022 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/classes.py b/classes.py new file mode 100644 index 0000000..66516af --- /dev/null +++ b/classes.py @@ -0,0 +1,85 @@ +from enum import Enum +import math + + +class ParsingMode(Enum): + LOCATIONS = 1 + FLIGHTSCHEDULE = 2 + FINDCONNECTION = 3 + + +class LocationType(Enum): + LOCATION = 1 + HALTESTELLE = 2 + FLUGHAFEN = 3 + + +class TransportData(): + def __init__(self, co2: float, speed: float, distance_penalty: float): + self.co2 = co2 + self.speed = speed + self.distance_penalty = distance_penalty + + +class TransportMethod(Enum): + """ + (co2 emission, speed, distance_penalty) + """ + WALK = TransportData(0, 4, 0.2) + CITY = TransportData(0.189, 30, 0.2) + AUTOBAHN = TransportData(0.189, 100, 0.2) + OEPNV = TransportData(0.055, 30, 0.1) + ICE = TransportData(0.055, 100, 0.1) + AIRPLANE = TransportData(0.2113, 900, 0.02) + + +class Coordinate: + def __init__(self, lat: float, long: float): + self.lat = lat + self.long = long + + def __str__(self): + return f"Lat: {self.lat}, Long: {self.long}" + + def __repr__(self): + return self.__str__() + + +class Location: + def __init__(self, coord: Coordinate, continent: int, name: str, type: LocationType): + self.coord = coord + self.continent = continent + self.name = name + self.type = type + + def __str__(self): + return f"{{Location: {self.coord}, continent: {self.continent}, name: {self.name}, type: {self.type}}}" + + def __repr__(self): + return self.__str__() + + def distance(self, loc2) -> float: + """ + Calculate the distance im km between two locations with the help of the + Seitenkosinussatz + """ + rErde = 6378.388 + lat1 = float(self.coord.lat) + long1 = float(self.coord.long) + lat2 = float(loc2.coord.lat) + long2 = float(loc2.coord.long) + inner = math.sin(lat1) * math.sin(lat2) + math.cos(lat1) * math.cos(lat2) * math.cos(long2 - long1) + return rErde * math.acos(inner) + + +class DataSet: + def __init__(self, locations: dict, flights: dict, connection: tuple): + self.locations = locations + self.flights = flights + self.connection = connection + + def __str__(self): + return f"locations: {self.locations}, flights: {self.flights}, connection: {self.connection}" + + def __repr__(self): + return self.__str__() diff --git a/dijkstra.py b/dijkstra.py new file mode 100644 index 0000000..1dec038 --- /dev/null +++ b/dijkstra.py @@ -0,0 +1,77 @@ +""" +Module, which holds the logic for the solving algorithms +""" + +from classes import DataSet, LocationType, Location, TransportMethod + + +def calc_co2(distance: float, method: TransportMethod) -> float: + """ + Return co2 emission in kg + """ + return (distance * method.value.distance_penalty) * method.value.co2 + + +def calc_time(distance: float, method: TransportMethod) -> float: + """ + Return time taking in h + """ + return (distance * method.value.distance_penalty) / method.value.speed + + +def add_node(graph: dict, frm: str, to: str, locs: dict, method: TransportMethod): + """ + Add a node into the graph + """ + distance = locs[frm].distance(locs[to]) + co2 = calc_co2(distance, method) + time = calc_time(distance, method) + new_connection: dict = {to: {"type": method, "co2": co2, "time": time}} + graph[frm].append(new_connection) + print(f"Added node from {frm} to {to} with type {method}.") + + +def create_graph(dataset: DataSet) -> dict: + """ + Creates the initial graph, with all edges + """ + locations = dataset.locations + graph: dict = {} + + # add nodes with no edges + for start in locations: + graph.update({start: []}) + + # add edges + for start in locations: + for dest in locations: + # skip, if we wouldnt go anywhere + if start == dest: + continue + print(f"Searching for nodes from {start} to {dest}...") + distance = locations[start].distance(locations[dest]) + + # Individualtransport + if distance <= 1: + add_node(graph, start, dest, locations, TransportMethod.WALK) + if distance <= 10 + 1: + add_node(graph, start, dest, locations, TransportMethod.CITY) + if distance <= 2000 + 10 + 1: + add_node(graph, start, dest, locations, TransportMethod.AUTOBAHN) + + # Train + if locations[start].type == LocationType.HALTESTELLE: + dest_type = locations[dest].type + # there are only trains between haltestellen or airports + if dest_type == LocationType.HALTESTELLE or dest_type == LocationType.FLUGHAFEN: + if distance <= 25: + add_node(graph, start, dest, locations, TransportMethod.OEPNV) + else: + add_node(graph, start, dest, locations, TransportMethod.ICE) + + # Flying + flights = dataset.flights + for flight in flights: + add_node(graph, start, dest, locations, TransportMethod.AIRPLANE) + + return graph diff --git a/file.txt b/file.txt new file mode 100644 index 0000000..cb1ed77 --- /dev/null +++ b/file.txt @@ -0,0 +1,13 @@ +Locations: +csu_zen ; Location; 48.176971; 11.5895754; 1; CSU-Zentrale +mun_hbf ; PublicTransportStop; 48.140235; 11.559417; 1; Muenchen Hbf. +mun_flugh; Airport; 48.140235; 11.770723; 1; Muenchen Flughafen +reichstag; Location; 52.518191; 13.3751725; 1; Reichstags Gebaeude +ber_flugh; Airport; 52.553625; 13.2901544; 1; Berlin Flughafen Tegel +ber_hbf ; PublicTransportStop; 52.524195; 13.3693013; 1; Berlin Hbf + +FlightSchedule: +mun_flugh; ber_flugh; 0; True + +FindBestConnections: +csu_zen; muc diff --git a/main.py b/main.py new file mode 100644 index 0000000..61d4073 --- /dev/null +++ b/main.py @@ -0,0 +1,21 @@ +""" +Run program from here. +Requires: python > 3.10.x +""" + +from parser import parse +from dijkstra import create_graph + + +INPUTFILE = "file.txt" + + +def main(): + dataset: dict = parse(INPUTFILE) + print(dataset) + graph: dict = create_graph(dataset) + print(graph) + + +if __name__ == "__main__": + main() diff --git a/parser.py b/parser.py new file mode 100644 index 0000000..b34f28d --- /dev/null +++ b/parser.py @@ -0,0 +1,78 @@ +""" +Parse the input file +""" + +from classes import ParsingMode, Coordinate, LocationType, Location, DataSet + + +def parse(filename: str) -> DataSet: + """ + Parse a given file with given format and return a DataSet containing the + parsed locations, flightschedules and wanted connection + """ + locations: dict = {} + flights: dict = {} + connection: tuple = () + with open(filename, "r") as file: + for line in file.readlines(): + line = line.replace("\n", "") # strip newline + line = line.replace(" ", "") # strip whitespaces + + # skip empty lines + if line == "": + continue + + # meta parsing + match line: + case "Locations:": + print("Parsing `Locations`...") + current_parsing = ParsingMode.LOCATIONS + continue + case "FlightSchedule:": + print("Parsing `FlightSchedule`...") + current_parsing = ParsingMode.FLIGHTSCHEDULE + continue + case "FindBestConnections:": + print("Parsing `FindBestConnections`...") + current_parsing = ParsingMode.FINDCONNECTION + continue + + match current_parsing: + case ParsingMode.LOCATIONS: + print("Parsing location...") + splitted = line.split(";") + assert(len(splitted) == 6) # make sure we have a location + id = splitted[0] + type = splitted[1] + match type: + case "Location": + type = LocationType.LOCATION + case "PublicTransportStop": + type = LocationType.HALTESTELLE + case "Airport": + type = LocationType.FLUGHAFEN + lat = splitted[2] + long = splitted[3] + continent = splitted[4] + name = splitted[5] + coord = Coordinate(lat, long) + location = Location(coord, continent, name, type) + locations.update({id: location}) + case ParsingMode.FLIGHTSCHEDULE: + print("Parsing flight schedule...") + splitted = line.split(";") + assert(len(splitted) >= 2) + id1 = splitted[0] + id2 = splitted[1] + if len(splitted) == 3: + stops = splitted[2] + else: + stops = 0 + flights.update({id1: {"to": id2, "stops": stops}}) + continue + case ParsingMode.FINDCONNECTION: + print("Parsing connection...") + splitted = line.split(";") + assert(len(splitted) == 2) + connection = (splitted[0], splitted[1]) + return DataSet(locations, flights, connection)