extract carpool informations from geography module

This commit is contained in:
sbriat
2023-09-07 14:30:07 +02:00
parent 57fe8d417f
commit d1a314f011
28 changed files with 586 additions and 611 deletions

View File

@@ -2,7 +2,7 @@ import { GeorouterPort } from '@modules/geography/core/application/ports/georout
import { GetRouteQuery } from '@modules/geography/core/application/queries/get-route/get-route.query';
import { GetRouteQueryHandler } from '@modules/geography/core/application/queries/get-route/get-route.query-handler';
import { RouteEntity } from '@modules/geography/core/domain/route.entity';
import { Role, Waypoint } from '@modules/geography/core/domain/route.types';
import { Waypoint } from '@modules/geography/core/domain/route.types';
import { GEOROUTER } from '@modules/geography/geography.di-tokens';
import { Test, TestingModule } from '@nestjs/testing';
@@ -18,7 +18,7 @@ const destinationWaypoint: Waypoint = {
};
const mockGeorouter: GeorouterPort = {
routes: jest.fn(),
route: jest.fn(),
};
describe('Get route query handler', () => {
@@ -44,9 +44,8 @@ describe('Get route query handler', () => {
});
describe('execution', () => {
it('should get a route for a driver only', async () => {
it('should get a route', async () => {
const getRoutequery = new GetRouteQuery(
[Role.DRIVER],
[originWaypoint, destinationWaypoint],
{
detailedDistance: false,

View File

@@ -1,18 +1,18 @@
import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
import { Coordinates } from '@modules/geography/core/domain/value-objects/coordinates.value-object';
import { Point } from '@modules/geography/core/domain/value-objects/point.value-object';
describe('Waypoint value object', () => {
it('should create a waypoint value object', () => {
const coordinatesVO = new Coordinates({
describe('Point value object', () => {
it('should create a point value object', () => {
const pointVO = new Point({
lat: 48.689445,
lon: 6.17651,
});
expect(coordinatesVO.lat).toBe(48.689445);
expect(coordinatesVO.lon).toBe(6.17651);
expect(pointVO.lat).toBe(48.689445);
expect(pointVO.lon).toBe(6.17651);
});
it('should throw an exception if longitude is invalid', () => {
try {
new Coordinates({
new Point({
lat: 48.689445,
lon: 186.17651,
});
@@ -20,7 +20,7 @@ describe('Waypoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new Coordinates({
new Point({
lat: 48.689445,
lon: -186.17651,
});
@@ -30,7 +30,7 @@ describe('Waypoint value object', () => {
});
it('should throw an exception if latitude is invalid', () => {
try {
new Coordinates({
new Point({
lat: 148.689445,
lon: 6.17651,
});
@@ -38,7 +38,7 @@ describe('Waypoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new Coordinates({
new Point({
lat: -148.689445,
lon: 6.17651,
});

View File

@@ -2,205 +2,56 @@ import { GeorouterPort } from '@modules/geography/core/application/ports/georout
import { RouteEntity } from '@modules/geography/core/domain/route.entity';
import { RouteNotFoundException } from '@modules/geography/core/domain/route.errors';
import {
Coordinates,
Point,
CreateRouteProps,
PathType,
Role,
} from '@modules/geography/core/domain/route.types';
const originCoordinates: Coordinates = {
const originPoint: Point = {
lat: 48.689445,
lon: 6.17651,
};
const destinationCoordinates: Coordinates = {
const destinationPoint: Point = {
lat: 48.8566,
lon: 2.3522,
};
const additionalCoordinates: Coordinates = {
lon: 48.7566,
lat: 4.4498,
};
const mockGeorouter: GeorouterPort = {
routes: jest
route: jest
.fn()
.mockImplementationOnce(() => [
{
type: PathType.DRIVER,
distance: 350101,
duration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [
{
lon: 6.1765102,
lat: 48.689445,
},
{
lon: 4.984578,
lat: 48.725687,
},
{
lon: 2.3522,
lat: 48.8566,
},
],
spacetimePoints: [],
},
])
.mockImplementationOnce(() => [
{
type: PathType.PASSENGER,
distance: 350102,
duration: 14423,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336545,
points: [
{
lon: 6.1765103,
lat: 48.689446,
},
{
lon: 4.984579,
lat: 48.725688,
},
{
lon: 2.3523,
lat: 48.8567,
},
],
spacetimePoints: [],
},
])
.mockImplementationOnce(() => [
{
type: PathType.GENERIC,
distance: 350100,
duration: 14421,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336543,
points: [
{
lon: 6.1765101,
lat: 48.689444,
},
{
lon: 4.984577,
lat: 48.725686,
},
{
lon: 2.3521,
lat: 48.8565,
},
],
spacetimePoints: [],
},
])
.mockImplementationOnce(() => [
{
type: PathType.GENERIC,
distance: 350108,
duration: 14428,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336548,
points: [
{
lon: 6.1765101,
lat: 48.689444,
},
{
lon: 4.984577,
lat: 48.725686,
},
{
lon: 2.3521,
lat: 48.8565,
},
],
spacetimePoints: [],
},
])
.mockImplementationOnce(() => ({
distance: 350101,
duration: 14422,
fwdAzimuth: 273,
backAzimuth: 93,
distanceAzimuth: 336544,
points: [
{
lon: 6.1765102,
lat: 48.689445,
},
{
lon: 4.984578,
lat: 48.725687,
},
{
lon: 2.3522,
lat: 48.8566,
},
],
steps: [],
}))
.mockImplementationOnce(() => []),
};
const createDriverRouteProps: CreateRouteProps = {
roles: [Role.DRIVER],
const createRouteProps: CreateRouteProps = {
waypoints: [
{
position: 0,
...originCoordinates,
...originPoint,
},
{
position: 1,
...destinationCoordinates,
},
],
georouter: mockGeorouter,
georouterSettings: {
points: true,
detailedDistance: false,
detailedDuration: false,
},
};
const createPassengerRouteProps: CreateRouteProps = {
roles: [Role.PASSENGER],
waypoints: [
{
position: 0,
...originCoordinates,
},
{
position: 1,
...destinationCoordinates,
},
],
georouter: mockGeorouter,
georouterSettings: {
points: true,
detailedDistance: false,
detailedDuration: false,
},
};
const createSimpleDriverAndPassengerRouteProps: CreateRouteProps = {
roles: [Role.DRIVER, Role.PASSENGER],
waypoints: [
{
position: 0,
...originCoordinates,
},
{
position: 1,
...destinationCoordinates,
},
],
georouter: mockGeorouter,
georouterSettings: {
points: true,
detailedDistance: false,
detailedDuration: false,
},
};
const createComplexDriverAndPassengerRouteProps: CreateRouteProps = {
roles: [Role.DRIVER, Role.PASSENGER],
waypoints: [
{
position: 0,
...originCoordinates,
},
{
position: 1,
...additionalCoordinates,
},
{
position: 2,
...destinationCoordinates,
...destinationPoint,
},
],
georouter: mockGeorouter,
@@ -212,43 +63,15 @@ const createComplexDriverAndPassengerRouteProps: CreateRouteProps = {
};
describe('Route entity create', () => {
it('should create a new entity for a driver only', async () => {
const route: RouteEntity = await RouteEntity.create(createDriverRouteProps);
it('should create a new entity', async () => {
const route: RouteEntity = await RouteEntity.create(createRouteProps);
expect(route.id.length).toBe(36);
expect(route.getProps().driverDuration).toBe(14422);
expect(route.getProps().passengerDistance).toBeUndefined();
});
it('should create a new entity for a passenger only', async () => {
const route: RouteEntity = await RouteEntity.create(
createPassengerRouteProps,
);
expect(route.id.length).toBe(36);
expect(route.getProps().passengerDuration).toBe(14423);
expect(route.getProps().driverDistance).toBeUndefined();
});
it('should create a new entity for a simple driver and passenger route', async () => {
const route: RouteEntity = await RouteEntity.create(
createSimpleDriverAndPassengerRouteProps,
);
expect(route.id.length).toBe(36);
expect(route.getProps().driverDuration).toBe(14421);
expect(route.getProps().driverDistance).toBe(350100);
expect(route.getProps().passengerDuration).toBe(14421);
expect(route.getProps().passengerDistance).toBe(350100);
});
it('should create a new entity for a complex driver and passenger route', async () => {
const route: RouteEntity = await RouteEntity.create(
createComplexDriverAndPassengerRouteProps,
);
expect(route.id.length).toBe(36);
expect(route.getProps().driverDuration).toBe(14428);
expect(route.getProps().driverDistance).toBe(350108);
expect(route.getProps().passengerDuration).toBe(14428);
expect(route.getProps().passengerDistance).toBe(350108);
expect(route.getProps().duration).toBe(14422);
});
it('should throw an exception if route is not found', async () => {
try {
await RouteEntity.create(createDriverRouteProps);
await RouteEntity.create(createRouteProps);
} catch (e: any) {
expect(e).toBeInstanceOf(RouteNotFoundException);
}

View File

@@ -2,24 +2,24 @@ import {
ArgumentInvalidException,
ArgumentOutOfRangeException,
} from '@mobicoop/ddd-library';
import { SpacetimePoint } from '@modules/geography/core/domain/value-objects/spacetime-point.value-object';
import { Step } from '@modules/geography/core/domain/value-objects/step.value-object';
describe('Timepoint value object', () => {
it('should create a timepoint value object', () => {
const timepointVO = new SpacetimePoint({
describe('Step value object', () => {
it('should create a step value object', () => {
const stepVO = new Step({
lat: 48.689445,
lon: 6.17651,
duration: 150,
distance: 12000,
});
expect(timepointVO.duration).toBe(150);
expect(timepointVO.distance).toBe(12000);
expect(timepointVO.lat).toBe(48.689445);
expect(timepointVO.lon).toBe(6.17651);
expect(stepVO.duration).toBe(150);
expect(stepVO.distance).toBe(12000);
expect(stepVO.lat).toBe(48.689445);
expect(stepVO.lon).toBe(6.17651);
});
it('should throw an exception if longitude is invalid', () => {
try {
new SpacetimePoint({
new Step({
lat: 48.689445,
lon: 186.17651,
duration: 150,
@@ -29,7 +29,7 @@ describe('Timepoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new SpacetimePoint({
new Step({
lon: 48.689445,
lat: -186.17651,
duration: 150,
@@ -41,7 +41,7 @@ describe('Timepoint value object', () => {
});
it('should throw an exception if latitude is invalid', () => {
try {
new SpacetimePoint({
new Step({
lat: 248.689445,
lon: 6.17651,
duration: 150,
@@ -51,7 +51,7 @@ describe('Timepoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
}
try {
new SpacetimePoint({
new Step({
lon: -148.689445,
lat: 6.17651,
duration: 150,
@@ -63,7 +63,7 @@ describe('Timepoint value object', () => {
});
it('should throw an exception if distance is invalid', () => {
try {
new SpacetimePoint({
new Step({
lat: 48.689445,
lon: 6.17651,
duration: 150,
@@ -75,7 +75,7 @@ describe('Timepoint value object', () => {
});
it('should throw an exception if duration is invalid', () => {
try {
new SpacetimePoint({
new Step({
lat: 48.689445,
lon: 6.17651,
duration: -150,

View File

@@ -4,7 +4,7 @@ import {
GeorouterUnavailableException,
RouteNotFoundException,
} from '@modules/geography/core/domain/route.errors';
import { PathType, Route } from '@modules/geography/core/domain/route.types';
import { Route } from '@modules/geography/core/domain/route.types';
import {
GEODESIC,
PARAMS_PROVIDER,
@@ -294,20 +294,17 @@ describe('Graphhopper Georouter', () => {
it('should fail if route is not found', async () => {
await expect(
graphhopperGeorouter.routes(
graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lon: 0,
lat: 0,
},
{
lon: 1,
lat: 1,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 1,
lat: 1,
},
],
{
@@ -321,20 +318,17 @@ describe('Graphhopper Georouter', () => {
it('should fail if georouter is unavailable', async () => {
await expect(
graphhopperGeorouter.routes(
graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lon: 0,
lat: 0,
},
{
lon: 1,
lat: 1,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 1,
lat: 1,
},
],
{
@@ -347,20 +341,17 @@ describe('Graphhopper Georouter', () => {
});
it('should create a basic route', async () => {
const routes: Route[] = await graphhopperGeorouter.routes(
const route: Route = await graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lon: 0,
lat: 0,
},
{
lon: 10,
lat: 10,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 10,
lat: 10,
},
],
{
@@ -369,25 +360,21 @@ describe('Graphhopper Georouter', () => {
points: false,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].distance).toBe(50000);
expect(route.distance).toBe(50000);
});
it('should create one route with points', async () => {
const routes = await graphhopperGeorouter.routes(
it('should create a route with points', async () => {
const route: Route = await graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 10,
lat: 10,
},
],
{
@@ -396,29 +383,25 @@ describe('Graphhopper Georouter', () => {
points: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].distance).toBe(50000);
expect(routes[0].duration).toBe(1800);
expect(routes[0].fwdAzimuth).toBe(45);
expect(routes[0].backAzimuth).toBe(225);
expect(routes[0].points).toHaveLength(11);
expect(route.distance).toBe(50000);
expect(route.duration).toBe(1800);
expect(route.fwdAzimuth).toBe(45);
expect(route.backAzimuth).toBe(225);
expect(route.points).toHaveLength(11);
});
it('should create one route with points and time', async () => {
const routes = await graphhopperGeorouter.routes(
it('should create a route with points and time', async () => {
const route: Route = await graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 10,
lat: 10,
},
],
{
@@ -427,31 +410,28 @@ describe('Graphhopper Georouter', () => {
points: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].spacetimeWaypoints).toHaveLength(2);
expect(routes[0].spacetimeWaypoints[1].duration).toBe(1800);
expect(routes[0].spacetimeWaypoints[1].distance).toBeUndefined();
expect(route.steps).toHaveLength(2);
expect(route.steps[1].duration).toBe(1800);
expect(route.steps[1].distance).toBeUndefined();
});
it('should create one route with points and missed waypoints extrapolations', async () => {
const routes = await graphhopperGeorouter.routes(
const route: Route = await graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lat: 0,
lon: 0,
},
{
lat: 5,
lon: 5,
},
{
lat: 10,
lon: 10,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 5,
lat: 5,
},
{
position: 2,
lon: 10,
lat: 10,
},
],
{
@@ -460,30 +440,26 @@ describe('Graphhopper Georouter', () => {
points: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].spacetimeWaypoints).toHaveLength(3);
expect(routes[0].distance).toBe(50000);
expect(routes[0].duration).toBe(1800);
expect(routes[0].fwdAzimuth).toBe(45);
expect(routes[0].backAzimuth).toBe(225);
expect(routes[0].points.length).toBe(9);
expect(route.steps).toHaveLength(3);
expect(route.distance).toBe(50000);
expect(route.duration).toBe(1800);
expect(route.fwdAzimuth).toBe(45);
expect(route.backAzimuth).toBe(225);
expect(route.points.length).toBe(9);
});
it('should create one route with points, time and distance', async () => {
const routes = await graphhopperGeorouter.routes(
it('should create a route with points, time and distance', async () => {
const route: Route = await graphhopperGeorouter.route(
[
{
type: PathType.DRIVER,
points: [
{
lat: 0,
lon: 0,
},
{
lat: 10,
lon: 10,
},
],
position: 0,
lon: 0,
lat: 0,
},
{
position: 1,
lon: 10,
lat: 10,
},
],
{
@@ -492,9 +468,8 @@ describe('Graphhopper Georouter', () => {
points: true,
},
);
expect(routes).toHaveLength(1);
expect(routes[0].spacetimeWaypoints.length).toBe(3);
expect(routes[0].spacetimeWaypoints[1].duration).toBe(990);
expect(routes[0].spacetimeWaypoints[1].distance).toBe(25000);
expect(route.steps.length).toBe(3);
expect(route.steps[1].duration).toBe(990);
expect(route.steps[1].distance).toBe(25000);
});
});

View File

@@ -1,4 +1,4 @@
import { Coordinates } from '@modules/geography/core/domain/route.types';
import { Point } from '@modules/geography/core/domain/route.types';
import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder';
describe('Postgres direction encoder', () => {
@@ -7,10 +7,10 @@ describe('Postgres direction encoder', () => {
new PostgresDirectionEncoder();
expect(postgresDirectionEncoder).toBeDefined();
});
it('should encode coordinates to a postgres direction', () => {
it('should encode points to a postgres direction', () => {
const postgresDirectionEncoder: PostgresDirectionEncoder =
new PostgresDirectionEncoder();
const coordinates: Coordinates[] = [
const points: Point[] = [
{
lon: 6,
lat: 47,
@@ -24,18 +24,17 @@ describe('Postgres direction encoder', () => {
lat: 47.2,
},
];
const direction = postgresDirectionEncoder.encode(coordinates);
const direction = postgresDirectionEncoder.encode(points);
expect(direction).toBe("'LINESTRING(6 47,6.1 47.1,6.2 47.2)'");
});
it('should decode a postgres direction to coordinates', () => {
const postgresDirectionEncoder: PostgresDirectionEncoder =
new PostgresDirectionEncoder();
const direction = "'LINESTRING(6 47,6.1 47.1,6.2 47.2)'";
const coordinates: Coordinates[] =
postgresDirectionEncoder.decode(direction);
expect(coordinates.length).toBe(3);
expect(coordinates[0].lat).toBe(47);
expect(coordinates[1].lon).toBe(6.1);
expect(coordinates[2].lat).toBe(47.2);
const points: Point[] = postgresDirectionEncoder.decode(direction);
expect(points.length).toBe(3);
expect(points[0].lat).toBe(47);
expect(points[1].lon).toBe(6.1);
expect(points[2].lat).toBe(47.2);
});
});

View File

@@ -1,4 +1,3 @@
import { Role } from '@modules/geography/core/domain/route.types';
import { GetBasicRouteController } from '@modules/geography/interface/controllers/get-basic-route.controller';
import { RouteMapper } from '@modules/geography/route.mapper';
import { QueryBus } from '@nestjs/cqrs';
@@ -48,7 +47,6 @@ describe('Get Basic Route Controller', () => {
it('should get a route', async () => {
jest.spyOn(mockQueryBus, 'execute');
await getBasicRouteController.get({
roles: [Role.DRIVER],
waypoints: [
{
position: 0,

View File

@@ -23,28 +23,23 @@ describe('Route Mapper', () => {
createdAt: now,
updatedAt: now,
props: {
driverDistance: 23000,
driverDuration: 900,
passengerDistance: 23000,
passengerDuration: 900,
distance: 23000,
duration: 900,
fwdAzimuth: 283,
backAzimuth: 93,
distanceAzimuth: 19840,
points: [],
waypoints: [
points: [
{
position: 0,
lon: 6.1765103,
lat: 48.689446,
},
{
position: 1,
lon: 2.3523,
lat: 48.8567,
},
],
},
});
expect(routeMapper.toResponse(routeEntity).driverDistance).toBe(23000);
expect(routeMapper.toResponse(routeEntity).distance).toBe(23000);
});
});