diff --git a/app.py b/app.py index ec5505b..5de9272 100644 --- a/app.py +++ b/app.py @@ -40,182 +40,200 @@ gpxHandler = GPXHandler(session) driverHandler = DriverHandler(session) vehicleHandler = VehicleHandler(session) + @app.route('/') @cross_origin() def serve_vue_app(): return send_from_directory(app.static_folder, 'index.html') -@app.route("/track", methods=['GET']) + +@app.route("/track", methods=['GET', 'DELETE']) @cross_origin() def getTrack(): app.logger.debug(f"found arguments {request.args}") - - if ("start" in request.args and "end" in request.args) or ("id" in request.args): - if "start" in request.args and "end" in request.args: - if "asMap" in request.args: - # get tracks by filter - start = request.args["start"] - end = request.args["end"] - - # Die GeoJSON-Daten aus der Datenbank abrufen - geojson_data = gpxHandler.getTracksInTimeWithGeoData(start, end) - app.logger.debug(f"returned track {geojson_data}") - # Die GeoJSON-Daten als JSON zurückgeben - return jsonify(geojson_data) - - # get tracks by filter - start = request.args["start"] - end = request.args["end"] - try: - return gpxHandler.getTracksInTime(start, request.args["end"]), 200 - except Exception as e: - app.logger.debug(f"failed to search tracks error {e} values: start={start}, end={end}") - return f"error {e}", 500 + match request.method: + case "GET": + if ("start" in request.args and "end" in request.args) or ("id" in request.args): + if "start" in request.args and "end" in request.args: + if "asMap" in request.args: + # get tracks by filter + start = request.args["start"] + end = request.args["end"] - elif "id" in request.args: - # get track by id - trackID = int(request.args["id"]) - try: - app.logger.debug(f"Request args: {request.args}") - app.logger.debug(f"track id {trackID}") - track = gpxHandler.getTrack(trackID) - - app.logger.debug(f"returned track {track}") - - return jsonify(track), 200 - except Exception as e: - app.logger.debug(f"fetching track failed with error {e}") - return f"error {e}", 500 - else: - try: - tracks = gpxHandler.getTracks() - if len(tracks) > 0: - # gets all tracks as list - return tracks, 200 + # Die GeoJSON-Daten aus der Datenbank abrufen + geojson_data = gpxHandler.getTracksInTimeWithGeoData( + start, end) + app.logger.debug(f"returned track {geojson_data}") + + # Die GeoJSON-Daten als JSON zurückgeben + return jsonify(geojson_data) + + # get tracks by filter + start = request.args["start"] + end = request.args["end"] + try: + return gpxHandler.getTracksInTime(start, request.args["end"]), 200 + except Exception as e: + app.logger.debug(f"failed to search tracks error { + e} values: start={start}, end={end}") + return f"error {e}", 500 + + elif "id" in request.args: + # get track by id + trackID = int(request.args["id"]) + try: + app.logger.debug(f"Request args: {request.args}") + app.logger.debug(f"track id {trackID}") + track = gpxHandler.getTrack(trackID) + + app.logger.debug(f"returned track {track}") + + return jsonify(track), 200 + except Exception as e: + app.logger.debug(f"fetching track failed with error {e}") + return f"error {e}", 500 else: - return [], 200 - except Exception as e: - app.logger.debug(f"fetching all tracks failed with error {e}") - return f"error {e}", 500 + try: + tracks = gpxHandler.getTracks() + if len(tracks) > 0: + # gets all tracks as list + return tracks, 200 + else: + return [], 200 + except Exception as e: + app.logger.debug(f"fetching all tracks failed with error {e}") + return f"error {e}", 500 + case "DELETE": + pass + @app.route("/track/meta", methods=['GET']) @cross_origin() def getTrackMeta(): app.logger.debug(f"found arguments {request.args}") - - if "id" in request.args: - # get track by id - trackID = int(request.args["id"]) - try: - app.logger.debug(f"Request args: {request.args}") - app.logger.debug(f"track id {trackID}") - track = gpxHandler.getTrackMeta(trackID) - - app.logger.debug(f"returned track {track}") - - return jsonify(track), 200 - except Exception as e: - app.logger.debug(f"fetching track failed with error {e}") - return f"error {e}", 500 -@app.route("/driver", methods=['GET', 'POST']) + if "id" in request.args: + # get track by id + trackID = int(request.args["id"]) + try: + app.logger.debug(f"Request args: {request.args}") + app.logger.debug(f"track id {trackID}") + track = gpxHandler.getTrackMeta(trackID) + + app.logger.debug(f"returned track {track}") + + return jsonify(track), 200 + except Exception as e: + app.logger.debug(f"fetching track failed with error {e}") + return f"error {e}", 500 + + +@app.route("/driver", methods=['GET', 'POST', 'PUT', 'DELETE']) @cross_origin() def handleDriverRoute(): - if request.method == "GET": - if 'driver' in request.args: + match request.method: + case "GET": + if 'driver' in request.args: + try: + driver = driverHandler.getDriver(int(request.args["driver"])) + return driver, 200 + except Exception as e: + app.logger.debug(f"getting driver failed with error {e}") + return "error" + " " + str(e), 500 + + else: + + try: + drivers = driverHandler.getDrivers() + if len(drivers) > 0: + return drivers, 200 + except Exception as e: + app.logger.debug(f"getting drivers failed with error {e}") + return "error" + " " + str(e), 500 + case "POST": + # grabbing json from request + data = request.get_json() + app.logger.debug(f"json request payload: {data}") + + if "name" not in data: + app.logger.debug(f"no driver name was found in request") + return "missing name", 400 + + app.logger.debug(f"driver name has passed check {data["name"]}") + + # handle creating vehicle try: - driver = driverHandler.getDriver(int(request.args["driver"])) - return driver, 200 + app.logger.debug(f"json request name: {data["name"]}") + driver = driverHandler.createDriver(data["name"]) + return jsonify({"name": driver.name, "id": driver.id}), 200 + except Exception as e: - app.logger.debug(f"getting driver failed with error {e}") + app.logger.debug(f"creating drivers failed with error {e}") return "error" + " " + str(e), 500 - - else: - - try: - drivers = driverHandler.getDrivers() - if len(drivers) > 0: - return drivers, 200 - except Exception as e: - app.logger.debug(f"getting drivers failed with error {e}") - return "error" + " " + str(e), 500 - - elif request.method == "POST": - - # grabbing json from request - data = request.get_json() - app.logger.debug(f"json request payload: {data}") - - if "name" not in data: - app.logger.debug(f"no driver name was found in request") - return "missing name", 400 - - app.logger.debug(f"driver name has passed check {data["name"]}") - - # handle creating vehicle - try: - app.logger.debug(f"json request name: {data["name"]}") - driver = driverHandler.createDriver(data["name"]) - return jsonify({"name": driver.name, "id": driver.id}), 200 - - except Exception as e: - app.logger.debug(f"creating drivers failed with error {e}") - return "error" + " " + str(e), 500 + case "PUT": + pass + case "DELETE": + pass -@app.route("/vehicle", methods=['GET', 'POST']) +@app.route("/vehicle", methods=['GET', 'POST', 'PUT', 'DELETE']) @cross_origin() def handleVehicleRoute(): - if request.method == "GET": - if 'vehicle' in request.args: - app.logger.debug(f"no vehicle id was found in request") - vehicle = int(request.args["vehicle"]) + match request.method: + case "GET": + if 'vehicle' in request.args: + app.logger.debug(f"no vehicle id was found in request") + vehicle = int(request.args["vehicle"]) + try: + foundVehicle = vehicleHandler.getVehicle(vehicle) + return foundVehicle, 200 + except Exception as e: + app.logger.debug(f"getting vehicle {vehicle} failed with error {e}") + return "error" + " " + str(e), 500 + + else: + + try: + foundVehicles = vehicleHandler.getVehicles() + return foundVehicles, 200 + except Exception as e: + app.logger.debug( + f"getting all vehicles failed with error {e}") + return "error" + " " + str(e), 500 + case "POST": + data = request.get_json() + app.logger.debug(f"json request payload: {data}") + + if "name" not in data: + return "missing name", 400 + + licenseplate = "" + + if "licensePlate" not in data: + licenseplate = "N/A" + else: + licenseplate = data["licensePlate"] + name = data["name"] + + # handle creating vehicle try: - foundVehicle = vehicleHandler.getVehicle(vehicle) - return foundVehicle, 200 + vehicle = vehicleHandler.createVehicle(name, licenseplate) + return jsonify({"id": vehicle.id, "name": vehicle.name}), 200 + except Exception as e: - app.logger.debug(f"getting vehicle {vehicle} failed with error {e}") + app.logger.debug(f"creating vehicle with name {name} failed with error {e}") return "error" + " " + str(e), 500 - - else: - - try: - foundVehicles = vehicleHandler.getVehicles() - return foundVehicles, 200 - except Exception as e: - app.logger.debug(f"getting all vehicles failed with error {e}") - return "error" + " " + str(e), 500 - - elif request.method == "POST": - data = request.get_json() - app.logger.debug(f"json request payload: {data}") - - if "name" not in data: - return "missing name", 400 - - licenseplate = "" - - if "licensePlate" not in data: - licenseplate = "N/A" - else: - licenseplate = data["licensePlate"] - name = data["name"] - - # handle creating vehicle - try: - vehicle = vehicleHandler.createVehicle(name, licenseplate) - return jsonify({"id": vehicle.id, "name": vehicle.name}), 200 - - except Exception as e: - app.logger.debug(f"creating vehicle with name {name} failed with error {e}") - return "error" + " " + str(e), 500 + case "PUT": + pass + case "DELETE": + pass @app.route('/upload', methods=['POST']) @@ -231,29 +249,29 @@ def uploadFile(): return "no file selected", 400 try: - app.logger.debug(f"Received file: {file.filename}, size: {len(file.read())} bytes") - file.seek(0) # Setzt den Datei-Zeiger zurück, nachdem die Größe abgerufen wurde. - + + file.seek(0) + file_path = f'./uploads/{file.filename}' with open(file_path, 'wb') as f: f.write(file.read()) - + driverID = int(request.form.get('driverID')) vehicleID = int(request.form.get('vehicleID')) - + driver = driverHandler.getDriver(driverID) vehicle = vehicleHandler.getVehicle(vehicleID) - + app.logger.debug(f"driver {driver.id}") app.logger.debug(f"vehicle {vehicle.id}") - + if not driver or not vehicle: raise ValueError("Driver or vehicle not found") - + app.logger.debug(f"attempting to parse file: {file.filename}") gpxHandler.parse(file.filename, driver, vehicle) - + return "file stored succesfull", 200 except Exception as e: app.logger.debug(f"storing gpx file failed with error {e}") diff --git a/web/src/components/fileUpload.vue b/web/src/components/fileUpload.vue index f093a61..312bb92 100644 --- a/web/src/components/fileUpload.vue +++ b/web/src/components/fileUpload.vue @@ -1,214 +1,244 @@ \ No newline at end of file diff --git a/web/src/components/map.vue b/web/src/components/map.vue index d808b5c..2504dc2 100644 --- a/web/src/components/map.vue +++ b/web/src/components/map.vue @@ -4,174 +4,203 @@ import "leaflet/dist/leaflet.css"; import L from "leaflet"; type Track = { - id: number - name: string - driver: string - vehicle: { - name: string - licenseplate: string - } - distance: number + id: number + name: string + driver: string + vehicle: { + name: string + licenseplate: string + } + distance: number } export default defineComponent({ - name: 'Map', - props: { - track: { - type: Number, - default: 0, - require: true, - }, - geoJsonData: { - type: Object as PropType, - required: true - }, - multiple: { - type: Boolean, - required: true, - }, + name: 'Map', + props: { + track: { + type: Number, + default: 0, + require: true, + }, + geoJsonData: { + type: Object as PropType, + required: true + }, + multiple: { + type: Boolean, + required: true, + }, - }, - setup(props) { - const mapDiv = ref(null); // ref to map container element - const mapInstance = ref(null); // reference for the Leaflet map - const track: Ref = ref({ id: 0, name: "N/A", driver: "N/A", vehicle: { name: "N/A", licenseplate: "N/A" }, distance: 0.0 }) - const multiple:Ref = ref(props.multiple); + }, + setup(props) { + const mapDiv = ref(null); // ref to map container element + const mapInstance = ref(null); // reference for the Leaflet map + const track: Ref = ref({ id: 0, name: "N/A", driver: "N/A", vehicle: { name: "N/A", licenseplate: "N/A" }, distance: 0.0 }) + const multiple: Ref = ref(props.multiple); - const initializeMap = () => { - if (mapDiv.value) { - mapInstance.value = L.map(mapDiv.value, { - center: [51.4819, 7.2162], - zoom: 13, - }); + // values for UI Information distribution + const messageType: Ref = ref("None"); + const message: Ref = ref(""); - L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {}).addTo(mapInstance.value); - } - }; + const initializeMap = () => { + if (mapDiv.value) { + mapInstance.value = L.map(mapDiv.value, { + center: [51.4819, 7.2162], + zoom: 13, + }); - onMounted(() => { - console.log(props.geoJsonData) - initializeMap(); - }); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {}).addTo(mapInstance.value); + } + }; - const clearMap = () => { - if (mapInstance.value) { - mapInstance.value.eachLayer((layer: any) => { - if (!(layer instanceof L.TileLayer)) { - mapInstance.value.removeLayer(layer); - } - }); - } - }; + onMounted(() => { + console.log(props.geoJsonData) + initializeMap(); + }); - const getTrackMeta = async (id: number) => { + const clearMap = () => { + if (mapInstance.value) { + mapInstance.value.eachLayer((layer: any) => { + if (!(layer instanceof L.TileLayer)) { + mapInstance.value.removeLayer(layer); + } + }); + } + }; - const headers: Headers = new Headers() - headers.set('Content-Type', 'application/json') - headers.set('Accept', 'application/json') + const getTrackMeta = async (id: number) => { - const request: RequestInfo = new Request(`http://localhost:5000/track/meta?id=${id}`, { - method: "GET", - headers: headers - }) + const headers: Headers = new Headers() + headers.set('Content-Type', 'application/json') + headers.set('Accept', 'application/json') - var response = await fetch(request) - // make sure the request was successfull - if (response.ok) { - var jsonBody = await response.json() - console.log(jsonBody) - track.value = { - id: jsonBody["id"], - name: jsonBody["name"], - driver: jsonBody["driver"]["name"], - vehicle: { - name: jsonBody["vehicle"]["name"], - licenseplate: jsonBody["vehicle"]["licenseplate"] ?? "N/A" - }, - distance: jsonBody["distance"] - } - console.log(track.value) - } - } + const request: RequestInfo = new Request(`http://localhost:5000/track/meta?id=${id}`, { + method: "GET", + headers: headers + }) - watch(() => props.geoJsonData, (newData) => { - if (newData) { - console.log("loading GeoJSON:", newData); + var response = await fetch(request) + // make sure the request was successfull + if (response.ok) { + var jsonBody = await response.json() + console.log(jsonBody) + track.value = { + id: jsonBody["id"], + name: jsonBody["name"], + driver: jsonBody["driver"]["name"], + vehicle: { + name: jsonBody["vehicle"]["name"], + licenseplate: jsonBody["vehicle"]["licenseplate"] ?? "N/A" + }, + distance: jsonBody["distance"] + } + console.log(track.value) + } else { + messageType.value = "error"; + message.value = `"failed to load track meta with error: ${await response.statusText}`; + } + } - if (mapInstance.value) { - if (!props.multiple){ - clearMap(); - } + watch(() => props.geoJsonData, (newData) => { + if (newData) { + console.log("loading GeoJSON:", newData); - var geoJsonLayer = L.geoJSON(newData).addTo(mapInstance.value); + if (mapInstance.value) { + if (!props.multiple) { + clearMap(); + } - // pull meta data for for a single track - // function wont be called if multiple tracks are loaded - if (!props.multiple) { - getTrackMeta(props.track); - } + var geoJsonLayer = L.geoJSON(newData).addTo(mapInstance.value); - // move camera view according to loaded track - const bounds = geoJsonLayer.getBounds(); - mapInstance.value.fitBounds(bounds); - } else { - console.error("Map or GeoJSON data not available."); - } - } - }); + // pull meta data for for a single track + // function wont be called if multiple tracks are loaded + if (!props.multiple) { + getTrackMeta(props.track); + } - return { - mapDiv, - multiple, - track - }; - }, + // move camera view according to loaded track + const bounds = geoJsonLayer.getBounds(); + mapInstance.value.fitBounds(bounds); + } else { + console.error("Map or GeoJSON data not available."); + messageType.value = "error"; + message.value = `"Map or GeoJSON data not available.`; + } + } + }); + + return { + mapDiv, + multiple, + track, + message, + messageType + }; + }, }); + diff --git a/web/src/components/message.vue b/web/src/components/message.vue new file mode 100644 index 0000000..7d0230d --- /dev/null +++ b/web/src/components/message.vue @@ -0,0 +1,46 @@ + + + + + + \ No newline at end of file diff --git a/web/src/components/pointcloud.vue b/web/src/components/pointcloud.vue index 9d4fdbc..b3f1c5a 100644 --- a/web/src/components/pointcloud.vue +++ b/web/src/components/pointcloud.vue @@ -13,7 +13,7 @@ type Route = { name: string; id: number; time: Date; - driver: {id:number, name:string} + driver: { id: number, name: string } }; @@ -21,12 +21,12 @@ export default defineComponent({ emits: ['close', 'response'], name: 'settings', props: { - routes: { - type: Array as () => Route[], - default: () => [], // Standard: leeres Array - required: true, + routes: { + type: Array as () => Route[], + default: () => [], // Standard: leeres Array + required: true, + }, }, -}, setup(props, { emit }: SetupContext) { console.log(props) if (!props.routes || props.routes.length <= 0) alert("no points to show"); @@ -37,8 +37,12 @@ export default defineComponent({ const router = useRouter(); const legend = ref>({}); const driverColors = ref>({}); - var drivers:driver[] = []; - const points:Route[] = props.routes; + var drivers: driver[] = []; + const points: Route[] = props.routes; + + // values for UI Information distribution + const messageType: Ref = ref("None"); + const message: Ref = ref(""); // handles sending webrequests to the backend const getDrivers = async () => { @@ -72,18 +76,20 @@ export default defineComponent({ } } else { console.log(await response.text()) + messageType.value = "error"; + message.value = `failed to get drivers with error: ${await response.statusText}`; } } // assigns a driver to a point based on the points driver id - const assignDriverToPoint = (points:Route[]) => { + const assignDriverToPoint = (points: Route[]) => { return points.reduce((acc, point) => { - - const driverId = point.driver.id; - console.log(`driverid: ${driverId}`) - acc[driverId] = acc[driverId] || []; - acc[driverId].push(point); - return acc; + + const driverId = point.driver.id; + console.log(`driverid: ${driverId}`) + acc[driverId] = acc[driverId] || []; + acc[driverId].push(point); + return acc; }, {} as Record); } @@ -97,13 +103,13 @@ export default defineComponent({ console.log(`routes: ${props.routes}`) const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true, }); renderer.setSize(window.innerWidth, window.innerHeight); const controls = new OrbitControls(camera, renderer.domElement); const raycaster = new THREE.Raycaster(); - const mouse = new THREE.Vector2(); + const mouse = new THREE.Vector2(); const groupedPoints = assignDriverToPoint(points) @@ -112,11 +118,9 @@ export default defineComponent({ const colors: number[] = []; const totalPoints = points.length; - const offsetXStep = 2; // Wir setzen den Abstand pro Gruppe + const offsetXStep = 2; let offsetX = 0; - - // Berechnungen für den Mittelpunkt der Punktwolke let sumX = 0, sumY = 0, sumZ = 0; const getColorForDriver = (driverId: number) => { @@ -128,7 +132,12 @@ export default defineComponent({ return color; }; - // complicated math things + /** + * complicated math things (i am scared of this part) + * + * calculates a points position and ensures that all points grouped by the same driver are placed together + * a point is a uploaded track and during this step it gets applied its color material which was generated whiles getting drivers + */ Object.entries(groupedPoints).forEach(([driverIdStr, driverPoints], index) => { const driverId = parseInt(driverIdStr, 10); console.log("Driver Colors:", driverColors.value[driverIdStr]); @@ -137,7 +146,7 @@ export default defineComponent({ console.error(`Missing color for Driver ID: ${driverId}`); } - const color = new THREE.Color(driverColors.value[driverId]); + const color = new THREE.Color(driverColors.value[driverId]); console.log(`Color for Driver ${driverId}:`, driverColors.value[driverId]); // calculate offset base on driverid @@ -158,8 +167,8 @@ export default defineComponent({ colors.push(color.r, color.g, color.b); }); }); - - // Mittelpunkt der Punktwolke berechnen + + // calculate clouds centerpoint const centerX = sumX / totalPoints; const centerY = sumY / totalPoints; const centerZ = sumZ / totalPoints; @@ -194,7 +203,10 @@ export default defineComponent({ } animate(); - // handles mouse actions + /** + * handles mouse events and moves 3D space + * @param event the event triggered when the mouse is moved inside the canvas + */ const onMouseMove = (event: MouseEvent) => { if (!canvasRef.value || !tooltipRef.value) return; @@ -211,22 +223,24 @@ export default defineComponent({ tooltipRef.value.style.display = "block"; - // 3D-Koordinaten des Punktes holen + + /** + * getting coordinates and calculating 2D coordinates from them + * this is done for the tooltip to be positions at the cursor + */ const positionArray = pointCloud.geometry.attributes.position.array; const x = positionArray[intersectIndex * 3]; const y = positionArray[intersectIndex * 3 + 1]; const z = positionArray[intersectIndex * 3 + 2]; - // 3D-Koordinaten in 2D Bildschirmkoordinaten umrechnen const screenPosition = new THREE.Vector3(x, y, z); screenPosition.project(camera); - // Umrechnung von NDC (Normalized Device Coordinates) zu Pixeln const tooltipX = (screenPosition.x * 0.5 + 0.5) * window.innerWidth; const tooltipY = (screenPosition.y * -0.5 + 0.5) * window.innerHeight; - tooltipRef.value.style.left = `${tooltipX + 10}px`; // 10px Abstand zum Punkt - tooltipRef.value.style.top = `${tooltipY + 10}px`; // 10px Abstand zum Punkt + tooltipRef.value.style.left = `${tooltipX + 10}px`; + tooltipRef.value.style.top = `${tooltipY + 10}px`; tooltipText.value = `${point.driver.name}: ${point.name} (${point.time.toLocaleString()})`; } else { @@ -250,6 +264,8 @@ export default defineComponent({ tooltipText, legend, driverColors, + message, + messageType }; }, }); @@ -257,6 +273,7 @@ export default defineComponent({ \ No newline at end of file diff --git a/web/src/views/driver.vue b/web/src/views/driver.vue index 0e06653..f146a83 100644 --- a/web/src/views/driver.vue +++ b/web/src/views/driver.vue @@ -13,6 +13,11 @@ export default defineComponent({ const driverName: Ref = ref("") const driverList: Ref = ref([]) + // values for UI Information distribution + const messageType: Ref = ref(""); + const message: Ref = ref(""); + + // handles sending webrequests to the backend const getDrivers = async () => { @@ -39,6 +44,8 @@ export default defineComponent({ } } else { console.log(await response.text()) + messageType.value = "error"; + message.value = `upload failed: ${response.text()}`; } } @@ -64,6 +71,8 @@ export default defineComponent({ driverList.value.push({ id: jsonBody["body"], name: driverName.value }) } else { console.log(await response.text()) + messageType.value = "error"; + message.value = `upload failed: ${response.text()}`; } } @@ -71,7 +80,9 @@ export default defineComponent({ return { createDriver, driverName, - driverList + driverList, + message, + messageType }; }, }); @@ -79,6 +90,7 @@ export default defineComponent({