import json import os import math from flask import Flask, jsonify, request, render_template, escape app = Flask(__name__) app.template_folder = "frontend/" app.static_folder = "frontend/assets/" PATH = "graph.json" graph = {} def bellmann_ford(graph, node_a): """ Calculate distance from node_a to all other nodes assuming no negative cycles. Kommentar: Auch wenn Dijkstra der effizientere Algorithmus in diesem Falle wäre, habe mich dazu entschieden Bellmann Ford zu implementieren. Dijkstra musste ich schon mindestens 4 mal in meinem Studium/Schulzeit implementieren. Ich hatte mal wieder Lust auf Dynamische Programmierung. """ # weights from_a_to = {node: float("inf") for node in graph} from_a_to[node_a] = 0 # save path so we dont have to backtrace later path_from_a_to = {node: [] for node in graph} path_from_a_to[node_a] = [node_a] for i in range(len(graph.keys())-1): for s,e in [(s,e) for s in graph for e in graph[s]]: if from_a_to[s] + graph[s][e] < from_a_to[e]: path_from_a_to[e] = path_from_a_to[s] + [e] from_a_to[e] = from_a_to[s] + graph[s][e] return path_from_a_to def error(message): return jsonify({"error": message}) def edges_to_json(graph): return_list = [] done = [] for s in graph: for e in graph[s]: if (e,s) not in done: return_list.append({"start":s,"end":e,"weight":graph[s][e]}) done.append((s,e)) return return_list # ----------------------------------------------------------------------------- # ------------------------------API-------------------------------------------- # ----------------------------------------------------------------------------- @app.route('/api/paths//', methods=['GET']) def get_path(start, end): if start == end: return error("Start and end node must be different."), 400 if start not in graph: return error("Start node does not exist."), 400 if end not in graph: return error("End node does not exist."), 400 path = bellmann_ford(graph, start)[end] if len(path) == 0: return error("There exists no path."), 400 return jsonify({'path': path}) @app.route('/api/nodes', methods=['POST']) def create_node(): if not request.json: return error("Request needs to be JSON."), 400 if 'name' not in request.json: return error("Name must be set."), 400 name = request.json['name'] if name == "": return error("Name cannot be empty."), 400 if name in graph: return error(f'Node with name "{escape(name)}" already exist.'), 400 graph[name] = {} return jsonify(name), 201 @app.route('/api/edges', methods=['POST']) def create_edge(): if not request.json: return error("Request needs to be JSON."), 400 if 'start' not in request.json: return error("Start node must be set."), 400 if 'end' not in request.json: return error("End node must be set."), 400 if 'weight' not in request.json: return error("Edge weight must be set."), 400 start = request.json['start'] end = request.json['end'] weight = request.json['weight'] try: weight = float(weight) if math.isnan(weight) or math.isinf(weight): raise ValueError() except ValueError: return error("Weight must be a number."), 400 if weight < 0: return error("Weight must be positive."), 400 if start == end: return error("Start and end node must be different."), 400 if start not in graph: return error("Start node does not exist."), 400 if end not in graph: return error("End node does not exist."), 400 if start in graph and end in graph[start]: return error("Edge already exists."), 400 edge = {"start":start,"end":end, "weight": weight} graph[start][end] = weight graph[end][start] = weight return jsonify(edge), 201 @app.route("/save") def save(): try: with open(PATH, 'w') as f: json.dump(graph, f) return jsonify({"info":"Saved serverside graph",**graph}), 200 except Exception: return error("Internal Server Error"), 500 @app.route("/clear") def clear(): global graph graph = {} return jsonify({"info":"Graph cleared"}), 200 @app.route("/") def index(): return render_template("index.html", nodes=list(graph.keys()), edges=edges_to_json(graph)) if __name__ == '__main__': if os.path.isfile(PATH) and os.access(PATH, os.R_OK): with open(PATH, 'r') as f: graph = json.load(f) app.run(host="127.0.0.1", port=5000)