refactor to ddh, first commit

This commit is contained in:
sbriat
2023-08-16 12:28:20 +02:00
parent 0a6e4c0bf6
commit ce48890a66
208 changed files with 2596 additions and 2052 deletions

View File

@@ -1,8 +0,0 @@
import { Injectable } from '@nestjs/common';
import { IFindTimezone } from '../../domain/interfaces/timezone-finder.interface';
import { find } from 'geo-tz';
@Injectable()
export class GeoTimezoneFinder implements IFindTimezone {
timezones = (lon: number, lat: number): string[] => find(lat, lon);
}

View File

@@ -1,27 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Geodesic as Geolib, GeodesicClass } from 'geographiclib-geodesic';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
@Injectable()
export class Geodesic implements IGeodesic {
private geod: GeodesicClass;
constructor() {
this.geod = Geolib.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

@@ -1,28 +0,0 @@
import { Injectable } from '@nestjs/common';
import { ICreateGeorouter } from '../../domain/interfaces/georouter-creator.interface';
import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { GraphhopperGeorouter } from './graphhopper-georouter';
import { HttpService } from '@nestjs/axios';
import { Geodesic } from './geodesic';
import { GeographyException } from '../../exceptions/geography.exception';
import { ExceptionCode } from '../../..//utils/exception-code.enum';
@Injectable()
export class GeorouterCreator implements ICreateGeorouter {
constructor(
private readonly httpService: HttpService,
private readonly geodesic: Geodesic,
) {}
create = (type: string, url: string): IGeorouter => {
switch (type) {
case 'graphhopper':
return new GraphhopperGeorouter(url, this.httpService, this.geodesic);
default:
throw new GeographyException(
ExceptionCode.INVALID_ARGUMENT,
'Unknown geocoder',
);
}
};
}

View File

@@ -1,330 +0,0 @@
import { HttpService } from '@nestjs/axios';
import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { Injectable } from '@nestjs/common';
import { catchError, lastValueFrom, map } from 'rxjs';
import { AxiosError, AxiosResponse } from 'axios';
import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface';
import { GeorouterSettings } from '../../domain/types/georouter-settings.type';
import { Path } from '../../domain/types/path.type';
import { NamedRoute } from '../../domain/types/named-route';
import { GeographyException } from '../../exceptions/geography.exception';
import { ExceptionCode } from '../../..//utils/exception-code.enum';
import { Route } from '../../domain/entities/route';
import { SpacetimePoint } from '../../domain/entities/spacetime-point';
@Injectable()
export class GraphhopperGeorouter implements IGeorouter {
private url: string;
private urlArgs: string[];
private withTime: boolean;
private withPoints: boolean;
private withDistance: boolean;
private paths: Path[];
private httpService: HttpService;
private geodesic: IGeodesic;
constructor(url: string, httpService: HttpService, geodesic: IGeodesic) {
this.url = url + '/route?';
this.httpService = httpService;
this.geodesic = geodesic;
}
route = async (
paths: Path[],
settings: GeorouterSettings,
): Promise<NamedRoute[]> => {
this.setDefaultUrlArgs();
this.setWithTime(settings.withTime);
this.setWithPoints(settings.withPoints);
this.setWithDistance(settings.withDistance);
this.paths = paths;
return await this.getRoutes();
};
private setDefaultUrlArgs = (): void => {
this.urlArgs = ['vehicle=car', 'weighting=fastest', 'points_encoded=false'];
};
private setWithTime = (withTime: boolean): void => {
this.withTime = withTime;
if (withTime) {
this.urlArgs.push('details=time');
}
};
private setWithPoints = (withPoints: boolean): void => {
this.withPoints = withPoints;
if (!withPoints) {
this.urlArgs.push('calc_points=false');
}
};
private setWithDistance = (withDistance: boolean): void => {
this.withDistance = withDistance;
if (withDistance) {
this.urlArgs.push('instructions=true');
} else {
this.urlArgs.push('instructions=false');
}
};
private getRoutes = async (): Promise<NamedRoute[]> => {
const routes = Promise.all(
this.paths.map(async (path) => {
const url: string = [
this.getUrl(),
'&point=',
path.points
.map((point) => [point.lat, point.lon].join('%2C'))
.join('&point='),
].join('');
const route = await lastValueFrom(
this.httpService.get(url).pipe(
map((res) => (res.data ? this.createRoute(res) : undefined)),
catchError((error: AxiosError) => {
if (error.code == AxiosError.ERR_BAD_REQUEST) {
throw new GeographyException(
ExceptionCode.OUT_OF_RANGE,
'No route found for given coordinates',
);
}
throw new GeographyException(
ExceptionCode.UNAVAILABLE,
'Georouter unavailable : ' + error.message,
);
}),
),
);
return <NamedRoute>{
key: path.key,
route,
};
}),
);
return routes;
};
private getUrl = (): string => {
return [this.url, this.urlArgs.join('&')].join('');
};
private createRoute = (
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.map((coordinate) => ({
lon: coordinate[0],
lat: coordinate[1],
})),
);
if (
shortestPath.details &&
shortestPath.details.time &&
shortestPath.snapped_waypoints &&
shortestPath.snapped_waypoints.coordinates
) {
let instructions: GraphhopperInstruction[] = [];
if (shortestPath.instructions)
instructions = shortestPath.instructions;
route.setSpacetimePoints(
this.generateSpacetimePoints(
shortestPath.points.coordinates,
shortestPath.snapped_waypoints.coordinates,
shortestPath.details.time,
instructions,
),
);
}
}
}
return route;
};
private generateSpacetimePoints = (
points: Array<number[]>,
snappedWaypoints: Array<number[]>,
durations: Array<number[]>,
instructions: GraphhopperInstruction[],
): 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(
{ lon: points[index][1], lat: points[index][0] },
times.find((time) => time.index == index)?.duration,
distances.find((distance) => distance.index == index)?.distance,
),
);
};
private getIndices = (
points: Array<number[]>,
snappedWaypoints: Array<number[]>,
): 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: 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;
};
private getTimes = (
durations: Array<number[]>,
indices: 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;
};
private getDistances = (
instructions: GraphhopperInstruction[],
indices: 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: number[];
points: GraphhopperCoordinates;
snapped_waypoints: GraphhopperCoordinates;
details: {
time: Array<number[]>;
};
instructions: GraphhopperInstruction[];
},
];
};
type GraphhopperCoordinates = {
coordinates: Array<number[]>;
};
type GraphhopperInstruction = {
distance: number;
heading: number;
sign: GraphhopperSign;
interval: number[];
text: string;
};
enum GraphhopperSign {
SIGN_START = 0,
SIGN_FINISH = 4,
SIGN_WAYPOINT = 5,
}

View File

@@ -1,11 +0,0 @@
import { Coordinate } from '../../domain/entities/coordinate';
import { IEncodeDirection } from '../../domain/interfaces/direction-encoder.interface';
export class PostgresDirectionEncoder implements IEncodeDirection {
encode = (coordinates: Coordinate[]): string =>
[
"'LINESTRING(",
coordinates.map((point) => [point.lon, point.lat].join(' ')).join(),
")'",
].join('');
}

View File

@@ -1,19 +0,0 @@
import { AutoMap } from '@automapper/classes';
import { IsLatitude, IsLongitude, IsNumber } from 'class-validator';
export class Coordinate {
constructor(lon: number, lat: number) {
this.lon = lon;
this.lat = lat;
}
@IsNumber()
@IsLongitude()
@AutoMap()
lon: number;
@IsNumber()
@IsLatitude()
@AutoMap()
lat: number;
}

View File

@@ -1,48 +0,0 @@
import { IGeodesic } from '../interfaces/geodesic.interface';
import { Point } from '../types/point.type';
import { SpacetimePoint } from './spacetime-point';
export class Route {
distance: number;
duration: number;
fwdAzimuth: number;
backAzimuth: number;
distanceAzimuth: number;
points: Point[];
spacetimePoints: SpacetimePoint[];
private geodesic: IGeodesic;
constructor(geodesic: IGeodesic) {
this.distance = undefined;
this.duration = undefined;
this.fwdAzimuth = undefined;
this.backAzimuth = undefined;
this.distanceAzimuth = undefined;
this.points = [];
this.spacetimePoints = [];
this.geodesic = geodesic;
}
setPoints = (points: Point[]): void => {
this.points = points;
this.setAzimuth(points);
};
setSpacetimePoints = (spacetimePoints: SpacetimePoint[]): void => {
this.spacetimePoints = spacetimePoints;
};
protected setAzimuth = (points: Point[]): void => {
const inverse = this.geodesic.inverse(
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;
};
}

View File

@@ -1,13 +0,0 @@
import { Coordinate } from './coordinate';
export class SpacetimePoint {
coordinate: Coordinate;
duration: number;
distance: number;
constructor(coordinate: Coordinate, duration: number, distance: number) {
this.coordinate = coordinate;
this.duration = duration;
this.distance = distance;
}
}

View File

@@ -1,5 +0,0 @@
import { Coordinate } from '../entities/coordinate';
export interface IEncodeDirection {
encode(coordinates: Coordinate[]): string;
}

View File

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

View File

@@ -1,5 +0,0 @@
import { IGeorouter } from './georouter.interface';
export interface ICreateGeorouter {
create(type: string, url: string): IGeorouter;
}

View File

@@ -1,7 +0,0 @@
import { GeorouterSettings } from '../types/georouter-settings.type';
import { NamedRoute } from '../types/named-route';
import { Path } from '../types/path.type';
export interface IGeorouter {
route(paths: Path[], settings: GeorouterSettings): Promise<NamedRoute[]>;
}

View File

@@ -1,3 +0,0 @@
export interface IFindTimezone {
timezones(lon: number, lat: number): string[];
}

View File

@@ -1,5 +0,0 @@
export type GeorouterSettings = {
withPoints: boolean;
withTime: boolean;
withDistance: boolean;
};

View File

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

View File

@@ -1,6 +0,0 @@
import { Point } from '../../../geography/domain/types/point.type';
export type Path = {
key: string;
points: Point[];
};

View File

@@ -1,7 +0,0 @@
export enum PointType {
HOUSE_NUMBER = 'HOUSE_NUMBER',
STREET_ADDRESS = 'STREET_ADDRESS',
LOCALITY = 'LOCALITY',
VENUE = 'VENUE',
OTHER = 'OTHER',
}

View File

@@ -1,6 +0,0 @@
import { PointType } from './point-type.enum';
import { Coordinate } from '../entities/coordinate';
export type Point = Coordinate & {
type?: PointType;
};

View File

@@ -1,6 +0,0 @@
import { IFindTimezone } from '../interfaces/timezone-finder.interface';
export type Timezoner = {
timezone: string;
finder: IFindTimezone;
};

View File

@@ -1,11 +0,0 @@
export class GeographyException implements Error {
name: string;
code: number;
message: string;
constructor(code: number, message: string) {
this.name = 'GeographyException';
this.code = code;
this.message = message;
}
}

View File

@@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { GeoTimezoneFinder } from './adapters/secondaries/geo-timezone-finder';
import { Geodesic } from './adapters/secondaries/geodesic';
@Module({
providers: [GeoTimezoneFinder, Geodesic],
exports: [GeoTimezoneFinder, Geodesic],
})
export class GeographyModule {}

View File

@@ -1,8 +0,0 @@
import { Coordinate } from '../../domain/entities/coordinate';
describe('Coordinate entity', () => {
it('should be defined', () => {
const coordinate: Coordinate = new Coordinate(6, 47);
expect(coordinate).toBeDefined();
});
});

View File

@@ -1,14 +0,0 @@
import { GeoTimezoneFinder } from '../../adapters/secondaries/geo-timezone-finder';
describe('Geo TZ Finder', () => {
it('should be defined', () => {
const timezoneFinder: GeoTimezoneFinder = new GeoTimezoneFinder();
expect(timezoneFinder).toBeDefined();
});
it('should get timezone for Nancy(France) as Europe/Paris', () => {
const timezoneFinder: GeoTimezoneFinder = new GeoTimezoneFinder();
const timezones = timezoneFinder.timezones(6.179373, 48.687913);
expect(timezones.length).toBe(1);
expect(timezones[0]).toBe('Europe/Paris');
});
});

View File

@@ -1,14 +0,0 @@
import { Geodesic } from '../../adapters/secondaries/geodesic';
describe('Matcher geodesic', () => {
it('should be defined', () => {
const geodesic: Geodesic = new Geodesic();
expect(geodesic).toBeDefined();
});
it('should get inverse values', () => {
const geodesic: Geodesic = new Geodesic();
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,47 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { GeorouterCreator } from '../../adapters/secondaries/georouter-creator';
import { Geodesic } from '../../adapters/secondaries/geodesic';
import { GraphhopperGeorouter } from '../../adapters/secondaries/graphhopper-georouter';
const mockHttpService = jest.fn();
const mockGeodesic = jest.fn();
describe('Georouter creator', () => {
let georouterCreator: GeorouterCreator;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
GeorouterCreator,
{
provide: HttpService,
useValue: mockHttpService,
},
{
provide: Geodesic,
useValue: mockGeodesic,
},
],
}).compile();
georouterCreator = module.get<GeorouterCreator>(GeorouterCreator);
});
it('should be defined', () => {
expect(georouterCreator).toBeDefined();
});
it('should create a graphhopper georouter', () => {
const georouter = georouterCreator.create(
'graphhopper',
'http://localhost',
);
expect(georouter).toBeInstanceOf(GraphhopperGeorouter);
});
it('should throw an exception if georouter type is unknown', () => {
expect(() =>
georouterCreator.create('unknown', 'http://localhost'),
).toThrow();
});
});

View File

@@ -1,456 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { of } from 'rxjs';
import { AxiosError } from 'axios';
import { GeorouterCreator } from '../../adapters/secondaries/georouter-creator';
import { IGeorouter } from '../../domain/interfaces/georouter.interface';
import { Geodesic } 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],
],
},
},
],
},
});
})
.mockImplementationOnce(() => {
return of({
status: 200,
data: {
paths: [
{
distance: 50000,
time: 1800000,
points: {
coordinates: [
[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4],
[7, 7],
[8, 8],
[9, 9],
[10, 10],
],
},
snapped_waypoints: {
coordinates: [
[0, 0],
[5, 5],
[10, 10],
],
},
details: {
time: [
[0, 1, 180000],
[1, 2, 180000],
[2, 3, 180000],
[3, 4, 180000],
[4, 7, 540000],
[7, 9, 360000],
[9, 10, 180000],
],
},
},
],
},
});
})
.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],
[5, 5],
[10, 10],
],
},
details: {
time: [
[0, 1, 180000],
[1, 2, 180000],
[2, 3, 180000],
[3, 4, 180000],
[4, 7, 540000],
[7, 9, 360000],
[9, 10, 180000],
],
},
instructions: [
{
distance: 25000,
sign: 0,
interval: [0, 5],
text: 'Some instructions',
time: 900000,
},
{
distance: 0,
sign: 5,
interval: [5, 5],
text: 'Waypoint 1',
time: 0,
},
{
distance: 25000,
sign: 2,
interval: [5, 10],
text: 'Some instructions',
time: 900000,
},
{
distance: 0.0,
sign: 4,
interval: [10, 10],
text: 'Arrive at destination',
time: 0,
},
],
},
],
},
});
}),
};
const mockGeodesic = {
// 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: Geodesic,
useValue: mockGeodesic,
},
],
}).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();
});
it('should create one route with points and missed waypoints extrapolations', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 0,
lon: 0,
},
{
lat: 5,
lon: 5,
},
{
lat: 10,
lon: 10,
},
],
},
],
{
withDistance: false,
withPoints: true,
withTime: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].route.spacetimePoints.length).toBe(3);
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(9);
});
it('should create one route with points, time and distance', async () => {
const routes = await graphhopperGeorouter.route(
[
{
key: 'route1',
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
},
],
{
withDistance: true,
withPoints: true,
withTime: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].route.spacetimePoints.length).toBe(3);
expect(routes[0].route.spacetimePoints[1].duration).toBe(990);
expect(routes[0].route.spacetimePoints[1].distance).toBe(25000);
});
});
});

View File

@@ -1,30 +0,0 @@
import { PostgresDirectionEncoder } from '../../adapters/secondaries/postgres-direction-encoder';
import { Coordinate } from '../../domain/entities/coordinate';
describe('Postgres direction encoder', () => {
it('should be defined', () => {
const postgresDirectionEncoder: PostgresDirectionEncoder =
new PostgresDirectionEncoder();
expect(postgresDirectionEncoder).toBeDefined();
});
it('should encode coordinates to a postgres direction', () => {
const postgresDirectionEncoder: PostgresDirectionEncoder =
new PostgresDirectionEncoder();
const coordinates: Coordinate[] = [
{
lon: 6,
lat: 47,
},
{
lon: 6.1,
lat: 47.1,
},
{
lon: 6.2,
lat: 47.2,
},
];
const direction = postgresDirectionEncoder.encode(coordinates);
expect(direction).toBe("'LINESTRING(6 47,6.1 47.1,6.2 47.2)'");
});
});

View File

@@ -1,48 +0,0 @@
import { Route } from '../../domain/entities/route';
import { SpacetimePoint } from '../../domain/entities/spacetime-point';
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 points and geodesic values for a route', () => {
const route = new Route(mockGeodesic);
route.setPoints([
{
lon: 10,
lat: 10,
},
{
lon: 20,
lat: 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({ lon: 0, lat: 0 }, 0, 0);
const spacetimePoint2 = new SpacetimePoint({ lon: 10, lat: 10 }, 500, 5000);
route.setSpacetimePoints([spacetimePoint1, spacetimePoint2]);
expect(route.spacetimePoints.length).toBe(2);
});
});