From 0aaf3be4c6aea889f49e89c9905583ab8bf09d1b Mon Sep 17 00:00:00 2001 From: Kevin Ebsen Date: Fri, 15 Sep 2023 19:22:29 +0200 Subject: [PATCH 1/5] #170: Using HTTPError instead of returning null in GeoJSONUtils --- Server/src/utils/geojsonUtils.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Server/src/utils/geojsonUtils.ts b/Server/src/utils/geojsonUtils.ts index 3de631e5..83affc67 100644 --- a/Server/src/utils/geojsonUtils.ts +++ b/Server/src/utils/geojsonUtils.ts @@ -1,3 +1,5 @@ +import { HTTPError } from "../models/error" + /** * Some utilities for simpler handling of GeoJSON */ @@ -21,11 +23,11 @@ export default class GeoJSONUtils { /** * Get track kilometer for given GeoJSON point (basically a wrapper for accessing this property) * @param point GeoJSON point to get the track kilometer for - * @returns track kilometer if available, `null` otherwise + * @returns track kilometer if available */ - public static getTrackKm(point: GeoJSON.Feature): number | null { + public static getTrackKm(point: GeoJSON.Feature): number { if (point.properties == null || point.properties["trackKm"] == null) { - return null + throw new HTTPError(`Could not get track kilometer of position ${JSON.stringify(point)}.`, 500) } return point.properties["trackKm"] } @@ -87,37 +89,37 @@ export default class GeoJSONUtils { /** * Parses JSON to a GeoJSON feature of a point (if possible) - * @param json JSON to parse - * @returns parsed GeoJSON feature or `null` if an error occured while parsing + * @param object JSON to parse + * @returns parsed GeoJSON feature */ - public static parseGeoJSONFeaturePoint(json: unknown): GeoJSON.Feature | null { - if (this.isGeoJSONFeaturePoint(json)) { - return json as GeoJSON.Feature - } else if (this.isGeoJSONPosition(json)) { + public static parseGeoJSONFeaturePoint(object: unknown): GeoJSON.Feature { + if (this.isGeoJSONFeaturePoint(object)) { + return object as GeoJSON.Feature + } else if (this.isGeoJSONPosition(object)) { // If we just have plain 2D coordinates, construct a point feature. const feature: GeoJSON.Feature = { type: "Feature", properties: {}, geometry: { type: "Point", - coordinates: json + coordinates: object } } return feature } - return null + throw new HTTPError(`Could not parse ${JSON.stringify(object)} as GeoJSON feature of point.`, 500) } /** * Try to parse anything to a GeoJSON feature collection of points (if possible) * @param object object to parse - * @returns parsed GeoJSON feature collection or `null` if an error occured while parsing + * @returns parsed GeoJSON feature collection */ - public static parseGeoJSONFeatureCollectionPoints(object: unknown): GeoJSON.FeatureCollection | null { + public static parseGeoJSONFeatureCollectionPoints(object: unknown): GeoJSON.FeatureCollection { if (this.isGeoJSONFeatureCollectionPoints(object)) { return object as GeoJSON.FeatureCollection } - return null + throw new HTTPError(`Could not parse ${JSON.stringify(object)} as GeoJSON feature collection of points.`, 500) } /** From 07a355de39f7b53993741a393e480b2a0239284b Mon Sep 17 00:00:00 2001 From: Kevin Ebsen Date: Fri, 15 Sep 2023 21:57:12 +0200 Subject: [PATCH 2/5] #170: Using HTTPError instead of returning null in services --- Server/src/services/poi.service.ts | 99 ++++++------------- Server/src/services/track.service.ts | 104 ++++++-------------- Server/src/services/vehicle.service.ts | 127 ++++++++++--------------- 3 files changed, 107 insertions(+), 223 deletions(-) diff --git a/Server/src/services/poi.service.ts b/Server/src/services/poi.service.ts index 38078215..474ed654 100644 --- a/Server/src/services/poi.service.ts +++ b/Server/src/services/poi.service.ts @@ -16,7 +16,7 @@ export default class POIService { * @param track `Track` the new POI belongs to, if no track is given, the closest will be chosen * @param description optional description of the new POI * @param isTurningPoint is the new POI a point, where one can turn around their vehicle (optional) - * @returns created `POI` if successful, `null` otherwise + * @returns created `POI` if successful */ public static async createPOI( position: GeoJSON.Feature, @@ -25,24 +25,18 @@ export default class POIService { track?: Track, description?: string, isTurningPoint?: boolean - ): Promise { + ): Promise { // TODO: check if poi is anywhere near the track // get closest track if none is given if (track == null) { - const tempTrack = await TrackService.getClosestTrack(position) - if (tempTrack == null) { - logger.error(`No closest track was found for position ${JSON.stringify(position)}.`) - return null - } - track = tempTrack + track = await TrackService.getClosestTrack(position) } // add kilometer value - const enrichedPoint = await this.enrichPOIPosition(position, track) - if (enrichedPoint == null) { - logger.error(`The position ${JSON.stringify(position)} could not be enriched.`) - return null - } + const enrichedPoint = await this.enrichPOIPosition( + position, + track ?? (await TrackService.getClosestTrack(position)) + ) // Note: geopos is from type GeoJSON.Feature and can't be parsed directly into Prisma.InputJsonValue // Therefore we cast it into unknown first. @@ -60,28 +54,19 @@ export default class POIService { * Add value of track kilometer to properties for a given point * @param point position of POI to enrich * @param track optional `TracK`, which is used to compute the track kilometer, if none is given the closest will be used - * @returns point with added track kilometer, `null` if not successful + * @returns point with added track kilometer */ public static async enrichPOIPosition( point: GeoJSON.Feature, track?: Track - ): Promise | null> { + ): Promise> { // initialize track if none is given if (track == null) { - const tempTrack = await TrackService.getClosestTrack(point) - if (tempTrack == null) { - logger.error(`No closest track was found for position ${JSON.stringify(point)}.`) - return null - } - track = tempTrack + track = await TrackService.getClosestTrack(point) } // calculate and set track kilometer - const trackKm = await TrackService.getPointTrackKm(point, track) - if (trackKm == null) { - logger.error(`Could not get track distance for position ${JSON.stringify(point)} on track with id ${track.uid}.`) - return null - } + const trackKm = TrackService.getPointTrackKm(point, track) GeoJSONUtils.setTrackKm(point, trackKm) return point } @@ -89,48 +74,30 @@ export default class POIService { /** * Wrapper to get distance of poi in kilometers along the assigned track * @param poi `POI` to get the distance for - * @returns track kilometer of `poi`, `null` if computation was not possible + * @returns track kilometer of `poi` */ - public static async getPOITrackDistanceKm(poi: POI): Promise { + public static async getPOITrackDistanceKm(poi: POI): Promise { // get closest track if none is given const poiPos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) - if (poiPos == null) { - logger.error(`Position ${JSON.stringify(poi.position)} could not be parsed.`) - return null - } // get track distance in kilometers let poiTrackKm = GeoJSONUtils.getTrackKm(poiPos) if (poiTrackKm == null) { - if (poiTrackKm == null) { - logger.info(`Position of POI with ID ${poi.uid} is not enriched yet.`) - // the poi position is not "enriched" yet. - // Therefore, obtain and typecast the position - const poiPos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) - if (poiPos == null) { - logger.error(`Position ${JSON.stringify(poi.position)} could not be parsed.`) - return null - } + logger.info(`Position of POI with ID ${poi.uid} is not enriched yet.`) + // the poi position is not "enriched" yet. + // Therefore, obtain and typecast the position + const poiPos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) - // get track of POI to enrich it - const track = await database.tracks.getById(poi.trackId) + // get track of POI to enrich it + const track = await database.tracks.getById(poi.trackId) - // then enrich it with the given track - const enrichedPos = await this.enrichPOIPosition(poiPos, track) - if (enrichedPos == null) { - logger.error(`Could not enrich position of POI with ID ${poi.uid}.`) - return null - } - // try to update the poi in the database, now that we have enriched it - await database.pois.update(poi.uid, { position: enrichedPos as unknown as Prisma.InputJsonValue }) + // then enrich it with the given track + const enrichedPos = await this.enrichPOIPosition(poiPos, track) + // try to update the poi in the database, now that we have enriched it + await database.pois.update(poi.uid, { position: enrichedPos as unknown as Prisma.InputJsonValue }) - // and re-calculate poiTrackKm (we do not care that much at this point if the update was successful) - poiTrackKm = GeoJSONUtils.getTrackKm(enrichedPos) - if (poiTrackKm == null) { - logger.error(`Could not get track kilometer of POI position ${JSON.stringify(enrichedPos)}.`) - return null - } - } + // and re-calculate poiTrackKm (we do not care that much at this point if the update was successful) + poiTrackKm = GeoJSONUtils.getTrackKm(enrichedPos) } return poiTrackKm } @@ -138,25 +105,15 @@ export default class POIService { /** * Compute distance of given POI as percentage along the assigned track * @param poi `POI` to compute distance for - * @returns percentage of track distance of `poi`, `null` if computation was not possible + * @returns percentage of track distance of `poi` */ - public static async getPOITrackDistancePercentage(poi: POI): Promise { + public static async getPOITrackDistancePercentage(poi: POI): Promise { // get track length const track = await database.tracks.getById(poi.trackId) - const trackLength = TrackService.getTrackLength(track) - if (trackLength == null) { - logger.error(`Length of track with id ${track.uid} could not be calculated.`) - return null - } - - const poiDistKm = await this.getPOITrackDistanceKm(poi) - if (poiDistKm == null) { - logger.error(`Could not get track kilometer of POI with ID ${poi.uid}.`) - return null - } // compute percentage + const poiDistKm = await this.getPOITrackDistanceKm(poi) return (poiDistKm / trackLength) * 100 } diff --git a/Server/src/services/track.service.ts b/Server/src/services/track.service.ts index 61a59ceb..4770aaa9 100644 --- a/Server/src/services/track.service.ts +++ b/Server/src/services/track.service.ts @@ -8,6 +8,7 @@ import * as turfMeta from "@turf/meta" import * as turfHelpers from "@turf/helpers" import bearing from "@turf/bearing" import { logger } from "../utils/logger" +import { HTTPError } from "../models/error" /** * Service for track management. This also includes handling the GeoJSON track data. @@ -18,7 +19,7 @@ export default class TrackService { * @param track `GeoJSON.FeatureCollection` of points of track, this has to be ordered * @param start starting location of the track * @param dest destination of track (currently in modelling start and end point do not differentiate) - * @returns `Track` if creation was successful, `null` otherwise + * @returns `Track` if creation was successful */ public static createTrack( track: GeoJSON.FeatureCollection, @@ -38,7 +39,7 @@ export default class TrackService { * @param path `GeoJSON.FeatureCollection` of points of track, this has to be ordered * @param start starting location of the track * @param dest destination of track (currently in modelling start and end point do not differentiate) - * @returns `Track` if creation was successful, `null` otherwise + * @returns `Track` if creation was successful */ public static updateTrack( track_uid: number, @@ -87,15 +88,11 @@ export default class TrackService { * Calculate projected track kilometer for a given position * @param position position to calculate track kilometer for (does not need to be on the track) * @param track `Track` to use for calculation - * @returns track kilometer of `position` projected on `track`, `null` if an error occurs + * @returns track kilometer of `position` projected on `track` */ - public static getPointTrackKm(position: GeoJSON.Feature, track: Track): number | null { + public static getPointTrackKm(position: GeoJSON.Feature, track: Track): number { // get the track kilometer value from projected point const projectedPoint = this.getProjectedPointOnTrack(position, track) - if (projectedPoint == null) { - logger.error(`Could not project position ${JSON.stringify(position)}.`) - return null - } return GeoJSONUtils.getTrackKm(projectedPoint) } @@ -103,20 +100,15 @@ export default class TrackService { * Calculate percentage value for given track kilometer of given track * @param trackKm track kilometer value to convert to percentage * @param track `Track` to use for calculation as reference - * @returns percentage value of `trackKm` regarding `track`, `null` if an error occurs + * @returns percentage value of `trackKm` regarding `track` */ - public static getTrackKmAsPercentage(trackKm: number, track: Track): number | null { + public static getTrackKmAsPercentage(trackKm: number, track: Track): number { // get total track length in kilometers const trackLength = this.getTrackLength(track) - if (trackLength == null) { - logger.error(`Could not compute track length from track with id ${track.uid} to convert track kilometer value.`) - return null - } // check if track kilometer is within bounds if (trackKm < 0 || trackKm > trackLength) { - logger.error(`Expected track kilometer to be between 0 and ${trackLength}, but got ${trackKm}.`) - return null + throw new HTTPError(`Expected track kilometer to be between 0 and ${trackLength}, but got ${trackKm}.`, 500) } // convert to percentage @@ -127,18 +119,14 @@ export default class TrackService { * Project a position onto a track * @param position position to project onto the track * @param track `Track` to project `position` onto - * @returns track point, which is the `position` projected onto `track`, enriched with a track kilometer value, `null` if an error occurs + * @returns track point, which is the `position` projected onto `track`, enriched with a track kilometer value */ public static getProjectedPointOnTrack( position: GeoJSON.Feature, track: Track - ): GeoJSON.Feature | null { + ): GeoJSON.Feature { // converting feature collection of points from track to linestring to project position onto it const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(track.data) - if (trackData == null) { - logger.error(`Could not parse track data of track with id ${track.uid}.`) - return null - } const lineStringData: GeoJSON.Feature = turfHelpers.lineString(turfMeta.coordAll(trackData)) // projecting point on linestring of track @@ -147,13 +135,13 @@ export default class TrackService { // for easier access we set the property of track kilometer to the already calculated value if (projectedPoint.properties["location"] == null) { - logger.error( + // this is a slight overreaction as we can still return the projected point, but the track kilometer property will not be accessible + throw new HTTPError( `Turf error: Could not calculate nearest point on line correctly for position ${JSON.stringify( position - )} and for linestring of track with id ${track.uid}.` + )} and for linestring of track with id ${track.uid}.`, + 500 ) - // this is a slight overreaction as we can still return the projected point, but the track kilometer property will not be accessible - return null } GeoJSONUtils.setTrackKm(projectedPoint, projectedPoint.properties["location"]) return projectedPoint @@ -163,31 +151,23 @@ export default class TrackService { * Calculate current heading of track for a given distance / track kilometer * @param track `Track` to get heading for * @param trackKm distance of `track` to get heading for - * @returns current heading (0-359) of `track` at distance `trackKm`, `null` if an error occurs + * @returns current heading (0-359) of `track` at distance `trackKm` */ - public static getTrackHeading(track: Track, trackKm: number): number | null { + public static getTrackHeading(track: Track, trackKm: number): number { // TODO quite inefficient? did not found anything from turf, that could do this in a simple way // TODO: maybe enrich track with bearing as well // validate track kilometer value const trackLength = this.getTrackLength(track) - if (trackLength == null) { - logger.error(`Length of track with id ${track.uid} could not be calculated.`) - return null - } if (trackKm < 0 || trackKm > trackLength) { - logger.error( - `Unexpected value for track kilometer: ${trackKm}. This needs to be more than 0 and less than ${trackLength}.` + throw new HTTPError( + `Unexpected value for track kilometer: ${trackKm}. This needs to be more than 0 and less than ${trackLength}.`, + 500 ) - return null } // get track data const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(track.data) - if (trackData == null) { - logger.error(`Could not parse track data of track with id ${track.uid}.`) - return null - } // check if we are right at the beginning of the track (not covered by loop below) if (trackKm == 0) { @@ -198,34 +178,27 @@ export default class TrackService { for (let i = 1; i < trackData.features.length; i++) { const trackPoint = trackData.features[i] const trackPointKm = GeoJSONUtils.getTrackKm(trackPoint) - if (trackPointKm == null) { - logger.error(`Could not access track kilometer value of track point ${i} of track with id ${track.uid}.`) - return null - } - - // actual check if (trackKm <= trackPointKm) { return bearing(trackData.features[i - 1], trackPoint) } } - logger.error( - `Track kilometer value ${trackKm} could not be found while iterating track points of track with id ${track.uid}.` + throw new HTTPError( + `Track kilometer value ${trackKm} could not be found while iterating track points of track with id ${track.uid}.`, + 500 ) - return null } /** * Look for the closest track for a given position * @param position position to search the closest track for - * @returns closest `Track` for `position` or `null` if an error occurs + * @returns closest `Track` for `position` */ - public static async getClosestTrack(position: GeoJSON.Feature): Promise { + public static async getClosestTrack(position: GeoJSON.Feature): Promise { const tracks = await database.tracks.getAll() // there are no tracks at all if (tracks.length == 0) { - logger.warn(`No track was found.`) - return null + throw new HTTPError(`No track was found.`, 404) } // find closest track by iterating @@ -233,10 +206,6 @@ export default class TrackService { let minTrack = -1 for (let i = 0; i < tracks.length; i++) { const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(tracks[i].data) - if (trackData == null) { - logger.error(`Could not parse track data of track with id ${tracks[i].uid}.`) - return null - } // converting feature collection of points to linestring to measure distance const lineStringData: GeoJSON.Feature = turfHelpers.lineString(turfMeta.coordAll(trackData)) @@ -260,8 +229,7 @@ export default class TrackService { // check if closest track was found if (minTrack < 0) { - logger.warn(`Somehow no closest track was found even after iterating all existing tracks.`) - return null + throw new HTTPError(`Somehow no closest track was found even after iterating all existing tracks.`, 500) } else { return tracks[minTrack] } @@ -271,37 +239,25 @@ export default class TrackService { * Get total distance for a given track. This is just for easier access as the track kilometer * of the last track point is essentially the length of the track. * @param track `Track` to get the length of - * @returns lenth of `track` in kilometers if possible, `null` otherwise (this could be caused by invalid track data) + * @returns lenth of `track` in kilometers if possible */ - public static getTrackLength(track: Track): number | null { + public static getTrackLength(track: Track): number { // load track data const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(track.data) - if (trackData == null) { - logger.error(`Could not parse track data of track with id ${track.uid}.`) - return null - } // get last points track kilometer const trackPointsLength = trackData.features.length const trackLength = GeoJSONUtils.getTrackKm(trackData.features[trackPointsLength - 1]) - if (trackLength == null) { - logger.error(`Could not access track kilometer value of last track point of track with id ${track.uid}.`) - return null - } return trackLength } /** * Wrapper for converting internal presentation of track data as points to a linestring * @param track `Track` to get linestring for - * @returns GeoJSON feature of a linestring. This only contains pure coordinates (i.e. no property values). `null` if an error occured. + * @returns GeoJSON feature of a linestring. This only contains pure coordinates (i.e. no property values). */ - public static getTrackAsLineString(track: Track): GeoJSON.Feature | null { + public static getTrackAsLineString(track: Track): GeoJSON.Feature { const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(track.data) - if (trackData == null) { - logger.error(`Could not parse track data of track with id ${track.uid}.`) - return null - } return turfHelpers.lineString(turfMeta.coordAll(trackData)) } } diff --git a/Server/src/services/vehicle.service.ts b/Server/src/services/vehicle.service.ts index e593dad1..9f43c783 100644 --- a/Server/src/services/vehicle.service.ts +++ b/Server/src/services/vehicle.service.ts @@ -23,11 +23,11 @@ import { Feature, GeoJsonProperties, Point } from "geojson" export type VehicleData = { vehicle: Vehicle position: GeoJSON.Feature - trackKm?: number - percentagePosition?: number + trackKm: number + percentagePosition: number speed: number heading: number - direction?: -1 | 1 + direction: -1 | 1 } /** Service for vehicle management. */ @@ -107,9 +107,6 @@ export default class VehicleService { let position: Feature | null = null if (trackerLogs.length > 0) { position = GeoJSONUtils.parseGeoJSONFeaturePoint(trackerLogs[0].position) - if (position == null) { - throw new HTTPError(`Could not parse position ${JSON.stringify(trackerLogs[0].position)}.`, 500) - } } // get heading and speed @@ -126,6 +123,7 @@ export default class VehicleService { position = TrackService.getProjectedPointOnTrack(position, track) } else { // compute position and track kilometer as well as percentage value + // TODO: try-catch and fallback (should be done in #169) position = this.computeVehiclePosition(trackerLogs, appLogs, heading, speed, track) } @@ -134,22 +132,12 @@ export default class VehicleService { } const trackKm = GeoJSONUtils.getTrackKm(position) - if (trackKm == null) { - throw new HTTPError(`Could not get track kilometer of position ${JSON.stringify(position)}.`, 500) - } - const percentagePosition = TrackService.getTrackKmAsPercentage(trackKm, track) - if (percentagePosition == null) { - throw new HTTPError( - `Could not compute percentage position for track kilometer ${trackKm} on track with id ${track.uid}.`, - 500 - ) - } return { vehicle, position, trackKm, - percentagePosition, + percentagePosition: TrackService.getTrackKmAsPercentage(trackKm, track), heading, speed, direction: this.computeVehicleTravellingDirection(trackKm, heading, track) @@ -164,7 +152,7 @@ export default class VehicleService { * @param vehicleSpeed heading of vehicle (>= 0), can be obtained with `getVehicleSpeed` * @param track `Track` assigned to the vehicle * @returns computed position of the vehicle based on log data, besides the GeoJSON point there is - * also the track kilometer in the returned GeoJSON properties field (could be null if an error occurs) + * also the track kilometer in the returned GeoJSON properties field */ private static computeVehiclePosition( trackerLogs: Log[], @@ -172,13 +160,8 @@ export default class VehicleService { vehicleHeading: number, vehicleSpeed: number, track: Track - ): GeoJSON.Feature | null { + ): GeoJSON.Feature { const lineStringData = TrackService.getTrackAsLineString(track) - if (lineStringData == null) { - logger.error(`Could not convert track with id ${track.uid} to a linestring.`) - // fallback - return GeoJSONUtils.parseGeoJSONFeaturePoint(trackerLogs[0].position) - } // add a weight to the tracker logs const weightedTrackerLogs = this.addWeightToLogs(trackerLogs, lineStringData) @@ -191,17 +174,17 @@ export default class VehicleService { // now it is unlikely, that weights can be added to the app logs, but we could at least try it logger.warn(`Could not add any weights to tracker logs.`) } else { - const tempWeightedTrackKm = this.weightedLogsToWeightedTrackKm( - weightedTrackerLogs, - vehicleSpeed, - vehicleHeading, - track - ) - if (tempWeightedTrackKm == null) { - // (if this does not work we can still try app logs, though it is also unlikely to work) - logger.warn(`Could not convert weighted tracker logs to weighted track kilometers.`) - } else { + let tempWeightedTrackKm + try { + tempWeightedTrackKm = this.weightedLogsToWeightedTrackKm( + weightedTrackerLogs, + vehicleSpeed, + vehicleHeading, + track + ) weightedTrackKm.push(...tempWeightedTrackKm) + } catch (err) { + logger.warn(`Could not convert weighted tracker logs to weighted track kilometers.`) } } @@ -211,16 +194,16 @@ export default class VehicleService { logger.warn(`Could not add any weights to app logs.`) } else { // try adding them to the list as well - const tempWeightedTrackKm = this.weightedLogsToWeightedTrackKm( - weightedAppLogs, - vehicleSpeed, - vehicleHeading, - track - ) - if (tempWeightedTrackKm == null) { - logger.warn(`Could not convert weighted app logs to weighted track kilometers.`) - } else { + try { + const tempWeightedTrackKm = this.weightedLogsToWeightedTrackKm( + weightedAppLogs, + vehicleSpeed, + vehicleHeading, + track + ) weightedTrackKm.push(...tempWeightedTrackKm) + } catch (err) { + logger.warn(`Could not convert weighted app logs to weighted track kilometers.`) } } @@ -232,10 +215,6 @@ export default class VehicleService { // build average track kilometer value const avgTrackKm = this.averageWeightedTrackKmValues(weightedTrackKm) - if (avgTrackKm == null) { - logger.info(`Could not compute average track kilometer. Perhaps the logs were not recent or accurate enough.`) - return GeoJSONUtils.parseGeoJSONFeaturePoint(trackerLogs[0].position) - } // in the end we just need to turn the track kilometer into a position again const avgPosition = along(lineStringData, avgTrackKm) @@ -259,7 +238,7 @@ export default class VehicleService { ): [number, number][] { // just need to check this for the next step if (weightedLogs.length === 0) { - return [] + throw new HTTPError(`Expected at least one log for converting to track kilometer`, 500) } // vehicle should be the same for all logs @@ -277,8 +256,10 @@ export default class VehicleService { } // get last known track kilometer - const lastTrackKm = this.getTrackKmFromLog(weightedLogs[i][0], track) - if (lastTrackKm == null) { + let lastTrackKm + try { + lastTrackKm = this.getTrackKmFromLog(weightedLogs[i][0], track) + } catch (err) { logger.warn(`Could not compute last track kilometer for last log with id ${weightedLogs[i][0].uid}.`) continue } @@ -299,26 +280,14 @@ export default class VehicleService { * Compute track kilometer for a position of a given log * @param log `Log` to compute track kilometer for * @param track related track of `log` - * @returns track kilometer value for `log`, `null` if an error occurs + * @returns track kilometer value for `log` */ - private static getTrackKmFromLog(log: Log, track: Track): number | null { + private static getTrackKmFromLog(log: Log, track: Track): number { // get position from log const logPosition = GeoJSONUtils.parseGeoJSONFeaturePoint(log.position) - if (logPosition == null) { - logger.error(`Position ${JSON.stringify(log.position)} could not be parsed.`) - return null - } // compute track kilometer for this position - const logTrackKm = TrackService.getPointTrackKm(logPosition, track) - if (logTrackKm == null) { - logger.error( - `Could not compute track kilometer for position ${JSON.stringify(logPosition)} and track with id ${track.uid}.` - ) - return null - } - - return logTrackKm + return TrackService.getPointTrackKm(logPosition, track) } /** @@ -350,8 +319,10 @@ export default class VehicleService { timeWeight = timeWeight > 0 ? timeWeight : 0 // get position from log - const logPosition = GeoJSONUtils.parseGeoJSONFeaturePoint(logs[i].position) - if (logPosition == null) { + let logPosition + try { + logPosition = GeoJSONUtils.parseGeoJSONFeaturePoint(logs[i].position) + } catch (err) { logger.warn(`Position ${JSON.stringify(logs[i].position)} could not be parsed.`) continue } @@ -378,16 +349,18 @@ export default class VehicleService { /** * Build average of weighted track kilometer values * @param weightedTrackKms list of track kilometer values (first) with their related positive weight (second) - * @returns averaged track kilometer value of `weightedTrackKms`, null if an error occurs + * @returns averaged track kilometer value of `weightedTrackKms` */ - private static averageWeightedTrackKmValues(weightedTrackKms: [number, number][]): number | null { + private static averageWeightedTrackKmValues(weightedTrackKms: [number, number][]): number { // calculate total of all weights let weightSum = 0 for (let i = 0; i < weightedTrackKms.length; i++) { // check if weight is negative (this could result in unwanted behaviour) if (weightedTrackKms[i][1] < 0) { - logger.error(`Expected positive weights for track kilometer values only, but got ${weightedTrackKms[i][1]}.`) - return null + throw new HTTPError( + `Expected positive weights for track kilometer values only, but got ${weightedTrackKms[i][1]}.`, + 500 + ) } weightSum += weightedTrackKms[i][1] @@ -395,8 +368,10 @@ export default class VehicleService { // avoid divide by zero if (weightSum == 0) { - logger.error(`All weights for track kilometers were 0`) - return null + throw new HTTPError( + `Expected at least one weight to be greater than 0 while computing average track kilometer.`, + 500 + ) } // normalizing weights and averaging track kilometer values @@ -441,8 +416,8 @@ export default class VehicleService { /** * Determine travelling direction of a vehicle related to its track (either "forward" or "backward") - * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` * @param trackKm track kilometer at which the vehicle currently is (can be found with `VehicleService.getVehicleTrackDistanceKm`) + * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` * @param track track to compute the direction of a vehicle with * @returns 1 or -1 if the vehicle is heading towards the end and start of the track respectively */ @@ -450,10 +425,6 @@ export default class VehicleService { // TODO: needs improvements (probably with #118 together), should be independent from track kilometer (position needs to be computed for that) // compute track heading const trackBearing = TrackService.getTrackHeading(track, trackKm) - // TODO - if (trackBearing == null) { - throw new HTTPError(`Could not compute heading of track with id ${track.uid} at track kilometer ${trackKm}.`, 500) - } // TODO: maybe give this a buffer of uncertainty if (vehicleHeading - trackBearing < 90 || vehicleHeading - trackBearing > -90) { return 1 From 5c5af57a2e20677bfc7fa84ad44ee8d07d7c0c2c Mon Sep 17 00:00:00 2001 From: Kevin Ebsen Date: Sat, 16 Sep 2023 19:14:21 +0200 Subject: [PATCH 3/5] #170: Added JSDoc for throwing errors --- Server/src/services/poi.service.ts | 29 ++++++++++++++++---- Server/src/services/track.service.ts | 34 ++++++++++++++++++++++- Server/src/services/vehicle.service.ts | 38 ++++++++++++++++++++++++++ Server/src/utils/geojsonUtils.ts | 3 ++ 4 files changed, 97 insertions(+), 7 deletions(-) diff --git a/Server/src/services/poi.service.ts b/Server/src/services/poi.service.ts index 474ed654..7ddca5e0 100644 --- a/Server/src/services/poi.service.ts +++ b/Server/src/services/poi.service.ts @@ -17,6 +17,10 @@ export default class POIService { * @param description optional description of the new POI * @param isTurningPoint is the new POI a point, where one can turn around their vehicle (optional) * @returns created `POI` if successful + * @throws `HTTPError` + * - if the closest track could not be computed (if none is given) + * - if the position could not be enriched + * @throws PrismaError, if saving the created POI to database was not possible */ public static async createPOI( position: GeoJSON.Feature, @@ -33,10 +37,7 @@ export default class POIService { } // add kilometer value - const enrichedPoint = await this.enrichPOIPosition( - position, - track ?? (await TrackService.getClosestTrack(position)) - ) + const enrichedPoint = await this.enrichPOIPosition(position, track) // Note: geopos is from type GeoJSON.Feature and can't be parsed directly into Prisma.InputJsonValue // Therefore we cast it into unknown first. @@ -55,6 +56,9 @@ export default class POIService { * @param point position of POI to enrich * @param track optional `TracK`, which is used to compute the track kilometer, if none is given the closest will be used * @returns point with added track kilometer + * @throws `HTTPError` + * - if the track kilometer value could not be computed + * - if the closest track could not be computed (if none is given) */ public static async enrichPOIPosition( point: GeoJSON.Feature, @@ -75,14 +79,23 @@ export default class POIService { * Wrapper to get distance of poi in kilometers along the assigned track * @param poi `POI` to get the distance for * @returns track kilometer of `poi` + * @throws `HTTPError` + * - if the track kilometer value of `poi` could not be accessed after trying to enrich it + * - if the position of `poi` could not be parsed + * - if the position of `poi` could not be enriched + * @throws PrismaError + * - if accessing the track of `poi` from the database was not possible + * - if updating `poi` in the database was not possible */ public static async getPOITrackDistanceKm(poi: POI): Promise { // get closest track if none is given const poiPos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) // get track distance in kilometers - let poiTrackKm = GeoJSONUtils.getTrackKm(poiPos) - if (poiTrackKm == null) { + let poiTrackKm + try { + poiTrackKm = GeoJSONUtils.getTrackKm(poiPos) + } catch (err) { logger.info(`Position of POI with ID ${poi.uid} is not enriched yet.`) // the poi position is not "enriched" yet. // Therefore, obtain and typecast the position @@ -106,6 +119,10 @@ export default class POIService { * Compute distance of given POI as percentage along the assigned track * @param poi `POI` to compute distance for * @returns percentage of track distance of `poi` + * @throws `HTTPError` + * - if the track length could not be computed + * - if the track kilometer of `poi` could not be computed + * @throws PrismaError, if the track of `poi` could not be accessed in the database */ public static async getPOITrackDistancePercentage(poi: POI): Promise { // get track length diff --git a/Server/src/services/track.service.ts b/Server/src/services/track.service.ts index 4770aaa9..a29eee60 100644 --- a/Server/src/services/track.service.ts +++ b/Server/src/services/track.service.ts @@ -20,6 +20,8 @@ export default class TrackService { * @param start starting location of the track * @param dest destination of track (currently in modelling start and end point do not differentiate) * @returns `Track` if creation was successful + * @throws `HTTPError`, if enriching the track data was not possible + * @throws PrismaError, if saving the track to database was not possible */ public static createTrack( track: GeoJSON.FeatureCollection, @@ -40,6 +42,8 @@ export default class TrackService { * @param start starting location of the track * @param dest destination of track (currently in modelling start and end point do not differentiate) * @returns `Track` if creation was successful + * @throws `HTTPError`, if enriching the track data was not possible + * @throws PrismaError, if updating the track in the database was not possible */ public static updateTrack( track_uid: number, @@ -62,6 +66,7 @@ export default class TrackService { * Assign each point of given track data its track kilometer * @param track `GeoJSON.FeatureCollection` of points of track to process * @returns enriched data of track + * @throws `HTTPError`, if a track kilometer value of a feature from track data could not be accessed */ private static enrichTrackData( track: GeoJSON.FeatureCollection @@ -89,6 +94,9 @@ export default class TrackService { * @param position position to calculate track kilometer for (does not need to be on the track) * @param track `Track` to use for calculation * @returns track kilometer of `position` projected on `track` + * @throws `HTTPError` + * - if `position` could not be projected onto the track + * - if the track kilometer value could not be accessed from the projected point */ public static getPointTrackKm(position: GeoJSON.Feature, track: Track): number { // get the track kilometer value from projected point @@ -101,6 +109,9 @@ export default class TrackService { * @param trackKm track kilometer value to convert to percentage * @param track `Track` to use for calculation as reference * @returns percentage value of `trackKm` regarding `track` + * @throws `HTTPError` + * - if the track length could not be computed + * - if `trackKm` is not between 0 and track length */ public static getTrackKmAsPercentage(trackKm: number, track: Track): number { // get total track length in kilometers @@ -120,6 +131,9 @@ export default class TrackService { * @param position position to project onto the track * @param track `Track` to project `position` onto * @returns track point, which is the `position` projected onto `track`, enriched with a track kilometer value + * @throws `HTTPError` + * - if turf did not set "location"-property while computing nearest-point-on-line + * - if the track data could not be parsed */ public static getProjectedPointOnTrack( position: GeoJSON.Feature, @@ -152,6 +166,13 @@ export default class TrackService { * @param track `Track` to get heading for * @param trackKm distance of `track` to get heading for * @returns current heading (0-359) of `track` at distance `trackKm` + * @throws getTrackKm + * @throws `HTTPError` + * - if the track length could not be computed + * - if `trackKm` is not between 0 and track length + * - if track kilometer value was not found while iterating through `track` + * - if the track kilometer value of a feature of the track could not be accessed + * - if the track data could not be parsed */ public static getTrackHeading(track: Track, trackKm: number): number { // TODO quite inefficient? did not found anything from turf, that could do this in a simple way @@ -193,6 +214,7 @@ export default class TrackService { * Look for the closest track for a given position * @param position position to search the closest track for * @returns closest `Track` for `position` + * @throws `HTTPError`, if no closest track was found or no track exists */ public static async getClosestTrack(position: GeoJSON.Feature): Promise { const tracks = await database.tracks.getAll() @@ -205,7 +227,13 @@ export default class TrackService { let minDistance = Number.POSITIVE_INFINITY let minTrack = -1 for (let i = 0; i < tracks.length; i++) { - const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(tracks[i].data) + let trackData + try { + trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(tracks[i].data) + } catch (err) { + logger.warn(`Could not parse data of track ${tracks[i].uid}`) + continue + } // converting feature collection of points to linestring to measure distance const lineStringData: GeoJSON.Feature = turfHelpers.lineString(turfMeta.coordAll(trackData)) @@ -240,6 +268,9 @@ export default class TrackService { * of the last track point is essentially the length of the track. * @param track `Track` to get the length of * @returns lenth of `track` in kilometers if possible + * @throws `HTTPError` + * - if the track kilometer value of the last feature of `track` could not be accessed + * - if the track data could not be parsed */ public static getTrackLength(track: Track): number { // load track data @@ -255,6 +286,7 @@ export default class TrackService { * Wrapper for converting internal presentation of track data as points to a linestring * @param track `Track` to get linestring for * @returns GeoJSON feature of a linestring. This only contains pure coordinates (i.e. no property values). + * @throws `HTTPError`, if the track data could not be parsed */ public static getTrackAsLineString(track: Track): GeoJSON.Feature { const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(track.data) diff --git a/Server/src/services/vehicle.service.ts b/Server/src/services/vehicle.service.ts index 9f43c783..58d9aa9e 100644 --- a/Server/src/services/vehicle.service.ts +++ b/Server/src/services/vehicle.service.ts @@ -32,6 +32,15 @@ export type VehicleData = { /** Service for vehicle management. */ export default class VehicleService { + /** + * Append log for a given vehicle + * @param vehicleId vehicle id to append the log for + * @param position position of the vehicle + * @param heading heading of the vehicle + * @param speed speed of the vehicle + * @returns appended log if successful + * @throws PrismaError, if appending log in the database was not possible + */ public static async appendLog( vehicleId: number, position: z.infer, @@ -73,6 +82,17 @@ export default class VehicleService { * @returns current `VehicleData` of `vehicle`, if no recent logs from an app are available `direction`, `trackKm` * and `percentagePosition` are not set and `heading` and `speed` are just from the tracker, also `position` is not * mapped on the track + * @throws `HTTPError` + * - if there are no recent logs of an app and no tracker logs at all + * - if the position could not be computed + * - if the track kilometer value of the position could not be accessed + * - if the tracker position could not be parsed + * - if the position of the vehicle as percentage on the track could not be computed + * - if the position could not be projected onto the track + * - if the travelling direction could not be computed + * @throws PrismaError + * - if track of vehicle could not be accessed in the database + * - if no last log of an existing tracker could be found in the database */ public static async getVehicleData(vehicle: Vehicle): Promise { // initialize track @@ -153,6 +173,12 @@ export default class VehicleService { * @param track `Track` assigned to the vehicle * @returns computed position of the vehicle based on log data, besides the GeoJSON point there is * also the track kilometer in the returned GeoJSON properties field + * @throws `HTTPError` + * - if the linestring of `track` could not be computed + * - if the tracker position could not be parsed + * - if adding weights to logs was not possible + * - if the weighted logs could not be converted to weighted track kilometer values + * - if averaging weighted logs was not possible */ private static computeVehiclePosition( trackerLogs: Log[], @@ -229,6 +255,10 @@ export default class VehicleService { * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` * @param track related track of `weightedLogs` * @returns list of weighted track kilometer values, could be less than count of `weightedLogs` (and even 0) if an error occurs + * @throws `HTTPError` + * - if no weighted log is given + * - if track kilometer value could not be accessed from a log + * - if the travelling direction could not be computed */ private static weightedLogsToWeightedTrackKm( weightedLogs: [Log, number][], @@ -281,6 +311,9 @@ export default class VehicleService { * @param log `Log` to compute track kilometer for * @param track related track of `log` * @returns track kilometer value for `log` + * @throws `HTTPError` + * - if the position of `log` could not be parsed + * - if the track kilometer value of the position of `log` could not be computed */ private static getTrackKmFromLog(log: Log, track: Track): number { // get position from log @@ -298,6 +331,7 @@ export default class VehicleService { * @param distanceCutoff value to cut the distance / accuracy factor off at, default is 50 meters (recommended for tracker logs) * @param averaging flag to decide wether all Logs should be averaged via their related weight * @returns list of `Log`s, each associated with a weight, could be less than count of `logs` (and even 0) if an error occurs + * @throws `HTTPError`, if a log position could not be parsed */ private static addWeightToLogs( logs: Log[], @@ -350,6 +384,9 @@ export default class VehicleService { * Build average of weighted track kilometer values * @param weightedTrackKms list of track kilometer values (first) with their related positive weight (second) * @returns averaged track kilometer value of `weightedTrackKms` + * @throws `HTTPError` + * - if there is a negative weight + * - if there was no weight greater than 0 */ private static averageWeightedTrackKmValues(weightedTrackKms: [number, number][]): number { // calculate total of all weights @@ -420,6 +457,7 @@ export default class VehicleService { * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` * @param track track to compute the direction of a vehicle with * @returns 1 or -1 if the vehicle is heading towards the end and start of the track respectively + * @throws `HTTPError`, if the heading of the track at `trackKm` could not be computed */ private static computeVehicleTravellingDirection(trackKm: number, vehicleHeading: number, track: Track): 1 | -1 { // TODO: needs improvements (probably with #118 together), should be independent from track kilometer (position needs to be computed for that) diff --git a/Server/src/utils/geojsonUtils.ts b/Server/src/utils/geojsonUtils.ts index 83affc67..b43972a3 100644 --- a/Server/src/utils/geojsonUtils.ts +++ b/Server/src/utils/geojsonUtils.ts @@ -24,6 +24,7 @@ export default class GeoJSONUtils { * Get track kilometer for given GeoJSON point (basically a wrapper for accessing this property) * @param point GeoJSON point to get the track kilometer for * @returns track kilometer if available + * @throws `HTTPError`, if track kilometer value is not set */ public static getTrackKm(point: GeoJSON.Feature): number { if (point.properties == null || point.properties["trackKm"] == null) { @@ -91,6 +92,7 @@ export default class GeoJSONUtils { * Parses JSON to a GeoJSON feature of a point (if possible) * @param object JSON to parse * @returns parsed GeoJSON feature + * @throws `HTTPError`, if parsing was not possible */ public static parseGeoJSONFeaturePoint(object: unknown): GeoJSON.Feature { if (this.isGeoJSONFeaturePoint(object)) { @@ -114,6 +116,7 @@ export default class GeoJSONUtils { * Try to parse anything to a GeoJSON feature collection of points (if possible) * @param object object to parse * @returns parsed GeoJSON feature collection + * @throws `HTTPError`, if parsing was not possible */ public static parseGeoJSONFeatureCollectionPoints(object: unknown): GeoJSON.FeatureCollection { if (this.isGeoJSONFeatureCollectionPoints(object)) { From 4d58576f4a639b76d21d122ada0c9229ca16eb41 Mon Sep 17 00:00:00 2001 From: Kevin Ebsen Date: Sat, 16 Sep 2023 20:49:15 +0200 Subject: [PATCH 4/5] #170: Adapted API to changes of error handling in services --- Server/src/routes/init.route.ts | 57 ++++++++++---------------------- Server/src/routes/poi.route.ts | 23 +++---------- Server/src/routes/track.route.ts | 47 ++++++++++++-------------- 3 files changed, 43 insertions(+), 84 deletions(-) diff --git a/Server/src/routes/init.route.ts b/Server/src/routes/init.route.ts index b7344c29..651532c1 100644 --- a/Server/src/routes/init.route.ts +++ b/Server/src/routes/init.route.ts @@ -63,25 +63,14 @@ export class InitRoute { const track: Track = await database.tracks.getById(id) - const lineString: Feature | null = TrackService.getTrackAsLineString(track) - if (!lineString) { - logger.error(`Could not convert track to line string`) - res.sendStatus(500) - return - } + const lineString: Feature = TrackService.getTrackAsLineString(track) const path: FeatureCollection = { type: "FeatureCollection", features: [lineString] } - const length: number | null = TrackService.getTrackLength(track) - if (length == null) { - logger.error(`Could not determine length of track with id ${id}`) - res.sendStatus(500) - return - } - + const length: number = TrackService.getTrackLength(track) const pois: POI[] = await POIService.getAllPOIsForTrack(track) const apiPois: z.infer[] = await this.getAppPoisFromDbPoi(pois) @@ -126,31 +115,13 @@ export class InitRoute { geometry: { type: "Point", coordinates: [pos.lng, pos.lat] }, properties: null } - const currentTrack: Track | null = await TrackService.getClosestTrack(backendPos) - - if (!currentTrack) { - logger.error(`Could not find current track with position {lat : ${pos.lat}, lng : ${pos.lng}}`) - res.sendStatus(500) - return - } - - const length: number | null = TrackService.getTrackLength(currentTrack) - - if (length == null) { - logger.error(`Length of track with id ${currentTrack.uid} could not be determined`) - res.sendStatus(500) - return - } + const currentTrack: Track = await TrackService.getClosestTrack(backendPos) + const length: number = TrackService.getTrackLength(currentTrack) const pois: POI[] = await POIService.getAllPOIsForTrack(currentTrack) const apiPois: z.infer[] = await this.getAppPoisFromDbPoi(pois) - const lineString: Feature | null = TrackService.getTrackAsLineString(currentTrack) - if (!lineString) { - logger.error(`Could not read track with id ${currentTrack.uid} as line string`) - res.sendStatus(500) - return - } + const lineString: Feature = TrackService.getTrackAsLineString(currentTrack) const path: FeatureCollection = { type: "FeatureCollection", @@ -195,18 +166,24 @@ export class InitRoute { // ensure that the app always gets an enum member. const appType: z.infer = poiIcon in POITypeIcon.enum ? poiIcon : POITypeIconEnum.Generic - const geoJsonPos: Feature | null = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) - if (!geoJsonPos) { - logger.error(`Could not find position of POI with id ${poi.uid}`) + let geoJsonPos: Feature + try { + geoJsonPos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) + } catch (error) { + logger.warn(`Could not find position of POI with id ${poi.uid}`) continue } + const pos: z.infer = { lat: GeoJSONUtils.getLatitude(geoJsonPos), lng: GeoJSONUtils.getLongitude(geoJsonPos) } - const percentagePosition: number | null = await POIService.getPOITrackDistancePercentage(poi) - if (percentagePosition == null) { - logger.error(`Could not determine percentage position of poi with id ${poi.uid}`) + + let percentagePosition: number + try { + percentagePosition = await POIService.getPOITrackDistancePercentage(poi) + } catch (err) { + logger.warn(`Could not determine percentage position of poi with id ${poi.uid}`) continue } diff --git a/Server/src/routes/poi.route.ts b/Server/src/routes/poi.route.ts index e46fa8c0..da4ff6ab 100644 --- a/Server/src/routes/poi.route.ts +++ b/Server/src/routes/poi.route.ts @@ -45,13 +45,9 @@ export class PoiRoute { private async getAllPOIs(_req: Request, res: Response): Promise { const pois: POI[] = await database.pois.getAll() - const typedPOIs: (z.infer | null)[] = pois.map( + const typedPOIs: z.infer[] = pois.map( ({ uid, name, trackId, description, isTurningPoint, typeId, position }) => { - const geoJsonPos: Feature | null = GeoJSONUtils.parseGeoJSONFeaturePoint(position) - if (!geoJsonPos) { - logger.error(`Could not find position of POI with id ${uid}`) - return null - } + const geoJsonPos: Feature = GeoJSONUtils.parseGeoJSONFeaturePoint(position) const pos: z.infer = { lat: GeoJSONUtils.getLatitude(geoJsonPos), lng: GeoJSONUtils.getLongitude(geoJsonPos) @@ -80,12 +76,7 @@ export class PoiRoute { const poi: POI = await database.pois.getById(poiId) - const geoPos: Feature | null = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) - if (!geoPos) { - logger.error(`Could not find position of POI with id ${poi.uid}`) - res.sendStatus(500) - return - } + const geoPos: Feature = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) const pos: z.infer = { lat: GeoJSONUtils.getLatitude(geoPos), lng: GeoJSONUtils.getLongitude(geoPos) @@ -126,7 +117,7 @@ export class PoiRoute { const type: POIType = await database.pois.getTypeById(poiPayload.typeId) - const newPOI: POI | null = await POIService.createPOI( + const newPOI: POI = await POIService.createPOI( geopos, poiPayload.name ? poiPayload.name : "", type, @@ -135,12 +126,6 @@ export class PoiRoute { poiPayload.isTurningPoint ) - if (!newPOI) { - logger.error(`Could not create new POI`) - res.sendStatus(500) - return - } - res.json({ id: newPOI.uid }) return } diff --git a/Server/src/routes/track.route.ts b/Server/src/routes/track.route.ts index 4a8f9a5c..79333cbf 100644 --- a/Server/src/routes/track.route.ts +++ b/Server/src/routes/track.route.ts @@ -5,7 +5,7 @@ import { POI, Track, Vehicle } from "@prisma/client" import please_dont_crash from "../utils/please_dont_crash" import { logger } from "../utils/logger" import { BareTrack, FullTrack, PointOfInterest, Position, UpdateTrack, Vehicle as APIVehicle } from "../models/api" -import VehicleService from "../services/vehicle.service" +import VehicleService, { VehicleData } from "../services/vehicle.service" import { Feature, LineString, Point } from "geojson" import POIService from "../services/poi.service" import GeoJSONUtils from "../utils/geojsonUtils" @@ -97,20 +97,8 @@ export class TrackRoute { const track: Track = await database.tracks.getById(trackId) // derive and transform the database data for easier digestion by the clients. - const path: Feature | null = TrackService.getTrackAsLineString(track) - const length: number | null = TrackService.getTrackLength(track) - - if (!path) { - logger.error(`Could not get track with id ${track.uid} as a line string`) - res.sendStatus(500) - return - } - - if (length == null) { - logger.error(`Length of track with id ${track.uid} could not be determined`) - res.sendStatus(500) - return - } + const path: Feature = TrackService.getTrackAsLineString(track) + const length: number = TrackService.getTrackLength(track) // Build the response object const api_track: z.infer = { @@ -197,9 +185,16 @@ export class TrackRoute { // obtain vehicles associated with the track from the db. const vehicles: Vehicle[] = await database.vehicles.getAll(track.uid) const ret: z.infer[] = await Promise.allSettled( - vehicles.map(async (vehicle: Vehicle) => { + vehicles.flatMap(async (vehicle: Vehicle) => { // get the current data of the vehicle - const vehicleData = await VehicleService.getVehicleData(vehicle) + let vehicleData: VehicleData + try { + vehicleData = await VehicleService.getVehicleData(vehicle) + } catch (err) { + logger.warn(`Could not compute vehicle data for vehicle ${vehicle.uid}.`) + return [] + } + // If we know that, convert it in the API format. const pos: z.infer | undefined = { lat: GeoJSONUtils.getLatitude(vehicleData.position), @@ -243,21 +238,23 @@ export class TrackRoute { const ret: z.infer[] = ( await Promise.all( pois.map(async (poi: POI) => { - const pos: Feature | null = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) - if (!pos) { - logger.error(`Could not find position of POI with id ${poi.uid}`) - // res.sendStatus(500) + let pos: Feature + try { + pos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position) + } catch (err) { + logger.warn(`Could not find position of POI with id ${poi.uid}`) return [] } const actualPos: z.infer = { lat: GeoJSONUtils.getLatitude(pos), lng: GeoJSONUtils.getLongitude(pos) } - const percentagePosition: number | null = await POIService.getPOITrackDistancePercentage(poi) - if (percentagePosition == null) { - logger.error(`Could not find percentage position of POI with id ${poi.uid}`) - // res.sendStatus(500) + let percentagePosition: number + try { + percentagePosition = await POIService.getPOITrackDistancePercentage(poi) + } catch (err) { + logger.warn(`Could not find percentage position of POI with id ${poi.uid}`) return [] } From 6b39223ec35dd64f9bb3c8a33307508482356410 Mon Sep 17 00:00:00 2001 From: Kevin Ebsen Date: Sat, 16 Sep 2023 20:57:40 +0200 Subject: [PATCH 5/5] Little fix for poi route --- Server/src/routes/track.route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/routes/track.route.ts b/Server/src/routes/track.route.ts index 79333cbf..96290f6c 100644 --- a/Server/src/routes/track.route.ts +++ b/Server/src/routes/track.route.ts @@ -237,7 +237,7 @@ export class TrackRoute { const pois: POI[] = await database.pois.getAll(trackId) const ret: z.infer[] = ( await Promise.all( - pois.map(async (poi: POI) => { + pois.flatMap(async (poi: POI) => { let pos: Feature try { pos = GeoJSONUtils.parseGeoJSONFeaturePoint(poi.position)