WIP graphhopper georouter

This commit is contained in:
sbriat
2023-04-19 17:32:42 +02:00
parent 10a9b94588
commit c759e81c23
24 changed files with 1033 additions and 254 deletions

View File

@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
import { Geodesic, GeodesicClass } from 'geographiclib-geodesic';
@Injectable()
export class MatcherGeodesic implements IGeodesic {
_geod: GeodesicClass;
constructor() {
this._geod = Geodesic.WGS84;
}
inverse(
lon1: number,
lat1: number,
lon2: number,
lat2: number,
): { azimuth: number; distance: number } {
const { azi2: azimuth, s12: distance } = this._geod.Inverse(
lat1,
lon1,
lat2,
lon2,
);
return { azimuth, distance };
}
}

View File

@@ -3,15 +3,19 @@ import { ICreateGeorouter } from '../../domain/interfaces/georouter-creator.inte
import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { GraphhopperGeorouter } from './graphhopper-georouter';
import { HttpService } from '@nestjs/axios';
import { MatcherGeodesic } from './geodesic';
@Injectable()
export class GeorouterCreator implements ICreateGeorouter {
constructor(private readonly httpService: HttpService) {}
constructor(
private readonly httpService: HttpService,
private readonly geodesic: MatcherGeodesic,
) {}
create(type: string, url: string): IGeorouter {
switch (type) {
case 'graphhopper':
return new GraphhopperGeorouter(url, this.httpService);
return new GraphhopperGeorouter(url, this.httpService, this.geodesic);
default:
throw new Error('Unknown geocoder');
}

View File

@@ -4,18 +4,11 @@ import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { GeorouterSettings } from '../../domain/types/georouter-settings.type';
import { Path } from '../../domain/types/path.type';
import { Injectable } from '@nestjs/common';
import {
catchError,
defer,
forkJoin,
from,
lastValueFrom,
map,
mergeAll,
mergeMap,
toArray,
} from 'rxjs';
import { AxiosError } from 'axios';
import { catchError, lastValueFrom, map } from 'rxjs';
import { AxiosError, AxiosResponse } from 'axios';
import { Route } from '../../domain/entities/route';
import { SpacetimePoint } from '../../domain/entities/spacetime-point';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
@Injectable()
export class GraphhopperGeorouter implements IGeorouter {
@@ -26,10 +19,12 @@ export class GraphhopperGeorouter implements IGeorouter {
_withDistance: boolean;
_paths: Array<Path>;
_httpService: HttpService;
_geodesic: IGeodesic;
constructor(url: string, httpService: HttpService) {
constructor(url: string, httpService: HttpService, geodesic: IGeodesic) {
this._url = url + '/route?';
this._httpService = httpService;
this._geodesic = geodesic;
}
async route(
@@ -41,9 +36,7 @@ export class GraphhopperGeorouter implements IGeorouter {
this._setWithPoints(settings.withPoints);
this._setWithDistance(settings.withDistance);
this._paths = paths;
const routes = await this._getRoutes();
console.log(routes.length);
return routes;
return await this._getRoutes();
}
_setDefaultUrlArgs(): void {
@@ -63,7 +56,7 @@ export class GraphhopperGeorouter implements IGeorouter {
_setWithPoints(withPoints: boolean): void {
this._withPoints = withPoints;
if (withPoints) {
if (!withPoints) {
this._urlArgs.push('calc_points=false');
}
}
@@ -87,9 +80,11 @@ export class GraphhopperGeorouter implements IGeorouter {
.map((point) => [point.lat, point.lon].join())
.join('&point='),
].join('');
const res = await lastValueFrom(
const route = await lastValueFrom(
this._httpService.get(url).pipe(
map((res) => res.data.paths[0].distance),
map((res) =>
res.data ? this._createRoute(path.key, res) : undefined,
),
catchError((error: AxiosError) => {
throw new Error(error.message);
}),
@@ -97,37 +92,230 @@ export class GraphhopperGeorouter implements IGeorouter {
);
return <NamedRoute>{
key: path.key,
route: res,
route,
};
}),
);
return routes;
// const date1 = new Date();
// const urls = this._paths.map((path) =>
// defer(() =>
// this._httpService
// .get(
// [
// this._getUrl(),
// '&point=',
// path.points
// .map((point) => [point.lat, point.lon].join())
// .join('&point='),
// ].join(''),
// )
// .pipe(map((res) => res.data.paths[0].distance)),
// ),
// );
// const observables = from(urls);
// const routes = observables.pipe(mergeAll(7), toArray());
// routes.subscribe(() => {
// const date2 = new Date();
// console.log(date2.getTime() - date1.getTime());
// });
// return [];
}
_getUrl(): string {
return [this._url, this._urlArgs.join('&')].join('');
}
_createRoute(
key: string,
response: AxiosResponse<GraphhopperResponse>,
): 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);
if (
shortestPath.details &&
shortestPath.details.time &&
shortestPath.snapped_waypoints &&
shortestPath.snapped_waypoints.coordinates
) {
let instructions: Array<GraphhopperInstruction> = [];
if (shortestPath.instructions)
instructions = shortestPath.instructions;
route.setSpacetimePoints(
this._generateSpacetimePoints(
shortestPath.points.coordinates,
shortestPath.snapped_waypoints.coordinates,
shortestPath.details.time,
instructions,
),
);
}
}
}
return route;
}
_generateSpacetimePoints(
points: Array<Array<number>>,
snappedWaypoints: Array<Array<number>>,
durations: Array<Array<number>>,
instructions: Array<GraphhopperInstruction>,
): Array<SpacetimePoint> {
const indices = this._getIndices(points, snappedWaypoints);
const times = this._getTimes(durations, indices);
const distances = this._getDistances(instructions, indices);
return indices.map(
(index) =>
new SpacetimePoint(
points[index],
times.find((time) => time.index == index)?.duration,
distances.find((distance) => distance.index == index)?.distance,
),
);
}
_getIndices(
points: Array<Array<number>>,
snappedWaypoints: Array<Array<number>>,
): Array<number> {
const indices = snappedWaypoints.map((waypoint) =>
points.findIndex(
(point) => point[0] == waypoint[0] && point[1] == waypoint[1],
),
);
if (indices.find((index) => index == -1) === undefined) return indices;
const missedWaypoints = indices
.map(
(value, index) =>
<
{
index: number;
originIndex: number;
waypoint: Array<number>;
nearest: number;
distance: number;
}
>{
index: value,
originIndex: index,
waypoint: snappedWaypoints[index],
nearest: undefined,
distance: 999999999,
},
)
.filter((element) => element.index == -1);
for (const index in points) {
for (const missedWaypoint of missedWaypoints) {
const inverse = this._geodesic.inverse(
missedWaypoint.waypoint[0],
missedWaypoint.waypoint[1],
points[index][0],
points[index][1],
);
if (inverse.distance < missedWaypoint.distance) {
missedWaypoint.distance = inverse.distance;
missedWaypoint.nearest = parseInt(index);
}
}
}
for (const missedWaypoint of missedWaypoints) {
indices[missedWaypoint.originIndex] = missedWaypoint.nearest;
}
return indices;
}
_getTimes(
durations: Array<Array<number>>,
indices: Array<number>,
): Array<{ index: number; duration: number }> {
const times: Array<{ index: number; duration: number }> = [];
let duration = 0;
for (const [origin, destination, stepDuration] of durations) {
let indexFound = false;
const indexAsOrigin = indices.find((index) => index == origin);
if (
indexAsOrigin !== undefined &&
times.find((time) => origin == time.index) == undefined
) {
times.push({
index: indexAsOrigin,
duration: Math.round(stepDuration / 1000),
});
indexFound = true;
}
if (!indexFound) {
const indexAsDestination = indices.find(
(index) => index == destination,
);
if (
indexAsDestination !== undefined &&
times.find((time) => destination == time.index) == undefined
) {
times.push({
index: indexAsDestination,
duration: Math.round((duration + stepDuration) / 1000),
});
indexFound = true;
}
}
if (!indexFound) {
const indexInBetween = indices.find(
(index) => origin < index && index < destination,
);
if (indexInBetween !== undefined) {
times.push({
index: indexInBetween,
duration: Math.round((duration + stepDuration / 2) / 1000),
});
}
}
duration += stepDuration;
}
return times;
}
_getDistances(
instructions: Array<GraphhopperInstruction>,
indices: Array<number>,
): Array<{ index: number; distance: number }> {
let distance = 0;
const distances: Array<{ index: number; distance: number }> = [
{
index: 0,
distance,
},
];
for (const instruction of instructions) {
distance += instruction.distance;
if (
(instruction.sign == GraphhopperSign.SIGN_WAYPOINT ||
instruction.sign == GraphhopperSign.SIGN_FINISH) &&
indices.find((index) => index == instruction.interval[0]) !== undefined
) {
distances.push({
index: instruction.interval[0],
distance: Math.round(distance),
});
}
}
return distances;
}
}
type GraphhopperResponse = {
paths: [
{
distance: number;
weight: number;
time: number;
points_encoded: boolean;
bbox: Array<number>;
points: GraphhopperCoordinates;
snapped_waypoints: GraphhopperCoordinates;
details: {
time: Array<Array<number>>;
};
instructions: Array<GraphhopperInstruction>;
},
];
};
type GraphhopperCoordinates = {
coordinates: Array<Array<number>>;
};
type GraphhopperInstruction = {
distance: number;
heading: number;
sign: GraphhopperSign;
interval: Array<number>;
text: string;
};
enum GraphhopperSign {
SIGN_START = 0,
SIGN_FINISH = 4,
SIGN_WAYPOINT = 5,
}

View File

@@ -0,0 +1,9 @@
import { Role } from '../types/role.enum';
import { Step } from '../types/step.enum';
import { Person } from './person';
export class Actor {
person: Person;
role: Role;
step: Step;
}

View File

@@ -1 +1,55 @@
export class Route {}
import { IGeodesic } from '../interfaces/geodesic.interface';
import { SpacetimePoint } from './spacetime-point';
import { Waypoint } from './waypoint';
export class Route {
distance: number;
duration: number;
fwdAzimuth: number;
backAzimuth: number;
distanceAzimuth: number;
waypoints: Array<Waypoint>;
points: Array<Array<number>>;
spacetimePoints: Array<SpacetimePoint>;
_geodesic: IGeodesic;
constructor(geodesic: IGeodesic) {
this.distance = undefined;
this.duration = undefined;
this.fwdAzimuth = undefined;
this.backAzimuth = undefined;
this.distanceAzimuth = undefined;
this.waypoints = [];
this.points = [];
this.spacetimePoints = [];
this._geodesic = geodesic;
}
setWaypoints(waypoints: Array<Waypoint>): void {
this.waypoints = waypoints;
this._setAzimuth(waypoints.map((waypoint) => waypoint.point));
}
setPoints(points: Array<Array<number>>): void {
this.points = points;
this._setAzimuth(points);
}
setSpacetimePoints(spacetimePoints: Array<SpacetimePoint>): void {
this.spacetimePoints = spacetimePoints;
}
_setAzimuth(points: Array<Array<number>>): void {
const inverse = this._geodesic.inverse(
points[0][0],
points[0][1],
points[points.length - 1][0],
points[points.length - 1][1],
);
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;
}
}

View File

@@ -0,0 +1,11 @@
export class SpacetimePoint {
point: Array<number>;
duration: number;
distance: number;
constructor(point: Array<number>, duration: number, distance: number) {
this.point = point;
this.duration = duration;
this.distance = distance;
}
}

View File

@@ -0,0 +1,6 @@
import { Actor } from './actor';
export class Waypoint {
point: Array<number>;
actors: Array<Actor>;
}

View File

@@ -0,0 +1,11 @@
export interface IGeodesic {
inverse(
lon1: number,
lat1: number,
lon2: number,
lat2: number,
): {
azimuth: number;
distance: number;
};
}

View File

@@ -17,54 +17,59 @@ export class MatchUseCase {
async execute(matchQuery: MatchQuery): Promise<ICollection<Match>> {
try {
const paths = [];
for (let i = 0; i < 2000; i++) {
paths.push({
key: 'route' + i,
points: [
{
lat: 48.110899,
lon: -1.68365,
},
{
lat: 48.131105,
lon: -1.690067,
},
{
lat: 48.56516,
lon: -1.923553,
},
{
lat: 48.622813,
lon: -1.997177,
},
{
lat: 48.67846,
lon: -1.8554,
},
],
});
}
const routes = await matchQuery.algorithmSettings.georouter.route(paths, {
withDistance: true,
withPoints: true,
withTime: false,
});
// const paths = [];
// for (let i = 0; i < 1; i++) {
// paths.push({
// key: 'route' + i,
// points: [
// {
// lat: 48.110899,
// lon: -1.68365,
// },
// {
// lat: 48.131105,
// lon: -1.690067,
// },
// {
// lat: 48.534769,
// lon: -1.894032,
// },
// {
// lat: 48.56516,
// lon: -1.923553,
// },
// {
// lat: 48.622813,
// lon: -1.997177,
// },
// {
// lat: 48.67846,
// lon: -1.8554,
// },
// ],
// });
// }
// const routes = await matchQuery.algorithmSettings.georouter.route(paths, {
// withDistance: false,
// withPoints: true,
// withTime: true,
// });
// routes.map((route) => console.log(route.route.spacetimePoints));
const match = new Match();
match.uuid = 'e23f9725-2c19-49a0-9ef6-17d8b9a5ec85';
// this._messager.publish('matcher.match', 'match !');
this._messager.publish('matcher.match', 'match !');
return {
data: [match],
total: 1,
};
} catch (error) {
// this._messager.publish(
// 'logging.matcher.match.crit',
// JSON.stringify({
// matchQuery,
// error,
// }),
// );
this._messager.publish(
'logging.matcher.match.crit',
JSON.stringify({
matchQuery,
error,
}),
);
throw error;
}
}

View File

@@ -14,6 +14,7 @@ import { redisStore } from 'cache-manager-ioredis-yet';
import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
import { GeorouterCreator } from './adapters/secondaries/georouter-creator';
import { HttpModule } from '@nestjs/axios';
import { MatcherGeodesic } from './adapters/secondaries/geodesic';
@Module({
imports: [
@@ -55,6 +56,7 @@ import { HttpModule } from '@nestjs/axios';
DefaultParamsProvider,
MatchUseCase,
GeorouterCreator,
MatcherGeodesic,
],
exports: [],
})

View File

@@ -1,7 +1,7 @@
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { DefaultParamsProvider } from '../../adapters/secondaries/default-params.provider';
import { IDefaultParams } from '../../domain/types/default-params.type';
import { DefaultParamsProvider } from '../../../../adapters/secondaries/default-params.provider';
import { IDefaultParams } from '../../../../domain/types/default-params.type';
const mockConfigService = {
get: jest.fn().mockImplementationOnce(() => 99),

View File

@@ -0,0 +1,14 @@
import { MatcherGeodesic } from '../../../../adapters/secondaries/geodesic';
describe('Matcher geodesic', () => {
it('should be defined', () => {
const geodesic: MatcherGeodesic = new MatcherGeodesic();
expect(geodesic).toBeDefined();
});
it('should get inverse values', () => {
const geodesic: MatcherGeodesic = new MatcherGeodesic();
const inv = geodesic.inverse(0, 0, 1, 1);
expect(Math.round(inv.azimuth)).toBe(45);
expect(Math.round(inv.distance)).toBe(156900);
});
});

View File

@@ -1,9 +1,11 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GeorouterCreator } from '../../adapters/secondaries/georouter-creator';
import { GraphhopperGeorouter } from '../../adapters/secondaries/graphhopper-georouter';
import { GeorouterCreator } from '../../../../adapters/secondaries/georouter-creator';
import { GraphhopperGeorouter } from '../../../../adapters/secondaries/graphhopper-georouter';
import { HttpService } from '@nestjs/axios';
import { MatcherGeodesic } from '../../../../adapters/secondaries/geodesic';
const mockHttpService = jest.fn();
const mockMatcherGeodesic = jest.fn();
describe('Georouter creator', () => {
let georouterCreator: GeorouterCreator;
@@ -17,6 +19,10 @@ describe('Georouter creator', () => {
provide: HttpService,
useValue: mockHttpService,
},
{
provide: MatcherGeodesic,
useValue: mockMatcherGeodesic,
},
],
}).compile();

View File

@@ -0,0 +1,268 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { GeorouterCreator } from '../../../../adapters/secondaries/georouter-creator';
import { IGeorouter } from '../../../../domain/interfaces/georouter.interface';
import { of } from 'rxjs';
import { AxiosError } from 'axios';
import { MatcherGeodesic } from '../../../../adapters/secondaries/geodesic';
const mockHttpService = {
get: jest
.fn()
.mockImplementationOnce(() => {
throw new AxiosError('Axios error !');
})
.mockImplementationOnce(() => {
return of({
status: 200,
data: {
paths: [
{
distance: 50000,
time: 1800000,
snapped_waypoints: {
coordinates: [
[0, 0],
[10, 10],
],
},
},
],
},
});
})
.mockImplementationOnce(() => {
return of({
status: 200,
data: {
paths: [
{
distance: 50000,
time: 1800000,
points: {
coordinates: [
[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5],
[6, 6],
[7, 7],
[8, 8],
[9, 9],
[10, 10],
],
},
snapped_waypoints: {
coordinates: [
[0, 0],
[10, 10],
],
},
},
],
},
});
})
.mockImplementationOnce(() => {
return of({
status: 200,
data: {
paths: [
{
distance: 50000,
time: 1800000,
points: {
coordinates: [
[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5],
[6, 6],
[7, 7],
[8, 8],
[9, 9],
[10, 10],
],
},
details: {
time: [
[0, 1, 180000],
[1, 2, 180000],
[2, 3, 180000],
[3, 4, 180000],
[4, 5, 180000],
[5, 6, 180000],
[6, 7, 180000],
[7, 9, 360000],
[9, 10, 180000],
],
},
snapped_waypoints: {
coordinates: [
[0, 0],
[10, 10],
],
},
},
],
},
});
}),
};
const mockMatcherGeodesic = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
inverse: jest.fn().mockImplementation(() => ({
azimuth: 45,
distance: 50000,
})),
};
describe('Graphhopper Georouter', () => {
let georouterCreator: GeorouterCreator;
let graphhopperGeorouter: IGeorouter;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
GeorouterCreator,
{
provide: HttpService,
useValue: mockHttpService,
},
{
provide: MatcherGeodesic,
useValue: mockMatcherGeodesic,
},
],
}).compile();
georouterCreator = module.get<GeorouterCreator>(GeorouterCreator);
graphhopperGeorouter = georouterCreator.create(
'graphhopper',
'http://localhost',
);
});
it('should be defined', () => {
expect(graphhopperGeorouter).toBeDefined();
});
describe('route function', () => {
it('should fail on axios error', async () => {
await expect(
graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 0,
lon: 0,
},
{
lat: 1,
lon: 1,
},
],
},
],
{
withDistance: false,
withPoints: false,
withTime: false,
},
),
).rejects.toBeInstanceOf(Error);
});
it('should create one route with all settings to false', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
},
],
{
withDistance: false,
withPoints: false,
withTime: false,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].route.distance).toBe(50000);
});
it('should create one route with points', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
},
],
{
withDistance: false,
withPoints: true,
withTime: false,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].route.distance).toBe(50000);
expect(routes[0].route.duration).toBe(1800);
expect(routes[0].route.fwdAzimuth).toBe(45);
expect(routes[0].route.backAzimuth).toBe(225);
expect(routes[0].route.points.length).toBe(11);
});
it('should create one route with points and time', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
},
],
{
withDistance: false,
withPoints: true,
withTime: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].route.spacetimePoints.length).toBe(2);
expect(routes[0].route.spacetimePoints[1].duration).toBe(1800);
expect(routes[0].route.spacetimePoints[1].distance).toBeUndefined();
});
});
});

View File

@@ -1,7 +1,7 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { Messager } from '../../adapters/secondaries/messager';
import { Messager } from '../../../../adapters/secondaries/messager';
const mockAmqpConnection = {
publish: jest.fn().mockImplementation(),

View File

@@ -1,4 +1,4 @@
import { Geography } from '../../domain/entities/geography';
import { Geography } from '../../../domain/entities/geography';
describe('Geography entity', () => {
it('should be defined', () => {

View File

@@ -1,13 +1,13 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Messager } from '../../adapters/secondaries/messager';
import { MatchUseCase } from '../../domain/usecases/match.usecase';
import { MatchRequest } from '../../domain/dtos/match.request';
import { MatchQuery } from '../../queries/match.query';
import { AdRepository } from '../../adapters/secondaries/ad.repository';
import { Messager } from '../../../adapters/secondaries/messager';
import { MatchUseCase } from '../../../domain/usecases/match.usecase';
import { MatchRequest } from '../../../domain/dtos/match.request';
import { MatchQuery } from '../../../queries/match.query';
import { AdRepository } from '../../../adapters/secondaries/ad.repository';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
import { IDefaultParams } from '../../domain/types/default-params.type';
import { Algorithm } from '../../domain/types/algorithm.enum';
import { IDefaultParams } from '../../../domain/types/default-params.type';
import { Algorithm } from '../../../domain/types/algorithm.enum';
const mockAdRepository = {};

View File

@@ -1,4 +1,4 @@
import { Person } from '../../domain/entities/person';
import { Person } from '../../../domain/entities/person';
const DEFAULT_IDENTIFIER = 0;
const MARGIN_DURATION = 900;

View File

@@ -0,0 +1,55 @@
import { Route } from '../../../domain/entities/route';
import { SpacetimePoint } from '../../../domain/entities/spacetime-point';
import { Waypoint } from '../../../domain/entities/waypoint';
const mockGeodesic = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
inverse: jest.fn().mockImplementation((lon1, lat1, lon2, lat2) => {
return lon1 == 0
? {
azimuth: 45,
distance: 50000,
}
: {
azimuth: -45,
distance: 60000,
};
}),
};
describe('Route entity', () => {
it('should be defined', () => {
const route = new Route(mockGeodesic);
expect(route).toBeDefined();
});
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];
route.setWaypoints([waypoint1, waypoint2]);
expect(route.waypoints.length).toBe(2);
expect(route.fwdAzimuth).toBe(45);
expect(route.backAzimuth).toBe(225);
expect(route.distanceAzimuth).toBe(50000);
});
it('should set points and geodesic values for a route', () => {
const route = new Route(mockGeodesic);
route.setPoints([
[10, 10],
[20, 20],
]);
expect(route.points.length).toBe(2);
expect(route.fwdAzimuth).toBe(315);
expect(route.backAzimuth).toBe(135);
expect(route.distanceAzimuth).toBe(60000);
});
it('should set spacetimePoints for a route', () => {
const route = new Route(mockGeodesic);
const spacetimePoint1 = new SpacetimePoint([0, 0], 0, 0);
const spacetimePoint2 = new SpacetimePoint([10, 10], 500, 5000);
route.setSpacetimePoints([spacetimePoint1, spacetimePoint2]);
expect(route.spacetimePoints.length).toBe(2);
});
});

View File

@@ -1,4 +1,4 @@
import { Time } from '../../domain/entities/time';
import { Time } from '../../../domain/entities/time';
const MARGIN_DURATION = 900;
const VALIDITY_DURATION = 365;

View File

@@ -1,141 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { NamedRoute } from '../../domain/entities/named-route';
import { GeorouterCreator } from '../../adapters/secondaries/georouter-creator';
import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { of } from 'rxjs';
import { AxiosError } from 'axios';
const mockHttpService = {
get: jest
.fn()
.mockImplementationOnce(() => {
throw new AxiosError('Axios error !');
})
.mockImplementation(() => {
return of({
status: 200,
data: [new NamedRoute()],
});
}),
};
describe('Graphhopper Georouter', () => {
let georouterCreator: GeorouterCreator;
let graphhopperGeorouter: IGeorouter;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
GeorouterCreator,
{
provide: HttpService,
useValue: mockHttpService,
},
],
}).compile();
georouterCreator = module.get<GeorouterCreator>(GeorouterCreator);
graphhopperGeorouter = georouterCreator.create(
'graphhopper',
'http://localhost',
);
});
it('should be defined', () => {
expect(graphhopperGeorouter).toBeDefined();
});
describe('route function', () => {
it('should fail on axios error', async () => {
await expect(
graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
],
{
withDistance: false,
withPoints: false,
withTime: false,
},
),
).rejects.toBeInstanceOf(Error);
});
it('should create one route with all settings to false', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
],
{
withDistance: false,
withPoints: false,
withTime: false,
},
);
expect(routes).toHaveLength(1);
});
it('should create 2 routes with distance, points and time', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
{
key: 'route2',
points: [
{
lat: 49.440041,
lon: 1.093912,
},
{
lat: 50.630992,
lon: 3.045432,
},
],
},
],
{
withDistance: true,
withPoints: true,
withTime: true,
},
);
expect(routes).toHaveLength(2);
});
});
});

View File

@@ -1,9 +1,9 @@
import { MatchRequest } from '../../domain/dtos/match.request';
import { Role } from '../../domain/types/role.enum';
import { TimingFrequency } from '../../domain/types/timing';
import { IDefaultParams } from '../../domain/types/default-params.type';
import { MatchQuery } from '../../queries/match.query';
import { Algorithm } from '../../domain/types/algorithm.enum';
import { MatchRequest } from '../../../domain/dtos/match.request';
import { Role } from '../../../domain/types/role.enum';
import { TimingFrequency } from '../../../domain/types/timing';
import { IDefaultParams } from '../../../domain/types/default-params.type';
import { MatchQuery } from '../../../queries/match.query';
import { Algorithm } from '../../../domain/types/algorithm.enum';
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,