From 6dd4837c891c9b951262953cc9fc638b22204b8f Mon Sep 17 00:00:00 2001 From: sbriat Date: Thu, 20 Apr 2023 15:52:01 +0200 Subject: [PATCH] geography entity with tests --- .../secondaries/default-params.provider.ts | 4 +- .../matcher/adapters/secondaries/geodesic.ts | 6 +- .../adapters/secondaries/georouter-creator.ts | 4 +- .../secondaries/graphhopper-georouter.ts | 65 ++++--- .../matcher/adapters/secondaries/messager.ts | 4 +- src/modules/matcher/domain/entities/actor.ts | 6 + .../matcher/domain/entities/geography.ts | 155 +++++++++++++-- .../matcher/domain/entities/named-route.ts | 4 +- src/modules/matcher/domain/entities/person.ts | 12 +- src/modules/matcher/domain/entities/route.ts | 27 +-- src/modules/matcher/domain/entities/time.ts | 36 ++-- .../matcher/domain/entities/waypoint.ts | 10 +- .../matcher/domain/types/point.type.ts | 3 + .../matcher/domain/usecases/match.usecase.ts | 4 +- src/modules/matcher/mappers/match.profile.ts | 2 +- src/modules/matcher/queries/match.query.ts | 33 ++-- .../tests/unit/domain/geography.spec.ts | 182 +++++++++++++++++- .../matcher/tests/unit/domain/route.spec.ts | 22 ++- 18 files changed, 458 insertions(+), 121 deletions(-) diff --git a/src/modules/matcher/adapters/secondaries/default-params.provider.ts b/src/modules/matcher/adapters/secondaries/default-params.provider.ts index 9b5bad6..c67dc10 100644 --- a/src/modules/matcher/adapters/secondaries/default-params.provider.ts +++ b/src/modules/matcher/adapters/secondaries/default-params.provider.ts @@ -6,7 +6,7 @@ import { IDefaultParams } from '../../domain/types/default-params.type'; export class DefaultParamsProvider { constructor(private readonly configService: ConfigService) {} - getParams(): IDefaultParams { + getParams = (): IDefaultParams => { return { DEFAULT_IDENTIFIER: parseInt( this.configService.get('DEFAULT_IDENTIFIER'), @@ -33,5 +33,5 @@ export class DefaultParamsProvider { georouterUrl: this.configService.get('GEOROUTER_URL'), }, }; - } + }; } diff --git a/src/modules/matcher/adapters/secondaries/geodesic.ts b/src/modules/matcher/adapters/secondaries/geodesic.ts index 56423a8..f2a9642 100644 --- a/src/modules/matcher/adapters/secondaries/geodesic.ts +++ b/src/modules/matcher/adapters/secondaries/geodesic.ts @@ -10,12 +10,12 @@ export class MatcherGeodesic implements IGeodesic { this._geod = Geodesic.WGS84; } - inverse( + inverse = ( lon1: number, lat1: number, lon2: number, lat2: number, - ): { azimuth: number; distance: number } { + ): { azimuth: number; distance: number } => { const { azi2: azimuth, s12: distance } = this._geod.Inverse( lat1, lon1, @@ -23,5 +23,5 @@ export class MatcherGeodesic implements IGeodesic { lon2, ); return { azimuth, distance }; - } + }; } diff --git a/src/modules/matcher/adapters/secondaries/georouter-creator.ts b/src/modules/matcher/adapters/secondaries/georouter-creator.ts index 22e65b3..379920a 100644 --- a/src/modules/matcher/adapters/secondaries/georouter-creator.ts +++ b/src/modules/matcher/adapters/secondaries/georouter-creator.ts @@ -12,12 +12,12 @@ export class GeorouterCreator implements ICreateGeorouter { private readonly geodesic: MatcherGeodesic, ) {} - create(type: string, url: string): IGeorouter { + create = (type: string, url: string): IGeorouter => { switch (type) { case 'graphhopper': return new GraphhopperGeorouter(url, this.httpService, this.geodesic); default: throw new Error('Unknown geocoder'); } - } + }; } diff --git a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts index 2ced628..5d1fd84 100644 --- a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts +++ b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts @@ -27,50 +27,50 @@ export class GraphhopperGeorouter implements IGeorouter { this._geodesic = geodesic; } - async route( + route = async ( paths: Array, settings: GeorouterSettings, - ): Promise> { + ): Promise> => { this._setDefaultUrlArgs(); this._setWithTime(settings.withTime); this._setWithPoints(settings.withPoints); this._setWithDistance(settings.withDistance); this._paths = paths; return await this._getRoutes(); - } + }; - _setDefaultUrlArgs(): void { + _setDefaultUrlArgs = (): void => { this._urlArgs = [ 'vehicle=car', 'weighting=fastest', 'points_encoded=false', ]; - } + }; - _setWithTime(withTime: boolean): void { + _setWithTime = (withTime: boolean): void => { this._withTime = withTime; if (withTime) { this._urlArgs.push('details=time'); } - } + }; - _setWithPoints(withPoints: boolean): void { + _setWithPoints = (withPoints: boolean): void => { this._withPoints = withPoints; if (!withPoints) { this._urlArgs.push('calc_points=false'); } - } + }; - _setWithDistance(withDistance: boolean): void { + _setWithDistance = (withDistance: boolean): void => { this._withDistance = withDistance; if (withDistance) { this._urlArgs.push('instructions=true'); } else { this._urlArgs.push('instructions=false'); } - } + }; - async _getRoutes(): Promise> { + _getRoutes = async (): Promise> => { const routes = Promise.all( this._paths.map(async (path) => { const url: string = [ @@ -95,20 +95,25 @@ export class GraphhopperGeorouter implements IGeorouter { }), ); return routes; - } + }; - _getUrl(): string { + _getUrl = (): string => { return [this._url, this._urlArgs.join('&')].join(''); - } + }; - _createRoute(response: AxiosResponse): Route { + _createRoute = (response: AxiosResponse): Route => { const route = new Route(this._geodesic); if (response.data.paths && response.data.paths[0]) { const shortestPath = response.data.paths[0]; route.distance = shortestPath.distance ?? 0; route.duration = shortestPath.time ? shortestPath.time / 1000 : 0; 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 ( shortestPath.details && shortestPath.details.time && @@ -130,14 +135,14 @@ export class GraphhopperGeorouter implements IGeorouter { } } return route; - } + }; - _generateSpacetimePoints( + _generateSpacetimePoints = ( points: Array>, snappedWaypoints: Array>, durations: Array>, instructions: Array, - ): Array { + ): Array => { const indices = this._getIndices(points, snappedWaypoints); const times = this._getTimes(durations, indices); const distances = this._getDistances(instructions, indices); @@ -149,12 +154,12 @@ export class GraphhopperGeorouter implements IGeorouter { distances.find((distance) => distance.index == index)?.distance, ), ); - } + }; - _getIndices( + _getIndices = ( points: Array>, snappedWaypoints: Array>, - ): Array { + ): Array => { const indices = snappedWaypoints.map((waypoint) => points.findIndex( (point) => point[0] == waypoint[0] && point[1] == waypoint[1], @@ -199,12 +204,12 @@ export class GraphhopperGeorouter implements IGeorouter { indices[missedWaypoint.originIndex] = missedWaypoint.nearest; } return indices; - } + }; - _getTimes( + _getTimes = ( durations: Array>, indices: Array, - ): Array<{ index: number; duration: number }> { + ): Array<{ index: number; duration: number }> => { const times: Array<{ index: number; duration: number }> = []; let duration = 0; for (const [origin, destination, stepDuration] of durations) { @@ -249,12 +254,12 @@ export class GraphhopperGeorouter implements IGeorouter { duration += stepDuration; } return times; - } + }; - _getDistances( + _getDistances = ( instructions: Array, indices: Array, - ): Array<{ index: number; distance: number }> { + ): Array<{ index: number; distance: number }> => { let distance = 0; const distances: Array<{ index: number; distance: number }> = [ { @@ -276,7 +281,7 @@ export class GraphhopperGeorouter implements IGeorouter { } } return distances; - } + }; } type GraphhopperResponse = { diff --git a/src/modules/matcher/adapters/secondaries/messager.ts b/src/modules/matcher/adapters/secondaries/messager.ts index e808bcf..96fa7cc 100644 --- a/src/modules/matcher/adapters/secondaries/messager.ts +++ b/src/modules/matcher/adapters/secondaries/messager.ts @@ -12,7 +12,7 @@ export class Messager extends MessageBroker { super(configService.get('RMQ_EXCHANGE')); } - publish(routingKey: string, message: string): void { + publish = (routingKey: string, message: string): void => { this._amqpConnection.publish(this.exchange, routingKey, message); - } + }; } diff --git a/src/modules/matcher/domain/entities/actor.ts b/src/modules/matcher/domain/entities/actor.ts index de9448a..075b8b4 100644 --- a/src/modules/matcher/domain/entities/actor.ts +++ b/src/modules/matcher/domain/entities/actor.ts @@ -6,4 +6,10 @@ export class Actor { person: Person; role: Role; step: Step; + + constructor(person: Person, role: Role, step: Step) { + this.person = person; + this.role = role; + this.step = step; + } } diff --git a/src/modules/matcher/domain/entities/geography.ts b/src/modules/matcher/domain/entities/geography.ts index 506e9b3..2fcfbee 100644 --- a/src/modules/matcher/domain/entities/geography.ts +++ b/src/modules/matcher/domain/entities/geography.ts @@ -3,32 +3,129 @@ import { IRequestGeography } from '../interfaces/geography-request.interface'; import { PointType } from '../types/geography.enum'; import { Point } from '../types/point.type'; import { find } from 'geo-tz'; -import { Waypoint } from '../types/waypoint'; 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 { _geographyRequest: IRequestGeography; - waypoints: Array; + _person: Person; + _points: Array; originType: PointType; destinationType: PointType; timezones: Array; driverRoute: Route; passengerRoute: Route; - constructor(geographyRequest: IRequestGeography, defaultTimezone: string) { + constructor( + geographyRequest: IRequestGeography, + defaultTimezone: string, + person: Person, + ) { this._geographyRequest = geographyRequest; - this.waypoints = []; - this.originType = PointType.OTHER; - this.destinationType = PointType.OTHER; + this._person = person; + this._points = []; + this.originType = undefined; + this.destinationType = undefined; this.timezones = [defaultTimezone]; } - init() { + init = (): void => { this._validateWaypoints(); this._setTimezones(); - } + this._setPointTypes(); + }; - _validateWaypoints() { + createRoutes = async ( + roles: Array, + georouter: IGeorouter, + ): Promise => { + let driverWaypoints: Array = []; + let passengerWaypoints: Array = []; + const paths: Array = []; + 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) { 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`, ); } - this.waypoints.push({ - point, - actors: [], - }); + this._points.push(point); }); - } + }; - _setTimezones() { + _setTimezones = (): void => { this.timezones = find( this._geographyRequest.waypoints[0].lat, 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 => this._isValidLongitude(point.lon) && this._isValidLatitude(point.lat); @@ -61,4 +164,24 @@ export class Geography { _isValidLatitude = (latitude: number): boolean => latitude >= -90 && latitude <= 90; + + _createWaypoints = (points: Array, role: Role): Array => { + 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', } diff --git a/src/modules/matcher/domain/entities/named-route.ts b/src/modules/matcher/domain/entities/named-route.ts index c75c4f1..c57f928 100644 --- a/src/modules/matcher/domain/entities/named-route.ts +++ b/src/modules/matcher/domain/entities/named-route.ts @@ -1,6 +1,6 @@ import { Route } from './route'; -export class NamedRoute { +export type NamedRoute = { key: string; route: Route; -} +}; diff --git a/src/modules/matcher/domain/entities/person.ts b/src/modules/matcher/domain/entities/person.ts index 40349f1..3a1473f 100644 --- a/src/modules/matcher/domain/entities/person.ts +++ b/src/modules/matcher/domain/entities/person.ts @@ -17,7 +17,7 @@ export class Person { this._defaultMarginDuration = defaultMarginDuration; } - init() { + init = (): void => { this.setIdentifier( this._personRequest.identifier ?? this._defaultIdentifier, ); @@ -30,13 +30,13 @@ export class Person { this._defaultMarginDuration, this._defaultMarginDuration, ]); - } + }; - setIdentifier(identifier: number) { + setIdentifier = (identifier: number): void => { this.identifier = identifier; - } + }; - setMarginDurations(marginDurations: Array) { + setMarginDurations = (marginDurations: Array): void => { this.marginDurations = marginDurations; - } + }; } diff --git a/src/modules/matcher/domain/entities/route.ts b/src/modules/matcher/domain/entities/route.ts index 324abec..6712699 100644 --- a/src/modules/matcher/domain/entities/route.ts +++ b/src/modules/matcher/domain/entities/route.ts @@ -1,4 +1,5 @@ import { IGeodesic } from '../interfaces/geodesic.interface'; +import { Point } from '../types/point.type'; import { SpacetimePoint } from './spacetime-point'; import { Waypoint } from './waypoint'; @@ -9,7 +10,7 @@ export class Route { backAzimuth: number; distanceAzimuth: number; waypoints: Array; - points: Array>; + points: Array; spacetimePoints: Array; _geodesic: IGeodesic; @@ -25,31 +26,31 @@ export class Route { this._geodesic = geodesic; } - setWaypoints(waypoints: Array): void { + setWaypoints = (waypoints: Array): void => { this.waypoints = waypoints; this._setAzimuth(waypoints.map((waypoint) => waypoint.point)); - } + }; - setPoints(points: Array>): void { + setPoints = (points: Array): void => { this.points = points; this._setAzimuth(points); - } + }; - setSpacetimePoints(spacetimePoints: Array): void { + setSpacetimePoints = (spacetimePoints: Array): void => { this.spacetimePoints = spacetimePoints; - } + }; - _setAzimuth(points: Array>): void { + _setAzimuth = (points: Array): void => { const inverse = this._geodesic.inverse( - points[0][0], - points[0][1], - points[points.length - 1][0], - points[points.length - 1][1], + points[0].lon, + points[0].lat, + points[points.length - 1].lon, + points[points.length - 1].lat, ); this.fwdAzimuth = inverse.azimuth >= 0 ? inverse.azimuth : 360 - Math.abs(inverse.azimuth); this.backAzimuth = this.fwdAzimuth > 180 ? this.fwdAzimuth - 180 : this.fwdAzimuth + 180; this.distanceAzimuth = inverse.distance; - } + }; } diff --git a/src/modules/matcher/domain/entities/time.ts b/src/modules/matcher/domain/entities/time.ts index a6be1f1..b3b2ad8 100644 --- a/src/modules/matcher/domain/entities/time.ts +++ b/src/modules/matcher/domain/entities/time.ts @@ -34,31 +34,31 @@ export class Time { }; } - init() { + init = (): void => { this._validateBaseDate(); this._validatePunctualRequest(); this._validateRecurrentRequest(); this._setPunctualRequest(); this._setRecurrentRequest(); this._setMargindurations(); - } + }; - _validateBaseDate() { + _validateBaseDate = (): void => { if (!this._timeRequest.departure && !this._timeRequest.fromDate) { throw new MatcherException(3, 'departure or fromDate is required'); } - } + }; - _validatePunctualRequest() { + _validatePunctualRequest = (): void => { if (this._timeRequest.departure) { this.fromDate = this.toDate = new Date(this._timeRequest.departure); if (!this._isDate(this.fromDate)) { throw new MatcherException(3, 'Wrong departure date'); } } - } + }; - _validateRecurrentRequest() { + _validateRecurrentRequest = (): void => { if (this._timeRequest.fromDate) { this.fromDate = new Date(this._timeRequest.fromDate); if (!this._isDate(this.fromDate)) { @@ -77,9 +77,9 @@ export class Time { if (this._timeRequest.fromDate) { this._validateSchedule(); } - } + }; - _validateSchedule() { + _validateSchedule = (): void => { if (!this._timeRequest.schedule) { throw new MatcherException(3, 'Schedule is required'); } @@ -96,17 +96,17 @@ export class Time { throw new MatcherException(3, `Wrong time for ${day} in schedule`); } }); - } + }; - _setPunctualRequest() { + _setPunctualRequest = (): void => { if (this._timeRequest.departure) { this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL; this.schedule[TimingDays[this.fromDate.getDay()]] = this.fromDate.getHours() + ':' + this.fromDate.getMinutes(); } - } + }; - _setRecurrentRequest() { + _setRecurrentRequest = (): void => { if (this._timeRequest.fromDate) { this.frequency = TimingFrequency.FREQUENCY_RECURRENT; if (!this.toDate) { @@ -117,15 +117,15 @@ export class Time { } this._setSchedule(); } - } + }; - _setSchedule() { + _setSchedule = (): void => { Object.keys(this._timeRequest.schedule).map((day) => { this.schedule[day] = this._timeRequest.schedule[day]; }); - } + }; - _setMargindurations() { + _setMargindurations = (): void => { if (this._timeRequest.marginDuration) { const duration = Math.abs(this._timeRequest.marginDuration); this.marginDurations = { @@ -155,7 +155,7 @@ export class Time { ); }); } - } + }; _isDate = (date: Date): boolean => { return date instanceof Date && isFinite(+date); diff --git a/src/modules/matcher/domain/entities/waypoint.ts b/src/modules/matcher/domain/entities/waypoint.ts index f44773a..fb93541 100644 --- a/src/modules/matcher/domain/entities/waypoint.ts +++ b/src/modules/matcher/domain/entities/waypoint.ts @@ -1,6 +1,14 @@ +import { Point } from '../types/point.type'; import { Actor } from './actor'; export class Waypoint { - point: Array; + point: Point; actors: Array; + + constructor(point: Point) { + this.point = point; + this.actors = []; + } + + addActor = (actor: Actor) => this.actors.push(actor); } diff --git a/src/modules/matcher/domain/types/point.type.ts b/src/modules/matcher/domain/types/point.type.ts index 9bb160e..8d32fe0 100644 --- a/src/modules/matcher/domain/types/point.type.ts +++ b/src/modules/matcher/domain/types/point.type.ts @@ -1,4 +1,7 @@ +import { PointType } from './geography.enum'; + export type Point = { lon: number; lat: number; + type?: PointType; }; diff --git a/src/modules/matcher/domain/usecases/match.usecase.ts b/src/modules/matcher/domain/usecases/match.usecase.ts index c0ae3d1..d80f508 100644 --- a/src/modules/matcher/domain/usecases/match.usecase.ts +++ b/src/modules/matcher/domain/usecases/match.usecase.ts @@ -15,7 +15,7 @@ export class MatchUseCase { @InjectMapper() private readonly _mapper: Mapper, ) {} - async execute(matchQuery: MatchQuery): Promise> { + execute = async (matchQuery: MatchQuery): Promise> => { try { // const paths = []; // for (let i = 0; i < 1; i++) { @@ -73,5 +73,5 @@ export class MatchUseCase { ); throw error; } - } + }; } diff --git a/src/modules/matcher/mappers/match.profile.ts b/src/modules/matcher/mappers/match.profile.ts index 9276c15..c709136 100644 --- a/src/modules/matcher/mappers/match.profile.ts +++ b/src/modules/matcher/mappers/match.profile.ts @@ -11,7 +11,7 @@ export class MatchProfile extends AutomapperProfile { } override get profile() { - return (mapper) => { + return (mapper: Mapper) => { createMap(mapper, Match, MatchPresenter); }; } diff --git a/src/modules/matcher/queries/match.query.ts b/src/modules/matcher/queries/match.query.ts index 55381ab..1380c65 100644 --- a/src/modules/matcher/queries/match.query.ts +++ b/src/modules/matcher/queries/match.query.ts @@ -39,60 +39,65 @@ export class MatchQuery { this._setExclusions(); } - _setPerson() { + createRoutes = (): void => { + this.geography.createRoutes(this.roles, this.algorithmSettings.georouter); + }; + + _setPerson = (): void => { this.person = new Person( this._matchRequest, this._defaultParams.DEFAULT_IDENTIFIER, this._defaultParams.MARGIN_DURATION, ); this.person.init(); - } + }; - _setRoles() { + _setRoles = (): void => { this.roles = []; if (this._matchRequest.driver) this.roles.push(Role.DRIVER); if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER); if (this.roles.length == 0) this.roles.push(Role.PASSENGER); - } + }; - _setTime() { + _setTime = (): void => { this.time = new Time( this._matchRequest, this._defaultParams.MARGIN_DURATION, this._defaultParams.VALIDITY_DURATION, ); this.time.init(); - } + }; - _setGeography() { + _setGeography = (): void => { this.geography = new Geography( this._matchRequest, this._defaultParams.DEFAULT_TIMEZONE, + this.person, ); this.geography.init(); - } + }; - _setRequirement() { + _setRequirement = (): void => { this.requirement = new Requirement( this._matchRequest, this._defaultParams.DEFAULT_SEATS, ); - } + }; - _setAlgorithmSettings() { + _setAlgorithmSettings = (): void => { this.algorithmSettings = new AlgorithmSettings( this._matchRequest, this._defaultParams.DEFAULT_ALGORITHM_SETTINGS, this.time.frequency, this._georouterCreator, ); - } + }; - _setExclusions() { + _setExclusions = (): void => { this.exclusions = []; if (this._matchRequest.identifier) this.exclusions.push(this._matchRequest.identifier); if (this._matchRequest.exclusions) this.exclusions.push(...this._matchRequest.exclusions); - } + }; } diff --git a/src/modules/matcher/tests/unit/domain/geography.spec.ts b/src/modules/matcher/tests/unit/domain/geography.spec.ts index e10b773..f616434 100644 --- a/src/modules/matcher/tests/unit/domain/geography.spec.ts +++ b/src/modules/matcher/tests/unit/domain/geography.spec.ts @@ -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 [ + { + key: RouteKey.COMMON, + route: new Route(mockGeodesic), + }, + ]; + }) + .mockImplementationOnce(() => { + return [ + { + key: RouteKey.DRIVER, + route: new Route(mockGeodesic), + }, + { + key: RouteKey.PASSENGER, + route: new Route(mockGeodesic), + }, + ]; + }) + .mockImplementationOnce(() => { + return [ + { + key: RouteKey.DRIVER, + route: new Route(mockGeodesic), + }, + ]; + }) + .mockImplementationOnce(() => { + return [ + { + key: RouteKey.PASSENGER, + route: new Route(mockGeodesic), + }, + ]; + }), +}; describe('Geography entity', () => { it('should be defined', () => { @@ -16,29 +78,35 @@ describe('Geography entity', () => { ], }, 'Europe/Paris', + person, ); expect(geography).toBeDefined(); }); describe('init', () => { - it('should initialize a geography request', () => { + it('should initialize a geography request with point types', () => { const geography = new Geography( { waypoints: [ { lat: 49.440041, lon: 1.093912, + type: PointType.LOCALITY, }, { lat: 50.630992, lon: 3.045432, + type: PointType.LOCALITY, }, ], }, 'Europe/Paris', + person, ); 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', () => { const geography = new Geography( @@ -46,6 +114,7 @@ describe('Geography entity', () => { waypoints: [], }, 'Europe/Paris', + person, ); expect(() => geography.init()).toThrow(); }); @@ -60,6 +129,7 @@ describe('Geography entity', () => { ], }, 'Europe/Paris', + person, ); expect(() => geography.init()).toThrow(); }); @@ -78,6 +148,7 @@ describe('Geography entity', () => { ], }, 'Europe/Paris', + person, ); expect(() => geography.init()).toThrow(); }); @@ -96,8 +167,113 @@ describe('Geography entity', () => { ], }, 'Europe/Paris', + person, ); 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(); + }); + }); }); diff --git a/src/modules/matcher/tests/unit/domain/route.spec.ts b/src/modules/matcher/tests/unit/domain/route.spec.ts index d281452..35b6214 100644 --- a/src/modules/matcher/tests/unit/domain/route.spec.ts +++ b/src/modules/matcher/tests/unit/domain/route.spec.ts @@ -24,10 +24,14 @@ describe('Route entity', () => { }); it('should set waypoints and geodesic values for a route', () => { const route = new Route(mockGeodesic); - const waypoint1: Waypoint = new Waypoint(); - waypoint1.point = [0, 0]; - const waypoint2: Waypoint = new Waypoint(); - waypoint2.point = [10, 10]; + const waypoint1: Waypoint = new Waypoint({ + lon: 0, + lat: 0, + }); + const waypoint2: Waypoint = new Waypoint({ + lon: 10, + lat: 10, + }); route.setWaypoints([waypoint1, waypoint2]); expect(route.waypoints.length).toBe(2); expect(route.fwdAzimuth).toBe(45); @@ -37,8 +41,14 @@ describe('Route entity', () => { it('should set points and geodesic values for a route', () => { const route = new Route(mockGeodesic); route.setPoints([ - [10, 10], - [20, 20], + { + lon: 10, + lat: 10, + }, + { + lon: 20, + lat: 20, + }, ]); expect(route.points.length).toBe(2); expect(route.fwdAzimuth).toBe(315);