engine tree
This commit is contained in:
parent
65b6042561
commit
45b33f1ce1
|
@ -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',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> =>
|
||||||
|
|
|
@ -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 [];
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 [];
|
||||||
|
};
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
}
|
|
@ -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 [];
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 [];
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Candidate } from '../candidate';
|
||||||
|
import { Selector } from './selector.abstract';
|
||||||
|
|
||||||
|
export class ClassicSelector extends Selector {
|
||||||
|
select = async (): Promise<Array<Candidate>> => {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
}
|
|
@ -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>>;
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue