engine tree

This commit is contained in:
sbriat 2023-04-21 14:59:25 +02:00
parent 65b6042561
commit 45b33f1ce1
18 changed files with 193 additions and 24 deletions

View File

@ -4,6 +4,10 @@ import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { GraphhopperGeorouter } from './graphhopper-georouter'; import { GraphhopperGeorouter } from './graphhopper-georouter';
import { HttpService } from '@nestjs/axios'; import { HttpService } from '@nestjs/axios';
import { MatcherGeodesic } from './geodesic'; import { MatcherGeodesic } from './geodesic';
import {
MatcherException,
MatcherExceptionCode,
} from '../../exceptions/matcher.exception';
@Injectable() @Injectable()
export class GeorouterCreator implements ICreateGeorouter { export class GeorouterCreator implements ICreateGeorouter {
@ -17,7 +21,10 @@ export class GeorouterCreator implements ICreateGeorouter {
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 MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Unknown geocoder',
);
} }
}; };
} }

View File

@ -9,6 +9,10 @@ import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
import { NamedRoute } from '../../domain/entities/ecosystem/named-route'; import { NamedRoute } from '../../domain/entities/ecosystem/named-route';
import { Route } from '../../domain/entities/ecosystem/route'; import { Route } from '../../domain/entities/ecosystem/route';
import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point'; import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point';
import {
MatcherException,
MatcherExceptionCode,
} from '../../exceptions/matcher.exception';
@Injectable() @Injectable()
export class GraphhopperGeorouter implements IGeorouter { export class GraphhopperGeorouter implements IGeorouter {
@ -84,7 +88,10 @@ export class GraphhopperGeorouter implements IGeorouter {
this._httpService.get(url).pipe( this._httpService.get(url).pipe(
map((res) => (res.data ? this._createRoute(res) : undefined)), map((res) => (res.data ? this._createRoute(res) : undefined)),
catchError((error: AxiosError) => { catchError((error: AxiosError) => {
throw new Error('Georouter unavailable : ' + error.message); throw new MatcherException(
MatcherExceptionCode.INTERNAL,
'Georouter unavailable : ' + error.message,
);
}), }),
), ),
); );

View File

@ -1,4 +1,7 @@
import { MatcherException } from '../../../exceptions/matcher.exception'; import {
MatcherException,
MatcherExceptionCode,
} from '../../../exceptions/matcher.exception';
import { IRequestGeography } from '../../interfaces/geography-request.interface'; 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';
@ -127,12 +130,15 @@ export class Geography {
_validateWaypoints = (): void => { _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(
MatcherExceptionCode.INVALID_ARGUMENT,
'At least 2 waypoints are required',
);
} }
this._geographyRequest.waypoints.map((point) => { this._geographyRequest.waypoints.map((point) => {
if (!this._isValidPoint(point)) { if (!this._isValidPoint(point)) {
throw new MatcherException( throw new MatcherException(
3, MatcherExceptionCode.INVALID_ARGUMENT,
`Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`, `Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`,
); );
} }

View File

@ -1,4 +1,7 @@
import { MatcherException } from '../../../exceptions/matcher.exception'; import {
MatcherException,
MatcherExceptionCode,
} from '../../../exceptions/matcher.exception';
import { MarginDurations } from '../../types/margin-durations.type'; import { MarginDurations } from '../../types/margin-durations.type';
import { IRequestTime } from '../../interfaces/time-request.interface'; import { IRequestTime } from '../../interfaces/time-request.interface';
import { TimingDays, TimingFrequency, Days } from '../../types/timing'; import { TimingDays, TimingFrequency, Days } from '../../types/timing';
@ -45,7 +48,10 @@ export class Time {
_validateBaseDate = (): void => { _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(
MatcherExceptionCode.INVALID_ARGUMENT,
'departure or fromDate is required',
);
} }
}; };
@ -53,7 +59,10 @@ export class Time {
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(
MatcherExceptionCode.INVALID_ARGUMENT,
'Wrong departure date',
);
} }
} }
}; };
@ -62,16 +71,25 @@ export class Time {
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)) {
throw new MatcherException(3, 'Wrong fromDate'); throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Wrong fromDate',
);
} }
} }
if (this._timeRequest.toDate) { if (this._timeRequest.toDate) {
this.toDate = new Date(this._timeRequest.toDate); this.toDate = new Date(this._timeRequest.toDate);
if (!this._isDate(this.toDate)) { if (!this._isDate(this.toDate)) {
throw new MatcherException(3, 'Wrong toDate'); throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Wrong toDate',
);
} }
if (this.toDate < this.fromDate) { if (this.toDate < this.fromDate) {
throw new MatcherException(3, 'toDate must be after fromDate'); throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'toDate must be after fromDate',
);
} }
} }
if (this._timeRequest.fromDate) { if (this._timeRequest.fromDate) {
@ -81,19 +99,28 @@ export class Time {
_validateSchedule = (): void => { _validateSchedule = (): void => {
if (!this._timeRequest.schedule) { if (!this._timeRequest.schedule) {
throw new MatcherException(3, 'Schedule is required'); throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Schedule is required',
);
} }
if ( if (
!Object.keys(this._timeRequest.schedule).some((elem) => !Object.keys(this._timeRequest.schedule).some((elem) =>
Days.includes(elem), Days.includes(elem),
) )
) { ) {
throw new MatcherException(3, 'No valid day in the given schedule'); throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'No valid day in the given schedule',
);
} }
Object.keys(this._timeRequest.schedule).map((day) => { Object.keys(this._timeRequest.schedule).map((day) => {
const time = new Date('1970-01-01 ' + this._timeRequest.schedule[day]); const time = new Date('1970-01-01 ' + this._timeRequest.schedule[day]);
if (!this._isDate(time)) { if (!this._isDate(time)) {
throw new MatcherException(3, `Wrong time for ${day} in schedule`); throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
`Wrong time for ${day} in schedule`,
);
} }
}); });
}; };
@ -145,7 +172,7 @@ export class Time {
) )
) { ) {
throw new MatcherException( throw new MatcherException(
3, MatcherExceptionCode.INVALID_ARGUMENT,
'No valid day in the given margin durations', 'No valid day in the given margin durations',
); );
} }

View File

@ -1,5 +1,5 @@
import { MatchQuery } from 'src/modules/matcher/queries/match.query'; import { MatchQuery } from 'src/modules/matcher/queries/match.query';
import { Processor } from '../processor.abstract'; import { Processor } from '../processor/processor.abstract';
import { Candidate } from '../candidate'; import { Candidate } from '../candidate';
export abstract class AlgorithmFactory { export abstract class AlgorithmFactory {

View File

@ -1,9 +1,18 @@
import { AlgorithmFactory } from './algorithm-factory.abstract'; import { AlgorithmFactory } from './algorithm-factory.abstract';
import { Processor } from '../processor.abstract';
import { ClassicWaypointsCompleter } from '../processor/completer/classic-waypoint.completer.processor'; import { ClassicWaypointsCompleter } from '../processor/completer/classic-waypoint.completer.processor';
import { RouteCompleter } from '../processor/completer/route.completer.processor';
import { ClassicGeoFilter } from '../processor/filter/geofilter/classic.filter.processor';
import { JourneyCompleter } from '../processor/completer/journey.completer.processor';
import { ClassicTimeFilter } from '../processor/filter/timefilter/classic.filter.processor';
import { Processor } from '../processor/processor.abstract';
export class ClassicAlgorithmFactory extends AlgorithmFactory { export class ClassicAlgorithmFactory extends AlgorithmFactory {
createProcessors(): Array<Processor> { createProcessors = (): Array<Processor> => [
return [new ClassicWaypointsCompleter(this._matchQuery)]; new ClassicWaypointsCompleter(this._matchQuery),
} new RouteCompleter(this._matchQuery, true, true, true),
new ClassicGeoFilter(this._matchQuery),
new RouteCompleter(this._matchQuery),
new JourneyCompleter(this._matchQuery),
new ClassicTimeFilter(this._matchQuery),
];
} }

View File

@ -1,3 +1,7 @@
import {
MatcherException,
MatcherExceptionCode,
} from 'src/modules/matcher/exceptions/matcher.exception';
import { MatchQuery } from '../../../queries/match.query'; import { MatchQuery } from '../../../queries/match.query';
import { Algorithm } from '../../types/algorithm.enum'; import { Algorithm } from '../../types/algorithm.enum';
import { Match } from '../ecosystem/match'; import { Match } from '../ecosystem/match';
@ -11,6 +15,12 @@ export class Matcher {
switch (matchQuery.algorithmSettings.algorithm) { switch (matchQuery.algorithmSettings.algorithm) {
case Algorithm.CLASSIC: case Algorithm.CLASSIC:
algorithm = new ClassicAlgorithmFactory(matchQuery); algorithm = new ClassicAlgorithmFactory(matchQuery);
break;
default:
throw new MatcherException(
MatcherExceptionCode.INVALID_ARGUMENT,
'Unknown algorithm',
);
} }
let candidates: Array<Candidate> = []; let candidates: Array<Candidate> = [];
for (const processor of algorithm.createProcessors()) { for (const processor of algorithm.createProcessors()) {

View File

@ -2,7 +2,8 @@ import { Candidate } from '../../candidate';
import { Completer } from './completer.abstract'; import { Completer } from './completer.abstract';
export class ClassicWaypointsCompleter extends Completer { export class ClassicWaypointsCompleter extends Completer {
complete(candidates: Array<Candidate>): Array<Candidate> { // eslint-disable-next-line @typescript-eslint/no-unused-vars
complete = (candidates: Array<Candidate>): Array<Candidate> => {
return []; return [];
} };
} }

View File

@ -1,5 +1,5 @@
import { Candidate } from '../../candidate'; import { Candidate } from '../../candidate';
import { Processor } from '../../processor.abstract'; import { Processor } from '../processor.abstract';
export abstract class Completer extends Processor { export abstract class Completer extends Processor {
execute = (candidates: Array<Candidate>): Array<Candidate> => execute = (candidates: Array<Candidate>): Array<Candidate> =>

View File

@ -0,0 +1,9 @@
import { Candidate } from '../../candidate';
import { Completer } from './completer.abstract';
export class JourneyCompleter extends Completer {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
complete = (candidates: Array<Candidate>): Array<Candidate> => {
return [];
};
}

View File

@ -0,0 +1,26 @@
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
import { Candidate } from '../../candidate';
import { Completer } from './completer.abstract';
export class RouteCompleter extends Completer {
_withPoints: boolean;
_withTime: boolean;
_withDistance: boolean;
constructor(
matchQuery: MatchQuery,
withPoints = false,
withTime = false,
withDistance = false,
) {
super(matchQuery);
this._withPoints = withPoints;
this._withTime = withTime;
this._withDistance = withDistance;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
complete = (candidates: Array<Candidate>): Array<Candidate> => {
return [];
};
}

View File

@ -0,0 +1,9 @@
import { Candidate } from '../../candidate';
import { Processor } from '../processor.abstract';
export abstract class Filter extends Processor {
execute = (candidates: Array<Candidate>): Array<Candidate> =>
this.filter(candidates);
abstract filter(candidates: Array<Candidate>): Array<Candidate>;
}

View File

@ -0,0 +1,9 @@
import { Candidate } from '../../../candidate';
import { Filter } from '../filter.abstract';
export class ClassicGeoFilter extends Filter {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filter = (candidates: Array<Candidate>): Array<Candidate> => {
return [];
};
}

View File

@ -0,0 +1,9 @@
import { Candidate } from '../../../candidate';
import { Filter } from '../filter.abstract';
export class ClassicTimeFilter extends Filter {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filter = (candidates: Array<Candidate>): Array<Candidate> => {
return [];
};
}

View File

@ -1,5 +1,5 @@
import { MatchQuery } from 'src/modules/matcher/queries/match.query'; import { MatchQuery } from 'src/modules/matcher/queries/match.query';
import { Candidate } from './candidate'; import { Candidate } from '../candidate';
export abstract class Processor { export abstract class Processor {
_matchQuery: MatchQuery; _matchQuery: MatchQuery;

View File

@ -0,0 +1,8 @@
import { Candidate } from '../candidate';
import { Selector } from './selector.abstract';
export class ClassicSelector extends Selector {
select = async (): Promise<Array<Candidate>> => {
return [];
};
}

View File

@ -0,0 +1,12 @@
import { MatchQuery } from 'src/modules/matcher/queries/match.query';
import { Candidate } from '../candidate';
export abstract class Selector {
_matchQuery: MatchQuery;
constructor(matchQuery: MatchQuery) {
this._matchQuery = matchQuery;
}
abstract select(): Promise<Array<Candidate>>;
}

View File

@ -11,3 +11,23 @@ export class MatcherException implements Error {
return this._code; return this._code;
} }
} }
export enum MatcherExceptionCode {
OK = 0,
CANCELLED = 1,
UNKNOWN = 2,
INVALID_ARGUMENT = 3,
DEADLINE_EXCEEDED = 4,
NOT_FOUND = 5,
ALREADY_EXISTS = 6,
PERMISSION_DENIED = 7,
RESOURCE_EXHAUSTED = 8,
FAILED_PRECONDITION = 9,
ABORTED = 10,
OUT_OF_RANGE = 11,
UNIMPLEMENTED = 12,
INTERNAL = 13,
UNAVAILABLE = 14,
DATA_LOSS = 15,
UNAUTHENTICATED = 16,
}