From 2cd46cb664690809292ce757d106d931ee36d7da Mon Sep 17 00:00:00 2001 From: nils_penzel Date: Thu, 5 Sep 2024 15:52:14 +0200 Subject: [PATCH 1/8] wip --- src/lib/api.ts | 20 + src/lib/compositionTypes.ts | 37 ++ src/lib/constants.ts | 5 +- src/lib/sqlHelpers.ts | 49 ++- src/routes/api/whitelist/+server.ts | 194 +++++++++ src/routes/api/whitelist/capacities.ts | 91 +++++ src/routes/api/whitelist/queries.ts | 285 ++++++++++++++ src/routes/api/whitelist/searchInterval.ts | 27 ++ src/routes/api/whitelist/tourScheduler.ts | 438 +++++++++++++++++++++ 9 files changed, 1144 insertions(+), 2 deletions(-) create mode 100644 src/lib/compositionTypes.ts create mode 100644 src/routes/api/whitelist/+server.ts create mode 100644 src/routes/api/whitelist/capacities.ts create mode 100644 src/routes/api/whitelist/queries.ts create mode 100644 src/routes/api/whitelist/searchInterval.ts create mode 100644 src/routes/api/whitelist/tourScheduler.ts diff --git a/src/lib/api.ts b/src/lib/api.ts index ded802f0..ae967769 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -67,6 +67,26 @@ export const addAvailability = async (vehicleId: number, from: Date, to: Date) = }); }; +export type BookingRequestParameters = { + userChosen: Coordinates; + busStops: Coordinates[]; + startFixed: boolean; + timeStamps: Date[][]; + numPassengers: number; + numWheelchairs: number; + numBikes: number; + luggage: number; +}; + +export const whitelisting = async (r: BookingRequestParameters) => { + return await fetch('/api/whitelisting', { + method: 'POST', + body: JSON.stringify({ + r + }) + }); +}; + export const booking = async ( from: Location, to: Location, diff --git a/src/lib/compositionTypes.ts b/src/lib/compositionTypes.ts new file mode 100644 index 00000000..4f3f6952 --- /dev/null +++ b/src/lib/compositionTypes.ts @@ -0,0 +1,37 @@ +import type { Interval } from './interval'; +import type { Coordinates } from './location'; + +export type Company = { + id: number; + coordinates: Coordinates; + vehicles: Vehicle[]; + zoneId: number; +}; +export type Vehicle = { + id: number; + bike_capacity: number; + storage_space: number; + wheelchair_capacity: number; + seats: number; + tours: Tour[]; + availabilities: Interval[]; +}; + +export type Tour = { + departure: Date; + arrival: Date; + id: number; + events: Event[]; +}; + +export type Event = { + passengers: number; + luggage: number; + wheelchairs: number; + bikes: number; + is_pickup: boolean; + time: Interval; + id: number; + coordinates: Coordinates; + tourId: number; +}; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index c87571a0..cf49418c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,5 +1,8 @@ -import { hoursToMs } from './time_utils'; +import { hoursToMs, minutesToMs } from './time_utils'; export const TZ = 'Europe/Berlin'; export const MIN_PREP_MINUTES = 30; export const MAX_TRAVEL_DURATION = hoursToMs(1); +export const MAX_PASSENGER_WAITING_TIME = minutesToMs(10); +export const SRID = 4326; +export const SEARCH_INTERVAL_SIZE = minutesToMs(30); diff --git a/src/lib/sqlHelpers.ts b/src/lib/sqlHelpers.ts index 3f8276fb..e8e47f8e 100644 --- a/src/lib/sqlHelpers.ts +++ b/src/lib/sqlHelpers.ts @@ -1,4 +1,8 @@ -import { db } from '$lib/database'; +import { sql, type SelectQueryBuilder } from 'kysely'; +import { db } from './database'; +import type { Coordinates } from './location'; +import type { Database } from './types'; +import { SRID } from './constants'; export const queryCompletedTours = async (companyId: number | undefined) => { return await db @@ -21,3 +25,46 @@ export const queryCompletedTours = async (companyId: number | undefined) => { ]) .execute(); }; + +export enum ZoneType { + Any, + Community, + CompulsoryArea +} + +export const selectZonesContainingCoordinates = ( + coordinates: Coordinates, + coordinates2: Coordinates | undefined, + zoneType: ZoneType +) => { + return db + .selectFrom('zone') + .$if(zoneType != ZoneType.Any, (qb) => + qb.where('zone.is_community', '=', zoneType == ZoneType.Community ? true : false) + ) + .$if(coordinates2 != undefined, (qb) => + qb.where( + sql`ST_Covers(zone.area, ST_SetSRID(ST_MakePoint(${coordinates2!.lng}, ${coordinates2!.lat}), ${SRID}))` + ) + ) + .where( + sql`ST_Covers(zone.area, ST_SetSRID(ST_MakePoint(${coordinates.lng}, ${coordinates.lat}), ${SRID}))` + ); +}; + +export const joinInitializedCompaniesOnZones = ( + query: SelectQueryBuilder +) => { + return query + .innerJoin('company', 'company.zone', 'zone.id') + .where((eb) => + eb.and([ + eb('company.latitude', 'is not', null), + eb('company.longitude', 'is not', null), + eb('company.address', 'is not', null), + eb('company.name', 'is not', null), + eb('company.zone', 'is not', null), + eb('company.community_area', 'is not', null) + ]) + ); +}; diff --git a/src/routes/api/whitelist/+server.ts b/src/routes/api/whitelist/+server.ts new file mode 100644 index 00000000..e9ae6c1e --- /dev/null +++ b/src/routes/api/whitelist/+server.ts @@ -0,0 +1,194 @@ +import { oneToMany, Direction, type BookingRequestParameters } from '$lib/api.js'; +import { Coordinates, Location } from '$lib/location.js'; +import { error, json } from '@sveltejs/kit'; +import {} from '$lib/utils.js'; +import { minutesToMs, secondsToMs } from '$lib/time_utils.js'; +import { MAX_TRAVEL_DURATION, MIN_PREP_MINUTES } from '$lib/constants.js'; +import { + areChosenCoordinatesInsideAnyZone, + bookingApiQuery, + doesVehicleWithCapacityExist, + type BookingApiQueryResult +} from './queries'; +import { TourScheduler } from './tourScheduler'; +import { computeSearchIntervals } from './searchInterval'; +import type { Capacity } from './capacities'; + +export type ReturnType = { + status: number; + message: string; +}; + +export type SimpleEvent = { + location: Location; + time: Date; +}; + +export const POST = async (event) => { + const customer = event.locals.user; + if (!customer) { + return error(403); + } + const parameters: BookingRequestParameters[] = JSON.parse(await event.request.json()); + if (parameters.length == 0) { + return json({ status: 1, message: 'Es wurden keine Daten übermittelt.' }, { status: 400 }); + } + const requiredCapacity: Capacity = { + bikes: parameters[0].numBikes, + luggage: parameters[0].luggage, + wheelchairs: parameters[0].numWheelchairs, + passengers: parameters[0].numPassengers + }; + const requests = parameters.length; + if(requests==0 || requests>2){ + + } + getValidBookings(parameters[0]); + getValidBookings(parameters[1]); +}; + +const getValidBookings = async ( + p: BookingRequestParameters +) => { + const requiredCapacity: Capacity = { + bikes: p.numBikes, + luggage: p.luggage, + wheelchairs: p.numWheelchairs, + passengers: p.numPassengers + }; + const oneCoordinates: Coordinates = p.userChosen; + if (p.busStops.length == 0) { + return json({ status: 1, message: 'Es wurden keine Haltestellen angegeben.' }, { status: 400 }); + } + + let travelDurations = []; + try { + travelDurations = (await oneToMany(oneCoordinates, p.busStops, Direction.Forward)).map((res) => + secondsToMs(res.duration) + ); + } catch (e) { + return json({ status: 500 }); + } + + if (travelDurations.find((d) => d <= MAX_TRAVEL_DURATION) == undefined) { + return json( + { + status: 2, + message: + 'Die ausgewählten Koordinaten konnten in den Open Street Map Daten nicht zugeordnet werden.' + }, + { status: 400 } + ); + } + const results = new Array(travelDurations.length); + + const maxIntervals = computeSearchIntervals( + p.startFixed, + p.timeStamps, + Math.max(...travelDurations) + ); + const earliestValidStartTime = new Date(Date.now() + minutesToMs(MIN_PREP_MINUTES)); + if (earliestValidStartTime > maxIntervals.startTimes.endTime) { + return json( + { + status: 3, + message: 'Die Anfrage verletzt die minimale Vorlaufzeit für alle Haltestellen.' + }, + { status: 400 } + ); + } + + const dbResult: BookingApiQueryResult = await bookingApiQuery( + oneCoordinates, + requiredCapacity, + maxIntervals.expandedSearchInterval, + p.busStops + ); + if (dbResult.companies.length == 0) { + return determineError(oneCoordinates, requiredCapacity); + } + + for (let index = 0; index != travelDurations.length; ++index) { + const travelDuration = travelDurations[index]; + if (travelDuration > MAX_TRAVEL_DURATION) { + results[index] = { + status: 6, + message: + 'Die Koordinaten der Haltestelle konnten in den Open Street Map Daten nicht zugeordnet werden.' + }; + continue; + } + const intervals = computeSearchIntervals(p.startFixed, p.timeStamps, travelDuration); + const possibleStartTimes = intervals.startTimes; + if (earliestValidStartTime > possibleStartTimes.endTime) { + results[index] = { + status: 7, + message: 'Die Anfrage verletzt die minimale Vorlaufzeit für diese Haltestelle.' + }; + continue; + } + const targetZones = dbResult.targetZoneIds.get(index); + if (targetZones == undefined) { + return json({ status: 500 }); + } + const currentCompanies = dbResult.companies.filter( + (c) => targetZones.find((zId) => zId == c.zoneId) != undefined + ); + if (currentCompanies.length == 0) { + results[index] = { + status: 7, + message: + 'Diese Haltestelle liegt nicht im selben Pflichtfahrgebiet wie die ausgewählten Koordinaten.' + }; + continue; + } + if (earliestValidStartTime > possibleStartTimes.startTime) { + possibleStartTimes.startTime = earliestValidStartTime; + } + } + + const tourConcatenations = new TourScheduler( + p.startFixed, + p.userChosen, + p.busStops, + p.timeStamps, + travelDurations, + dbResult.companies, + requiredCapacity + ); + tourConcatenations.createTourConcatenations(); +}; + +const determineError = async ( + start: Coordinates, + requiredCapacity: Capacity +): Promise => { + if (!(await areChosenCoordinatesInsideAnyZone(start))) { + return json( + { + status: 4, + message: + 'Die angegebenen Koordinaten sind in keinem Pflichtfahrgebiet das vom PrimaÖV-Projekt bedient wird enthalten.' + }, + { status: 400 } + ); + } + if (!(await doesVehicleWithCapacityExist(start, requiredCapacity))) { + return json( + { + status: 5, + message: + 'Kein Unternehmen im relevanten Pflichtfahrgebiet hat ein Fahrzeug mit ausreichend Kapazität.' + }, + { status: 400 } + ); + } + return json( + { + status: 6, + message: + 'Kein Unternehmen im relevanten Pflichtfahrgebiet hat ein Fahrzeug mit ausreichend Kapazität, das zwischen Start und Ende der Anfrage verfügbar ist.' + }, + { status: 400 } + ); +}; diff --git a/src/routes/api/whitelist/capacities.ts b/src/routes/api/whitelist/capacities.ts new file mode 100644 index 00000000..3ebaf334 --- /dev/null +++ b/src/routes/api/whitelist/capacities.ts @@ -0,0 +1,91 @@ +import type { Event } from '$lib/compositionTypes.js'; + +export class Capacity { + wheelchairs!: number; + bikes!: number; + passengers!: number; + luggage!: number; +} + +export type Range = { + earliestPickup: number; + latestDropoff: number; +}; + +export class CapacitySimulation { + constructor( + bikeCapacity: number, + wheelchairCapacity: number, + seats: number, + storageSpace: number + ) { + this.bikeCapacity = bikeCapacity; + this.wheelchairCapacity = wheelchairCapacity; + this.seats = seats; + this.storageSpace = storageSpace; + this.bikes = 0; + this.wheelchairs = 0; + this.passengers = 0; + this.luggage = 0; + } + private bikeCapacity: number; + private wheelchairCapacity: number; + private seats: number; + private storageSpace: number; + private bikes: number; + private wheelchairs: number; + private passengers: number; + private luggage: number; + + private adjustValues(event: Event | Capacity) { + if (event instanceof Capacity || event.is_pickup) { + this.bikes += event.bikes; + this.wheelchairs += event.wheelchairs; + this.passengers += event.passengers; + this.luggage += event.luggage; + } else { + this.bikes -= event.bikes; + this.wheelchairs -= event.wheelchairs; + this.passengers -= event.passengers; + this.luggage -= event.luggage; + } + } + + private isValid(): boolean { + return ( + this.bikeCapacity >= this.bikes && + this.wheelchairCapacity >= this.wheelchairs && + this.storageSpace + this.seats >= this.luggage + this.passengers && + this.seats >= this.passengers + ); + } + + getPossibleInsertionRanges = (events: Event[], toInsert: Capacity): Range[] => { + this.reset(); + const possibleInsertions: Range[] = []; + this.adjustValues(toInsert); + let start: number | undefined = undefined; + for (let i = 0; i != events.length; i++) { + this.adjustValues(events[i]); + if (!this.isValid()) { + if (start != undefined) { + possibleInsertions.push({ earliestPickup: start, latestDropoff: i }); + start = undefined; + } + continue; + } + start = start == undefined ? i : start; + } + if (start != undefined) { + possibleInsertions.push({ earliestPickup: start, latestDropoff: events.length }); + } + return possibleInsertions; + }; + + private reset() { + this.bikes = 0; + this.wheelchairs = 0; + this.passengers = 0; + this.luggage = 0; + } +} diff --git a/src/routes/api/whitelist/queries.ts b/src/routes/api/whitelist/queries.ts new file mode 100644 index 00000000..ce0086f3 --- /dev/null +++ b/src/routes/api/whitelist/queries.ts @@ -0,0 +1,285 @@ +import { Coordinates } from '$lib/location.js'; +import { Interval } from '$lib/interval.js'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { sql } from 'kysely'; +import { db } from '$lib/database'; +import type { ExpressionBuilder } from 'kysely'; +import type { Database } from '$lib/types'; +import { SRID } from '$lib/constants'; +import { groupBy } from '$lib/collection_utils'; +import type { Capacity } from './capacities'; +import { + joinInitializedCompaniesOnZones, + selectZonesContainingCoordinates, + ZoneType +} from '$lib/sqlHelpers'; +import type { Company } from '$lib/compositionTypes'; + +export type BookingApiQueryResult = { + companies: Company[]; + targetZoneIds: Map; +}; + +const selectAvailabilities = (eb: ExpressionBuilder, interval: Interval) => { + return jsonArrayFrom( + eb + .selectFrom('availability') + .whereRef('availability.vehicle', '=', 'vehicle.id') + .where((eb) => + eb.and([ + eb('availability.start_time', '<=', interval.endTime), + eb('availability.end_time', '>=', interval.startTime) + ]) + ) + .select(['availability.start_time', 'availability.end_time']) + ).as('availabilities'); +}; + +const selectEvents = (eb: ExpressionBuilder) => { + return jsonArrayFrom( + eb + .selectFrom('request') + .whereRef('request.tour', '=', 'tour.id') + .innerJoin('event', 'request.id', 'event.request') + .select([ + 'event.id', + 'event.communicated_time', + 'event.scheduled_time', + 'event.latitude', + 'event.longitude', + 'request.passengers', + 'request.bikes', + 'request.luggage', + 'request.wheelchairs', + 'event.is_pickup' + ]) + ).as('events'); +}; + +const selectTours = (eb: ExpressionBuilder, interval: Interval) => { + return jsonArrayFrom( + eb + .selectFrom('tour') + .whereRef('tour.vehicle', '=', 'vehicle.id') + .where((eb) => + eb.and([ + eb('tour.departure', '<=', interval.endTime), + eb('tour.arrival', '>=', interval.startTime) + ]) + ) + .select((eb) => ['tour.id', 'tour.departure', 'tour.arrival', selectEvents(eb)]) + ).as('tours'); +}; + +const selectVehicles = ( + eb: ExpressionBuilder, + interval: Interval, + requiredCapacities: Capacity +) => { + return jsonArrayFrom( + eb + .selectFrom('vehicle') + .whereRef('vehicle.company', '=', 'company.id') + .where((eb) => + eb.and([ + eb('vehicle.wheelchair_capacity', '>=', requiredCapacities.wheelchairs), + eb('vehicle.bike_capacity', '>=', requiredCapacities.bikes), + eb('vehicle.seats', '>=', requiredCapacities.passengers), + eb( + 'vehicle.storage_space', + '>=', + sql`cast(${requiredCapacities.passengers} as integer) + cast(${requiredCapacities.luggage} as integer) - ${eb.ref('vehicle.seats')}` + ) + ]) + ) + .select((eb) => [ + 'vehicle.id', + 'vehicle.bike_capacity', + 'vehicle.storage_space', + 'vehicle.wheelchair_capacity', + 'vehicle.seats', + selectTours(eb, interval), + selectAvailabilities(eb, interval) + ]) + ).as('vehicles'); +}; + +const selectCompanies = ( + eb: ExpressionBuilder, + interval: Interval, + requiredCapacities: Capacity +) => { + return jsonArrayFrom( + eb + .selectFrom('company') + .whereRef('company.zone', '=', 'zone.id') + .where((eb) => + eb.and([ + eb('company.latitude', 'is not', null), + eb('company.longitude', 'is not', null), + eb('company.address', 'is not', null), + eb('company.name', 'is not', null), + eb('company.zone', 'is not', null), + eb('company.community_area', 'is not', null) + ]) + ) + .select([ + 'company.latitude', + 'company.longitude', + 'company.id', + 'company.zone', + selectVehicles(eb, interval, requiredCapacities) + ]) + ).as('companies'); +}; + +export const bookingApiQuery = async ( + start: Coordinates, + requiredCapacities: Capacity, + expandedSearchInterval: Interval, + targets: Coordinates[] +): Promise => { + interface CoordinateTable { + index: number; + longitude: number; + latitude: number; + } + + const dbResult = await db + .with('targets', (db) => { + const cteValues = targets.map( + (target, i) => + sql`SELECT cast(${i} as integer) AS index, ${target.lat} AS latitude, ${target.lng} AS longitude` + ); + return db + .selectFrom( + sql`(${sql.join(cteValues, sql` UNION ALL `)})`.as('cte') + ) + .selectAll(); + }) + .selectFrom('zone') + .where('zone.is_community', '=', false) + .where( + sql`ST_Covers(zone.area, ST_SetSRID(ST_MakePoint(${start.lng}, ${start.lat}), ${SRID}))` + ) + .select((eb) => [ + selectCompanies(eb, expandedSearchInterval, requiredCapacities), + jsonArrayFrom( + eb + .selectFrom('targets') + .where( + sql`ST_Covers(zone.area, ST_SetSRID(ST_MakePoint(cast(targets.longitude as float), cast(targets.latitude as float)), ${SRID}))` + ) + .select(['targets.index as targetIndex', 'zone.id as zoneId']) + ).as('target') + ]) + .executeTakeFirst(); + + if (dbResult == undefined) { + return { companies: [], targetZoneIds: new Map() }; + } + + const companies = dbResult.companies + .map((c) => { + return { + id: c.id, + coordinates: new Coordinates(c.latitude!, c.longitude!), + zoneId: c.zone!, + vehicles: c.vehicles + .filter((v) => v.availabilities.length != 0) + .map((v) => { + return { + id: v.id, + bike_capacity: v.bike_capacity, + seats: v.seats, + wheelchair_capacity: v.wheelchair_capacity, + storage_space: v.storage_space, + availabilities: Interval.merge( + v.availabilities.map((a) => new Interval(a.start_time, a.end_time)) + ), + tours: v.tours.map((t) => { + return { + id: t.id, + departure: t.departure, + arrival: t.arrival, + events: t.events.map((e) => { + const scheduled: Date = new Date(e.scheduled_time); + const communicated: Date = new Date(e.communicated_time); + return { + tourId: t.id, + id: e.id, + bikes: e.bikes, + wheelchairs: e.wheelchairs, + luggage: e.luggage, + passengers: e.passengers, + is_pickup: e.is_pickup, + coordinates: new Coordinates(e.latitude, e.longitude), + time: new Interval( + new Date(Math.min(scheduled.getTime(), communicated.getTime())), + new Date(Math.max(scheduled.getTime(), communicated.getTime())) + ) + }; + }) + }; + }) + }; + }) + }; + }) + .filter((c) => c.vehicles.length != 0); + companies.forEach((c) => + c.vehicles.forEach((v) => { + v.tours.sort((t1, t2) => t1.departure.getTime() - t2.departure.getTime()); + v.tours.forEach((t) => + t.events.sort((e1, e2) => e1.time.startTime.getTime() - e2.time.startTime.getTime()) + ); + }) + ); + return { + companies, + targetZoneIds: groupBy( + dbResult.target, + (t) => t.targetIndex, + (t) => t.zoneId + ) + }; +}; + +export const areChosenCoordinatesInsideAnyZone = async ( + coordinates: Coordinates +): Promise => { + return ( + (await selectZonesContainingCoordinates(coordinates, undefined, ZoneType.CompulsoryArea) + .selectAll() + .executeTakeFirst()) != undefined + ); +}; + +export const doesVehicleWithCapacityExist = async ( + coordinates: Coordinates, + requiredCapacities: Capacity +): Promise => { + return ( + (await joinInitializedCompaniesOnZones( + selectZonesContainingCoordinates(coordinates, undefined, ZoneType.CompulsoryArea) + ) + .innerJoin( + (eb) => + eb + .selectFrom('vehicle') + .selectAll() + .where((eb) => + eb.and([ + eb('vehicle.wheelchair_capacity', '>=', requiredCapacities.wheelchairs), + eb('vehicle.bike_capacity', '>=', requiredCapacities.bikes), + eb('vehicle.seats', '>=', requiredCapacities.passengers), + eb('vehicle.storage_space', '>=', requiredCapacities.luggage) + ]) + ) + .as('vehicle'), + (join) => join.onRef('vehicle.company', '=', 'company.id') + ) + .selectAll() + .executeTakeFirst()) == undefined + ); +}; diff --git a/src/routes/api/whitelist/searchInterval.ts b/src/routes/api/whitelist/searchInterval.ts new file mode 100644 index 00000000..04a69516 --- /dev/null +++ b/src/routes/api/whitelist/searchInterval.ts @@ -0,0 +1,27 @@ +import { Interval } from '$lib/interval.js'; +import { MAX_TRAVEL_DURATION, SEARCH_INTERVAL_SIZE } from '$lib/constants.js'; + +export const computeSearchIntervals = ( + startFixed: boolean, + times: Date[][], + travelDuration: number +): { + startTimes: Interval; + searchInterval: Interval; + expandedSearchInterval: Interval; +} => { + const time = times.flatMap((t)=>t)[0]; + const possibleStartTimes = new Interval( + startFixed ? time : new Date(time.getTime() - SEARCH_INTERVAL_SIZE - travelDuration), + startFixed + ? new Date(time.getTime() + SEARCH_INTERVAL_SIZE) + : new Date(time.getTime() - travelDuration) + ); + const searchInterval = possibleStartTimes.expand(0, travelDuration); + const expandedSearchInterval = searchInterval.expand(MAX_TRAVEL_DURATION, MAX_TRAVEL_DURATION); + return { + startTimes: possibleStartTimes, + searchInterval, + expandedSearchInterval + }; +}; diff --git a/src/routes/api/whitelist/tourScheduler.ts b/src/routes/api/whitelist/tourScheduler.ts new file mode 100644 index 00000000..03c4e0ea --- /dev/null +++ b/src/routes/api/whitelist/tourScheduler.ts @@ -0,0 +1,438 @@ +import { Interval } from '$lib/interval.js'; +import { Coordinates } from '$lib/location.js'; +import { minutesToMs } from '$lib/time_utils.js'; +import { Capacity, CapacitySimulation, type Range } from './capacities.js'; +import { type Company, type Event } from '$lib/compositionTypes.js'; +import type { SimpleEvent } from './+server.js'; +import { Direction, oneToMany } from '$lib/api.js'; + +enum InsertionType { + CONNECT, + APPEND, + PREPEND, + INSERT +} + +type StartTimesWithDuration = { + possibleStartTimes: Interval[]; + duration: number; +}; + +abstract class TCC { + constructor() { + this.oneRoutingResultIdx = undefined; + this.manyRoutingResultIdx = undefined; + this.fullTravelDurations = []; + } + abstract getStartCoordinates(): Coordinates; + abstract getTargetCoordinates(): Coordinates; + abstract cmpFullTravelDurations( + durationStart: number[], + durationsTargets: number[][], + travelDurations: number[] + ): void; + oneRoutingResultIdx: number | undefined; + manyRoutingResultIdx: number | undefined; + fullTravelDurations: number[][]; +} + +export class TourConcatenation { + constructor(companyId: number, toIdx: number) { + this.companyId = companyId; + this.toIdx = toIdx; + this.oneRoutingResultIdx = undefined; + this.manyRoutingResultIdx = undefined; + this.fullTravelDuration = undefined; + } + companyId: number; + toIdx: number; + oneRoutingResultIdx: number | undefined; + manyRoutingResultIdx: number | undefined; + fullTravelDuration: number | undefined; + getStartCoordinates = (): Coordinates => { + return new Coordinates(0, 0); + }; + getTargetCoordinates = (): Coordinates => { + return new Coordinates(0, 0); + }; + getValidStarts(startFixed: boolean, availabilities: Interval[], arrivalTimes: Date[][]) { + const PASSENGER_MAX_WAITING_TIME = minutesToMs(20); + console.assert(this.fullTravelDuration != undefined); + const validStarts = new Array(arrivalTimes.length); + const times = arrivalTimes[this.toIdx]; + for (let t = 0; t != arrivalTimes.length; ++t) { + const time = times[t]; + const searchInterval = startFixed + ? new Interval(new Date(time.getTime() - PASSENGER_MAX_WAITING_TIME), time) + : new Interval(time, new Date(time.getTime() + PASSENGER_MAX_WAITING_TIME)); + const relevantAvailabilities = availabilities + .filter((a) => a.getDurationMs() >= this.fullTravelDuration!) + .filter((a) => a.overlaps(searchInterval)) + .map((a) => (a.contains(searchInterval) ? a : a.cut(searchInterval))); + if (relevantAvailabilities.length == 0) { + validStarts[t] = { + possibleStartTimes: [], + duration: 0 + }; + continue; + } + } + return validStarts; + } +} + +class EventInsertion { + constructor( + startFixed: boolean, + fromUserChosenDuration: number, + toUserChosenDuration: number, + fromBusStopDurations: number[], + toBusStopDurations: number[], + travelDurations: number[] + ) { + console.assert(fromBusStopDurations.length == toBusStopDurations.length); + console.assert(fromBusStopDurations.length == travelDurations.length); + this.userChosenDuration = fromUserChosenDuration + toUserChosenDuration; + this.busStopDurations = new Array(fromBusStopDurations.length); + this.bothDurations = new Array(fromBusStopDurations.length); + for (let i = 0; i != fromBusStopDurations.length; ++i) { + this.busStopDurations[i] = fromBusStopDurations[i] + toBusStopDurations[i]; + this.bothDurations[i] = + (startFixed ? fromBusStopDurations[i] : fromUserChosenDuration) + + travelDurations[i] + + (startFixed ? toUserChosenDuration : toBusStopDurations[i]); + } + } + userChosenDuration: number; + busStopDurations: number[]; + bothDurations: number[]; +} + +type Answer = { + companyId: number; + vehicleId: number; + pickupAfterEventId: number | undefined; + dropoffAfterEventId: number | undefined; + type: InsertionType; +}; + +export class TourScheduler { + constructor( + startFixed: boolean, + userChosen: Coordinates, + busStops: Coordinates[], + timestamps: Date[][], + travelDurations: number[], + companies: Company[], + required: Capacity + ) { + this.timestamps = timestamps; + this.required = required; + this.companies = companies; + this.travelDurations = travelDurations; + this.insertDurations = new Array(companies.length); + this.appendDurations = new Array(companies.length); + this.prependDurations = new Array(companies.length); + this.connectDurations = new Array(companies.length); + this.possibleInsertionsByVehicle = new Map(); + this.startFixed = startFixed; + this.userChosen = userChosen; + this.userChosenFromMany = []; + this.userChosenToMany = []; + this.busStops = busStops; + this.busStopFromMany = new Array(busStops.length); + this.busStopToMany = new Array(busStops.length); + this.userChosenToDuration = []; + this.userChosenFromDuration = []; + this.busStopToDurations = new Array(busStops.length); + this.busStopFromDurations = new Array(busStops.length); + this.answers = new Array(busStops.length); + } + timestamps: Date[][]; + required: Capacity; + companies: Company[]; + startFixed: boolean; + travelDurations: number[]; + userChosen: Coordinates; + busStops: Coordinates[]; + + userChosenFromMany: Coordinates[]; + userChosenToMany: Coordinates[]; + busStopFromMany: Coordinates[][]; + busStopToMany: Coordinates[][]; + + userChosenToDuration: number[]; + userChosenFromDuration: number[]; + busStopToDurations: number[][]; + busStopFromDurations: number[][]; + + insertDurations: EventInsertion[][][]; + appendDurations: EventInsertion[][][]; + prependDurations: EventInsertion[][][]; + connectDurations: EventInsertion[][][]; + + possibleInsertionsByVehicle: Map; + answers: Answer[][]; + + createTourConcatenations = async () => { + //this.newTours.concat(companies.map((c) => new NewTour(c.id, 1, c.coordinates))); + this.simulateCapacities(); + this.gatherRoutingCoordinates(); + this.routing(); + this.computeTravelDurations(); + this.createInsertionPairs(); + }; + + private simulateCapacities() { + this.companies.forEach((c) => { + c.vehicles.forEach((v) => { + const allEvents = v.tours.flatMap((t) => t.events); + const simulation = new CapacitySimulation( + v.bike_capacity, + v.wheelchair_capacity, + v.seats, + v.storage_space + ); + const insertions = simulation.getPossibleInsertionRanges(allEvents, this.required); + this.possibleInsertionsByVehicle.set(v.id, insertions); + }); + }); + } + + private gatherRoutingCoordinates() { + this.companies.forEach((c) => { + for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { + this.busStopFromMany[busStopIdx].push(c.coordinates); + this.busStopToMany[busStopIdx].push(c.coordinates); + } + this.userChosenFromMany.push(c.coordinates); + this.userChosenToMany.push(c.coordinates); + c.vehicles.forEach((v) => { + const allEvents = v.tours.flatMap((t) => t.events); + const insertions = this.possibleInsertionsByVehicle.get(v.id)!; + forEachInsertion(insertions, (insertionIdx) => { + const prev = allEvents[insertionIdx].coordinates; + const next = allEvents[insertionIdx + 1].coordinates; + for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { + this.busStopFromMany[busStopIdx].push(prev); + this.busStopToMany[busStopIdx].push(next); + } + this.userChosenFromMany.push(prev); + this.userChosenToMany.push(next); + }); + }); + }); + } + + private async routing() { + this.userChosenFromDuration = ( + await oneToMany(this.userChosen, this.userChosenFromMany, Direction.Backward) + ).map((r) => r.duration); + this.userChosenToDuration = ( + await oneToMany(this.userChosen, this.userChosenToMany, Direction.Forward) + ).map((r) => r.duration); + for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { + this.busStopFromDurations[busStopIdx] = ( + await oneToMany( + this.busStops[busStopIdx], + this.busStopFromMany[busStopIdx], + Direction.Backward + ) + ).map((r) => r.duration); + this.busStopToDurations[busStopIdx] = ( + await oneToMany( + this.busStops[busStopIdx], + this.busStopToMany[busStopIdx], + Direction.Forward + ) + ).map((r) => r.duration); + } + } + + private computeTravelDurations() { + this.companies.forEach((c, companyIdx) => { + this.insertDurations[companyIdx] = new Array(c.vehicles.length); + this.appendDurations[companyIdx] = new Array(c.vehicles.length); + this.prependDurations[companyIdx] = new Array(c.vehicles.length); + this.connectDurations[companyIdx] = new Array(c.vehicles.length); + c.vehicles.forEach((v, vehicleIdx) => { + const allEvents = v.tours.flatMap((t) => t.events); + const insertions = this.possibleInsertionsByVehicle.get(v.id)!; + if (insertions.length == 0) { + return; + } + const lastInsertionIdx = insertions[insertions.length - 1].latestDropoff; + this.insertDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); + this.appendDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); + this.prependDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); + this.connectDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); + forEachInsertion(insertions, (insertionIdx) => { + const prev = allEvents[insertionIdx]; + const next = allEvents[insertionIdx + 1]; + if (prev.tourId == next.tourId) { + this.insertDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( + this.startFixed, + this.userChosenFromDuration[insertionIdx], + this.userChosenToDuration[insertionIdx], + this.busStopFromDurations[insertionIdx], + this.busStopToDurations[insertionIdx], + this.travelDurations + ); + } else { + this.appendDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( + this.startFixed, + this.userChosenFromDuration[insertionIdx], + this.userChosenToDuration[companyIdx], + this.busStopFromDurations[insertionIdx], + this.busStopToDurations[companyIdx], + this.travelDurations + ); + this.prependDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( + this.startFixed, + this.userChosenFromDuration[companyIdx], + this.userChosenToDuration[insertionIdx], + this.busStopFromDurations[companyIdx], + this.busStopToDurations[insertionIdx], + this.travelDurations + ); + this.connectDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( + this.startFixed, + this.userChosenFromDuration[companyIdx], + this.userChosenToDuration[companyIdx], + this.busStopFromDurations[companyIdx], + this.busStopToDurations[companyIdx], + this.travelDurations + ); + } + }); + }); + }); + } + + private createInsertionPairs() { + this.companies.forEach((c, companyIdx) => { + c.vehicles.forEach((v, vehicleIdx) => { + const insertions = this.possibleInsertionsByVehicle.get(v.id)!; + const allEvents = v.tours.flatMap((t) => t.events); + insertions.forEach((insertion) => { + for ( + let pickupIdx = insertion.earliestPickup; + pickupIdx != insertion.latestDropoff; + ++pickupIdx + ) { + for (let dropoffIdx = pickupIdx; dropoffIdx != insertion.latestDropoff; ++dropoffIdx) { + if (allEvents[pickupIdx + 1].tourId != allEvents[dropoffIdx].tourId) { + break; + } + this.createInsertionPair( + allEvents, + pickupIdx, + dropoffIdx, + companyIdx, + c.id, + vehicleIdx, + v.id + ); + } + } + }); + }); + }); + } + + private createInsertionPair( + allEvents: Event[], + pickupIdx: number, + dropoffIdx: number, + companyIdx: number, + companyId: number, + vehicleIdx: number, + vehicleId: number + ) { + const prevPickup = allEvents[pickupIdx]; + const nextPickup = allEvents[pickupIdx + 1]; + const prevDropoff = allEvents[dropoffIdx]; + const nextDropoff = allEvents[dropoffIdx + 1]; + const eventTimeDifference = + nextPickup.time.startTime.getTime() - prevPickup.time.endTime.getTime(); + this.busStops.forEach((_, busStopIdx) => { + let duration: number | undefined = undefined; + let connectDuration: number | undefined = undefined; + let appendDuration: number | undefined = undefined; + let prependDuration: number | undefined = undefined; + if (pickupIdx == dropoffIdx) { + if (prevPickup.tourId != nextPickup.tourId) { + connectDuration = + this.connectDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + appendDuration = + this.appendDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + prependDuration = + this.prependDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + } else { + duration = + this.insertDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + } + } else { + if (prevPickup.tourId != nextPickup.tourId) { + } else { + const busStopDuration = + this.insertDurations[companyIdx][vehicleIdx][this.startFixed ? pickupIdx : dropoffIdx] + .busStopDurations[busStopIdx]; + const userChosenDuration = + this.insertDurations[companyIdx][vehicleIdx][this.startFixed ? dropoffIdx : pickupIdx] + .userChosenDuration; + duration = userChosenDuration + busStopDuration; + } + } + + if (duration != undefined && duration <= eventTimeDifference) { + this.answers[busStopIdx].push({ + companyId, + vehicleId, + pickupAfterEventId: prevPickup.id, + dropoffAfterEventId: prevDropoff.id, + type: InsertionType.INSERT + }); + } + if (connectDuration != undefined && connectDuration <= eventTimeDifference) { + this.answers[busStopIdx].push({ + companyId, + vehicleId, + pickupAfterEventId: prevPickup.id, + dropoffAfterEventId: prevDropoff.id, + type: InsertionType.CONNECT + }); + } + if (appendDuration != undefined && appendDuration <= eventTimeDifference) { + this.answers[busStopIdx].push({ + companyId, + vehicleId, + pickupAfterEventId: prevPickup.id, + dropoffAfterEventId: prevDropoff.id, + type: InsertionType.APPEND + }); + } + if (prependDuration != undefined && prependDuration <= eventTimeDifference) { + this.answers[busStopIdx].push({ + companyId, + vehicleId, + pickupAfterEventId: prevPickup.id, + dropoffAfterEventId: prevDropoff.id, + type: InsertionType.PREPEND + }); + } + }); + } +} + +function forEachInsertion(insertions: Range[], fn: (insertionIdx: number) => T) { + insertions.forEach((insertion) => { + for (let i = insertion.earliestPickup; i != insertion.latestDropoff; ++i) { + fn(i); + } + }); +} + +function beelineCheck(insertion: EventInsertion, se: SimpleEvent): boolean { + return true; //TODO +} From 93ef127bee7ad3e7e4f5dc821ac5d9a9428289e9 Mon Sep 17 00:00:00 2001 From: nils_penzel Date: Thu, 5 Sep 2024 17:43:58 +0200 Subject: [PATCH 2/8] wip --- src/{routes/api/whitelist => lib}/capacities.ts | 0 src/routes/api/whitelist/+server.ts | 2 +- src/routes/api/whitelist/queries.ts | 2 +- src/routes/api/whitelist/tourScheduler.ts | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/{routes/api/whitelist => lib}/capacities.ts (100%) diff --git a/src/routes/api/whitelist/capacities.ts b/src/lib/capacities.ts similarity index 100% rename from src/routes/api/whitelist/capacities.ts rename to src/lib/capacities.ts diff --git a/src/routes/api/whitelist/+server.ts b/src/routes/api/whitelist/+server.ts index e9ae6c1e..6fdb181c 100644 --- a/src/routes/api/whitelist/+server.ts +++ b/src/routes/api/whitelist/+server.ts @@ -12,7 +12,7 @@ import { } from './queries'; import { TourScheduler } from './tourScheduler'; import { computeSearchIntervals } from './searchInterval'; -import type { Capacity } from './capacities'; +import type { Capacity } from '$lib/capacities'; export type ReturnType = { status: number; diff --git a/src/routes/api/whitelist/queries.ts b/src/routes/api/whitelist/queries.ts index ce0086f3..79bf1775 100644 --- a/src/routes/api/whitelist/queries.ts +++ b/src/routes/api/whitelist/queries.ts @@ -7,7 +7,7 @@ import type { ExpressionBuilder } from 'kysely'; import type { Database } from '$lib/types'; import { SRID } from '$lib/constants'; import { groupBy } from '$lib/collection_utils'; -import type { Capacity } from './capacities'; +import type { Capacity } from '$lib/capacities'; import { joinInitializedCompaniesOnZones, selectZonesContainingCoordinates, diff --git a/src/routes/api/whitelist/tourScheduler.ts b/src/routes/api/whitelist/tourScheduler.ts index 03c4e0ea..c5f90175 100644 --- a/src/routes/api/whitelist/tourScheduler.ts +++ b/src/routes/api/whitelist/tourScheduler.ts @@ -1,7 +1,7 @@ import { Interval } from '$lib/interval.js'; import { Coordinates } from '$lib/location.js'; import { minutesToMs } from '$lib/time_utils.js'; -import { Capacity, CapacitySimulation, type Range } from './capacities.js'; +import { Capacity, CapacitySimulation, type Range } from '$lib/capacities.js'; import { type Company, type Event } from '$lib/compositionTypes.js'; import type { SimpleEvent } from './+server.js'; import { Direction, oneToMany } from '$lib/api.js'; From 68b27e30ad86b04e12ec7530194a2d1158fa9f60 Mon Sep 17 00:00:00 2001 From: nils_penzel Date: Thu, 5 Sep 2024 21:49:36 +0200 Subject: [PATCH 3/8] wip --- src/routes/api/whitelist/queries.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/routes/api/whitelist/queries.ts b/src/routes/api/whitelist/queries.ts index 79bf1775..3aae114e 100644 --- a/src/routes/api/whitelist/queries.ts +++ b/src/routes/api/whitelist/queries.ts @@ -189,30 +189,20 @@ export const bookingApiQuery = async ( .filter((v) => v.availabilities.length != 0) .map((v) => { return { + ...v, id: v.id, - bike_capacity: v.bike_capacity, - seats: v.seats, - wheelchair_capacity: v.wheelchair_capacity, - storage_space: v.storage_space, availabilities: Interval.merge( v.availabilities.map((a) => new Interval(a.start_time, a.end_time)) ), tours: v.tours.map((t) => { return { - id: t.id, - departure: t.departure, - arrival: t.arrival, + ...t, events: t.events.map((e) => { const scheduled: Date = new Date(e.scheduled_time); const communicated: Date = new Date(e.communicated_time); return { tourId: t.id, - id: e.id, - bikes: e.bikes, - wheelchairs: e.wheelchairs, - luggage: e.luggage, - passengers: e.passengers, - is_pickup: e.is_pickup, + ...e, coordinates: new Coordinates(e.latitude, e.longitude), time: new Interval( new Date(Math.min(scheduled.getTime(), communicated.getTime())), From 448ef0244be4ef7944c03fb2b18db222a92fecac Mon Sep 17 00:00:00 2001 From: nils_penzel Date: Thu, 5 Sep 2024 21:50:46 +0200 Subject: [PATCH 4/8] wip --- src/routes/api/whitelist/queries.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/api/whitelist/queries.ts b/src/routes/api/whitelist/queries.ts index 3aae114e..229db709 100644 --- a/src/routes/api/whitelist/queries.ts +++ b/src/routes/api/whitelist/queries.ts @@ -190,7 +190,6 @@ export const bookingApiQuery = async ( .map((v) => { return { ...v, - id: v.id, availabilities: Interval.merge( v.availabilities.map((a) => new Interval(a.start_time, a.end_time)) ), From 5121e36ef26827e3a17b4b6ccd1489495cc2db07 Mon Sep 17 00:00:00 2001 From: nils_penzel Date: Sun, 8 Sep 2024 13:37:18 +0200 Subject: [PATCH 5/8] wip --- src/routes/api/whitelist/+server.ts | 30 +++---- src/routes/api/whitelist/queries.ts | 48 +++++++---- src/routes/api/whitelist/tourScheduler.ts | 100 ++++++++++++++++------ 3 files changed, 117 insertions(+), 61 deletions(-) diff --git a/src/routes/api/whitelist/+server.ts b/src/routes/api/whitelist/+server.ts index 6fdb181c..477f902f 100644 --- a/src/routes/api/whitelist/+server.ts +++ b/src/routes/api/whitelist/+server.ts @@ -30,18 +30,12 @@ export const POST = async (event) => { return error(403); } const parameters: BookingRequestParameters[] = JSON.parse(await event.request.json()); - if (parameters.length == 0) { + const requests = parameters.length; + if (requests == 0) { return json({ status: 1, message: 'Es wurden keine Daten übermittelt.' }, { status: 400 }); } - const requiredCapacity: Capacity = { - bikes: parameters[0].numBikes, - luggage: parameters[0].luggage, - wheelchairs: parameters[0].numWheelchairs, - passengers: parameters[0].numPassengers - }; - const requests = parameters.length; - if(requests==0 || requests>2){ - + if(requests>2){ + return json({ status: 1, message: 'Die API erwartet ein Array mit entweder einem oder zwei Einträgen, für die erste und letzte Meile.' }, { status: 400 }); } getValidBookings(parameters[0]); getValidBookings(parameters[1]); @@ -56,14 +50,14 @@ const getValidBookings = async ( wheelchairs: p.numWheelchairs, passengers: p.numPassengers }; - const oneCoordinates: Coordinates = p.userChosen; + const userChosen: Coordinates = p.userChosen; if (p.busStops.length == 0) { return json({ status: 1, message: 'Es wurden keine Haltestellen angegeben.' }, { status: 400 }); } let travelDurations = []; try { - travelDurations = (await oneToMany(oneCoordinates, p.busStops, Direction.Forward)).map((res) => + travelDurations = (await oneToMany(userChosen, p.busStops, Direction.Forward)).map((res) => secondsToMs(res.duration) ); } catch (e) { @@ -75,7 +69,7 @@ const getValidBookings = async ( { status: 2, message: - 'Die ausgewählten Koordinaten konnten in den Open Street Map Daten nicht zugeordnet werden.' + 'Das Straßenrouting war nicht erfolgreich. Mögliche Gründe: (1) Die angegebenen Koordinaten wurden nicht in den Open Street Map Daten gefunden, (2) Die Reisezeit überschreitet eine Stunde.' }, { status: 400 } ); @@ -99,13 +93,13 @@ const getValidBookings = async ( } const dbResult: BookingApiQueryResult = await bookingApiQuery( - oneCoordinates, + userChosen, requiredCapacity, maxIntervals.expandedSearchInterval, p.busStops ); if (dbResult.companies.length == 0) { - return determineError(oneCoordinates, requiredCapacity); + return determineError(userChosen, requiredCapacity); } for (let index = 0; index != travelDurations.length; ++index) { @@ -127,12 +121,12 @@ const getValidBookings = async ( }; continue; } - const targetZones = dbResult.targetZoneIds.get(index); - if (targetZones == undefined) { + const busStopZones = dbResult.busStopZoneIds.get(index); + if (busStopZones == undefined) { return json({ status: 500 }); } const currentCompanies = dbResult.companies.filter( - (c) => targetZones.find((zId) => zId == c.zoneId) != undefined + (c) => busStopZones.find((zId) => zId == c.zoneId) != undefined ); if (currentCompanies.length == 0) { results[index] = { diff --git a/src/routes/api/whitelist/queries.ts b/src/routes/api/whitelist/queries.ts index 229db709..f8309379 100644 --- a/src/routes/api/whitelist/queries.ts +++ b/src/routes/api/whitelist/queries.ts @@ -17,7 +17,7 @@ import type { Company } from '$lib/compositionTypes'; export type BookingApiQueryResult = { companies: Company[]; - targetZoneIds: Map; + busStopZoneIds: Map; }; const selectAvailabilities = (eb: ExpressionBuilder, interval: Interval) => { @@ -137,7 +137,7 @@ export const bookingApiQuery = async ( start: Coordinates, requiredCapacities: Capacity, expandedSearchInterval: Interval, - targets: Coordinates[] + busStops: Coordinates[] ): Promise => { interface CoordinateTable { index: number; @@ -146,10 +146,10 @@ export const bookingApiQuery = async ( } const dbResult = await db - .with('targets', (db) => { - const cteValues = targets.map( - (target, i) => - sql`SELECT cast(${i} as integer) AS index, ${target.lat} AS latitude, ${target.lng} AS longitude` + .with('busStops', (db) => { + const cteValues = busStops.map( + (busStop, i) => + sql`SELECT cast(${i} as integer) AS index, ${busStop.lat} AS latitude, ${busStop.lng} AS longitude` ); return db .selectFrom( @@ -166,17 +166,17 @@ export const bookingApiQuery = async ( selectCompanies(eb, expandedSearchInterval, requiredCapacities), jsonArrayFrom( eb - .selectFrom('targets') + .selectFrom('busStops') .where( - sql`ST_Covers(zone.area, ST_SetSRID(ST_MakePoint(cast(targets.longitude as float), cast(targets.latitude as float)), ${SRID}))` + sql`ST_Covers(zone.area, ST_SetSRID(ST_MakePoint(cast(busStops.longitude as float), cast(busStops.latitude as float)), ${SRID}))` ) - .select(['targets.index as targetIndex', 'zone.id as zoneId']) - ).as('target') + .select(['busStops.index as busStopIndex', 'zone.id as zoneId']) + ).as('busStopZone') ]) .executeTakeFirst(); if (dbResult == undefined) { - return { companies: [], targetZoneIds: new Map() }; + return { companies: [], busStopZoneIds: new Map() }; } const companies = dbResult.companies @@ -224,12 +224,30 @@ export const bookingApiQuery = async ( ); }) ); + const a = groupBy( + dbResult.busStopZone, + (b) => b.busStopIndex, + (b) => b.zoneId + ); + const zoneContainsBusStop: (boolean)[][] = []; + for(let busStopIdx=0;busStopIdx!=busStops.length;++busStopIdx){ + const buffer=new Array(dbResult.companies.length); + const busStopZones=a.get(busStopIdx); + if(busStopZones!=undefined){ + for(let companyIdx=0;companyIdx!=dbResult.companies.length;++companyIdx){ + buffer[companyIdx]=busStopZones.find((z)=>z==dbResult.companies[companyIdx].zone)!=undefined; + } + zoneContainsBusStop[busStopIdx]=buffer; + }else{ + zoneContainsBusStop[busStopIdx]=[]; + } + } return { companies, - targetZoneIds: groupBy( - dbResult.target, - (t) => t.targetIndex, - (t) => t.zoneId + busStopZoneIds: groupBy( + dbResult.busStopZone, + (b) => b.busStopIndex, + (b) => b.zoneId ) }; }; diff --git a/src/routes/api/whitelist/tourScheduler.ts b/src/routes/api/whitelist/tourScheduler.ts index c5f90175..0823fc50 100644 --- a/src/routes/api/whitelist/tourScheduler.ts +++ b/src/routes/api/whitelist/tourScheduler.ts @@ -124,30 +124,48 @@ export class TourScheduler { timestamps: Date[][], travelDurations: number[], companies: Company[], - required: Capacity + required: Capacity, + companyMayServeBusStop:boolean[][] ) { + this.companyMayServeBusStop = companyMayServeBusStop; this.timestamps = timestamps; this.required = required; this.companies = companies; this.travelDurations = travelDurations; - this.insertDurations = new Array(companies.length); - this.appendDurations = new Array(companies.length); - this.prependDurations = new Array(companies.length); - this.connectDurations = new Array(companies.length); - this.possibleInsertionsByVehicle = new Map(); this.startFixed = startFixed; this.userChosen = userChosen; + this.busStops = busStops; + + this.possibleInsertionsByVehicle = new Map(); + this.userChosenFromMany = []; this.userChosenToMany = []; - this.busStops = busStops; this.busStopFromMany = new Array(busStops.length); this.busStopToMany = new Array(busStops.length); + this.userChosenToDuration = []; this.userChosenFromDuration = []; this.busStopToDurations = new Array(busStops.length); this.busStopFromDurations = new Array(busStops.length); + + this.insertDurations = new Array(companies.length); + this.appendDurations = new Array(companies.length); + this.prependDurations = new Array(companies.length); + this.connectDurations = new Array(companies.length); + + this.insertionIndexesUserChosenFromDurationIndexes=[]; + this.insertionIndexesUserChosenToDurationIndexes=[]; + this.insertionIndexesBusStopFromDurationIndexes=[]; + this.insertionIndexesBusStopToDurationIndexes=[]; + + this.companyIndexesUserChosenFromDurationIndexes=new Array(companies.length); + this.companyIndexesUserChosenToDurationIndexes=new Array(companies.length); + this.companyIndexesBusStopFromDurationIndexes=new Array(companies.length); + this.companyIndexesBusStopToDurationIndexes=new Array(companies.length); + this.answers = new Array(busStops.length); } + companyMayServeBusStop: boolean[][]; timestamps: Date[][]; required: Capacity; companies: Company[]; @@ -155,6 +173,8 @@ export class TourScheduler { travelDurations: number[]; userChosen: Coordinates; busStops: Coordinates[]; + + possibleInsertionsByVehicle: Map; userChosenFromMany: Coordinates[]; userChosenToMany: Coordinates[]; @@ -166,16 +186,24 @@ export class TourScheduler { busStopToDurations: number[][]; busStopFromDurations: number[][]; + insertionIndexesUserChosenFromDurationIndexes: number[][][]; + insertionIndexesUserChosenToDurationIndexes: number[][][]; + insertionIndexesBusStopFromDurationIndexes: number[][][][]; + insertionIndexesBusStopToDurationIndexes: number[][][][]; + + companyIndexesUserChosenFromDurationIndexes: number[]; + companyIndexesUserChosenToDurationIndexes: number[]; + companyIndexesBusStopFromDurationIndexes: number[][]; + companyIndexesBusStopToDurationIndexes: number[][]; + insertDurations: EventInsertion[][][]; appendDurations: EventInsertion[][][]; prependDurations: EventInsertion[][][]; connectDurations: EventInsertion[][][]; - possibleInsertionsByVehicle: Map; answers: Answer[][]; createTourConcatenations = async () => { - //this.newTours.concat(companies.map((c) => new NewTour(c.id, 1, c.coordinates))); this.simulateCapacities(); this.gatherRoutingCoordinates(); this.routing(); @@ -186,44 +214,60 @@ export class TourScheduler { private simulateCapacities() { this.companies.forEach((c) => { c.vehicles.forEach((v) => { - const allEvents = v.tours.flatMap((t) => t.events); const simulation = new CapacitySimulation( v.bike_capacity, v.wheelchair_capacity, v.seats, v.storage_space ); - const insertions = simulation.getPossibleInsertionRanges(allEvents, this.required); - this.possibleInsertionsByVehicle.set(v.id, insertions); + this.possibleInsertionsByVehicle.set(v.id, simulation.getPossibleInsertionRanges(v.tours.flatMap((t) => t.events), this.required)); }); }); } private gatherRoutingCoordinates() { - this.companies.forEach((c) => { - for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { - this.busStopFromMany[busStopIdx].push(c.coordinates); - this.busStopToMany[busStopIdx].push(c.coordinates); - } - this.userChosenFromMany.push(c.coordinates); - this.userChosenToMany.push(c.coordinates); - c.vehicles.forEach((v) => { + this.companies.forEach((c, companyIdx) => { + this.addCompanyCoordinates(c.coordinates, companyIdx); + c.vehicles.forEach((v, vehicleIdx) => { const allEvents = v.tours.flatMap((t) => t.events); const insertions = this.possibleInsertionsByVehicle.get(v.id)!; forEachInsertion(insertions, (insertionIdx) => { - const prev = allEvents[insertionIdx].coordinates; - const next = allEvents[insertionIdx + 1].coordinates; - for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { - this.busStopFromMany[busStopIdx].push(prev); - this.busStopToMany[busStopIdx].push(next); - } - this.userChosenFromMany.push(prev); - this.userChosenToMany.push(next); + this.addCoordinates(allEvents[insertionIdx].coordinates, allEvents[insertionIdx + 1].coordinates, companyIdx, vehicleIdx, insertionIdx); }); }); }); } + private addCompanyCoordinates(c: Coordinates, companyIdx: number){ + for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { + if(!this.companyMayServeBusStop[busStopIdx][companyIdx]){ + continue; + } + this.busStopFromMany[busStopIdx].push(c); + this.companyIndexesBusStopFromDurationIndexes[companyIdx][busStopIdx] = this.busStopFromMany.length; + this.busStopToMany[busStopIdx].push(c); + this.companyIndexesBusStopToDurationIndexes[companyIdx][busStopIdx] = this.busStopToMany.length; + } + this.userChosenFromMany.push(c); + this.userChosenToMany.push(c); + } + + private addCoordinates(prev: Coordinates, next: Coordinates, companyIdx: number, vehicleIdx: number, insertionIdx: number) { + for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { + if(!this.companyMayServeBusStop[busStopIdx][companyIdx]){ + continue; + } + this.busStopFromMany[busStopIdx].push(prev); + this.insertionIndexesBusStopFromDurationIndexes[companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopFromMany[busStopIdx].length; + this.busStopToMany[busStopIdx].push(next); + this.insertionIndexesBusStopToDurationIndexes[companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopToMany[busStopIdx].length; + } + this.userChosenFromMany.push(prev); + this.insertionIndexesUserChosenFromDurationIndexes[companyIdx][vehicleIdx][insertionIdx] = this.busStopFromMany.length; + this.userChosenToMany.push(next); + this.insertionIndexesUserChosenToDurationIndexes[companyIdx][vehicleIdx][insertionIdx] = this.busStopToMany.length; + } + private async routing() { this.userChosenFromDuration = ( await oneToMany(this.userChosen, this.userChosenFromMany, Direction.Backward) From e922e1425acf75b11c5dbaec5bf7812a7fa4dd3e Mon Sep 17 00:00:00 2001 From: nils_penzel Date: Mon, 9 Sep 2024 03:12:22 +0200 Subject: [PATCH 6/8] wip --- src/lib/interval.ts | 21 ++ src/routes/api/whitelist/tourScheduler.ts | 342 +++++++++------------- 2 files changed, 160 insertions(+), 203 deletions(-) diff --git a/src/lib/interval.ts b/src/lib/interval.ts index 596d7ddb..d2385954 100644 --- a/src/lib/interval.ts +++ b/src/lib/interval.ts @@ -91,4 +91,25 @@ export class Interval { merged.push(unmerged.pop()!); return merged; }; + + intersect(other:Interval):Interval|undefined{ + if(this.overlaps(other)){ + return new Interval(new Date(Math.max(this.startTime.getTime(), other.startTime.getTime())), new Date(Math.min(this.endTime.getTime(), other.endTime.getTime()))); + } + return undefined; + }; + + static intersect = (many: Interval[], one: Interval): Interval[] => { + const result: Interval[] = []; + for(let i=0;i!=many.length;++i){ + if(one.startTime.getTime() > many[i].endTime.getTime()){ + break; + } + if(!many[i].overlaps(one)){ + continue; + } + result.push(many[i].intersect(one)!); + } + return result; + } } diff --git a/src/routes/api/whitelist/tourScheduler.ts b/src/routes/api/whitelist/tourScheduler.ts index 0823fc50..e42d3b35 100644 --- a/src/routes/api/whitelist/tourScheduler.ts +++ b/src/routes/api/whitelist/tourScheduler.ts @@ -1,10 +1,10 @@ import { Interval } from '$lib/interval.js'; import { Coordinates } from '$lib/location.js'; -import { minutesToMs } from '$lib/time_utils.js'; import { Capacity, CapacitySimulation, type Range } from '$lib/capacities.js'; import { type Company, type Event } from '$lib/compositionTypes.js'; import type { SimpleEvent } from './+server.js'; import { Direction, oneToMany } from '$lib/api.js'; +import { MAX_PASSENGER_WAITING_TIME } from '$lib/constants.js'; enum InsertionType { CONNECT, @@ -13,72 +13,9 @@ enum InsertionType { INSERT } -type StartTimesWithDuration = { - possibleStartTimes: Interval[]; - duration: number; -}; - -abstract class TCC { - constructor() { - this.oneRoutingResultIdx = undefined; - this.manyRoutingResultIdx = undefined; - this.fullTravelDurations = []; - } - abstract getStartCoordinates(): Coordinates; - abstract getTargetCoordinates(): Coordinates; - abstract cmpFullTravelDurations( - durationStart: number[], - durationsTargets: number[][], - travelDurations: number[] - ): void; - oneRoutingResultIdx: number | undefined; - manyRoutingResultIdx: number | undefined; - fullTravelDurations: number[][]; -} - -export class TourConcatenation { - constructor(companyId: number, toIdx: number) { - this.companyId = companyId; - this.toIdx = toIdx; - this.oneRoutingResultIdx = undefined; - this.manyRoutingResultIdx = undefined; - this.fullTravelDuration = undefined; - } - companyId: number; - toIdx: number; - oneRoutingResultIdx: number | undefined; - manyRoutingResultIdx: number | undefined; - fullTravelDuration: number | undefined; - getStartCoordinates = (): Coordinates => { - return new Coordinates(0, 0); - }; - getTargetCoordinates = (): Coordinates => { - return new Coordinates(0, 0); - }; - getValidStarts(startFixed: boolean, availabilities: Interval[], arrivalTimes: Date[][]) { - const PASSENGER_MAX_WAITING_TIME = minutesToMs(20); - console.assert(this.fullTravelDuration != undefined); - const validStarts = new Array(arrivalTimes.length); - const times = arrivalTimes[this.toIdx]; - for (let t = 0; t != arrivalTimes.length; ++t) { - const time = times[t]; - const searchInterval = startFixed - ? new Interval(new Date(time.getTime() - PASSENGER_MAX_WAITING_TIME), time) - : new Interval(time, new Date(time.getTime() + PASSENGER_MAX_WAITING_TIME)); - const relevantAvailabilities = availabilities - .filter((a) => a.getDurationMs() >= this.fullTravelDuration!) - .filter((a) => a.overlaps(searchInterval)) - .map((a) => (a.contains(searchInterval) ? a : a.cut(searchInterval))); - if (relevantAvailabilities.length == 0) { - validStarts[t] = { - possibleStartTimes: [], - duration: 0 - }; - continue; - } - } - return validStarts; - } +enum Timing { + BEFORE, + AFTER } class EventInsertion { @@ -88,24 +25,34 @@ class EventInsertion { toUserChosenDuration: number, fromBusStopDurations: number[], toBusStopDurations: number[], - travelDurations: number[] + travelDurations: number[], + window: Interval, + busStopTimes: Interval[][], + availabilities: Interval[], + type: InsertionType ) { console.assert(fromBusStopDurations.length == toBusStopDurations.length); console.assert(fromBusStopDurations.length == travelDurations.length); - this.userChosenDuration = fromUserChosenDuration + toUserChosenDuration; - this.busStopDurations = new Array(fromBusStopDurations.length); - this.bothDurations = new Array(fromBusStopDurations.length); - for (let i = 0; i != fromBusStopDurations.length; ++i) { - this.busStopDurations[i] = fromBusStopDurations[i] + toBusStopDurations[i]; - this.bothDurations[i] = - (startFixed ? fromBusStopDurations[i] : fromUserChosenDuration) + - travelDurations[i] + - (startFixed ? toUserChosenDuration : toBusStopDurations[i]); + this.busStops = new Array(busStopTimes.length); + this.both=new Array(busStopTimes.length); + + const availabilitiesInWindow: Interval[] = type!=InsertionType.INSERT?[window]:Interval.intersect(availabilities, window); + + this.userChosen=availabilitiesInWindow.filter((a)=>a.getDurationMs()>=fromUserChosenDuration + toUserChosenDuration!); + for(let i=0;i!=travelDurations.length;++i){ + const duration = fromBusStopDurations[i] + toBusStopDurations[i]; + const bothDuration = (startFixed ? fromBusStopDurations[i] : fromUserChosenDuration) + + travelDurations[i] + + (startFixed ? toUserChosenDuration : toBusStopDurations[i]); + for(let j=0;j!=busStopTimes[i].length;++j){ + this.busStops[i][j] = Interval.intersect(availabilitiesInWindow.filter((a)=>a.getDurationMs()>=duration), busStopTimes[i][j]); + this.both[i][j] = Interval.intersect(availabilitiesInWindow.filter((a)=>a.getDurationMs()>=bothDuration), busStopTimes[i][j]); + } } } - userChosenDuration: number; - busStopDurations: number[]; - bothDurations: number[]; + userChosen:Interval[]; + busStops:Interval[][][]; + both:Interval[][][]; } type Answer = { @@ -121,14 +68,14 @@ export class TourScheduler { startFixed: boolean, userChosen: Coordinates, busStops: Coordinates[], - timestamps: Date[][], + busStopTimes: Date[][], travelDurations: number[], companies: Company[], required: Capacity, companyMayServeBusStop:boolean[][] ) { this.companyMayServeBusStop = companyMayServeBusStop; - this.timestamps = timestamps; + this.busStopTimes = busStopTimes.map((times)=>times.map((t)=>new Interval(startFixed?t:new Date(t.getTime()-MAX_PASSENGER_WAITING_TIME), startFixed?new Date(t.getTime()+MAX_PASSENGER_WAITING_TIME):t))); this.required = required; this.companies = companies; this.travelDurations = travelDurations; @@ -138,35 +85,24 @@ export class TourScheduler { this.possibleInsertionsByVehicle = new Map(); - this.userChosenFromMany = []; - this.userChosenToMany = []; - this.busStopFromMany = new Array(busStops.length); - this.busStopToMany = new Array(busStops.length); + this.userChosenMany = new Array(2); + this.busStopMany = new Array(2); - this.userChosenToDuration = []; - this.userChosenFromDuration = []; - this.busStopToDurations = new Array(busStops.length); - this.busStopFromDurations = new Array(busStops.length); + this.userChosenDuration = new Array(2); + this.busStopDurations = new Array(2); - this.insertDurations = new Array(companies.length); - this.appendDurations = new Array(companies.length); - this.prependDurations = new Array(companies.length); - this.connectDurations = new Array(companies.length); + this.insertDurations = new Array(companies.length); - this.insertionIndexesUserChosenFromDurationIndexes=[]; - this.insertionIndexesUserChosenToDurationIndexes=[]; - this.insertionIndexesBusStopFromDurationIndexes=[]; - this.insertionIndexesBusStopToDurationIndexes=[]; + this.insertionIndexesUserChosenDurationIndexes=[]; + this.insertionIndexesBusStopDurationIndexes=[]; - this.companyIndexesUserChosenFromDurationIndexes=new Array(companies.length); - this.companyIndexesUserChosenToDurationIndexes=new Array(companies.length); - this.companyIndexesBusStopFromDurationIndexes=new Array(companies.length); - this.companyIndexesBusStopToDurationIndexes=new Array(companies.length); + this.companyIndexesUserChosenDurationIndexes=new Array(companies.length); + this.companyIndexesBusStopDurationIndexes=new Array(companies.length); this.answers = new Array(busStops.length); } companyMayServeBusStop: boolean[][]; - timestamps: Date[][]; + busStopTimes: Interval[][]; required: Capacity; companies: Company[]; startFixed: boolean; @@ -176,30 +112,19 @@ export class TourScheduler { possibleInsertionsByVehicle: Map; - userChosenFromMany: Coordinates[]; - userChosenToMany: Coordinates[]; - busStopFromMany: Coordinates[][]; - busStopToMany: Coordinates[][]; + userChosenMany: Coordinates[][]; + busStopMany: Coordinates[][][]; - userChosenToDuration: number[]; - userChosenFromDuration: number[]; - busStopToDurations: number[][]; - busStopFromDurations: number[][]; + userChosenDuration: number[][]; + busStopDurations: number[][][]; - insertionIndexesUserChosenFromDurationIndexes: number[][][]; - insertionIndexesUserChosenToDurationIndexes: number[][][]; - insertionIndexesBusStopFromDurationIndexes: number[][][][]; - insertionIndexesBusStopToDurationIndexes: number[][][][]; + insertionIndexesUserChosenDurationIndexes: number[][][][]; + insertionIndexesBusStopDurationIndexes: number[][][][][]; - companyIndexesUserChosenFromDurationIndexes: number[]; - companyIndexesUserChosenToDurationIndexes: number[]; - companyIndexesBusStopFromDurationIndexes: number[][]; - companyIndexesBusStopToDurationIndexes: number[][]; + companyIndexesUserChosenDurationIndexes: number[][]; + companyIndexesBusStopDurationIndexes: number[][][]; - insertDurations: EventInsertion[][][]; - appendDurations: EventInsertion[][][]; - prependDurations: EventInsertion[][][]; - connectDurations: EventInsertion[][][]; + insertDurations: EventInsertion[][][][]; answers: Answer[][]; @@ -243,13 +168,13 @@ export class TourScheduler { if(!this.companyMayServeBusStop[busStopIdx][companyIdx]){ continue; } - this.busStopFromMany[busStopIdx].push(c); - this.companyIndexesBusStopFromDurationIndexes[companyIdx][busStopIdx] = this.busStopFromMany.length; - this.busStopToMany[busStopIdx].push(c); - this.companyIndexesBusStopToDurationIndexes[companyIdx][busStopIdx] = this.busStopToMany.length; + this.busStopMany[Timing.BEFORE][busStopIdx].push(c); + this.companyIndexesBusStopDurationIndexes[Timing.BEFORE][companyIdx][busStopIdx] = this.busStopMany.length; + this.busStopMany[Timing.AFTER][busStopIdx].push(c); + this.companyIndexesBusStopDurationIndexes[Timing.AFTER][companyIdx][busStopIdx] = this.busStopMany.length; } - this.userChosenFromMany.push(c); - this.userChosenToMany.push(c); + this.userChosenMany[Timing.BEFORE].push(c); + this.userChosenMany[Timing.AFTER].push(c); } private addCoordinates(prev: Coordinates, next: Coordinates, companyIdx: number, vehicleIdx: number, insertionIdx: number) { @@ -257,36 +182,36 @@ export class TourScheduler { if(!this.companyMayServeBusStop[busStopIdx][companyIdx]){ continue; } - this.busStopFromMany[busStopIdx].push(prev); - this.insertionIndexesBusStopFromDurationIndexes[companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopFromMany[busStopIdx].length; - this.busStopToMany[busStopIdx].push(next); - this.insertionIndexesBusStopToDurationIndexes[companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopToMany[busStopIdx].length; + this.busStopMany[Timing.BEFORE][busStopIdx].push(prev); + this.insertionIndexesBusStopDurationIndexes[Timing.BEFORE][companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopMany[busStopIdx].length; + this.busStopMany[Timing.AFTER][busStopIdx].push(next); + this.insertionIndexesBusStopDurationIndexes[Timing.AFTER][companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopMany[busStopIdx].length; } - this.userChosenFromMany.push(prev); - this.insertionIndexesUserChosenFromDurationIndexes[companyIdx][vehicleIdx][insertionIdx] = this.busStopFromMany.length; - this.userChosenToMany.push(next); - this.insertionIndexesUserChosenToDurationIndexes[companyIdx][vehicleIdx][insertionIdx] = this.busStopToMany.length; + this.userChosenMany[Timing.BEFORE].push(prev); + this.insertionIndexesUserChosenDurationIndexes[Timing.BEFORE][companyIdx][vehicleIdx][insertionIdx] = this.busStopMany.length; + this.userChosenMany[Timing.AFTER].push(next); + this.insertionIndexesUserChosenDurationIndexes[Timing.AFTER][companyIdx][vehicleIdx][insertionIdx] = this.busStopMany.length; } private async routing() { - this.userChosenFromDuration = ( - await oneToMany(this.userChosen, this.userChosenFromMany, Direction.Backward) + this.userChosenDuration[Timing.BEFORE] = ( + await oneToMany(this.userChosen, this.userChosenMany[Timing.BEFORE], Direction.Backward) ).map((r) => r.duration); - this.userChosenToDuration = ( - await oneToMany(this.userChosen, this.userChosenToMany, Direction.Forward) + this.userChosenDuration[Timing.AFTER] = ( + await oneToMany(this.userChosen, this.userChosenMany[Timing.AFTER], Direction.Forward) ).map((r) => r.duration); for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { - this.busStopFromDurations[busStopIdx] = ( + this.busStopDurations[Timing.BEFORE][busStopIdx] = ( await oneToMany( this.busStops[busStopIdx], - this.busStopFromMany[busStopIdx], + this.busStopMany[Timing.BEFORE][busStopIdx], Direction.Backward ) ).map((r) => r.duration); - this.busStopToDurations[busStopIdx] = ( + this.busStopDurations[Timing.AFTER][busStopIdx] = ( await oneToMany( this.busStops[busStopIdx], - this.busStopToMany[busStopIdx], + this.busStopMany[Timing.AFTER][busStopIdx], Direction.Forward ) ).map((r) => r.duration); @@ -294,11 +219,9 @@ export class TourScheduler { } private computeTravelDurations() { + const cases = [InsertionType.CONNECT, InsertionType.APPEND, InsertionType.PREPEND, InsertionType.INSERT]; this.companies.forEach((c, companyIdx) => { - this.insertDurations[companyIdx] = new Array(c.vehicles.length); - this.appendDurations[companyIdx] = new Array(c.vehicles.length); - this.prependDurations[companyIdx] = new Array(c.vehicles.length); - this.connectDurations[companyIdx] = new Array(c.vehicles.length); + this.insertDurations[companyIdx] = new Array(c.vehicles.length); c.vehicles.forEach((v, vehicleIdx) => { const allEvents = v.tours.flatMap((t) => t.events); const insertions = this.possibleInsertionsByVehicle.get(v.id)!; @@ -306,48 +229,35 @@ export class TourScheduler { return; } const lastInsertionIdx = insertions[insertions.length - 1].latestDropoff; - this.insertDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); - this.appendDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); - this.prependDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); - this.connectDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); + this.insertDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); forEachInsertion(insertions, (insertionIdx) => { + this.insertDurations[companyIdx][vehicleIdx][insertionIdx] = new Array(4); const prev = allEvents[insertionIdx]; const next = allEvents[insertionIdx + 1]; - if (prev.tourId == next.tourId) { - this.insertDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( - this.startFixed, - this.userChosenFromDuration[insertionIdx], - this.userChosenToDuration[insertionIdx], - this.busStopFromDurations[insertionIdx], - this.busStopToDurations[insertionIdx], - this.travelDurations - ); - } else { - this.appendDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( - this.startFixed, - this.userChosenFromDuration[insertionIdx], - this.userChosenToDuration[companyIdx], - this.busStopFromDurations[insertionIdx], - this.busStopToDurations[companyIdx], - this.travelDurations - ); - this.prependDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( - this.startFixed, - this.userChosenFromDuration[companyIdx], - this.userChosenToDuration[insertionIdx], - this.busStopFromDurations[companyIdx], - this.busStopToDurations[insertionIdx], - this.travelDurations - ); - this.connectDurations[companyIdx][vehicleIdx][insertionIdx] = new EventInsertion( + const departure = v.tours.find((t) => t.id == next.tourId)!.departure; + const arrival = v.tours.find((t) => t.id == prev.tourId)!.arrival; + cases.forEach((type) => { + if((prev.tourId == next.tourId) != (type == InsertionType.INSERT)){ + return; + } + const isAppend = type === InsertionType.CONNECT || type === InsertionType.APPEND; + const isPrepend = type === InsertionType.CONNECT || type === InsertionType.PREPEND; + const interv = new Interval(isAppend?arrival:prev.time.startTime, isPrepend?departure:next.time.endTime); + const p = isAppend?companyIdx: insertionIdx; + const n = isPrepend?companyIdx: insertionIdx; + this.insertDurations[companyIdx][vehicleIdx][insertionIdx][type] = new EventInsertion( this.startFixed, - this.userChosenFromDuration[companyIdx], - this.userChosenToDuration[companyIdx], - this.busStopFromDurations[companyIdx], - this.busStopToDurations[companyIdx], - this.travelDurations + this.userChosenDuration[Timing.BEFORE][p], + this.userChosenDuration[Timing.AFTER][n], + this.busStopDurations[Timing.BEFORE][p], + this.busStopDurations[Timing.AFTER][n], + this.travelDurations, + interv, + this.busStopTimes, + v.availabilities, + type ); - } + }); }); }); }); @@ -365,18 +275,45 @@ export class TourScheduler { ++pickupIdx ) { for (let dropoffIdx = pickupIdx; dropoffIdx != insertion.latestDropoff; ++dropoffIdx) { - if (allEvents[pickupIdx + 1].tourId != allEvents[dropoffIdx].tourId) { + const prevPickup = allEvents[pickupIdx]; + const nextPickup = allEvents[pickupIdx + 1]; + const prevDropoff = allEvents[dropoffIdx]; + const nextDropoff = allEvents[dropoffIdx + 1]; + const pickupTimeDifference = + nextPickup.time.startTime.getTime() - prevPickup.time.endTime.getTime(); + if (nextPickup.tourId != prevDropoff.tourId) { break; } - this.createInsertionPair( - allEvents, - pickupIdx, - dropoffIdx, - companyIdx, - c.id, - vehicleIdx, - v.id - ); + if(prevPickup.tourId == nextDropoff.tourId) { + if(prevPickup.id == prevDropoff.id) { + this.busStops.forEach((_, busStopIdx) => { + const duration = + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + if (duration != undefined && duration <= pickupTimeDifference) { + this.answers[busStopIdx].push({ + companyId: c.id, + vehicleId: v.id, + pickupAfterEventId: prevPickup.id, + dropoffAfterEventId: prevDropoff.id, + type: InsertionType.INSERT + }); + } + }); + } + else{ + + } + continue; + } + if(prevPickup.tourId == nextPickup.tourId) { + + continue; + } + if(prevDropoff.tourId == nextDropoff.tourId) { + + continue; + } + } } }); @@ -407,25 +344,24 @@ export class TourScheduler { if (pickupIdx == dropoffIdx) { if (prevPickup.tourId != nextPickup.tourId) { connectDuration = - this.connectDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.CONNECT][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; appendDuration = - this.appendDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.APPEND][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; prependDuration = - this.prependDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.PREPEND][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; } else { duration = - this.insertDurations[companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; } } else { if (prevPickup.tourId != nextPickup.tourId) { } else { const busStopDuration = - this.insertDurations[companyIdx][vehicleIdx][this.startFixed ? pickupIdx : dropoffIdx] + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][this.startFixed ? pickupIdx : dropoffIdx] .busStopDurations[busStopIdx]; const userChosenDuration = - this.insertDurations[companyIdx][vehicleIdx][this.startFixed ? dropoffIdx : pickupIdx] + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][this.startFixed ? dropoffIdx : pickupIdx] .userChosenDuration; - duration = userChosenDuration + busStopDuration; } } From a188e9e7daeeb79366d83f33253df4e0580e522a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCndling?= Date: Mon, 9 Sep 2024 16:54:36 +0200 Subject: [PATCH 7/8] tests --- src/index.test.ts | 7 - src/lib/capacities.ts | 12 +- src/routes/api/whitelist/searchInterval.ts | 2 +- src/routes/api/whitelist/tourScheduler.ts | 176 ++++++++++++++------- 4 files changed, 124 insertions(+), 73 deletions(-) delete mode 100644 src/index.test.ts diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index e07cbbd7..00000000 --- a/src/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); -}); diff --git a/src/lib/capacities.ts b/src/lib/capacities.ts index 3ebaf334..b6b1ab0b 100644 --- a/src/lib/capacities.ts +++ b/src/lib/capacities.ts @@ -1,11 +1,11 @@ import type { Event } from '$lib/compositionTypes.js'; -export class Capacity { - wheelchairs!: number; - bikes!: number; - passengers!: number; - luggage!: number; -} +export type Capacity = { + wheelchairs: number; + bikes: number; + passengers: number; + luggage: number; +}; export type Range = { earliestPickup: number; diff --git a/src/routes/api/whitelist/searchInterval.ts b/src/routes/api/whitelist/searchInterval.ts index 04a69516..f40f2f63 100644 --- a/src/routes/api/whitelist/searchInterval.ts +++ b/src/routes/api/whitelist/searchInterval.ts @@ -10,7 +10,7 @@ export const computeSearchIntervals = ( searchInterval: Interval; expandedSearchInterval: Interval; } => { - const time = times.flatMap((t)=>t)[0]; + const time = times.flatMap((t) => t)[0]; const possibleStartTimes = new Interval( startFixed ? time : new Date(time.getTime() - SEARCH_INTERVAL_SIZE - travelDuration), startFixed diff --git a/src/routes/api/whitelist/tourScheduler.ts b/src/routes/api/whitelist/tourScheduler.ts index e42d3b35..8ec5c473 100644 --- a/src/routes/api/whitelist/tourScheduler.ts +++ b/src/routes/api/whitelist/tourScheduler.ts @@ -34,25 +34,35 @@ class EventInsertion { console.assert(fromBusStopDurations.length == toBusStopDurations.length); console.assert(fromBusStopDurations.length == travelDurations.length); this.busStops = new Array(busStopTimes.length); - this.both=new Array(busStopTimes.length); + this.both = new Array(busStopTimes.length); - const availabilitiesInWindow: Interval[] = type!=InsertionType.INSERT?[window]:Interval.intersect(availabilities, window); - - this.userChosen=availabilitiesInWindow.filter((a)=>a.getDurationMs()>=fromUserChosenDuration + toUserChosenDuration!); - for(let i=0;i!=travelDurations.length;++i){ + const availabilitiesInWindow: Interval[] = + type != InsertionType.INSERT ? [window] : Interval.intersect(availabilities, window); + + this.userChosen = availabilitiesInWindow.filter( + (a) => a.getDurationMs() >= fromUserChosenDuration + toUserChosenDuration! + ); + for (let i = 0; i != travelDurations.length; ++i) { const duration = fromBusStopDurations[i] + toBusStopDurations[i]; - const bothDuration = (startFixed ? fromBusStopDurations[i] : fromUserChosenDuration) + - travelDurations[i] + - (startFixed ? toUserChosenDuration : toBusStopDurations[i]); - for(let j=0;j!=busStopTimes[i].length;++j){ - this.busStops[i][j] = Interval.intersect(availabilitiesInWindow.filter((a)=>a.getDurationMs()>=duration), busStopTimes[i][j]); - this.both[i][j] = Interval.intersect(availabilitiesInWindow.filter((a)=>a.getDurationMs()>=bothDuration), busStopTimes[i][j]); + const bothDuration = + (startFixed ? fromBusStopDurations[i] : fromUserChosenDuration) + + travelDurations[i] + + (startFixed ? toUserChosenDuration : toBusStopDurations[i]); + for (let j = 0; j != busStopTimes[i].length; ++j) { + this.busStops[i][j] = Interval.intersect( + availabilitiesInWindow.filter((a) => a.getDurationMs() >= duration), + busStopTimes[i][j] + ); + this.both[i][j] = Interval.intersect( + availabilitiesInWindow.filter((a) => a.getDurationMs() >= bothDuration), + busStopTimes[i][j] + ); } } } - userChosen:Interval[]; - busStops:Interval[][][]; - both:Interval[][][]; + userChosen: Interval[]; + busStops: Interval[][][]; + both: Interval[][][]; } type Answer = { @@ -72,17 +82,25 @@ export class TourScheduler { travelDurations: number[], companies: Company[], required: Capacity, - companyMayServeBusStop:boolean[][] + companyMayServeBusStop: boolean[][] ) { this.companyMayServeBusStop = companyMayServeBusStop; - this.busStopTimes = busStopTimes.map((times)=>times.map((t)=>new Interval(startFixed?t:new Date(t.getTime()-MAX_PASSENGER_WAITING_TIME), startFixed?new Date(t.getTime()+MAX_PASSENGER_WAITING_TIME):t))); + this.busStopTimes = busStopTimes.map((times) => + times.map( + (t) => + new Interval( + startFixed ? t : new Date(t.getTime() - MAX_PASSENGER_WAITING_TIME), + startFixed ? new Date(t.getTime() + MAX_PASSENGER_WAITING_TIME) : t + ) + ) + ); this.required = required; this.companies = companies; this.travelDurations = travelDurations; this.startFixed = startFixed; this.userChosen = userChosen; this.busStops = busStops; - + this.possibleInsertionsByVehicle = new Map(); this.userChosenMany = new Array(2); @@ -93,11 +111,11 @@ export class TourScheduler { this.insertDurations = new Array(companies.length); - this.insertionIndexesUserChosenDurationIndexes=[]; - this.insertionIndexesBusStopDurationIndexes=[]; + this.insertionIndexesUserChosenDurationIndexes = []; + this.insertionIndexesBusStopDurationIndexes = []; - this.companyIndexesUserChosenDurationIndexes=new Array(companies.length); - this.companyIndexesBusStopDurationIndexes=new Array(companies.length); + this.companyIndexesUserChosenDurationIndexes = new Array(companies.length); + this.companyIndexesBusStopDurationIndexes = new Array(companies.length); this.answers = new Array(busStops.length); } @@ -109,7 +127,7 @@ export class TourScheduler { travelDurations: number[]; userChosen: Coordinates; busStops: Coordinates[]; - + possibleInsertionsByVehicle: Map; userChosenMany: Coordinates[][]; @@ -145,7 +163,13 @@ export class TourScheduler { v.seats, v.storage_space ); - this.possibleInsertionsByVehicle.set(v.id, simulation.getPossibleInsertionRanges(v.tours.flatMap((t) => t.events), this.required)); + this.possibleInsertionsByVehicle.set( + v.id, + simulation.getPossibleInsertionRanges( + v.tours.flatMap((t) => t.events), + this.required + ) + ); }); }); } @@ -157,40 +181,62 @@ export class TourScheduler { const allEvents = v.tours.flatMap((t) => t.events); const insertions = this.possibleInsertionsByVehicle.get(v.id)!; forEachInsertion(insertions, (insertionIdx) => { - this.addCoordinates(allEvents[insertionIdx].coordinates, allEvents[insertionIdx + 1].coordinates, companyIdx, vehicleIdx, insertionIdx); + this.addCoordinates( + allEvents[insertionIdx].coordinates, + allEvents[insertionIdx + 1].coordinates, + companyIdx, + vehicleIdx, + insertionIdx + ); }); }); }); } - private addCompanyCoordinates(c: Coordinates, companyIdx: number){ + private addCompanyCoordinates(c: Coordinates, companyIdx: number) { for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { - if(!this.companyMayServeBusStop[busStopIdx][companyIdx]){ + if (!this.companyMayServeBusStop[busStopIdx][companyIdx]) { continue; } this.busStopMany[Timing.BEFORE][busStopIdx].push(c); - this.companyIndexesBusStopDurationIndexes[Timing.BEFORE][companyIdx][busStopIdx] = this.busStopMany.length; + this.companyIndexesBusStopDurationIndexes[Timing.BEFORE][companyIdx][busStopIdx] = + this.busStopMany.length; this.busStopMany[Timing.AFTER][busStopIdx].push(c); - this.companyIndexesBusStopDurationIndexes[Timing.AFTER][companyIdx][busStopIdx] = this.busStopMany.length; + this.companyIndexesBusStopDurationIndexes[Timing.AFTER][companyIdx][busStopIdx] = + this.busStopMany.length; } this.userChosenMany[Timing.BEFORE].push(c); this.userChosenMany[Timing.AFTER].push(c); } - private addCoordinates(prev: Coordinates, next: Coordinates, companyIdx: number, vehicleIdx: number, insertionIdx: number) { + private addCoordinates( + prev: Coordinates, + next: Coordinates, + companyIdx: number, + vehicleIdx: number, + insertionIdx: number + ) { for (let busStopIdx = 0; busStopIdx != this.busStops.length; ++busStopIdx) { - if(!this.companyMayServeBusStop[busStopIdx][companyIdx]){ + if (!this.companyMayServeBusStop[busStopIdx][companyIdx]) { continue; } this.busStopMany[Timing.BEFORE][busStopIdx].push(prev); - this.insertionIndexesBusStopDurationIndexes[Timing.BEFORE][companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopMany[busStopIdx].length; + this.insertionIndexesBusStopDurationIndexes[Timing.BEFORE][companyIdx][vehicleIdx][ + insertionIdx + ][busStopIdx] = this.busStopMany[busStopIdx].length; this.busStopMany[Timing.AFTER][busStopIdx].push(next); - this.insertionIndexesBusStopDurationIndexes[Timing.AFTER][companyIdx][vehicleIdx][insertionIdx][busStopIdx] = this.busStopMany[busStopIdx].length; + this.insertionIndexesBusStopDurationIndexes[Timing.AFTER][companyIdx][vehicleIdx][ + insertionIdx + ][busStopIdx] = this.busStopMany[busStopIdx].length; } this.userChosenMany[Timing.BEFORE].push(prev); - this.insertionIndexesUserChosenDurationIndexes[Timing.BEFORE][companyIdx][vehicleIdx][insertionIdx] = this.busStopMany.length; + this.insertionIndexesUserChosenDurationIndexes[Timing.BEFORE][companyIdx][vehicleIdx][ + insertionIdx + ] = this.busStopMany.length; this.userChosenMany[Timing.AFTER].push(next); - this.insertionIndexesUserChosenDurationIndexes[Timing.AFTER][companyIdx][vehicleIdx][insertionIdx] = this.busStopMany.length; + this.insertionIndexesUserChosenDurationIndexes[Timing.AFTER][companyIdx][vehicleIdx][ + insertionIdx + ] = this.busStopMany.length; } private async routing() { @@ -219,7 +265,12 @@ export class TourScheduler { } private computeTravelDurations() { - const cases = [InsertionType.CONNECT, InsertionType.APPEND, InsertionType.PREPEND, InsertionType.INSERT]; + const cases = [ + InsertionType.CONNECT, + InsertionType.APPEND, + InsertionType.PREPEND, + InsertionType.INSERT + ]; this.companies.forEach((c, companyIdx) => { this.insertDurations[companyIdx] = new Array(c.vehicles.length); c.vehicles.forEach((v, vehicleIdx) => { @@ -229,7 +280,9 @@ export class TourScheduler { return; } const lastInsertionIdx = insertions[insertions.length - 1].latestDropoff; - this.insertDurations[companyIdx][vehicleIdx] = new Array(lastInsertionIdx); + this.insertDurations[companyIdx][vehicleIdx] = new Array( + lastInsertionIdx + ); forEachInsertion(insertions, (insertionIdx) => { this.insertDurations[companyIdx][vehicleIdx][insertionIdx] = new Array(4); const prev = allEvents[insertionIdx]; @@ -237,14 +290,17 @@ export class TourScheduler { const departure = v.tours.find((t) => t.id == next.tourId)!.departure; const arrival = v.tours.find((t) => t.id == prev.tourId)!.arrival; cases.forEach((type) => { - if((prev.tourId == next.tourId) != (type == InsertionType.INSERT)){ + if ((prev.tourId == next.tourId) != (type == InsertionType.INSERT)) { return; } const isAppend = type === InsertionType.CONNECT || type === InsertionType.APPEND; const isPrepend = type === InsertionType.CONNECT || type === InsertionType.PREPEND; - const interv = new Interval(isAppend?arrival:prev.time.startTime, isPrepend?departure:next.time.endTime); - const p = isAppend?companyIdx: insertionIdx; - const n = isPrepend?companyIdx: insertionIdx; + const interv = new Interval( + isAppend ? arrival : prev.time.startTime, + isPrepend ? departure : next.time.endTime + ); + const p = isAppend ? companyIdx : insertionIdx; + const n = isPrepend ? companyIdx : insertionIdx; this.insertDurations[companyIdx][vehicleIdx][insertionIdx][type] = new EventInsertion( this.startFixed, this.userChosenDuration[Timing.BEFORE][p], @@ -284,11 +340,12 @@ export class TourScheduler { if (nextPickup.tourId != prevDropoff.tourId) { break; } - if(prevPickup.tourId == nextDropoff.tourId) { - if(prevPickup.id == prevDropoff.id) { + if (prevPickup.tourId == nextDropoff.tourId) { + if (prevPickup.id == prevDropoff.id) { this.busStops.forEach((_, busStopIdx) => { const duration = - this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][pickupIdx] + .bothDurations[busStopIdx]; if (duration != undefined && duration <= pickupTimeDifference) { this.answers[busStopIdx].push({ companyId: c.id, @@ -299,21 +356,16 @@ export class TourScheduler { }); } }); - } - else{ - + } else { } continue; } - if(prevPickup.tourId == nextPickup.tourId) { - + if (prevPickup.tourId == nextPickup.tourId) { continue; } - if(prevDropoff.tourId == nextDropoff.tourId) { - + if (prevDropoff.tourId == nextDropoff.tourId) { continue; } - } } }); @@ -344,24 +396,30 @@ export class TourScheduler { if (pickupIdx == dropoffIdx) { if (prevPickup.tourId != nextPickup.tourId) { connectDuration = - this.insertDurations[InsertionType.CONNECT][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.CONNECT][companyIdx][vehicleIdx][pickupIdx] + .bothDurations[busStopIdx]; appendDuration = - this.insertDurations[InsertionType.APPEND][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.APPEND][companyIdx][vehicleIdx][pickupIdx] + .bothDurations[busStopIdx]; prependDuration = - this.insertDurations[InsertionType.PREPEND][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.PREPEND][companyIdx][vehicleIdx][pickupIdx] + .bothDurations[busStopIdx]; } else { duration = - this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][pickupIdx].bothDurations[busStopIdx]; + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][pickupIdx] + .bothDurations[busStopIdx]; } } else { if (prevPickup.tourId != nextPickup.tourId) { } else { const busStopDuration = - this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][this.startFixed ? pickupIdx : dropoffIdx] - .busStopDurations[busStopIdx]; + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][ + this.startFixed ? pickupIdx : dropoffIdx + ].busStopDurations[busStopIdx]; const userChosenDuration = - this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][this.startFixed ? dropoffIdx : pickupIdx] - .userChosenDuration; + this.insertDurations[InsertionType.INSERT][companyIdx][vehicleIdx][ + this.startFixed ? dropoffIdx : pickupIdx + ].userChosenDuration; } } From 682209f00412f5b3b5d58aaabf8d88c263e36399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20G=C3=BCndling?= Date: Wed, 11 Sep 2024 15:02:08 +0200 Subject: [PATCH 8/8] add missing file (wip) --- .../api/whitelist/searchInterval.test.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/routes/api/whitelist/searchInterval.test.ts diff --git a/src/routes/api/whitelist/searchInterval.test.ts b/src/routes/api/whitelist/searchInterval.test.ts new file mode 100644 index 00000000..7fb38526 --- /dev/null +++ b/src/routes/api/whitelist/searchInterval.test.ts @@ -0,0 +1,62 @@ +// import { computeSearchIntervals } from './searchInterval'; +import { db } from '$lib/database'; +import { describe, it, expect, beforeAll } from 'vitest'; + +let plate = 1; + +const addCompany = async (): Promise => { + return (await db + .insertInto('company') + .values({ address: null }) + .returning('id') + .executeTakeFirst())!.id; +}; + +const addTaxi = async (company: number): Promise => { + ++plate; + return (await db + .insertInto('vehicle') + .values({ + license_plate: String(plate), + company, + seats: 3, + wheelchair_capacity: 0, + bike_capacity: 0, + storage_space: 0 + }) + .returning('id') + .executeTakeFirst())!.id; +}; + +const clearDatabase = async () => { + await Promise.all([ + db.deleteFrom('company').execute(), + db.deleteFrom('vehicle').execute(), + db.deleteFrom('tour').execute(), + db.deleteFrom('availability').execute(), + db.deleteFrom('auth_user').execute(), + db.deleteFrom('user_session').execute(), + db.deleteFrom('event').execute(), + db.deleteFrom('address').execute(), + db.deleteFrom('request').execute() + ]); +}; + +describe('sum test', () => { + beforeAll(async () => { + await clearDatabase(); + }); + + it('adds 1 + 2 to equal 3', async () => { + const company = await addCompany(); + const taxi1 = await addTaxi(company); + const taxi2 = await addTaxi(company); + + console.log(taxi1, taxi2); + + // await setAvailability(taxi1, ['2024-09-23T17:00', '2024-09-23T18:00']); + // await setAvailability(taxi2, ['2024-09-23T17:30', '2024-09-23T18:30']); + + // await addBooking(); + }); +});