diff --git a/Server/src/routes/track.route.ts b/Server/src/routes/track.route.ts index fdce82e..a1293cb 100644 --- a/Server/src/routes/track.route.ts +++ b/Server/src/routes/track.route.ts @@ -223,22 +223,13 @@ export class TrackRoute { const vehicles: Vehicle[] = await database.vehicles.getAll(track.uid) const ret: z.infer[] = await Promise.all( vehicles.map(async (vehicle: Vehicle) => { - // get the current position of the vehicle - const heading: number = await VehicleService.getVehicleHeading(vehicle) - const speed: number = await VehicleService.getVehicleSpeed(vehicle) - const geo_pos = await VehicleService.getVehiclePosition(vehicle, heading, speed) - const trackKm = geo_pos ? GeoJSONUtils.getTrackKm(geo_pos) : undefined + // get the current data of the vehicle + const vehicleData = await VehicleService.getVehicleData(vehicle) // If we know that, convert it in the API format. - const pos: z.infer | undefined = geo_pos - ? { - lat: GeoJSONUtils.getLatitude(geo_pos), - lng: GeoJSONUtils.getLongitude(geo_pos) - } - : undefined - // Also acquire the percentage position. It might happen that a percentage position is known, while the position is not. - // This might not make much sense. - const percentagePosition: number | undefined = - trackKm != null ? (await TrackService.getTrackKmAsPercentage(trackKm, track)) ?? undefined : undefined + const pos: z.infer | undefined = { + lat: GeoJSONUtils.getLatitude(vehicleData.position), + lng: GeoJSONUtils.getLongitude(vehicleData.position) + } return { id: vehicle.uid, track: vehicle.trackId, @@ -246,9 +237,9 @@ export class TrackRoute { type: vehicle.typeId, trackerIds: (await database.trackers.getByVehicleId(vehicle.uid)).map(y => y.uid), pos, - percentagePosition, - heading, - speed + percentagePosition: vehicleData.percentagePosition, + heading: vehicleData.heading, + speed: vehicleData.speed } }) ) diff --git a/Server/src/routes/tracker.route.ts b/Server/src/routes/tracker.route.ts index cf66716..403c7e7 100644 --- a/Server/src/routes/tracker.route.ts +++ b/Server/src/routes/tracker.route.ts @@ -4,7 +4,7 @@ import { authenticateJWT, jsonParser } from "." import TrackerService from "../services/tracker.service" import { LteRecordField0, LteRecordField6, UplinkLteTracker, UplinkTracker } from "../models/api.tracker" import please_dont_crash from "../utils/please_dont_crash" -import { Log, Prisma, Tracker, Vehicle } from "@prisma/client" +import { Prisma, Tracker, Vehicle } from "@prisma/client" import database from "../services/database.service" import { Tracker as APITracker } from "../models/api" import { z } from "zod" @@ -71,7 +71,7 @@ export class TrackerRoute { return } - const lastLog = await database.logs.getLatestLog(undefined, tracker.uid,) + const lastLog = await database.logs.getLatestLog(undefined, tracker.uid) const apiTracker: z.infer = { id: tracker.uid, diff --git a/Server/src/routes/vehicle.route.ts b/Server/src/routes/vehicle.route.ts index 29ea742..7eb5191 100644 --- a/Server/src/routes/vehicle.route.ts +++ b/Server/src/routes/vehicle.route.ts @@ -5,11 +5,9 @@ import { logger } from "../utils/logger" import { authenticateJWT, jsonParser } from "." import { Log, Track, Tracker, Vehicle, VehicleType } from "@prisma/client" import VehicleService from "../services/vehicle.service" -import { Feature, Point } from "geojson" import please_dont_crash from "../utils/please_dont_crash" import database from "../services/database.service" import GeoJSONUtils from "../utils/geojsonUtils" -import TrackService from "../services/track.service" import { z } from "zod" /** @@ -116,18 +114,13 @@ export class VehicleRoute { } } - const heading: number = await VehicleService.getVehicleHeading(userVehicle) - const speed: number = await VehicleService.getVehicleSpeed(userVehicle) - const pos: Feature | null = await VehicleService.getVehiclePosition(userVehicle, heading, speed) - if (!pos) { - logger.error(`Could not find position of vehicle with id ${userVehicle.uid}`) - res.sendStatus(404) - return - } + const userVehicleData = await VehicleService.getVehicleData(userVehicle) + const position: z.infer = { - lat: GeoJSONUtils.getLatitude(pos), - lng: GeoJSONUtils.getLongitude(pos) + lat: GeoJSONUtils.getLatitude(userVehicleData.position), + lng: GeoJSONUtils.getLongitude(userVehicleData.position) } + // TODO: this is a quite unnecessary database query, remove it? const track: Track | null = await database.tracks.getById(userVehicle.trackId) if (!track) { logger.error(`Could not find track with id ${userVehicle.trackId} @@ -135,18 +128,6 @@ export class VehicleRoute { res.sendStatus(500) return } - const userVehicleTrackKm: number | null = GeoJSONUtils.getTrackKm(pos) - if (userVehicleTrackKm == null) { - logger.error(`Could not compute track kilometer for vehicle with id ${userVehicle.uid} - at track with id ${userVehicle.trackId}`) - res.sendStatus(500) - return - } - const userVehicleSimplifiedHeading: number = await VehicleService.getVehicleTrackHeading( - userVehicle, - userVehicleTrackKm, - heading - ) const allVehiclesOnTrack: Vehicle[] | null = await database.vehicles.getAll(userVehicle.trackId) if (allVehiclesOnTrack == null) { @@ -157,64 +138,57 @@ export class VehicleRoute { const appVehiclesNearUser: z.infer[] = ( await Promise.all( allVehiclesOnTrack.map(async v => { - const heading = await VehicleService.getVehicleHeading(v) - const speed = await VehicleService.getVehicleSpeed(v) - const pos = await VehicleService.getVehiclePosition(v, heading, speed) + const vehicleData = await VehicleService.getVehicleData(v) const trackers = await database.trackers.getByVehicleId(v.uid) - const nearbyVehicleTrackKm: number | null = pos ? GeoJSONUtils.getTrackKm(pos) : null - if (nearbyVehicleTrackKm == null) { - logger.error(`Could not compute track kilometer for vehicle with id ${v.uid} - at track with id ${v.trackId}`) + // TODO: is this check really necessary? (could be be in the other return statement as well) + if (vehicleData.direction == null) { + logger.error(`Could not compute travelling direction for vehicle with id ${v.uid} + at track wit id ${v.trackId}`) return { id: v.uid, name: v.name, track: v.trackId, type: v.typeId, trackerIds: trackers.map(t => t.uid), - pos: pos ? { lat: GeoJSONUtils.getLatitude(pos), lng: GeoJSONUtils.getLongitude(pos) } : undefined, - percentagePosition: -1, - heading: heading, + pos: { + lat: GeoJSONUtils.getLatitude(vehicleData.position), + lng: GeoJSONUtils.getLongitude(vehicleData.position) + }, + percentagePosition: vehicleData.direction ?? -1, + heading: vehicleData.heading, headingTowardsUser: undefined } } - const nearbySimplifiedVehicleHeading: number = await VehicleService.getVehicleTrackHeading( - v, - nearbyVehicleTrackKm, - heading - ) return { id: v.uid, name: v.name, track: v.trackId, type: v.typeId, trackerIds: trackers.map(t => t.uid), - pos: pos ? { lat: GeoJSONUtils.getLatitude(pos), lng: GeoJSONUtils.getLongitude(pos) } : undefined, - percentagePosition: (await TrackService.getTrackKmAsPercentage(nearbyVehicleTrackKm, track)) ?? -1, - heading: heading, + pos: { + lat: GeoJSONUtils.getLatitude(vehicleData.position), + lng: GeoJSONUtils.getLongitude(vehicleData.position) + }, + percentagePosition: vehicleData.percentagePosition ?? -1, + heading: vehicleData.heading, headingTowardsUser: - userVehicleSimplifiedHeading !== 0 && nearbySimplifiedVehicleHeading !== 0 - ? nearbySimplifiedVehicleHeading != userVehicleSimplifiedHeading - : undefined + userVehicleData.direction != null ? userVehicleData.direction != vehicleData.direction : undefined } }) ) ).filter(v => v.id !== userVehicle.uid && v.track === track.uid && v.percentagePosition !== -1) - const percentagePositionOnTrack: number | null = await TrackService.getTrackKmAsPercentage( - userVehicleTrackKm, - track - ) - if (percentagePositionOnTrack == null) { + if (userVehicleData.percentagePosition == null) { logger.error(`Could not determine percentage position on track for user with vehicle ${userVehicle.uid}`) res.sendStatus(500) return } const ret: z.infer = { pos: position, - heading: heading, + heading: userVehicleData.heading, vehiclesNearUser: appVehiclesNearUser, - speed: speed, - percentagePositionOnTrack: percentagePositionOnTrack, + speed: userVehicleData.speed, + percentagePositionOnTrack: userVehicleData.percentagePosition, passingPosition: undefined // TODO: Find out passingPosition } res.json(ret) diff --git a/Server/src/services/track.service.ts b/Server/src/services/track.service.ts index 616e631..9bbc2e8 100644 --- a/Server/src/services/track.service.ts +++ b/Server/src/services/track.service.ts @@ -86,12 +86,12 @@ 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 optional`Track` to use for calculation, if none is given, the closest will be used + * @param track `Track` to use for calculation * @returns track kilometer of `position` projected on `track`, `null` if an error occurs */ - public static async getPointTrackKm(position: GeoJSON.Feature, track?: Track): Promise { + public static getPointTrackKm(position: GeoJSON.Feature, track: Track): number | null { // get the track kilometer value from projected point - const projectedPoint = await this.getProjectedPointOnTrack(position, track) + const projectedPoint = this.getProjectedPointOnTrack(position, track) if (projectedPoint == null) { logger.error(`Could not project position ${JSON.stringify(position)}.`) return null @@ -105,7 +105,7 @@ export default class TrackService { * @param track `Track` to use for calculation as reference * @returns percentage value of `trackKm` regarding `track`, `null` if an error occurs */ - public static async getTrackKmAsPercentage(trackKm: number, track: Track): Promise { + public static getTrackKmAsPercentage(trackKm: number, track: Track): number | null { // get total track length in kilometers const trackLength = this.getTrackLength(track) if (trackLength == null) { @@ -126,25 +126,13 @@ export default class TrackService { /** * Project a position onto a track * @param position position to project onto the track - * @param track optional `Track` to project `position` onto, closest will be used, if none is given + * @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 */ - public static async getProjectedPointOnTrack( + public static getProjectedPointOnTrack( position: GeoJSON.Feature, - track?: Track - ): Promise | null> { - // check if track is given and else find the closest one - if (track == null) { - const tempTrack = await this.getClosestTrack(position) - - // if an error occured while trying to find the closest track, there is nothing we can do - if (tempTrack == null) { - logger.error(`Could not find closest track for position ${JSON.stringify(position)}.`) - return null - } - track = tempTrack - } - + track: Track + ): GeoJSON.Feature | null { // converting feature collection of points from track to linestring to project position onto it const trackData = GeoJSONUtils.parseGeoJSONFeatureCollectionPoints(track.data) if (trackData == null) { @@ -177,8 +165,9 @@ export default class TrackService { * @param trackKm distance of `track` to get heading for * @returns current heading (0-359) of `track` at distance `trackKm`, `null` if an error occurs */ - public static async getTrackHeading(track: Track, trackKm: number): Promise { + public static getTrackHeading(track: Track, trackKm: number): number | null { // 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) diff --git a/Server/src/services/vehicle.service.ts b/Server/src/services/vehicle.service.ts index c6e0083..7401d79 100644 --- a/Server/src/services/vehicle.service.ts +++ b/Server/src/services/vehicle.service.ts @@ -9,6 +9,25 @@ import nearestPointOnLine from "@turf/nearest-point-on-line" import { Position } from "../models/api" import { z } from "zod" +/** + * Data structure used by `getVehicleData` for data related to a certain `vehicle` with: + * - `position` (mapped on track) + * - `trackKm` (extracted from `position`) + * - `percentagePosition` respective to track kilometer + * - `speed` + * - `heading` (mapped on track path) + * - `direction` (1 if travelling to the end of the track, -1 accordingly) + */ +export type VehicleData = { + vehicle: Vehicle + position: GeoJSON.Feature + trackKm?: number + percentagePosition?: number + speed: number + heading: number + direction?: -1 | 1 +} + /** Service for vehicle management. */ export default class VehicleService { public static async appendLog( @@ -48,54 +67,116 @@ export default class VehicleService { } /** - * Compute vehicle position considering different log entries. - * @param vehicle `Vehicle` to get the position for - * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` - * @param vehicleSpeed heading of vehicle (>= 0), can be obtained with `getVehicleSpeed` - * @returns computed position of `vehicle` based on tracker data (besides the GeoJSON point there is - * also the track kilometer in the returned GeoJSON properties field), `null` if an error occurs + * Compute all data (e.g. position, speed and heading) for a given vehicle + * @param vehicle `Vehicle` to compute data for + * @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 */ - public static async getVehiclePosition( - vehicle: Vehicle, - vehicleHeading: number, - vehicleSpeed: number - ): Promise | null> { - // get track and related track data as linestring (used later) + public static async getVehicleData(vehicle: Vehicle): Promise { + // initialize track const track = await database.tracks.getById(vehicle.trackId) + // TODO if (track == null) { - logger.error(`Assigned track with id ${vehicle.trackId} for vehicle with id ${vehicle.uid} could not be found.`) - // fallback - return this.getLastKnownVehiclePosition(vehicle) - } - const lineStringData = TrackService.getTrackAsLineString(track) - if (lineStringData == null) { - logger.error(`Could not convert track with id ${vehicle.trackId} to a linestring.`) - // fallback - return this.getLastKnownVehiclePosition(vehicle) + throw Error(`Track with id ${vehicle.trackId} was not found`) } - // get all trackers for given vehicle + // initialize logs for trackers const trackers = await database.trackers.getByVehicleId(vehicle.uid) - // there should be at least one tracker for each vehicle - // TODO: for testing this was not possible, but for a real production system all vehicles should have at least one tracker again - if (trackers.length == 0) { - logger.info(`Could not find any tracker associated with vehicle ${vehicle.uid}.`) - // return null - } - - // get all latest tracker logs const trackerLogs: Log[] = [] for (let i = 0; i < trackers.length; i++) { - const latestTrackerLog = await database.logs.getLatestLog(vehicle.uid, trackers[i].uid) - if (latestTrackerLog == null) { - logger.warn(`No last log entry was found for tracker with id ${trackers[i].uid}.`) + const trackerLog = await database.logs.getLatestLog(vehicle.uid, trackers[i].uid) + if (trackerLog == null) { + logger.warn(`No log found for tracker with id ${trackers[i].uid}.`) continue } - trackerLogs.push(latestTrackerLog) + trackerLogs.push(trackerLog) + } + + // initialize logs for apps and check if there are any + // TODO: use updated function of database + const appLogs = (await database.logs.getNewestLogs(vehicle.uid, 30)).filter(function (log) { + return log.trackerId == null + }) + if (appLogs.length === 0 && trackerLogs.length === 0) { + throw Error(`There are no recent app logs and no tracker logs at all for vehicle with id ${vehicle.uid}.`) + } + + // fallback solution if there are no app logs + let position = GeoJSONUtils.parseGeoJSONFeaturePoint(trackerLogs[0].position) + // TODO + if (position == null) { + throw Error(`Could not parse position ${JSON.stringify(trackerLogs[0].position)}.`) + } + + // get heading and speed + const heading = this.computeVehicleHeading(appLogs) + const speed = this.computeVehicleSpeed(appLogs) + + // check if we can compute current position with app logs + if (appLogs.length === 0) { + // in this case we need to add the track kilometer value + logger.info(`No recent app logs of vehicle with id ${vehicle.uid} were found.`) + position = TrackService.getProjectedPointOnTrack(position, track) + } else { + // compute position and track kilometer as well as percentage value + position = this.computeVehiclePosition(trackerLogs, appLogs, heading, speed, track) + } + + // TODO + if (position == null) { + throw Error(`Could not compute position for vehicle with id ${vehicle.uid}.`) + } + + const trackKm = GeoJSONUtils.getTrackKm(position) + // TODO + if (trackKm == null) { + throw Error(`Could not get track kilometer of position ${JSON.stringify(position)}.`) + } + const percentagePosition = TrackService.getTrackKmAsPercentage(trackKm, track) + // TODO + if (percentagePosition == null) { + throw Error(`Could not compute percentage position for track kilometer ${trackKm} on track with id ${track.uid}.`) + } + + // return everything + return { + vehicle, + position, + trackKm, + percentagePosition, + heading, + speed, + direction: this.computeVehicleTravellingDirection(trackKm, heading, track) + } + } + + /** + * Compute vehicle position considering different log entries. + * @param trackerLogs all latest logs of all trackers of the related vehicle + * @param appLogs some recent logs of apps of the related vehicle + * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` + * @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) + */ + private static computeVehiclePosition( + trackerLogs: Log[], + appLogs: Log[], + vehicleHeading: number, + vehicleSpeed: number, + track: Track + ): GeoJSON.Feature | null { + 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 = await this.addWeightToLogs(trackerLogs, lineStringData) + const weightedTrackerLogs = this.addWeightToLogs(trackerLogs, lineStringData) // list of all resulting track kilometers const weightedTrackKm: [number, number][] = [] @@ -103,9 +184,9 @@ export default class VehicleService { // convert weighted tracker logs to weighted track kilometers (by projecting positions onto the track) if (weightedTrackerLogs.length === 0) { // 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 for vehicle with id ${vehicle.uid}.`) + logger.warn(`Could not add any weights to tracker logs.`) } else { - const tempWeightedTrackKm = await this.weightedLogsToWeightedTrackKm( + const tempWeightedTrackKm = this.weightedLogsToWeightedTrackKm( weightedTrackerLogs, vehicleSpeed, vehicleHeading, @@ -113,34 +194,26 @@ export default class VehicleService { ) 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 for vehicle with id ${vehicle.uid}.` - ) + logger.warn(`Could not convert weighted tracker logs to weighted track kilometers.`) } else { weightedTrackKm.push(...tempWeightedTrackKm) } } - // get app logs from last 30 seconds (because they do not have any tracker id, we cannot do it the same way as above) - const appLogs = (await database.logs.getNewestLogs(vehicle.uid, 30)).filter(function (log) { - return log.trackerId == null - }) // add weight to app logs - const weightedAppLogs = await this.addWeightToLogs(appLogs, lineStringData, 30, 15, true) + const weightedAppLogs = this.addWeightToLogs(appLogs, lineStringData, 30, 15, true) if (weightedAppLogs.length === 0) { - logger.warn(`Could not add any weights to app logs for vehicle with id ${vehicle.uid}.`) + logger.warn(`Could not add any weights to app logs.`) } else { // try adding them to the list as well - const tempWeightedTrackKm = await this.weightedLogsToWeightedTrackKm( + const tempWeightedTrackKm = this.weightedLogsToWeightedTrackKm( weightedAppLogs, vehicleSpeed, vehicleHeading, track ) if (tempWeightedTrackKm == null) { - logger.warn( - `Could not convert weighted app logs to weighted track kilometers for vehicle with id ${vehicle.uid}.` - ) + logger.warn(`Could not convert weighted app logs to weighted track kilometers.`) } else { weightedTrackKm.push(...tempWeightedTrackKm) } @@ -149,14 +222,14 @@ export default class VehicleService { // if we did not add any positions at all, we should return the last known position if (weightedTrackKm.length == 0) { logger.info(`Could not find any recent position while trying to compute vehicle's position.`) - return this.getLastKnownVehiclePosition(vehicle) + return GeoJSONUtils.parseGeoJSONFeaturePoint(trackerLogs[0].position) } // build average track kilometer value - const avgTrackKm = await this.averageWeightedTrackKmValues(weightedTrackKm) + 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 this.getLastKnownVehiclePosition(vehicle) + return GeoJSONUtils.parseGeoJSONFeaturePoint(trackerLogs[0].position) } // in the end we just need to turn the track kilometer into a position again @@ -170,15 +243,15 @@ export default class VehicleService { * @param weightedLogs list of weighted logs to be converted, all need to be from the same vehicle * @param vehicleSpeed heading of vehicle (>= 0), can be obtained with `getVehicleSpeed` * @param vehicleHeading heading of vehicle (0-359), can be obtained with `getVehicleHeading` - * @param track optional track, which can be provided, if already computed + * @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 */ - private static async weightedLogsToWeightedTrackKm( + private static weightedLogsToWeightedTrackKm( weightedLogs: [Log, number][], vehicleSpeed: number, vehicleHeading: number, - track?: Track - ): Promise<[number, number][] | null> { + track: Track + ): [number, number][] { // just need to check this for the next step if (weightedLogs.length === 0) { return [] @@ -186,11 +259,6 @@ export default class VehicleService { // vehicle should be the same for all logs const vehicleId = weightedLogs[0][0].vehicleId - const vehicle = await database.vehicles.getById(vehicleId) - if (vehicle == null) { - logger.warn(`Vehicle with id ${weightedLogs[0][0].vehicleId} was not found.`) - return null - } // predict their current position const weightedTrackKms: [number, number][] = [] @@ -204,20 +272,15 @@ export default class VehicleService { } // get last known track kilometer - const lastTrackKm = await this.getTrackKmFromLog(weightedLogs[i][0], track) + const lastTrackKm = this.getTrackKmFromLog(weightedLogs[i][0], track) if (lastTrackKm == null) { logger.warn(`Could not compute last track kilometer for last log with id ${weightedLogs[i][0].uid}.`) continue } // get travelling direction - const trackHeading = await this.getVehicleTrackHeading(vehicle, lastTrackKm, vehicleHeading, track) - if (trackHeading === 0) { - logger.warn( - `Could not determine travelling direction of vehicle with id ${vehicle.uid} by log with id ${weightedLogs[i][0].uid}.` - ) - continue - } + // TODO: should be a parameter replacing vehicleHeading (function needs to be adjusted for that) + const trackHeading = this.computeVehicleTravellingDirection(lastTrackKm, vehicleHeading, track) // calculate the current track kilometer and add it to the list const timePassedSec = Date.now() / 1000 - weightedLogs[i][0].timestamp.getTime() / 1000 @@ -230,29 +293,10 @@ export default class VehicleService { /** * Compute track kilometer for a position of a given log * @param log `Log` to compute track kilometer for - * @param track optional track, that can be provided if already computed, - * if none is given the track of the vehicle of the given `log` will be used + * @param track related track of `log` * @returns track kilometer value for `log`, `null` if an error occurs */ - private static async getTrackKmFromLog(log: Log, track?: Track): Promise { - // check if track is given and if not initialize it - if (track == null) { - const vehicle = await database.vehicles.getById(log.vehicleId) - if (vehicle == null) { - logger.error( - `Vehicle with id ${log.vehicleId} was not found while trying to look up track for log with id ${log.uid}.` - ) - return null - } - - const logTrack = await database.tracks.getById(vehicle.trackId) - if (logTrack == null) { - logger.error(`Track with id ${vehicle.trackId} was not found.`) - return null - } - track = logTrack - } - + private static getTrackKmFromLog(log: Log, track: Track): number | null { // get position from log const logPosition = GeoJSONUtils.parseGeoJSONFeaturePoint(log.position) if (logPosition == null) { @@ -261,7 +305,7 @@ export default class VehicleService { } // compute track kilometer for this position - const logTrackKm = await TrackService.getPointTrackKm(logPosition, track) + 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}.` @@ -281,13 +325,13 @@ export default class VehicleService { * @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 */ - private static async addWeightToLogs( + private static addWeightToLogs( logs: Log[], lineStringOfTrack: GeoJSON.Feature, timeCutoff = 180, distanceCutoff = 50, averaging: boolean = false - ): Promise<[Log, number][]> { + ): [Log, number][] { // resulting list const weightedLogs: [Log, number][] = [] @@ -331,7 +375,7 @@ export default class VehicleService { * @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 */ - private static async averageWeightedTrackKmValues(weightedTrackKms: [number, number][]): Promise { + private static averageWeightedTrackKmValues(weightedTrackKms: [number, number][]): number | null { // calculate total of all weights let weightSum = 0 for (let i = 0; i < weightedTrackKms.length; i++) { @@ -363,91 +407,47 @@ export default class VehicleService { } /** - * - * @param vehicle `Vehicle` to get the last known position from - * @returns the last known position of `vehicle` (not mapped on track and no kilometer value set!), null if an error occurs + * Compute heading for given vehicle + * @param vehicle `Vehicle` to get heading for + * @returns heading (0-359) of `vehicle` mapped on track + * @todo does not get mapped on track yet */ - private static async getLastKnownVehiclePosition(vehicle: Vehicle): Promise | null> { - // get last log and track of vehicle - const lastLog = await database.logs.getLatestLog(vehicle.uid) - if (lastLog == null) { - logger.error(`No log entry for vehicle ${vehicle.uid} was found.`) - return null - } - // parsing to GeoJSON - return GeoJSONUtils.parseGeoJSONFeaturePoint(lastLog.position) + public static async getVehicleHeading(vehicle: Vehicle): Promise { + // initialize app logs and compute heading with them + const appLogs = (await database.logs.getNewestLogs(vehicle.uid, 30)).filter(function (log) { + return log.trackerId == null + }) + return this.computeVehicleHeading(appLogs) } /** - * Compute average heading of all trackers assigned to a specified vehicle. - * No headings from app will be used here due to mobility. - * @param vehicle `Vehicle` to get the heading for - * @returns average heading (between 0 and 359) of `vehicle` based on tracker data, -1 if heading is unknown + * Compute average heading of given tracker logs + * @param logs logs to get the average heading from + * @returns average heading (between 0 and 359) of `logs` */ - public static async getVehicleHeading(vehicle: Vehicle): Promise { - // get all trackers for given vehicle - const trackers = await database.trackers.getByVehicleId(vehicle.uid) - if (trackers.length == 0) { - logger.error(`No tracker found for vehicle ${vehicle.uid}.`) - return -1 - } - - // get all last known tracker logs - const lastLogs: Log[] = [] - for (let i = 0; i < trackers.length; i++) { - // get last log entry for this tracker - const lastLog = await database.logs.getLatestLog(vehicle.uid, trackers[i].uid) - if (lastLog == null) { - // just try other trackers if there are no logs for this tracker - logger.warn(`Did not find any entry for vehicle ${vehicle.uid} and tracker ${trackers[i].uid}.`) - continue - } - lastLogs.push(lastLog) - } - - // check if we got any log - if (lastLogs.length == 0) { - logger.error(`Could not find any tracker log for vehicle ${vehicle.uid}`) - return -1 - } - - // actually computing average + private static computeVehicleHeading(logs: Log[]): number { + // TODO: needs to be improved (see #118) let avgHeading = 0 - for (let i = 0; i < lastLogs.length; i++) { - avgHeading += lastLogs[i].heading / lastLogs.length + for (let i = 0; i < logs.length; i++) { + avgHeading += logs[i].heading / logs.length } return avgHeading } /** - * Determine heading of a vehicle related to its track (either "forward" or "backward") - * @param vehicle `Vehicle` to get the heading for + * 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 track optional track to compute the direction of a vehicle with, if none is given the one assigned to `vehicle` will be used - * @returns 1 or -1 if the vehicle is heading towards the end and start of the track respectively, 0 if heading is unknown + * @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 */ - public static async getVehicleTrackHeading( - vehicle: Vehicle, - trackKm: number, - vehicleHeading: number, - track?: Track - ): Promise { - // initialize track - if (track == null) { - const tempTrack = await database.tracks.getById(vehicle.trackId) - if (tempTrack == null) { - logger.error(`Track with id ${vehicle.trackId} was not found.`) - return 0 - } - track = tempTrack - } - + 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) // compute track heading - const trackBearing = await TrackService.getTrackHeading(track, trackKm) + const trackBearing = TrackService.getTrackHeading(track, trackKm) + // TODO if (trackBearing == null) { - logger.error(`Could not compute heading of track with id ${track.uid} at track kilometer ${trackKm}.`) - return 0 + throw Error(`Could not compute heading of track with id ${track.uid} at track kilometer ${trackKm}.`) } // TODO: maybe give this a buffer of uncertainty if (vehicleHeading - trackBearing < 90 || vehicleHeading - trackBearing > -90) { @@ -458,43 +458,28 @@ export default class VehicleService { } /** - * Compute average speed of all trackers assigned to a specified vehicle. - * No speed from app will be used here due to mobility. - * @param vehicle `Vehicle` to get the speed for - * @returns average speed of `vehicle` based on tracker data, -1 if speed is unknown + * Compute speed of given vehicle + * @param vehicle `Vehicle` to get speed for + * @returns speed of `vehicle` */ public static async getVehicleSpeed(vehicle: Vehicle): Promise { - // get all trackers for given vehicle - // TODO: remove necessity of trackers - const trackers = await database.trackers.getByVehicleId(vehicle.uid) - if (trackers.length == 0) { - logger.error(`No tracker found for vehicle ${vehicle.uid}.`) - return -1 - } - - // get all last known tracker logs - const lastLogs: Log[] = [] - for (let i = 0; i < trackers.length; i++) { - // get last log entry for this tracker - const lastLog = await database.logs.getLatestLog(vehicle.uid, trackers[i].uid) - if (lastLog == null) { - // just try other trackers if there are no logs for this tracker - logger.warn(`Did not find any entry for vehicle ${vehicle.uid} and tracker ${trackers[i].uid}.`) - continue - } - lastLogs.push(lastLog) - } - - // check if we got any log - if (lastLogs.length == 0) { - logger.error(`Could not find any tracker log for vehicle ${vehicle.uid}`) - return -1 - } + // initialize app logs and compute speed with them + const appLogs = (await database.logs.getNewestLogs(vehicle.uid, 30)).filter(function (log) { + return log.trackerId == null + }) + return this.computeVehicleSpeed(appLogs) + } - // actually computing average + /** + * Compute average speed of given logs + * @param logs logs to get the average speed from + * @returns average speed of `logs` + */ + private static computeVehicleSpeed(logs: Log[]): number { + // TODO: needs improvement (see #132) let avgSpeed = 0 - for (let i = 0; i < lastLogs.length; i++) { - avgSpeed += lastLogs[i].speed / lastLogs.length + for (let i = 0; i < logs.length; i++) { + avgSpeed += logs[i].speed / logs.length } return avgSpeed }