geography entity with tests

This commit is contained in:
sbriat 2023-04-20 15:52:01 +02:00
parent ed99b442eb
commit 6dd4837c89
18 changed files with 458 additions and 121 deletions

View File

@ -6,7 +6,7 @@ import { IDefaultParams } from '../../domain/types/default-params.type';
export class DefaultParamsProvider { export class DefaultParamsProvider {
constructor(private readonly configService: ConfigService) {} constructor(private readonly configService: ConfigService) {}
getParams(): IDefaultParams { getParams = (): IDefaultParams => {
return { return {
DEFAULT_IDENTIFIER: parseInt( DEFAULT_IDENTIFIER: parseInt(
this.configService.get('DEFAULT_IDENTIFIER'), this.configService.get('DEFAULT_IDENTIFIER'),
@ -33,5 +33,5 @@ export class DefaultParamsProvider {
georouterUrl: this.configService.get('GEOROUTER_URL'), georouterUrl: this.configService.get('GEOROUTER_URL'),
}, },
}; };
} };
} }

View File

@ -10,12 +10,12 @@ export class MatcherGeodesic implements IGeodesic {
this._geod = Geodesic.WGS84; this._geod = Geodesic.WGS84;
} }
inverse( inverse = (
lon1: number, lon1: number,
lat1: number, lat1: number,
lon2: number, lon2: number,
lat2: number, lat2: number,
): { azimuth: number; distance: number } { ): { azimuth: number; distance: number } => {
const { azi2: azimuth, s12: distance } = this._geod.Inverse( const { azi2: azimuth, s12: distance } = this._geod.Inverse(
lat1, lat1,
lon1, lon1,
@ -23,5 +23,5 @@ export class MatcherGeodesic implements IGeodesic {
lon2, lon2,
); );
return { azimuth, distance }; return { azimuth, distance };
} };
} }

View File

@ -12,12 +12,12 @@ export class GeorouterCreator implements ICreateGeorouter {
private readonly geodesic: MatcherGeodesic, private readonly geodesic: MatcherGeodesic,
) {} ) {}
create(type: string, url: string): IGeorouter { create = (type: string, url: string): IGeorouter => {
switch (type) { switch (type) {
case 'graphhopper': case 'graphhopper':
return new GraphhopperGeorouter(url, this.httpService, this.geodesic); return new GraphhopperGeorouter(url, this.httpService, this.geodesic);
default: default:
throw new Error('Unknown geocoder'); throw new Error('Unknown geocoder');
} }
} };
} }

View File

@ -27,50 +27,50 @@ export class GraphhopperGeorouter implements IGeorouter {
this._geodesic = geodesic; this._geodesic = geodesic;
} }
async route( route = async (
paths: Array<Path>, paths: Array<Path>,
settings: GeorouterSettings, settings: GeorouterSettings,
): Promise<Array<NamedRoute>> { ): Promise<Array<NamedRoute>> => {
this._setDefaultUrlArgs(); this._setDefaultUrlArgs();
this._setWithTime(settings.withTime); this._setWithTime(settings.withTime);
this._setWithPoints(settings.withPoints); this._setWithPoints(settings.withPoints);
this._setWithDistance(settings.withDistance); this._setWithDistance(settings.withDistance);
this._paths = paths; this._paths = paths;
return await this._getRoutes(); return await this._getRoutes();
} };
_setDefaultUrlArgs(): void { _setDefaultUrlArgs = (): void => {
this._urlArgs = [ this._urlArgs = [
'vehicle=car', 'vehicle=car',
'weighting=fastest', 'weighting=fastest',
'points_encoded=false', 'points_encoded=false',
]; ];
} };
_setWithTime(withTime: boolean): void { _setWithTime = (withTime: boolean): void => {
this._withTime = withTime; this._withTime = withTime;
if (withTime) { if (withTime) {
this._urlArgs.push('details=time'); this._urlArgs.push('details=time');
} }
} };
_setWithPoints(withPoints: boolean): void { _setWithPoints = (withPoints: boolean): void => {
this._withPoints = withPoints; this._withPoints = withPoints;
if (!withPoints) { if (!withPoints) {
this._urlArgs.push('calc_points=false'); this._urlArgs.push('calc_points=false');
} }
} };
_setWithDistance(withDistance: boolean): void { _setWithDistance = (withDistance: boolean): void => {
this._withDistance = withDistance; this._withDistance = withDistance;
if (withDistance) { if (withDistance) {
this._urlArgs.push('instructions=true'); this._urlArgs.push('instructions=true');
} else { } else {
this._urlArgs.push('instructions=false'); this._urlArgs.push('instructions=false');
} }
} };
async _getRoutes(): Promise<Array<NamedRoute>> { _getRoutes = async (): Promise<Array<NamedRoute>> => {
const routes = Promise.all( const routes = Promise.all(
this._paths.map(async (path) => { this._paths.map(async (path) => {
const url: string = [ const url: string = [
@ -95,20 +95,25 @@ export class GraphhopperGeorouter implements IGeorouter {
}), }),
); );
return routes; return routes;
} };
_getUrl(): string { _getUrl = (): string => {
return [this._url, this._urlArgs.join('&')].join(''); return [this._url, this._urlArgs.join('&')].join('');
} };
_createRoute(response: AxiosResponse<GraphhopperResponse>): Route { _createRoute = (response: AxiosResponse<GraphhopperResponse>): Route => {
const route = new Route(this._geodesic); const route = new Route(this._geodesic);
if (response.data.paths && response.data.paths[0]) { if (response.data.paths && response.data.paths[0]) {
const shortestPath = response.data.paths[0]; const shortestPath = response.data.paths[0];
route.distance = shortestPath.distance ?? 0; route.distance = shortestPath.distance ?? 0;
route.duration = shortestPath.time ? shortestPath.time / 1000 : 0; route.duration = shortestPath.time ? shortestPath.time / 1000 : 0;
if (shortestPath.points && shortestPath.points.coordinates) { if (shortestPath.points && shortestPath.points.coordinates) {
route.setPoints(shortestPath.points.coordinates); route.setPoints(
shortestPath.points.coordinates.map((coordinate) => ({
lon: coordinate[0],
lat: coordinate[1],
})),
);
if ( if (
shortestPath.details && shortestPath.details &&
shortestPath.details.time && shortestPath.details.time &&
@ -130,14 +135,14 @@ export class GraphhopperGeorouter implements IGeorouter {
} }
} }
return route; return route;
} };
_generateSpacetimePoints( _generateSpacetimePoints = (
points: Array<Array<number>>, points: Array<Array<number>>,
snappedWaypoints: Array<Array<number>>, snappedWaypoints: Array<Array<number>>,
durations: Array<Array<number>>, durations: Array<Array<number>>,
instructions: Array<GraphhopperInstruction>, instructions: Array<GraphhopperInstruction>,
): Array<SpacetimePoint> { ): Array<SpacetimePoint> => {
const indices = this._getIndices(points, snappedWaypoints); const indices = this._getIndices(points, snappedWaypoints);
const times = this._getTimes(durations, indices); const times = this._getTimes(durations, indices);
const distances = this._getDistances(instructions, indices); const distances = this._getDistances(instructions, indices);
@ -149,12 +154,12 @@ export class GraphhopperGeorouter implements IGeorouter {
distances.find((distance) => distance.index == index)?.distance, distances.find((distance) => distance.index == index)?.distance,
), ),
); );
} };
_getIndices( _getIndices = (
points: Array<Array<number>>, points: Array<Array<number>>,
snappedWaypoints: Array<Array<number>>, snappedWaypoints: Array<Array<number>>,
): Array<number> { ): Array<number> => {
const indices = snappedWaypoints.map((waypoint) => const indices = snappedWaypoints.map((waypoint) =>
points.findIndex( points.findIndex(
(point) => point[0] == waypoint[0] && point[1] == waypoint[1], (point) => point[0] == waypoint[0] && point[1] == waypoint[1],
@ -199,12 +204,12 @@ export class GraphhopperGeorouter implements IGeorouter {
indices[missedWaypoint.originIndex] = missedWaypoint.nearest; indices[missedWaypoint.originIndex] = missedWaypoint.nearest;
} }
return indices; return indices;
} };
_getTimes( _getTimes = (
durations: Array<Array<number>>, durations: Array<Array<number>>,
indices: Array<number>, indices: Array<number>,
): Array<{ index: number; duration: number }> { ): Array<{ index: number; duration: number }> => {
const times: Array<{ index: number; duration: number }> = []; const times: Array<{ index: number; duration: number }> = [];
let duration = 0; let duration = 0;
for (const [origin, destination, stepDuration] of durations) { for (const [origin, destination, stepDuration] of durations) {
@ -249,12 +254,12 @@ export class GraphhopperGeorouter implements IGeorouter {
duration += stepDuration; duration += stepDuration;
} }
return times; return times;
} };
_getDistances( _getDistances = (
instructions: Array<GraphhopperInstruction>, instructions: Array<GraphhopperInstruction>,
indices: Array<number>, indices: Array<number>,
): Array<{ index: number; distance: number }> { ): Array<{ index: number; distance: number }> => {
let distance = 0; let distance = 0;
const distances: Array<{ index: number; distance: number }> = [ const distances: Array<{ index: number; distance: number }> = [
{ {
@ -276,7 +281,7 @@ export class GraphhopperGeorouter implements IGeorouter {
} }
} }
return distances; return distances;
} };
} }
type GraphhopperResponse = { type GraphhopperResponse = {

View File

@ -12,7 +12,7 @@ export class Messager extends MessageBroker {
super(configService.get<string>('RMQ_EXCHANGE')); super(configService.get<string>('RMQ_EXCHANGE'));
} }
publish(routingKey: string, message: string): void { publish = (routingKey: string, message: string): void => {
this._amqpConnection.publish(this.exchange, routingKey, message); this._amqpConnection.publish(this.exchange, routingKey, message);
} };
} }

View File

@ -6,4 +6,10 @@ export class Actor {
person: Person; person: Person;
role: Role; role: Role;
step: Step; step: Step;
constructor(person: Person, role: Role, step: Step) {
this.person = person;
this.role = role;
this.step = step;
}
} }

View File

@ -3,32 +3,129 @@ import { IRequestGeography } from '../interfaces/geography-request.interface';
import { PointType } from '../types/geography.enum'; import { PointType } from '../types/geography.enum';
import { Point } from '../types/point.type'; import { Point } from '../types/point.type';
import { find } from 'geo-tz'; import { find } from 'geo-tz';
import { Waypoint } from '../types/waypoint';
import { Route } from './route'; import { Route } from './route';
import { Role } from '../types/role.enum';
import { IGeorouter } from '../interfaces/georouter.interface';
import { Waypoint } from './waypoint';
import { Actor } from './actor';
import { Person } from './person';
import { Step } from '../types/step.enum';
import { Path } from '../types/path.type';
export class Geography { export class Geography {
_geographyRequest: IRequestGeography; _geographyRequest: IRequestGeography;
waypoints: Array<Waypoint>; _person: Person;
_points: Array<Point>;
originType: PointType; originType: PointType;
destinationType: PointType; destinationType: PointType;
timezones: Array<string>; timezones: Array<string>;
driverRoute: Route; driverRoute: Route;
passengerRoute: Route; passengerRoute: Route;
constructor(geographyRequest: IRequestGeography, defaultTimezone: string) { constructor(
geographyRequest: IRequestGeography,
defaultTimezone: string,
person: Person,
) {
this._geographyRequest = geographyRequest; this._geographyRequest = geographyRequest;
this.waypoints = []; this._person = person;
this.originType = PointType.OTHER; this._points = [];
this.destinationType = PointType.OTHER; this.originType = undefined;
this.destinationType = undefined;
this.timezones = [defaultTimezone]; this.timezones = [defaultTimezone];
} }
init() { init = (): void => {
this._validateWaypoints(); this._validateWaypoints();
this._setTimezones(); this._setTimezones();
} this._setPointTypes();
};
_validateWaypoints() { createRoutes = async (
roles: Array<Role>,
georouter: IGeorouter,
): Promise<void> => {
let driverWaypoints: Array<Waypoint> = [];
let passengerWaypoints: Array<Waypoint> = [];
const paths: Array<Path> = [];
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
if (this._points.length == 2) {
// 2 points => same route for driver and passenger
const commonPath: Path = {
key: RouteKey.COMMON,
points: this._points,
};
driverWaypoints = this._createWaypoints(commonPath.points, Role.DRIVER);
passengerWaypoints = this._createWaypoints(
commonPath.points,
Role.PASSENGER,
);
paths.push(commonPath);
} else {
const driverPath: Path = {
key: RouteKey.DRIVER,
points: this._points,
};
driverWaypoints = this._createWaypoints(driverPath.points, Role.DRIVER);
const passengerPath: Path = {
key: RouteKey.PASSENGER,
points: [this._points[0], this._points[this._points.length - 1]],
};
passengerWaypoints = this._createWaypoints(
passengerPath.points,
Role.PASSENGER,
);
paths.push(driverPath, passengerPath);
}
} else if (roles.includes(Role.DRIVER)) {
const driverPath: Path = {
key: RouteKey.DRIVER,
points: this._points,
};
driverWaypoints = this._createWaypoints(driverPath.points, Role.DRIVER);
paths.push(driverPath);
} else if (roles.includes(Role.PASSENGER)) {
const passengerPath: Path = {
key: RouteKey.PASSENGER,
points: [this._points[0], this._points[this._points.length - 1]],
};
passengerWaypoints = this._createWaypoints(
passengerPath.points,
Role.PASSENGER,
);
paths.push(passengerPath);
}
const routes = await georouter.route(paths, {
withDistance: false,
withPoints: true,
withTime: false,
});
if (routes.some((route) => route.key == RouteKey.COMMON)) {
this.driverRoute = routes.find(
(route) => route.key == RouteKey.COMMON,
).route;
this.passengerRoute = routes.find(
(route) => route.key == RouteKey.COMMON,
).route;
this.driverRoute.setWaypoints(driverWaypoints);
this.passengerRoute.setWaypoints(passengerWaypoints);
} else {
if (routes.some((route) => route.key == RouteKey.DRIVER)) {
this.driverRoute = routes.find(
(route) => route.key == RouteKey.DRIVER,
).route;
this.driverRoute.setWaypoints(driverWaypoints);
}
if (routes.some((route) => route.key == RouteKey.PASSENGER)) {
this.passengerRoute = routes.find(
(route) => route.key == RouteKey.PASSENGER,
).route;
this.passengerRoute.setWaypoints(passengerWaypoints);
}
}
};
_validateWaypoints = (): void => {
if (this._geographyRequest.waypoints.length < 2) { if (this._geographyRequest.waypoints.length < 2) {
throw new MatcherException(3, 'At least 2 waypoints are required'); throw new MatcherException(3, 'At least 2 waypoints are required');
} }
@ -39,19 +136,25 @@ export class Geography {
`Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`, `Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`,
); );
} }
this.waypoints.push({ this._points.push(point);
point,
actors: [],
});
}); });
} };
_setTimezones() { _setTimezones = (): void => {
this.timezones = find( this.timezones = find(
this._geographyRequest.waypoints[0].lat, this._geographyRequest.waypoints[0].lat,
this._geographyRequest.waypoints[0].lon, this._geographyRequest.waypoints[0].lon,
); );
} };
_setPointTypes = (): void => {
this.originType =
this._geographyRequest.waypoints[0].type ?? PointType.OTHER;
this.destinationType =
this._geographyRequest.waypoints[
this._geographyRequest.waypoints.length - 1
].type ?? PointType.OTHER;
};
_isValidPoint = (point: Point): boolean => _isValidPoint = (point: Point): boolean =>
this._isValidLongitude(point.lon) && this._isValidLatitude(point.lat); this._isValidLongitude(point.lon) && this._isValidLatitude(point.lat);
@ -61,4 +164,24 @@ export class Geography {
_isValidLatitude = (latitude: number): boolean => _isValidLatitude = (latitude: number): boolean =>
latitude >= -90 && latitude <= 90; latitude >= -90 && latitude <= 90;
_createWaypoints = (points: Array<Point>, role: Role): Array<Waypoint> => {
return points.map((point, index) => {
const waypoint = new Waypoint(point);
if (index == 0) {
waypoint.addActor(new Actor(this._person, role, Step.START));
} else if (index == points.length - 1) {
waypoint.addActor(new Actor(this._person, role, Step.FINISH));
} else {
waypoint.addActor(new Actor(this._person, role, Step.INTERMEDIATE));
}
return waypoint;
});
};
}
export enum RouteKey {
COMMON = 'common',
DRIVER = 'driver',
PASSENGER = 'passenger',
} }

View File

@ -1,6 +1,6 @@
import { Route } from './route'; import { Route } from './route';
export class NamedRoute { export type NamedRoute = {
key: string; key: string;
route: Route; route: Route;
} };

View File

@ -17,7 +17,7 @@ export class Person {
this._defaultMarginDuration = defaultMarginDuration; this._defaultMarginDuration = defaultMarginDuration;
} }
init() { init = (): void => {
this.setIdentifier( this.setIdentifier(
this._personRequest.identifier ?? this._defaultIdentifier, this._personRequest.identifier ?? this._defaultIdentifier,
); );
@ -30,13 +30,13 @@ export class Person {
this._defaultMarginDuration, this._defaultMarginDuration,
this._defaultMarginDuration, this._defaultMarginDuration,
]); ]);
} };
setIdentifier(identifier: number) { setIdentifier = (identifier: number): void => {
this.identifier = identifier; this.identifier = identifier;
} };
setMarginDurations(marginDurations: Array<number>) { setMarginDurations = (marginDurations: Array<number>): void => {
this.marginDurations = marginDurations; this.marginDurations = marginDurations;
} };
} }

View File

@ -1,4 +1,5 @@
import { IGeodesic } from '../interfaces/geodesic.interface'; import { IGeodesic } from '../interfaces/geodesic.interface';
import { Point } from '../types/point.type';
import { SpacetimePoint } from './spacetime-point'; import { SpacetimePoint } from './spacetime-point';
import { Waypoint } from './waypoint'; import { Waypoint } from './waypoint';
@ -9,7 +10,7 @@ export class Route {
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
waypoints: Array<Waypoint>; waypoints: Array<Waypoint>;
points: Array<Array<number>>; points: Array<Point>;
spacetimePoints: Array<SpacetimePoint>; spacetimePoints: Array<SpacetimePoint>;
_geodesic: IGeodesic; _geodesic: IGeodesic;
@ -25,31 +26,31 @@ export class Route {
this._geodesic = geodesic; this._geodesic = geodesic;
} }
setWaypoints(waypoints: Array<Waypoint>): void { setWaypoints = (waypoints: Array<Waypoint>): void => {
this.waypoints = waypoints; this.waypoints = waypoints;
this._setAzimuth(waypoints.map((waypoint) => waypoint.point)); this._setAzimuth(waypoints.map((waypoint) => waypoint.point));
} };
setPoints(points: Array<Array<number>>): void { setPoints = (points: Array<Point>): void => {
this.points = points; this.points = points;
this._setAzimuth(points); this._setAzimuth(points);
} };
setSpacetimePoints(spacetimePoints: Array<SpacetimePoint>): void { setSpacetimePoints = (spacetimePoints: Array<SpacetimePoint>): void => {
this.spacetimePoints = spacetimePoints; this.spacetimePoints = spacetimePoints;
} };
_setAzimuth(points: Array<Array<number>>): void { _setAzimuth = (points: Array<Point>): void => {
const inverse = this._geodesic.inverse( const inverse = this._geodesic.inverse(
points[0][0], points[0].lon,
points[0][1], points[0].lat,
points[points.length - 1][0], points[points.length - 1].lon,
points[points.length - 1][1], points[points.length - 1].lat,
); );
this.fwdAzimuth = this.fwdAzimuth =
inverse.azimuth >= 0 ? inverse.azimuth : 360 - Math.abs(inverse.azimuth); inverse.azimuth >= 0 ? inverse.azimuth : 360 - Math.abs(inverse.azimuth);
this.backAzimuth = this.backAzimuth =
this.fwdAzimuth > 180 ? this.fwdAzimuth - 180 : this.fwdAzimuth + 180; this.fwdAzimuth > 180 ? this.fwdAzimuth - 180 : this.fwdAzimuth + 180;
this.distanceAzimuth = inverse.distance; this.distanceAzimuth = inverse.distance;
} };
} }

View File

@ -34,31 +34,31 @@ export class Time {
}; };
} }
init() { init = (): void => {
this._validateBaseDate(); this._validateBaseDate();
this._validatePunctualRequest(); this._validatePunctualRequest();
this._validateRecurrentRequest(); this._validateRecurrentRequest();
this._setPunctualRequest(); this._setPunctualRequest();
this._setRecurrentRequest(); this._setRecurrentRequest();
this._setMargindurations(); this._setMargindurations();
} };
_validateBaseDate() { _validateBaseDate = (): void => {
if (!this._timeRequest.departure && !this._timeRequest.fromDate) { if (!this._timeRequest.departure && !this._timeRequest.fromDate) {
throw new MatcherException(3, 'departure or fromDate is required'); throw new MatcherException(3, 'departure or fromDate is required');
} }
} };
_validatePunctualRequest() { _validatePunctualRequest = (): void => {
if (this._timeRequest.departure) { if (this._timeRequest.departure) {
this.fromDate = this.toDate = new Date(this._timeRequest.departure); this.fromDate = this.toDate = new Date(this._timeRequest.departure);
if (!this._isDate(this.fromDate)) { if (!this._isDate(this.fromDate)) {
throw new MatcherException(3, 'Wrong departure date'); throw new MatcherException(3, 'Wrong departure date');
} }
} }
} };
_validateRecurrentRequest() { _validateRecurrentRequest = (): void => {
if (this._timeRequest.fromDate) { if (this._timeRequest.fromDate) {
this.fromDate = new Date(this._timeRequest.fromDate); this.fromDate = new Date(this._timeRequest.fromDate);
if (!this._isDate(this.fromDate)) { if (!this._isDate(this.fromDate)) {
@ -77,9 +77,9 @@ export class Time {
if (this._timeRequest.fromDate) { if (this._timeRequest.fromDate) {
this._validateSchedule(); this._validateSchedule();
} }
} };
_validateSchedule() { _validateSchedule = (): void => {
if (!this._timeRequest.schedule) { if (!this._timeRequest.schedule) {
throw new MatcherException(3, 'Schedule is required'); throw new MatcherException(3, 'Schedule is required');
} }
@ -96,17 +96,17 @@ export class Time {
throw new MatcherException(3, `Wrong time for ${day} in schedule`); throw new MatcherException(3, `Wrong time for ${day} in schedule`);
} }
}); });
} };
_setPunctualRequest() { _setPunctualRequest = (): void => {
if (this._timeRequest.departure) { if (this._timeRequest.departure) {
this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL; this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL;
this.schedule[TimingDays[this.fromDate.getDay()]] = this.schedule[TimingDays[this.fromDate.getDay()]] =
this.fromDate.getHours() + ':' + this.fromDate.getMinutes(); this.fromDate.getHours() + ':' + this.fromDate.getMinutes();
} }
} };
_setRecurrentRequest() { _setRecurrentRequest = (): void => {
if (this._timeRequest.fromDate) { if (this._timeRequest.fromDate) {
this.frequency = TimingFrequency.FREQUENCY_RECURRENT; this.frequency = TimingFrequency.FREQUENCY_RECURRENT;
if (!this.toDate) { if (!this.toDate) {
@ -117,15 +117,15 @@ export class Time {
} }
this._setSchedule(); this._setSchedule();
} }
} };
_setSchedule() { _setSchedule = (): void => {
Object.keys(this._timeRequest.schedule).map((day) => { Object.keys(this._timeRequest.schedule).map((day) => {
this.schedule[day] = this._timeRequest.schedule[day]; this.schedule[day] = this._timeRequest.schedule[day];
}); });
} };
_setMargindurations() { _setMargindurations = (): void => {
if (this._timeRequest.marginDuration) { if (this._timeRequest.marginDuration) {
const duration = Math.abs(this._timeRequest.marginDuration); const duration = Math.abs(this._timeRequest.marginDuration);
this.marginDurations = { this.marginDurations = {
@ -155,7 +155,7 @@ export class Time {
); );
}); });
} }
} };
_isDate = (date: Date): boolean => { _isDate = (date: Date): boolean => {
return date instanceof Date && isFinite(+date); return date instanceof Date && isFinite(+date);

View File

@ -1,6 +1,14 @@
import { Point } from '../types/point.type';
import { Actor } from './actor'; import { Actor } from './actor';
export class Waypoint { export class Waypoint {
point: Array<number>; point: Point;
actors: Array<Actor>; actors: Array<Actor>;
constructor(point: Point) {
this.point = point;
this.actors = [];
}
addActor = (actor: Actor) => this.actors.push(actor);
} }

View File

@ -1,4 +1,7 @@
import { PointType } from './geography.enum';
export type Point = { export type Point = {
lon: number; lon: number;
lat: number; lat: number;
type?: PointType;
}; };

View File

@ -15,7 +15,7 @@ export class MatchUseCase {
@InjectMapper() private readonly _mapper: Mapper, @InjectMapper() private readonly _mapper: Mapper,
) {} ) {}
async execute(matchQuery: MatchQuery): Promise<ICollection<Match>> { execute = async (matchQuery: MatchQuery): Promise<ICollection<Match>> => {
try { try {
// const paths = []; // const paths = [];
// for (let i = 0; i < 1; i++) { // for (let i = 0; i < 1; i++) {
@ -73,5 +73,5 @@ export class MatchUseCase {
); );
throw error; throw error;
} }
} };
} }

View File

@ -11,7 +11,7 @@ export class MatchProfile extends AutomapperProfile {
} }
override get profile() { override get profile() {
return (mapper) => { return (mapper: Mapper) => {
createMap(mapper, Match, MatchPresenter); createMap(mapper, Match, MatchPresenter);
}; };
} }

View File

@ -39,60 +39,65 @@ export class MatchQuery {
this._setExclusions(); this._setExclusions();
} }
_setPerson() { createRoutes = (): void => {
this.geography.createRoutes(this.roles, this.algorithmSettings.georouter);
};
_setPerson = (): void => {
this.person = new Person( this.person = new Person(
this._matchRequest, this._matchRequest,
this._defaultParams.DEFAULT_IDENTIFIER, this._defaultParams.DEFAULT_IDENTIFIER,
this._defaultParams.MARGIN_DURATION, this._defaultParams.MARGIN_DURATION,
); );
this.person.init(); this.person.init();
} };
_setRoles() { _setRoles = (): void => {
this.roles = []; this.roles = [];
if (this._matchRequest.driver) this.roles.push(Role.DRIVER); if (this._matchRequest.driver) this.roles.push(Role.DRIVER);
if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER); if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER);
if (this.roles.length == 0) this.roles.push(Role.PASSENGER); if (this.roles.length == 0) this.roles.push(Role.PASSENGER);
} };
_setTime() { _setTime = (): void => {
this.time = new Time( this.time = new Time(
this._matchRequest, this._matchRequest,
this._defaultParams.MARGIN_DURATION, this._defaultParams.MARGIN_DURATION,
this._defaultParams.VALIDITY_DURATION, this._defaultParams.VALIDITY_DURATION,
); );
this.time.init(); this.time.init();
} };
_setGeography() { _setGeography = (): void => {
this.geography = new Geography( this.geography = new Geography(
this._matchRequest, this._matchRequest,
this._defaultParams.DEFAULT_TIMEZONE, this._defaultParams.DEFAULT_TIMEZONE,
this.person,
); );
this.geography.init(); this.geography.init();
} };
_setRequirement() { _setRequirement = (): void => {
this.requirement = new Requirement( this.requirement = new Requirement(
this._matchRequest, this._matchRequest,
this._defaultParams.DEFAULT_SEATS, this._defaultParams.DEFAULT_SEATS,
); );
} };
_setAlgorithmSettings() { _setAlgorithmSettings = (): void => {
this.algorithmSettings = new AlgorithmSettings( this.algorithmSettings = new AlgorithmSettings(
this._matchRequest, this._matchRequest,
this._defaultParams.DEFAULT_ALGORITHM_SETTINGS, this._defaultParams.DEFAULT_ALGORITHM_SETTINGS,
this.time.frequency, this.time.frequency,
this._georouterCreator, this._georouterCreator,
); );
} };
_setExclusions() { _setExclusions = (): void => {
this.exclusions = []; this.exclusions = [];
if (this._matchRequest.identifier) if (this._matchRequest.identifier)
this.exclusions.push(this._matchRequest.identifier); this.exclusions.push(this._matchRequest.identifier);
if (this._matchRequest.exclusions) if (this._matchRequest.exclusions)
this.exclusions.push(...this._matchRequest.exclusions); this.exclusions.push(...this._matchRequest.exclusions);
} };
} }

View File

@ -1,4 +1,66 @@
import { Geography } from '../../../domain/entities/geography'; import { Person } from '../../../domain/entities/person';
import { Geography, RouteKey } from '../../../domain/entities/geography';
import { Role } from '../../../domain/types/role.enum';
import { NamedRoute } from '../../../domain/entities/named-route';
import { Route } from '../../../domain/entities/route';
import { IGeodesic } from '../../../domain/interfaces/geodesic.interface';
import { PointType } from '../../../domain/types/geography.enum';
const person: Person = new Person(
{
identifier: 1,
},
0,
900,
);
const mockGeodesic: IGeodesic = {
inverse: jest.fn().mockImplementation(() => ({
azimuth: 45,
distance: 50000,
})),
};
const mockGeorouter = {
route: jest
.fn()
.mockImplementationOnce(() => {
return [
<NamedRoute>{
key: RouteKey.COMMON,
route: new Route(mockGeodesic),
},
];
})
.mockImplementationOnce(() => {
return [
<NamedRoute>{
key: RouteKey.DRIVER,
route: new Route(mockGeodesic),
},
<NamedRoute>{
key: RouteKey.PASSENGER,
route: new Route(mockGeodesic),
},
];
})
.mockImplementationOnce(() => {
return [
<NamedRoute>{
key: RouteKey.DRIVER,
route: new Route(mockGeodesic),
},
];
})
.mockImplementationOnce(() => {
return [
<NamedRoute>{
key: RouteKey.PASSENGER,
route: new Route(mockGeodesic),
},
];
}),
};
describe('Geography entity', () => { describe('Geography entity', () => {
it('should be defined', () => { it('should be defined', () => {
@ -16,29 +78,35 @@ describe('Geography entity', () => {
], ],
}, },
'Europe/Paris', 'Europe/Paris',
person,
); );
expect(geography).toBeDefined(); expect(geography).toBeDefined();
}); });
describe('init', () => { describe('init', () => {
it('should initialize a geography request', () => { it('should initialize a geography request with point types', () => {
const geography = new Geography( const geography = new Geography(
{ {
waypoints: [ waypoints: [
{ {
lat: 49.440041, lat: 49.440041,
lon: 1.093912, lon: 1.093912,
type: PointType.LOCALITY,
}, },
{ {
lat: 50.630992, lat: 50.630992,
lon: 3.045432, lon: 3.045432,
type: PointType.LOCALITY,
}, },
], ],
}, },
'Europe/Paris', 'Europe/Paris',
person,
); );
geography.init(); geography.init();
expect(geography.waypoints.length).toBe(2); expect(geography._points.length).toBe(2);
expect(geography.originType).toBe(PointType.LOCALITY);
expect(geography.destinationType).toBe(PointType.LOCALITY);
}); });
it('should throw an exception if waypoints are empty', () => { it('should throw an exception if waypoints are empty', () => {
const geography = new Geography( const geography = new Geography(
@ -46,6 +114,7 @@ describe('Geography entity', () => {
waypoints: [], waypoints: [],
}, },
'Europe/Paris', 'Europe/Paris',
person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
}); });
@ -60,6 +129,7 @@ describe('Geography entity', () => {
], ],
}, },
'Europe/Paris', 'Europe/Paris',
person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
}); });
@ -78,6 +148,7 @@ describe('Geography entity', () => {
], ],
}, },
'Europe/Paris', 'Europe/Paris',
person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
}); });
@ -96,8 +167,113 @@ describe('Geography entity', () => {
], ],
}, },
'Europe/Paris', 'Europe/Paris',
person,
); );
expect(() => geography.init()).toThrow(); expect(() => geography.init()).toThrow();
}); });
}); });
describe('create route', () => {
it('should create routes as driver and passenger', async () => {
const geography = new Geography(
{
waypoints: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
'Europe/Paris',
person,
);
geography.init();
await geography.createRoutes(
[Role.DRIVER, Role.PASSENGER],
mockGeorouter,
);
expect(geography.driverRoute.waypoints.length).toBe(2);
expect(geography.passengerRoute.waypoints.length).toBe(2);
});
it('should create routes as driver and passenger with 3 waypoints', async () => {
const geography = new Geography(
{
waypoints: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 49.781215,
lon: 2.198475,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
'Europe/Paris',
person,
);
geography.init();
await geography.createRoutes(
[Role.DRIVER, Role.PASSENGER],
mockGeorouter,
);
expect(geography.driverRoute.waypoints.length).toBe(3);
expect(geography.passengerRoute.waypoints.length).toBe(2);
});
it('should create routes as driver', async () => {
const geography = new Geography(
{
waypoints: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
'Europe/Paris',
person,
);
geography.init();
await geography.createRoutes([Role.DRIVER], mockGeorouter);
expect(geography.driverRoute.waypoints.length).toBe(2);
expect(geography.passengerRoute).toBeUndefined();
});
it('should create routes as passenger', async () => {
const geography = new Geography(
{
waypoints: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
'Europe/Paris',
person,
);
geography.init();
await geography.createRoutes([Role.PASSENGER], mockGeorouter);
expect(geography.passengerRoute.waypoints.length).toBe(2);
expect(geography.driverRoute).toBeUndefined();
});
});
}); });

View File

@ -24,10 +24,14 @@ describe('Route entity', () => {
}); });
it('should set waypoints and geodesic values for a route', () => { it('should set waypoints and geodesic values for a route', () => {
const route = new Route(mockGeodesic); const route = new Route(mockGeodesic);
const waypoint1: Waypoint = new Waypoint(); const waypoint1: Waypoint = new Waypoint({
waypoint1.point = [0, 0]; lon: 0,
const waypoint2: Waypoint = new Waypoint(); lat: 0,
waypoint2.point = [10, 10]; });
const waypoint2: Waypoint = new Waypoint({
lon: 10,
lat: 10,
});
route.setWaypoints([waypoint1, waypoint2]); route.setWaypoints([waypoint1, waypoint2]);
expect(route.waypoints.length).toBe(2); expect(route.waypoints.length).toBe(2);
expect(route.fwdAzimuth).toBe(45); expect(route.fwdAzimuth).toBe(45);
@ -37,8 +41,14 @@ describe('Route entity', () => {
it('should set points and geodesic values for a route', () => { it('should set points and geodesic values for a route', () => {
const route = new Route(mockGeodesic); const route = new Route(mockGeodesic);
route.setPoints([ route.setPoints([
[10, 10], {
[20, 20], lon: 10,
lat: 10,
},
{
lon: 20,
lat: 20,
},
]); ]);
expect(route.points.length).toBe(2); expect(route.points.length).toBe(2);
expect(route.fwdAzimuth).toBe(315); expect(route.fwdAzimuth).toBe(315);