From 695eaf576e6d7db81ae651bab6e3bb94afc135ce Mon Sep 17 00:00:00 2001 From: steev Date: Sun, 5 Jan 2025 20:34:15 +0100 Subject: [PATCH] feat: :sparkles: implemented api --- class/driverHandler.py | 39 ++++++--- class/errors/NotFoundException.py | 7 ++ class/gpxInterpreter.py | 138 ++++++++++++++++++++++++++++-- class/model/geoObjects.py | 8 +- class/vehicleHandler.py | 22 ++++- 5 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 class/errors/NotFoundException.py diff --git a/class/driverHandler.py b/class/driverHandler.py index 78ac1d5..89243a9 100644 --- a/class/driverHandler.py +++ b/class/driverHandler.py @@ -2,28 +2,45 @@ from sqlalchemy.orm import Session from geoObjects import Driver + class DriverHandler: __dbSession: Session - - def __init__(self, session:Session): + + def __init__(self, session: Session): self.__dbSession = session pass - + # handles creating a driver in the database - def createDriver(self, name:str): - + def createDriver(self, name: str): + # makes sure that a name always is provided if not name: raise ValueError("name is empty") - + self.__dbSession.add(Driver(name=name)) self.__dbSession.commit() pass - + # handles getting a driver by its id from the database - def getDriver(self, id:int): - pass - + def getDriver(self, driverID: int): + driver = self.__dbSession.query(Driver).filter_by(id=driverID).first() + + return { + "id": driver.id, + "name": driver.name + } + # handles getting all drivers from the database def getDrivers(self): - pass \ No newline at end of file + drivers = self.__dbSession.query(Driver).all() + + driverList = [ + { + "id": driver.id, + "name": driver.name + } + # iterates all drivers and appends them to the list + for driver in drivers + ] + + return driverList diff --git a/class/errors/NotFoundException.py b/class/errors/NotFoundException.py new file mode 100644 index 0000000..450fcbb --- /dev/null +++ b/class/errors/NotFoundException.py @@ -0,0 +1,7 @@ +class NotFoundError(Exception): + def __init__(self, message, errors): + # Call the base class constructor with the parameters it needs + super().__init__(message) + + # Now for your custom code... + self.errors = errors \ No newline at end of file diff --git a/class/gpxInterpreter.py b/class/gpxInterpreter.py index 09e002f..8a16c11 100644 --- a/class/gpxInterpreter.py +++ b/class/gpxInterpreter.py @@ -1,6 +1,12 @@ +import datetime import gpxpy import gpxpy.gpx from sqlalchemy.orm import Session +from geojson import Feature, LineString +from geopy.distance import geodesic + +from errors.NotFoundException import NotFoundError +from geoObjects import Track, Waypoint class GPXHandler: __dbSession: Session @@ -10,14 +16,128 @@ class GPXHandler: pass # handles converting a gpx file into usable data - def parse(self, file): - self.gpx = gpxpy.parse(file) - pass - + def parse(self, file, driver, vehicle): + self.__gpx = gpxpy.parse(file) + + if not driver: + raise ValueError("no driver found") + + if not vehicle: + raise ValueError("no vehicle found") + + if not file: + raise ValueError("no file found") + + for track in self.__gpx.tracks: + # sets track name + # if no name is found at a track default to using date + trackName = track.name or f"Track-{datetime.now().isoformat()}" # todo using time.now might end up being misleading and to be reworked + + # initializes track values + self.__startTime, + self.__endTime, + self.__trackDistance, + self.__waypoints = None, None, 0, [] + + # grab all waypoints from a track + for segment in track.segments: + for point in segment.points: + self.__waypoints.append(point) + if start_time is None or point.time < start_time: + start_time = point.time + if end_time is None or point.time > end_time: + end_time = point.time + + # calculate distance between 2 waypoints + for i in range(1, len(self.__waypoints)): + total_distance += self.__waypoints[i - 1].distance_3d(self.__waypoints[i]) + + # push values to the database + track = Track( + trackName=trackName, + vehicle=vehicle.id, + driver=driver.id, + date=start_time.date() if start_time else None, + distance=total_distance, + speed=0, + start=start_time, + end=end_time + ) + + self.__dbSession.add(track) + self.__dbSession.commit() + + for point in self.__waypoints: + waypoint = Waypoint( + lat=point.latitude, + lon=point.longitude, + ele=point.elevation, + speed=None, + time=point.time, + track=track.id + ) + self.__dbSession.add(waypoint) + + self.__dbSession.commit() + # handles a route from db and converting it into geoJSON - def getRoute(self, route): - pass + def getTrack(self, trackID): + track = self.__dbSession.query(Track).filter_by(id=trackID).first() + if not track: + raise NotFoundError(f"track with id {trackID} not found") + + # fetches waypoints for a given track and converts them into geoJSON + waypoints = self.__dbSession.query(Waypoint).filter_by(track=track.id).all() + coordinates = [(wp.lon, wp.lat) for wp in waypoints] + feature = Feature(geometry=LineString(coordinates)) + return feature - # handles calculating the distance between two points - def calcDist(self, p1, p2): - pass \ No newline at end of file + # grabs only the tracks from the database and returns them as json object + def getTracks(self): + tracks = self.__dbSession.query(Track).all() + + track_list = [ + { + "id": track.id, + "name": track.trackName, + "driver": { + "id": track.driver.id, + "name": track.driver.name + } if track.driver else None, + "vehicle": { + "id": track.vehicle.id, + "name": track.vehicle.name + } if track.vehicle else None, + "distance": track.distance, + "start_time": track.start.isoformat() if track.start else None, + "end_time": track.end.isoformat() if track.end else None, + } + for track in tracks # iterates all tracks and appends them to the list + ] + + return track_list + + + def getTracksInTime(self, start, end): + tracks = self.__dbSession.query(Track).filter(Track.start.between(start, end)).all() + + track_list = [ + { + "id": track.id, + "name": track.trackName, + "driver": { + "id": track.driver.id, + "name": track.driver.name + } if track.driver else None, + "vehicle": { + "id": track.vehicle.id, + "name": track.vehicle.name + } if track.vehicle else None, + "distance": track.distance, + "start_time": track.start.isoformat() if track.start else None, + "end_time": track.end.isoformat() if track.end else None, + } + for track in tracks # iterates all tracks and appends them to the list + ] + + return track_list diff --git a/class/model/geoObjects.py b/class/model/geoObjects.py index 376d499..8a9ceef 100644 --- a/class/model/geoObjects.py +++ b/class/model/geoObjects.py @@ -12,6 +12,10 @@ def getBase(): def createTables(engine): Base.metadata.create_all(engine) + engine.add(Driver(name="default")) + engine.add(Vehicle(name="default")) + engine.commit() + class Track(Base): __tablename__ = 'track' id = Column(Integer, primary_key=True, autoincrement=True) @@ -25,8 +29,8 @@ class Track(Base): end = Column(DateTime, nullable=False) waypoints = relationship('Waypoint', backref='track', lazy=True) - driver = relationship('Driver', back_populates='track') - vehicle = relationship('Vehicle', back_populates='track') + driver = relationship('Driver', backref='track') + vehicle = relationship('Vehicle', backref='track') pass diff --git a/class/vehicleHandler.py b/class/vehicleHandler.py index 91e7909..31db030 100644 --- a/class/vehicleHandler.py +++ b/class/vehicleHandler.py @@ -22,9 +22,25 @@ class VehicleHandler: pass # handles getting a vehicle identified with its id from the database - def getVehicle(self, id:int): - pass + def getVehicle(self, vehicleID:int): + vehicle = self.__dbSession.query(Vehicle).filter_by(id=vehicleID).first() + + return { + "id": vehicle.id, + "name": vehicle.name + } # handles getting all vehicles from database def getVehicles(self): - pass \ No newline at end of file + vehicles = self.__dbSession.query(Vehicle).all() + + driverList = [ + { + "id": vehicle.id, + "name": vehicle.name + } + # iterates all drivers and appends them to the list + for vehicle in vehicles + ] + + return driverList