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

@ -4,7 +4,7 @@ import { Selector } from '../algorithm.abstract';
import { AdReadModel } from '@modules/ad/infrastructure/ad.repository'; import { AdReadModel } from '@modules/ad/infrastructure/ad.repository';
import { ScheduleItem } from '../match.query'; import { ScheduleItem } from '../match.query';
import { Waypoint } from '../../../types/waypoint.type'; import { Waypoint } from '../../../types/waypoint.type';
import { Coordinates } from '../../../types/coordinates.type'; import { Point } from '../../../types/point.type';
export class PassengerOrientedSelector extends Selector { export class PassengerOrientedSelector extends Selector {
select = async (): Promise<Candidate[]> => { select = async (): Promise<Candidate[]> => {
@ -238,7 +238,7 @@ export class PassengerOrientedSelector extends Selector {
${this.query.remoteness}`; ${this.query.remoteness}`;
case Role.DRIVER: case Role.DRIVER:
const lineStringPoints: string[] = []; const lineStringPoints: string[] = [];
this.query.carpoolRoute?.points.forEach((point: Coordinates) => this.query.carpoolRoute?.points.forEach((point: Point) =>
lineStringPoints.push( lineStringPoints.push(
`public.st_makepoint(${point.lon},${point.lat})`, `public.st_makepoint(${point.lon},${point.lat})`,
), ),

View File

@ -1,4 +1,4 @@
import { Coordinates } from './coordinates.type'; import { Point } from './point.type';
export type Address = { export type Address = {
name?: string; name?: string;
@ -7,4 +7,4 @@ export type Address = {
locality?: string; locality?: string;
postalCode?: string; postalCode?: string;
country?: string; country?: string;
} & Coordinates; } & Point;

View File

@ -1,4 +1,4 @@
import { Coordinates } from './coordinates.type'; import { Point } from './point.type';
/** /**
* A carpool route is a route with distance and duration as driver and / or passenger * A carpool route is a route with distance and duration as driver and / or passenger
@ -10,5 +10,5 @@ export type CarpoolRoute = {
passengerDuration?: number; passengerDuration?: number;
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
points: Coordinates[]; points: Point[];
}; };

View File

@ -1,4 +1,4 @@
export type Coordinates = { export type Point = {
lon: number; lon: number;
lat: number; lat: number;
}; };

View File

@ -5,6 +5,7 @@ import { Waypoint } from '../core/application/types/waypoint.type';
import { Role } from '../core/domain/ad.types'; import { Role } from '../core/domain/ad.types';
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port'; import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
import { AD_GET_BASIC_ROUTE_CONTROLLER } from '../ad.di-tokens'; import { AD_GET_BASIC_ROUTE_CONTROLLER } from '../ad.di-tokens';
import { Route } from '@modules/geography/core/domain/route.types';
@Injectable() @Injectable()
export class CarpoolRouteProvider implements CarpoolRouteProviderPort { export class CarpoolRouteProvider implements CarpoolRouteProviderPort {
@ -16,9 +17,116 @@ export class CarpoolRouteProvider implements CarpoolRouteProviderPort {
getBasic = async ( getBasic = async (
roles: Role[], roles: Role[],
waypoints: Waypoint[], waypoints: Waypoint[],
): Promise<CarpoolRoute> => ): Promise<CarpoolRoute> => {
await this.getBasicRouteController.get({ const paths: Path[] = this.getPaths(roles, waypoints);
roles, const typeRoutes: TypeRoute[] = await Promise.all(
waypoints, paths.map(
}); async (path: Path) =>
<TypeRoute>{
type: path.type,
route: await this.getBasicRouteController.get({
waypoints,
}),
},
),
);
return this._toCarpoolRoute(typeRoutes);
};
private _toCarpoolRoute = (typeRoutes: TypeRoute[]): CarpoolRoute => {
let baseRoute: Route;
let driverRoute: Route | undefined;
let passengerRoute: Route | undefined;
if (
typeRoutes.some(
(typeRoute: TypeRoute) => typeRoute.type == PathType.GENERIC,
)
) {
driverRoute = passengerRoute = typeRoutes.find(
(typeRoute: TypeRoute) => typeRoute.type == PathType.GENERIC,
)?.route;
} else {
driverRoute = typeRoutes.some(
(typeRoute: TypeRoute) => typeRoute.type == PathType.DRIVER,
)
? typeRoutes.find(
(typeRoute: TypeRoute) => typeRoute.type == PathType.DRIVER,
)?.route
: undefined;
passengerRoute = typeRoutes.some(
(typeRoute: TypeRoute) => typeRoute.type == PathType.PASSENGER,
)
? typeRoutes.find(
(typeRoute: TypeRoute) => typeRoute.type == PathType.PASSENGER,
)?.route
: undefined;
}
if (driverRoute) {
baseRoute = driverRoute;
} else {
baseRoute = passengerRoute as Route;
}
return {
driverDistance: driverRoute?.distance,
driverDuration: driverRoute?.duration,
passengerDistance: passengerRoute?.distance,
passengerDuration: passengerRoute?.duration,
fwdAzimuth: baseRoute.fwdAzimuth,
backAzimuth: baseRoute.backAzimuth,
points: baseRoute.points,
};
};
private getPaths = (roles: Role[], waypoints: Waypoint[]): Path[] => {
const paths: Path[] = [];
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
if (waypoints.length == 2) {
// 2 points => same route for driver and passenger
paths.push(this.createGenericPath(waypoints));
} else {
paths.push(
this.createDriverPath(waypoints),
this.createPassengerPath(waypoints),
);
}
} else if (roles.includes(Role.DRIVER)) {
paths.push(this.createDriverPath(waypoints));
} else if (roles.includes(Role.PASSENGER)) {
paths.push(this.createPassengerPath(waypoints));
}
return paths;
};
private createGenericPath = (waypoints: Waypoint[]): Path =>
this.createPath(waypoints, PathType.GENERIC);
private createDriverPath = (waypoints: Waypoint[]): Path =>
this.createPath(waypoints, PathType.DRIVER);
private createPassengerPath = (waypoints: Waypoint[]): Path =>
this.createPath(
[waypoints[0], waypoints[waypoints.length - 1]],
PathType.PASSENGER,
);
private createPath = (waypoints: Waypoint[], type: PathType): Path => ({
type,
waypoints,
});
}
type Path = {
type: PathType;
waypoints: Waypoint[];
};
type TypeRoute = {
type: PathType;
route: Route;
};
enum PathType {
GENERIC = 'generic',
DRIVER = 'driver',
PASSENGER = 'passenger',
} }

View File

@ -1,30 +1,133 @@
import { AD_GET_BASIC_ROUTE_CONTROLLER } from '@modules/ad/ad.di-tokens'; import { AD_GET_BASIC_ROUTE_CONTROLLER } from '@modules/ad/ad.di-tokens';
import { CarpoolRoute } from '@modules/ad/core/application/types/carpool-route.type'; import { CarpoolRoute } from '@modules/ad/core/application/types/carpool-route.type';
import { Point } from '@modules/ad/core/application/types/point.type';
import { Role } from '@modules/ad/core/domain/ad.types'; import { Role } from '@modules/ad/core/domain/ad.types';
import { CarpoolRouteProvider } from '@modules/ad/infrastructure/carpool-route-provider'; import { CarpoolRouteProvider } from '@modules/ad/infrastructure/carpool-route-provider';
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port'; import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
const originPoint: Point = {
lat: 48.689445,
lon: 6.17651,
};
const destinationPoint: Point = {
lat: 48.8566,
lon: 2.3522,
};
const additionalPoint: Point = {
lon: 48.7566,
lat: 4.4498,
};
const mockGetBasicRouteController: GetBasicRouteControllerPort = { const mockGetBasicRouteController: GetBasicRouteControllerPort = {
get: jest.fn().mockImplementation(() => ({ get: jest
driverDistance: 23000, .fn()
driverDuration: 900, .mockImplementationOnce(() => ({
passengerDistance: 23000, distance: 350101,
passengerDuration: 900, duration: 14422,
fwdAzimuth: 283, fwdAzimuth: 273,
backAzimuth: 93, backAzimuth: 93,
distanceAzimuth: 19840, distanceAzimuth: 336544,
points: [ points: [
{ {
lon: 6.1765103, lon: 6.1765102,
lat: 48.689446, lat: 48.689445,
}, },
{ {
lon: 2.3523, lon: 4.984578,
lat: 48.8567, lat: 48.725687,
}, },
], {
})), lon: 2.3522,
lat: 48.8566,
},
],
}))
.mockImplementationOnce(() => ({
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,
},
],
}))
.mockImplementationOnce(() => ({
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,
},
],
}))
.mockImplementationOnce(() => ({
distance: 350107,
duration: 14427,
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,
},
],
}))
.mockImplementationOnce(() => ({
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,
},
],
}))
.mockImplementationOnce(() => []),
}; };
describe('Carpool route provider', () => { describe('Carpool route provider', () => {
@ -49,22 +152,79 @@ describe('Carpool route provider', () => {
expect(carpoolRouteProvider).toBeDefined(); expect(carpoolRouteProvider).toBeDefined();
}); });
it('should provide a carpool route', async () => { it('should provide a carpool route for a driver only', async () => {
const carpoolRoute: CarpoolRoute = await carpoolRouteProvider.getBasic( const carpoolRoute: CarpoolRoute = await carpoolRouteProvider.getBasic(
[Role.DRIVER], [Role.DRIVER],
[ [
{ {
position: 0, position: 0,
lat: 48.689445, ...originPoint,
lon: 6.1765102,
}, },
{ {
position: 1, position: 1,
lat: 48.8566, ...destinationPoint,
lon: 2.3522,
}, },
], ],
); );
expect(carpoolRoute.driverDistance).toBe(23000); expect(carpoolRoute.driverDistance).toBe(350101);
expect(carpoolRoute.passengerDuration).toBeUndefined();
});
it('should provide a carpool route for a passenger only', async () => {
const carpoolRoute: CarpoolRoute = await carpoolRouteProvider.getBasic(
[Role.PASSENGER],
[
{
position: 0,
...originPoint,
},
{
position: 1,
...destinationPoint,
},
],
);
expect(carpoolRoute.passengerDistance).toBe(350102);
expect(carpoolRoute.driverDuration).toBeUndefined();
});
it('should provide a simple carpool route for a driver and passenger', async () => {
const carpoolRoute: CarpoolRoute = await carpoolRouteProvider.getBasic(
[Role.DRIVER, Role.PASSENGER],
[
{
position: 0,
...originPoint,
},
{
position: 1,
...destinationPoint,
},
],
);
expect(carpoolRoute.driverDuration).toBe(14421);
expect(carpoolRoute.passengerDistance).toBe(350100);
});
it('should provide a complex carpool route for a driver and passenger', async () => {
const carpoolRoute: CarpoolRoute = await carpoolRouteProvider.getBasic(
[Role.DRIVER, Role.PASSENGER],
[
{
position: 0,
...originPoint,
},
{
position: 1,
...additionalPoint,
},
{
position: 2,
...destinationPoint,
},
],
);
expect(carpoolRoute.driverDistance).toBe(350107);
expect(carpoolRoute.passengerDuration).toBe(14428);
}); });
}); });

View File

@ -1,6 +1,6 @@
import { Coordinates } from '../../domain/route.types'; import { Point } from '../../domain/route.types';
export interface DirectionEncoderPort { export interface DirectionEncoderPort {
encode(coordinates: Coordinates[]): string; encode(coordinates: Point[]): string;
decode(direction: string): Coordinates[]; decode(direction: string): Point[];
} }

View File

@ -1,6 +1,6 @@
import { Path, Route } from '../../domain/route.types'; import { Route, Waypoint } from '../../domain/route.types';
import { GeorouterSettings } from '../types/georouter-settings.type'; import { GeorouterSettings } from '../types/georouter-settings.type';
export interface GeorouterPort { export interface GeorouterPort {
routes(paths: Path[], settings: GeorouterSettings): Promise<Route[]>; route(waypoints: Waypoint[], settings: GeorouterSettings): Promise<Route>;
} }

View File

@ -11,7 +11,6 @@ export class GetRouteQueryHandler implements IQueryHandler {
execute = async (query: GetRouteQuery): Promise<RouteEntity> => execute = async (query: GetRouteQuery): Promise<RouteEntity> =>
await RouteEntity.create({ await RouteEntity.create({
roles: query.roles,
waypoints: query.waypoints, waypoints: query.waypoints,
georouter: this.georouter, georouter: this.georouter,
georouterSettings: query.georouterSettings, georouterSettings: query.georouterSettings,

View File

@ -1,19 +1,13 @@
import { QueryBase } from '@mobicoop/ddd-library'; import { QueryBase } from '@mobicoop/ddd-library';
import { Role, Waypoint } from '@modules/geography/core/domain/route.types'; import { Waypoint } from '@modules/geography/core/domain/route.types';
import { GeorouterSettings } from '../../types/georouter-settings.type'; import { GeorouterSettings } from '../../types/georouter-settings.type';
export class GetRouteQuery extends QueryBase { export class GetRouteQuery extends QueryBase {
readonly roles: Role[];
readonly waypoints: Waypoint[]; readonly waypoints: Waypoint[];
readonly georouterSettings: GeorouterSettings; readonly georouterSettings: GeorouterSettings;
constructor( constructor(waypoints: Waypoint[], georouterSettings: GeorouterSettings) {
roles: Role[],
waypoints: Waypoint[],
georouterSettings: GeorouterSettings,
) {
super(); super();
this.roles = roles;
this.waypoints = waypoints; this.waypoints = waypoints;
this.georouterSettings = georouterSettings; this.georouterSettings = georouterSettings;
} }

View File

@ -1,13 +1,5 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library'; import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { import { CreateRouteProps, RouteProps, Route } from './route.types';
CreateRouteProps,
Path,
Role,
RouteProps,
PathType,
Route,
} from './route.types';
import { WaypointProps } from './value-objects/waypoint.value-object';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { RouteNotFoundException } from './route.errors'; import { RouteNotFoundException } from './route.errors';
@ -15,43 +7,18 @@ export class RouteEntity extends AggregateRoot<RouteProps> {
protected readonly _id: AggregateID; protected readonly _id: AggregateID;
static create = async (create: CreateRouteProps): Promise<RouteEntity> => { static create = async (create: CreateRouteProps): Promise<RouteEntity> => {
const routes: Route[] = await create.georouter.routes( const route: Route = await create.georouter.route(
this.getPaths(create.roles, create.waypoints), create.waypoints,
create.georouterSettings, create.georouterSettings,
); );
if (!routes || routes.length == 0) throw new RouteNotFoundException(); if (!route) throw new RouteNotFoundException();
let baseRoute: Route;
let driverRoute: Route | undefined;
let passengerRoute: Route | undefined;
if (routes.some((route: Route) => route.type == PathType.GENERIC)) {
driverRoute = passengerRoute = routes.find(
(route: Route) => route.type == PathType.GENERIC,
);
} else {
driverRoute = routes.some((route: Route) => route.type == PathType.DRIVER)
? routes.find((route: Route) => route.type == PathType.DRIVER)
: undefined;
passengerRoute = routes.some(
(route: Route) => route.type == PathType.PASSENGER,
)
? routes.find((route: Route) => route.type == PathType.PASSENGER)
: undefined;
}
if (driverRoute) {
baseRoute = driverRoute;
} else {
baseRoute = passengerRoute as Route;
}
const routeProps: RouteProps = { const routeProps: RouteProps = {
driverDistance: driverRoute?.distance, distance: route.distance,
driverDuration: driverRoute?.duration, duration: route.duration,
passengerDistance: passengerRoute?.distance, fwdAzimuth: route.fwdAzimuth,
passengerDuration: passengerRoute?.duration, backAzimuth: route.backAzimuth,
fwdAzimuth: baseRoute.fwdAzimuth, distanceAzimuth: route.distanceAzimuth,
backAzimuth: baseRoute.backAzimuth, points: route.points,
distanceAzimuth: baseRoute.distanceAzimuth,
waypoints: create.waypoints,
points: baseRoute.points,
}; };
return new RouteEntity({ return new RouteEntity({
id: v4(), id: v4(),
@ -63,46 +30,46 @@ export class RouteEntity extends AggregateRoot<RouteProps> {
// entity business rules validation to protect it's invariant before saving entity to a database // entity business rules validation to protect it's invariant before saving entity to a database
} }
private static getPaths = ( // private static getPaths = (
roles: Role[], // roles: Role[],
waypoints: WaypointProps[], // waypoints: WaypointProps[],
): Path[] => { // ): Path[] => {
const paths: Path[] = []; // const paths: Path[] = [];
if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) { // if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) {
if (waypoints.length == 2) { // if (waypoints.length == 2) {
// 2 points => same route for driver and passenger // // 2 points => same route for driver and passenger
paths.push(this.createGenericPath(waypoints)); // paths.push(this.createGenericPath(waypoints));
} else { // } else {
paths.push( // paths.push(
this.createDriverPath(waypoints), // this.createDriverPath(waypoints),
this.createPassengerPath(waypoints), // this.createPassengerPath(waypoints),
); // );
} // }
} else if (roles.includes(Role.DRIVER)) { // } else if (roles.includes(Role.DRIVER)) {
paths.push(this.createDriverPath(waypoints)); // paths.push(this.createDriverPath(waypoints));
} else if (roles.includes(Role.PASSENGER)) { // } else if (roles.includes(Role.PASSENGER)) {
paths.push(this.createPassengerPath(waypoints)); // paths.push(this.createPassengerPath(waypoints));
} // }
return paths; // return paths;
}; // };
private static createGenericPath = (waypoints: WaypointProps[]): Path => // private static createGenericPath = (waypoints: WaypointProps[]): Path =>
this.createPath(waypoints, PathType.GENERIC); // this.createPath(waypoints, PathType.GENERIC);
private static createDriverPath = (waypoints: WaypointProps[]): Path => // private static createDriverPath = (waypoints: WaypointProps[]): Path =>
this.createPath(waypoints, PathType.DRIVER); // this.createPath(waypoints, PathType.DRIVER);
private static createPassengerPath = (waypoints: WaypointProps[]): Path => // private static createPassengerPath = (waypoints: WaypointProps[]): Path =>
this.createPath( // this.createPath(
[waypoints[0], waypoints[waypoints.length - 1]], // [waypoints[0], waypoints[waypoints.length - 1]],
PathType.PASSENGER, // PathType.PASSENGER,
); // );
private static createPath = ( // private static createPath = (
points: WaypointProps[], // points: WaypointProps[],
type: PathType, // type: PathType,
): Path => ({ // ): Path => ({
type, // type,
points, // points,
}); // });
} }

View File

@ -1,67 +1,49 @@
import { GeorouterPort } from '../application/ports/georouter.port'; import { GeorouterPort } from '../application/ports/georouter.port';
import { GeorouterSettings } from '../application/types/georouter-settings.type'; import { GeorouterSettings } from '../application/types/georouter-settings.type';
import { CoordinatesProps } from './value-objects/coordinates.value-object'; import { PointProps } from './value-objects/point.value-object';
import { SpacetimePointProps } from './value-objects/spacetime-point.value-object';
import { WaypointProps } from './value-objects/waypoint.value-object'; import { WaypointProps } from './value-objects/waypoint.value-object';
// All properties that a Route has // All properties that a Route has
export interface RouteProps { export interface RouteProps {
driverDistance?: number; distance: number;
driverDuration?: number; duration: number;
passengerDistance?: number;
passengerDuration?: number;
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
waypoints: WaypointProps[]; points: PointProps[];
points: SpacetimePointProps[] | CoordinatesProps[];
} }
// Properties that are needed for a Route creation // Properties that are needed for a Route creation
export interface CreateRouteProps { export interface CreateRouteProps {
roles: Role[];
waypoints: WaypointProps[]; waypoints: WaypointProps[];
georouter: GeorouterPort; georouter: GeorouterPort;
georouterSettings: GeorouterSettings; georouterSettings: GeorouterSettings;
} }
export type Route = { export type Route = {
type: PathType;
distance: number; distance: number;
duration: number; duration: number;
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
points: Coordinates[]; points: Point[];
spacetimeWaypoints: SpacetimePoint[]; steps: Step[];
}; };
export type Path = { export type Point = {
type: PathType;
points: Coordinates[];
};
export type Coordinates = {
lon: number; lon: number;
lat: number; lat: number;
}; };
export type Waypoint = Coordinates & { export type Waypoint = Point & {
position: number; position: number;
}; };
export type SpacetimePoint = Coordinates & { export type Spacetime = {
duration: number; duration: number;
distance?: number; distance?: number;
}; };
export enum Role { export type Step = Point & Spacetime;
DRIVER = 'DRIVER',
PASSENGER = 'PASSENGER',
}
export enum PathType { export type Waystep = Waypoint & Spacetime;
GENERIC = 'generic',
DRIVER = 'driver',
PASSENGER = 'passenger',
}

View File

@ -8,12 +8,12 @@ import {
* other Value Objects inside if needed. * other Value Objects inside if needed.
* */ * */
export interface CoordinatesProps { export interface PointProps {
lon: number; lon: number;
lat: number; lat: number;
} }
export class Coordinates extends ValueObject<CoordinatesProps> { export class Point extends ValueObject<PointProps> {
get lon(): number { get lon(): number {
return this.props.lon; return this.props.lon;
} }
@ -22,7 +22,7 @@ export class Coordinates extends ValueObject<CoordinatesProps> {
return this.props.lat; return this.props.lat;
} }
protected validate(props: CoordinatesProps): void { protected validate(props: PointProps): void {
if (props.lon > 180 || props.lon < -180) if (props.lon > 180 || props.lon < -180)
throw new ArgumentOutOfRangeException('lon must be between -180 and 180'); throw new ArgumentOutOfRangeException('lon must be between -180 and 180');
if (props.lat > 90 || props.lat < -90) if (props.lat > 90 || props.lat < -90)

View File

@ -9,14 +9,14 @@ import {
* other Value Objects inside if needed. * other Value Objects inside if needed.
* */ * */
export interface SpacetimePointProps { export interface StepProps {
lon: number; lon: number;
lat: number; lat: number;
duration: number; duration: number;
distance: number; distance: number;
} }
export class SpacetimePoint extends ValueObject<SpacetimePointProps> { export class Step extends ValueObject<StepProps> {
get lon(): number { get lon(): number {
return this.props.lon; return this.props.lon;
} }
@ -33,7 +33,7 @@ export class SpacetimePoint extends ValueObject<SpacetimePointProps> {
return this.props.distance; return this.props.distance;
} }
protected validate(props: SpacetimePointProps): void { protected validate(props: StepProps): void {
if (props.duration < 0) if (props.duration < 0)
throw new ArgumentInvalidException( throw new ArgumentInvalidException(
'duration must be greater than or equal to 0', 'duration must be greater than or equal to 0',

View File

@ -2,12 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios'; import { HttpService } from '@nestjs/axios';
import { GeorouterPort } from '../core/application/ports/georouter.port'; import { GeorouterPort } from '../core/application/ports/georouter.port';
import { GeorouterSettings } from '../core/application/types/georouter-settings.type'; import { GeorouterSettings } from '../core/application/types/georouter-settings.type';
import { import { Route, Step, Waypoint } from '../core/domain/route.types';
Path,
PathType,
Route,
SpacetimePoint,
} from '../core/domain/route.types';
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port'; import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
import { GEODESIC, PARAMS_PROVIDER } from '../geography.di-tokens'; import { GEODESIC, PARAMS_PROVIDER } from '../geography.di-tokens';
import { catchError, lastValueFrom, map } from 'rxjs'; import { catchError, lastValueFrom, map } from 'rxjs';
@ -35,13 +30,13 @@ export class GraphhopperGeorouter implements GeorouterPort {
].join(''); ].join('');
} }
routes = async ( route = async (
paths: Path[], waypoints: Waypoint[],
settings: GeorouterSettings, settings: GeorouterSettings,
): Promise<Route[]> => { ): Promise<Route> => {
this._setDefaultUrlArgs(); this._setDefaultUrlArgs();
this._setSettings(settings); this._setSettings(settings);
return this._getRoutes(paths); return this._getRoute(waypoints);
}; };
private _setDefaultUrlArgs = (): void => { private _setDefaultUrlArgs = (): void => {
@ -62,46 +57,39 @@ export class GraphhopperGeorouter implements GeorouterPort {
} }
}; };
private _getRoutes = async (paths: Path[]): Promise<Route[]> => { private _getRoute = async (waypoints: Waypoint[]): Promise<Route> => {
const routes = Promise.all( const url: string = [
paths.map(async (path) => { this.getUrl(),
const url: string = [ '&point=',
this.getUrl(), waypoints
'&point=', .map((waypoint: Waypoint) => [waypoint.lat, waypoint.lon].join('%2C'))
path.points .join('&point='),
.map((point) => [point.lat, point.lon].join('%2C')) ].join('');
.join('&point='), return await lastValueFrom(
].join(''); this.httpService.get(url).pipe(
return await lastValueFrom( map((response) => {
this.httpService.get(url).pipe( if (response.data) return this.createRoute(response);
map((response) => { throw new Error();
if (response.data) return this.createRoute(response, path.type); }),
throw new Error(); catchError((error: AxiosError) => {
}), if (error.code == AxiosError.ERR_BAD_REQUEST) {
catchError((error: AxiosError) => { throw new RouteNotFoundException(
if (error.code == AxiosError.ERR_BAD_REQUEST) { error,
throw new RouteNotFoundException( 'No route found for given coordinates',
error, );
'No route found for given coordinates', }
); throw new GeorouterUnavailableException(error);
} }),
throw new GeorouterUnavailableException(error); ),
}),
),
);
}),
); );
return routes;
}; };
private getUrl = (): string => [this.url, this.urlArgs.join('&')].join(''); private getUrl = (): string => [this.url, this.urlArgs.join('&')].join('');
private createRoute = ( private createRoute = (
response: AxiosResponse<GraphhopperResponse>, response: AxiosResponse<GraphhopperResponse>,
type: PathType,
): Route => { ): Route => {
const route = {} as Route; const route = {} as Route;
route.type = type;
if (response.data.paths && response.data.paths[0]) { if (response.data.paths && response.data.paths[0]) {
const shortestPath = response.data.paths[0]; const shortestPath = response.data.paths[0];
route.distance = shortestPath.distance ?? 0; route.distance = shortestPath.distance ?? 0;
@ -135,7 +123,7 @@ export class GraphhopperGeorouter implements GeorouterPort {
let instructions: GraphhopperInstruction[] = []; let instructions: GraphhopperInstruction[] = [];
if (shortestPath.instructions) if (shortestPath.instructions)
instructions = shortestPath.instructions; instructions = shortestPath.instructions;
route.spacetimeWaypoints = this.generateSpacetimePoints( route.steps = this.generateSteps(
shortestPath.points.coordinates, shortestPath.points.coordinates,
shortestPath.snapped_waypoints.coordinates, shortestPath.snapped_waypoints.coordinates,
shortestPath.details.time, shortestPath.details.time,
@ -147,12 +135,12 @@ export class GraphhopperGeorouter implements GeorouterPort {
return route; return route;
}; };
private generateSpacetimePoints = ( private generateSteps = (
points: [[number, number]], points: [[number, number]],
snappedWaypoints: [[number, number]], snappedWaypoints: [[number, number]],
durations: [[number, number, number]], durations: [[number, number, number]],
instructions: GraphhopperInstruction[], instructions: GraphhopperInstruction[],
): SpacetimePoint[] => { ): Step[] => {
const indices = this.getIndices(points, snappedWaypoints); const indices = this.getIndices(points, snappedWaypoints);
const times = this.getTimes(durations, indices); const times = this.getTimes(durations, indices);
const distances = this.getDistances(instructions, indices); const distances = this.getDistances(instructions, indices);

View File

@ -1,16 +1,16 @@
import { DirectionEncoderPort } from '../core/application/ports/direction-encoder.port'; import { DirectionEncoderPort } from '../core/application/ports/direction-encoder.port';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Coordinates } from '../core/domain/route.types'; import { Point } from '../core/domain/route.types';
@Injectable() @Injectable()
export class PostgresDirectionEncoder implements DirectionEncoderPort { export class PostgresDirectionEncoder implements DirectionEncoderPort {
encode = (coordinates: Coordinates[]): string => encode = (coordinates: Point[]): string =>
[ [
"'LINESTRING(", "'LINESTRING(",
coordinates.map((point) => [point.lon, point.lat].join(' ')).join(), coordinates.map((point) => [point.lon, point.lat].join(' ')).join(),
")'", ")'",
].join(''); ].join('');
decode = (direction: string): Coordinates[] => decode = (direction: string): Point[] =>
direction direction
.split('(')[1] .split('(')[1]
.split(')')[0] .split(')')[0]

View File

@ -1,6 +1,5 @@
import { Role, Waypoint } from '@modules/geography/core/domain/route.types'; import { Waypoint } from '@modules/geography/core/domain/route.types';
export type GetRouteRequestDto = { export type GetRouteRequestDto = {
roles: Role[];
waypoints: Waypoint[]; waypoints: Waypoint[];
}; };

View File

@ -16,7 +16,7 @@ export class GetBasicRouteController implements GetBasicRouteControllerPort {
async get(data: GetRouteRequestDto): Promise<RouteResponseDto> { async get(data: GetRouteRequestDto): Promise<RouteResponseDto> {
const route: RouteEntity = await this.queryBus.execute( const route: RouteEntity = await this.queryBus.execute(
new GetRouteQuery(data.roles, data.waypoints, { new GetRouteQuery(data.waypoints, {
detailedDistance: false, detailedDistance: false,
detailedDuration: false, detailedDuration: false,
points: true, points: true,

View File

@ -1,15 +1,10 @@
import { import { Point, Step } from '@modules/geography/core/domain/route.types';
Coordinates,
SpacetimePoint,
} from '@modules/geography/core/domain/route.types';
export class RouteResponseDto { export class RouteResponseDto {
driverDistance?: number; distance?: number;
driverDuration?: number; duration?: number;
passengerDistance?: number;
passengerDuration?: number;
fwdAzimuth: number; fwdAzimuth: number;
backAzimuth: number; backAzimuth: number;
distanceAzimuth: number; distanceAzimuth: number;
points: SpacetimePoint[] | Coordinates[]; points: Step[] | Point[];
} }

View File

@ -16,17 +16,11 @@ export class RouteMapper
{ {
toResponse = (entity: RouteEntity): RouteResponseDto => { toResponse = (entity: RouteEntity): RouteResponseDto => {
const response = new RouteResponseDto(); const response = new RouteResponseDto();
response.driverDistance = entity.getProps().driverDistance response.distance = entity.getProps().distance
? Math.round(entity.getProps().driverDistance as number) ? Math.round(entity.getProps().distance as number)
: undefined; : undefined;
response.driverDuration = entity.getProps().driverDuration response.duration = entity.getProps().duration
? Math.round(entity.getProps().driverDuration as number) ? Math.round(entity.getProps().duration as number)
: undefined;
response.passengerDistance = entity.getProps().passengerDistance
? Math.round(entity.getProps().passengerDistance as number)
: undefined;
response.passengerDuration = entity.getProps().passengerDuration
? Math.round(entity.getProps().passengerDuration as number)
: undefined; : undefined;
response.fwdAzimuth = Math.round(entity.getProps().fwdAzimuth); response.fwdAzimuth = Math.round(entity.getProps().fwdAzimuth);
response.backAzimuth = Math.round(entity.getProps().backAzimuth); response.backAzimuth = Math.round(entity.getProps().backAzimuth);

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

View File

@ -1,18 +1,18 @@
import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library'; 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', () => { describe('Point value object', () => {
it('should create a waypoint value object', () => { it('should create a point value object', () => {
const coordinatesVO = new Coordinates({ const pointVO = new Point({
lat: 48.689445, lat: 48.689445,
lon: 6.17651, lon: 6.17651,
}); });
expect(coordinatesVO.lat).toBe(48.689445); expect(pointVO.lat).toBe(48.689445);
expect(coordinatesVO.lon).toBe(6.17651); expect(pointVO.lon).toBe(6.17651);
}); });
it('should throw an exception if longitude is invalid', () => { it('should throw an exception if longitude is invalid', () => {
try { try {
new Coordinates({ new Point({
lat: 48.689445, lat: 48.689445,
lon: 186.17651, lon: 186.17651,
}); });
@ -20,7 +20,7 @@ describe('Waypoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException); expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
} }
try { try {
new Coordinates({ new Point({
lat: 48.689445, lat: 48.689445,
lon: -186.17651, lon: -186.17651,
}); });
@ -30,7 +30,7 @@ describe('Waypoint value object', () => {
}); });
it('should throw an exception if latitude is invalid', () => { it('should throw an exception if latitude is invalid', () => {
try { try {
new Coordinates({ new Point({
lat: 148.689445, lat: 148.689445,
lon: 6.17651, lon: 6.17651,
}); });
@ -38,7 +38,7 @@ describe('Waypoint value object', () => {
expect(e).toBeInstanceOf(ArgumentOutOfRangeException); expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
} }
try { try {
new Coordinates({ new Point({
lat: -148.689445, lat: -148.689445,
lon: 6.17651, 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 { RouteEntity } from '@modules/geography/core/domain/route.entity';
import { RouteNotFoundException } from '@modules/geography/core/domain/route.errors'; import { RouteNotFoundException } from '@modules/geography/core/domain/route.errors';
import { import {
Coordinates, Point,
CreateRouteProps, CreateRouteProps,
PathType,
Role,
} from '@modules/geography/core/domain/route.types'; } from '@modules/geography/core/domain/route.types';
const originCoordinates: Coordinates = { const originPoint: Point = {
lat: 48.689445, lat: 48.689445,
lon: 6.17651, lon: 6.17651,
}; };
const destinationCoordinates: Coordinates = { const destinationPoint: Point = {
lat: 48.8566, lat: 48.8566,
lon: 2.3522, lon: 2.3522,
}; };
const additionalCoordinates: Coordinates = {
lon: 48.7566,
lat: 4.4498,
};
const mockGeorouter: GeorouterPort = { const mockGeorouter: GeorouterPort = {
routes: jest route: jest
.fn() .fn()
.mockImplementationOnce(() => [ .mockImplementationOnce(() => ({
{ distance: 350101,
type: PathType.DRIVER, duration: 14422,
distance: 350101, fwdAzimuth: 273,
duration: 14422, backAzimuth: 93,
fwdAzimuth: 273, distanceAzimuth: 336544,
backAzimuth: 93, points: [
distanceAzimuth: 336544, {
points: [ lon: 6.1765102,
{ lat: 48.689445,
lon: 6.1765102, },
lat: 48.689445, {
}, lon: 4.984578,
{ lat: 48.725687,
lon: 4.984578, },
lat: 48.725687, {
}, lon: 2.3522,
{ lat: 48.8566,
lon: 2.3522, },
lat: 48.8566, ],
}, steps: [],
], }))
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(() => []), .mockImplementationOnce(() => []),
}; };
const createDriverRouteProps: CreateRouteProps = { const createRouteProps: CreateRouteProps = {
roles: [Role.DRIVER],
waypoints: [ waypoints: [
{ {
position: 0, position: 0,
...originCoordinates, ...originPoint,
}, },
{ {
position: 1, position: 1,
...destinationCoordinates, ...destinationPoint,
},
],
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,
}, },
], ],
georouter: mockGeorouter, georouter: mockGeorouter,
@ -212,43 +63,15 @@ const createComplexDriverAndPassengerRouteProps: CreateRouteProps = {
}; };
describe('Route entity create', () => { describe('Route entity create', () => {
it('should create a new entity for a driver only', async () => { it('should create a new entity', async () => {
const route: RouteEntity = await RouteEntity.create(createDriverRouteProps); const route: RouteEntity = await RouteEntity.create(createRouteProps);
expect(route.id.length).toBe(36); expect(route.id.length).toBe(36);
expect(route.getProps().driverDuration).toBe(14422); expect(route.getProps().duration).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);
}); });
it('should throw an exception if route is not found', async () => { it('should throw an exception if route is not found', async () => {
try { try {
await RouteEntity.create(createDriverRouteProps); await RouteEntity.create(createRouteProps);
} catch (e: any) { } catch (e: any) {
expect(e).toBeInstanceOf(RouteNotFoundException); expect(e).toBeInstanceOf(RouteNotFoundException);
} }

View File

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

View File

@ -4,7 +4,7 @@ import {
GeorouterUnavailableException, GeorouterUnavailableException,
RouteNotFoundException, RouteNotFoundException,
} from '@modules/geography/core/domain/route.errors'; } 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 { import {
GEODESIC, GEODESIC,
PARAMS_PROVIDER, PARAMS_PROVIDER,
@ -294,20 +294,17 @@ describe('Graphhopper Georouter', () => {
it('should fail if route is not found', async () => { it('should fail if route is not found', async () => {
await expect( await expect(
graphhopperGeorouter.routes( graphhopperGeorouter.route(
[ [
{ {
type: PathType.DRIVER, position: 0,
points: [ lon: 0,
{ lat: 0,
lon: 0, },
lat: 0, {
}, position: 1,
{ lon: 1,
lon: 1, lat: 1,
lat: 1,
},
],
}, },
], ],
{ {
@ -321,20 +318,17 @@ describe('Graphhopper Georouter', () => {
it('should fail if georouter is unavailable', async () => { it('should fail if georouter is unavailable', async () => {
await expect( await expect(
graphhopperGeorouter.routes( graphhopperGeorouter.route(
[ [
{ {
type: PathType.DRIVER, position: 0,
points: [ lon: 0,
{ lat: 0,
lon: 0, },
lat: 0, {
}, position: 1,
{ lon: 1,
lon: 1, lat: 1,
lat: 1,
},
],
}, },
], ],
{ {
@ -347,20 +341,17 @@ describe('Graphhopper Georouter', () => {
}); });
it('should create a basic route', async () => { it('should create a basic route', async () => {
const routes: Route[] = await graphhopperGeorouter.routes( const route: Route = await graphhopperGeorouter.route(
[ [
{ {
type: PathType.DRIVER, position: 0,
points: [ lon: 0,
{ lat: 0,
lon: 0, },
lat: 0, {
}, position: 1,
{ lon: 10,
lon: 10, lat: 10,
lat: 10,
},
],
}, },
], ],
{ {
@ -369,25 +360,21 @@ describe('Graphhopper Georouter', () => {
points: false, points: false,
}, },
); );
expect(routes).toHaveLength(1); expect(route.distance).toBe(50000);
expect(routes[0].distance).toBe(50000);
}); });
it('should create one route with points', async () => { it('should create a route with points', async () => {
const routes = await graphhopperGeorouter.routes( const route: Route = await graphhopperGeorouter.route(
[ [
{ {
type: PathType.DRIVER, position: 0,
points: [ lon: 0,
{ lat: 0,
lat: 0, },
lon: 0, {
}, position: 1,
{ lon: 10,
lat: 10, lat: 10,
lon: 10,
},
],
}, },
], ],
{ {
@ -396,29 +383,25 @@ describe('Graphhopper Georouter', () => {
points: true, points: true,
}, },
); );
expect(routes).toHaveLength(1); expect(route.distance).toBe(50000);
expect(routes[0].distance).toBe(50000); expect(route.duration).toBe(1800);
expect(routes[0].duration).toBe(1800); expect(route.fwdAzimuth).toBe(45);
expect(routes[0].fwdAzimuth).toBe(45); expect(route.backAzimuth).toBe(225);
expect(routes[0].backAzimuth).toBe(225); expect(route.points).toHaveLength(11);
expect(routes[0].points).toHaveLength(11);
}); });
it('should create one route with points and time', async () => { it('should create a route with points and time', async () => {
const routes = await graphhopperGeorouter.routes( const route: Route = await graphhopperGeorouter.route(
[ [
{ {
type: PathType.DRIVER, position: 0,
points: [ lon: 0,
{ lat: 0,
lat: 0, },
lon: 0, {
}, position: 1,
{ lon: 10,
lat: 10, lat: 10,
lon: 10,
},
],
}, },
], ],
{ {
@ -427,31 +410,28 @@ describe('Graphhopper Georouter', () => {
points: true, points: true,
}, },
); );
expect(routes).toHaveLength(1); expect(route.steps).toHaveLength(2);
expect(routes[0].spacetimeWaypoints).toHaveLength(2); expect(route.steps[1].duration).toBe(1800);
expect(routes[0].spacetimeWaypoints[1].duration).toBe(1800); expect(route.steps[1].distance).toBeUndefined();
expect(routes[0].spacetimeWaypoints[1].distance).toBeUndefined();
}); });
it('should create one route with points and missed waypoints extrapolations', async () => { 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, position: 0,
points: [ lon: 0,
{ lat: 0,
lat: 0, },
lon: 0, {
}, position: 1,
{ lon: 5,
lat: 5, lat: 5,
lon: 5, },
}, {
{ position: 2,
lat: 10, lon: 10,
lon: 10, lat: 10,
},
],
}, },
], ],
{ {
@ -460,30 +440,26 @@ describe('Graphhopper Georouter', () => {
points: true, points: true,
}, },
); );
expect(routes).toHaveLength(1); expect(route.steps).toHaveLength(3);
expect(routes[0].spacetimeWaypoints).toHaveLength(3); expect(route.distance).toBe(50000);
expect(routes[0].distance).toBe(50000); expect(route.duration).toBe(1800);
expect(routes[0].duration).toBe(1800); expect(route.fwdAzimuth).toBe(45);
expect(routes[0].fwdAzimuth).toBe(45); expect(route.backAzimuth).toBe(225);
expect(routes[0].backAzimuth).toBe(225); expect(route.points.length).toBe(9);
expect(routes[0].points.length).toBe(9);
}); });
it('should create one route with points, time and distance', async () => { it('should create a route with points, time and distance', async () => {
const routes = await graphhopperGeorouter.routes( const route: Route = await graphhopperGeorouter.route(
[ [
{ {
type: PathType.DRIVER, position: 0,
points: [ lon: 0,
{ lat: 0,
lat: 0, },
lon: 0, {
}, position: 1,
{ lon: 10,
lat: 10, lat: 10,
lon: 10,
},
],
}, },
], ],
{ {
@ -492,9 +468,8 @@ describe('Graphhopper Georouter', () => {
points: true, points: true,
}, },
); );
expect(routes).toHaveLength(1); expect(route.steps.length).toBe(3);
expect(routes[0].spacetimeWaypoints.length).toBe(3); expect(route.steps[1].duration).toBe(990);
expect(routes[0].spacetimeWaypoints[1].duration).toBe(990); expect(route.steps[1].distance).toBe(25000);
expect(routes[0].spacetimeWaypoints[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'; import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder';
describe('Postgres direction encoder', () => { describe('Postgres direction encoder', () => {
@ -7,10 +7,10 @@ describe('Postgres direction encoder', () => {
new PostgresDirectionEncoder(); new PostgresDirectionEncoder();
expect(postgresDirectionEncoder).toBeDefined(); expect(postgresDirectionEncoder).toBeDefined();
}); });
it('should encode coordinates to a postgres direction', () => { it('should encode points to a postgres direction', () => {
const postgresDirectionEncoder: PostgresDirectionEncoder = const postgresDirectionEncoder: PostgresDirectionEncoder =
new PostgresDirectionEncoder(); new PostgresDirectionEncoder();
const coordinates: Coordinates[] = [ const points: Point[] = [
{ {
lon: 6, lon: 6,
lat: 47, lat: 47,
@ -24,18 +24,17 @@ describe('Postgres direction encoder', () => {
lat: 47.2, 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)'"); expect(direction).toBe("'LINESTRING(6 47,6.1 47.1,6.2 47.2)'");
}); });
it('should decode a postgres direction to coordinates', () => { it('should decode a postgres direction to coordinates', () => {
const postgresDirectionEncoder: PostgresDirectionEncoder = const postgresDirectionEncoder: PostgresDirectionEncoder =
new PostgresDirectionEncoder(); new PostgresDirectionEncoder();
const direction = "'LINESTRING(6 47,6.1 47.1,6.2 47.2)'"; const direction = "'LINESTRING(6 47,6.1 47.1,6.2 47.2)'";
const coordinates: Coordinates[] = const points: Point[] = postgresDirectionEncoder.decode(direction);
postgresDirectionEncoder.decode(direction); expect(points.length).toBe(3);
expect(coordinates.length).toBe(3); expect(points[0].lat).toBe(47);
expect(coordinates[0].lat).toBe(47); expect(points[1].lon).toBe(6.1);
expect(coordinates[1].lon).toBe(6.1); expect(points[2].lat).toBe(47.2);
expect(coordinates[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 { GetBasicRouteController } from '@modules/geography/interface/controllers/get-basic-route.controller';
import { RouteMapper } from '@modules/geography/route.mapper'; import { RouteMapper } from '@modules/geography/route.mapper';
import { QueryBus } from '@nestjs/cqrs'; import { QueryBus } from '@nestjs/cqrs';
@ -48,7 +47,6 @@ describe('Get Basic Route Controller', () => {
it('should get a route', async () => { it('should get a route', async () => {
jest.spyOn(mockQueryBus, 'execute'); jest.spyOn(mockQueryBus, 'execute');
await getBasicRouteController.get({ await getBasicRouteController.get({
roles: [Role.DRIVER],
waypoints: [ waypoints: [
{ {
position: 0, position: 0,

View File

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