Skip to content

Commit

Permalink
feat(common,app,back): TripRequest/LianeRequest : arriveAt, returnAt
Browse files Browse the repository at this point in the history
  • Loading branch information
agjini committed Oct 11, 2024
1 parent 59e53e6 commit fae1604
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 98 deletions.
4 changes: 2 additions & 2 deletions app/src/screens/communities/CommunitiesChatScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ export const CommunitiesChatScreen = () => {

const created = await services.liane.post({
liane: liane!.id!,
departureTime: time[0].toISOString(),
returnTime: time[1]?.toISOString(),
arriveAt: time[0].toISOString(),
returnAt: time[1]?.toISOString(),
from: from ?? me!.lianeRequest.wayPoints[0].id!,
to: to ?? me!.lianeRequest.wayPoints[1].id!,
availableSeats: me!.lianeRequest.canDrive ? 1 : -1,
Expand Down
27 changes: 25 additions & 2 deletions back/src/Liane/Liane.Api/Routing/IRoutingService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Liane.Api.Trip;

namespace Liane.Api.Routing;

Expand All @@ -16,13 +18,34 @@ public interface IRoutingService
Task<ImmutableList<Route>> GetAlternatives(RoutingQuery routingQuery);
Task<DeltaRoute> CrossAWayPoint(RoutingWithPointQuery routingWithPointQuery);

Task<ImmutableList<WayPoint>?> GetTrip(DepartureOrArrivalTime departureOrArrival, RouteSegment endpoints, IEnumerable<RouteSegment> segments)
=> departureOrArrival.Direction switch
{
Direction.Departure => GetTrip(departureOrArrival.At, endpoints, segments),
Direction.Arrival => GetTripArriveBefore(departureOrArrival.At, endpoints, segments),
_ => throw new ArgumentOutOfRangeException()
};

async Task<ImmutableList<WayPoint>?> GetTripArriveBefore(DateTime arriveBefore, RouteSegment endpoints, IEnumerable<RouteSegment> segments)
{
var trip = await GetTrip(arriveBefore, endpoints, segments);
if (trip is null)
{
return null;
}

var diff = trip.Last().Eta - arriveBefore;
return trip.Select(wp => wp with { Eta = wp.Eta - diff })
.ToImmutableList();
}

/// <summary>
/// Solves the Travelling Salesman Problem while respecting precedence constraints provided by each pair of rallying points
/// using a simple Nearest Neighbour heuristic.
/// </summary>
/// <param name="departureTime"></param>
/// <param name="extremities">The (start, end) points of the trip</param>
/// <param name="endpoints">The (start, end) points of the trip</param>
/// <param name="segments">The (from, to) segments</param>
/// <returns>A sorted set of WayPoints or null if no solution exist that satisfies given constraints</returns>
Task<ImmutableList<WayPoint>?> GetTrip(DateTime departureTime, RouteSegment extremities, IEnumerable<RouteSegment> segments);
Task<ImmutableList<WayPoint>?> GetTrip(DateTime departureTime, RouteSegment endpoints, IEnumerable<RouteSegment> segments);
}
8 changes: 7 additions & 1 deletion back/src/Liane/Liane.Api/Routing/WayPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@

namespace Liane.Api.Routing;

public sealed record WayPoint(RallyingPoint RallyingPoint, int Duration, int Distance, DateTime Eta, DateTime? EffectiveTime = null);
public sealed record WayPoint(
RallyingPoint RallyingPoint,
int Duration,
int Distance,
DateTime Eta,
DateTime? EffectiveTime = null
);
8 changes: 4 additions & 4 deletions back/src/Liane/Liane.Api/Trip/Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ public enum Direction
Arrival
}

public record DepartureOrArrivalTime(
DateTime DateTime,
public sealed record DepartureOrArrivalTime(
DateTime At,
Direction Direction
)
{
public override string ToString()
{
return Direction switch
{
Direction.Departure => $"Starting at {DateTime}",
_ => $"Arriving at {DateTime}"
Direction.Departure => $"Starting at {At}",
_ => $"Arriving at {At}"
};
}
}
Expand Down
19 changes: 5 additions & 14 deletions back/src/Liane/Liane.Api/Trip/TripRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,13 @@

namespace Liane.Api.Trip;

public record BaseLianeRequest(
DateTime DepartureTime,
DateTime? ReturnTime,
int AvailableSeats,
[property: SerializeAsResolvedRef] Ref<RallyingPoint> From,
[property: SerializeAsResolvedRef] Ref<RallyingPoint> To,
GeolocationLevel GeolocationLevel
);

public sealed record TripRequest(
string? Id,
Ref<Community.Liane> Liane,
DateTime DepartureTime,
DateTime? ReturnTime,
DateTime ArriveAt,
DateTime? ReturnAt,
int AvailableSeats,
Ref<RallyingPoint> From,
Ref<RallyingPoint> To,
[property: SerializeAsResolvedRef] Ref<RallyingPoint> From,
[property: SerializeAsResolvedRef] Ref<RallyingPoint> To,
GeolocationLevel GeolocationLevel
) : BaseLianeRequest(DepartureTime, ReturnTime, AvailableSeats, From, To, GeolocationLevel), IIdentity<string>;
) : IIdentity<string>;
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,8 @@

namespace Liane.Service.Internal.Routing;

using LngLatTuple = Tuple<double, double>;

public sealed class RoutingServiceImpl : IRoutingService
public sealed class RoutingServiceImpl(IOsrmService osrmService, ILogger<RoutingServiceImpl> logger) : IRoutingService
{
private readonly IOsrmService osrmService;
private readonly ILogger<RoutingServiceImpl> logger;

public RoutingServiceImpl(IOsrmService osrmService, ILogger<RoutingServiceImpl> logger)
{
this.osrmService = osrmService;
this.logger = logger;
}

public Task<Route> GetRoute(RoutingQuery query, CancellationToken cancellationToken = default) => GetRoute(query.Coordinates, cancellationToken);

public Task<Route> GetRoute(LatLng from, LatLng to, CancellationToken cancellationToken = default) => GetRoute(GetFromTo(from, to), cancellationToken);
Expand Down Expand Up @@ -115,10 +104,10 @@ public async Task<DeltaRoute> CrossAWayPoint(RoutingWithPointQuery query)
return matrix;
}

public async Task<ImmutableList<WayPoint>?> GetTrip(DateTime departureTime, RouteSegment extremities, IEnumerable<RouteSegment> segments)
public async Task<ImmutableList<WayPoint>?> GetTrip(DateTime departureTime, RouteSegment endpoints, IEnumerable<RouteSegment> segments)
{
var start = extremities.From;
var end = extremities.To;
var start = endpoints.From;
var end = endpoints.To;
// A dictionary holding each point's constraints
// The HashSet contains all points that must be visited before this point can be added to the trip.
// If the hashset of a given point P contains P, it indicates this point is no longer visitable.
Expand All @@ -145,15 +134,15 @@ public async Task<DeltaRoute> CrossAWayPoint(RoutingWithPointQuery query)
}

// End is marked with precedence constraints from all other points except itself and start
pointsDictionary[end] = pointsDictionary.Keys.Except(new[] { start, end }).ToHashSet();
pointsDictionary[end] = pointsDictionary.Keys.Except([start, end]).ToHashSet();

// Get distance matrix for points
if (pointsDictionary.Keys.Count < 2)
{
return null;
}

var matrix = await GetDurationMatrix(pointsDictionary.Keys.ToImmutableArray());
var matrix = await GetDurationMatrix([..pointsDictionary.Keys]);

var eta = departureTime;

Expand All @@ -166,7 +155,7 @@ public async Task<DeltaRoute> CrossAWayPoint(RoutingWithPointQuery query)
var visitable = pointsDictionary.Where(kv => kv.Value.Count == 0).Select(kv => kv.Key).ToHashSet();
var currentPoint = start;

while (visitable.Any())
while (visitable.Count != 0)
{
// Get next point amongst visitable
var nextPointData = matrix[currentPoint].IntersectBy(visitable, kv => kv.Key).MinBy(kv => kv.Value);
Expand Down
43 changes: 17 additions & 26 deletions back/src/Liane/Liane.Service/Internal/Trip/TripServiceImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,20 @@ public sealed class TripServiceImpl(
{
var toCreate = new List<LianeDb>();
// Handle return here
if (entity.ReturnTime is not null)
if (entity.ReturnAt is not null)
{
var createdReturn = await ToDb(
entity with { DepartureTime = entity.ReturnTime.Value, From = entity.To, To = entity.From, ReturnTime = null },
entity with { From = entity.To, To = entity.From },
new DepartureOrArrivalTime(entity.ReturnAt.Value, Direction.Departure),
ObjectId.GenerateNewId().ToString()!,
createdAt,
createdBy
);
toCreate.Add(createdReturn);
}

var created = await ToDb(entity with { ReturnTime = null }, ObjectId.GenerateNewId().ToString()!, createdAt, createdBy);
toCreate.Add(entity.ReturnTime is null ? created : created with { Return = toCreate[0].Id });

if (!currentContext.AllowPastResourceCreation())
{
toCreate = toCreate.Where(l => l.DepartureTime > createdAt).ToList();
}

if (toCreate.Count == 0)
{
throw new ArgumentException($"Cannot create liane with departure time: {entity.DepartureTime}");
}
var created = await ToDb(entity, new DepartureOrArrivalTime(entity.ArriveAt, Direction.Arrival), ObjectId.GenerateNewId().ToString()!, createdAt, createdBy);
toCreate.Add(entity.ReturnAt is null ? created : created with { Return = toCreate[0].Id });

await Mongo.GetCollection<LianeDb>().InsertManyAsync(toCreate);
foreach (var lianeDb in toCreate)
Expand All @@ -87,12 +78,12 @@ public sealed class TripServiceImpl(
}

await userStatService.IncrementTotalCreatedTrips(createdBy);
await rallyingPointService.UpdateStats([entity.From, entity.To], entity.ReturnTime ?? entity.DepartureTime, entity.ReturnTime is null ? 1 : 2);
await rallyingPointService.UpdateStats([entity.From, entity.To], entity.ReturnAt ?? created.DepartureTime, entity.ReturnAt is null ? 1 : 2);
return await Get(created.Id);
}


private async Task<LianeDb> ToDb(TripRequest tripRequest, string originalId, DateTime createdAt, string createdBy)
private async Task<LianeDb> ToDb(TripRequest tripRequest, DepartureOrArrivalTime at, string originalId, DateTime createdAt, string createdBy)
{
if (tripRequest.From == tripRequest.To)
{
Expand All @@ -101,9 +92,9 @@ private async Task<LianeDb> ToDb(TripRequest tripRequest, string originalId, Dat

var members = new List<TripMember> { new(createdBy, tripRequest.From, tripRequest.To, tripRequest.AvailableSeats, GeolocationLevel: tripRequest.GeolocationLevel) };
var driverData = new Driver(createdBy, tripRequest.AvailableSeats > 0);
var wayPoints = await GetWayPoints(tripRequest.DepartureTime, driverData.User, members);
var wayPoints = await GetWayPoints(at, driverData.User, members);
var wayPointDbs = wayPoints.Select(w => new WayPointDb(w.RallyingPoint, w.Duration, w.Distance, w.Eta)).ToImmutableList();
return new LianeDb(originalId, createdBy, createdAt, tripRequest.DepartureTime, null, members.ToImmutableList(), driverData,
return new LianeDb(originalId, createdBy, createdAt, wayPoints[0].Eta, null, members.ToImmutableList(), driverData,
TripStatus.NotStarted, wayPointDbs, ImmutableList<UserPing>.Empty, tripRequest.Liane);
}

Expand Down Expand Up @@ -136,13 +127,13 @@ public async Task<PaginatedResponse<LianeMatch>> Match(Filter filter, Pagination
DateTime lowerBound, upperBound;
if (filter.TargetTime.Direction == Direction.Departure)
{
lowerBound = filter.TargetTime.DateTime;
upperBound = filter.TargetTime.DateTime.AddHours(LianeMatchPageDeltaInHours);
lowerBound = filter.TargetTime.At;
upperBound = filter.TargetTime.At.AddHours(LianeMatchPageDeltaInHours);
}
else
{
lowerBound = filter.TargetTime.DateTime.AddHours(-LianeMatchPageDeltaInHours);
upperBound = filter.TargetTime.DateTime;
lowerBound = filter.TargetTime.At.AddHours(-LianeMatchPageDeltaInHours);
upperBound = filter.TargetTime.At;
}

var timer = new Stopwatch();
Expand Down Expand Up @@ -409,10 +400,10 @@ public async Task<string> GetContact(Ref<Api.Trip.Trip> id, Ref<Api.Auth.User> r
return m.Phone;
}

private async Task<ImmutableList<WayPoint>> GetWayPoints(DateTime departureTime, Ref<Api.Auth.User> driver, IEnumerable<TripMember> lianeMembers)
private async Task<ImmutableList<WayPoint>> GetWayPoints(DepartureOrArrivalTime at, Ref<Api.Auth.User> driver, IEnumerable<TripMember> lianeMembers)
{
var (driverSegment, segments) = await ExtractRouteSegments(driver, lianeMembers);
var result = await routingService.GetTrip(departureTime, driverSegment, segments);
var result = await routingService.GetTrip(at, driverSegment, segments);
if (result == null)
{
throw new ValidationException("members", ValidationMessage.WrongFormat);
Expand Down Expand Up @@ -495,7 +486,7 @@ private async Task<ImmutableList<LianeSegment>> GetLianeSegments(IEnumerable<Api

private async Task<UpdateDefinition<LianeDb>> GetTripUpdate(DateTime departureTime, Ref<Api.Auth.User> driver, IEnumerable<TripMember> members)
{
var wayPoints = await GetWayPoints(departureTime, driver, members);
var wayPoints = await GetWayPoints(new DepartureOrArrivalTime(departureTime, Direction.Departure), driver, members);
return Builders<LianeDb>.Update
.Set(l => l.WayPoints, wayPoints.ToDb());
}
Expand All @@ -507,7 +498,7 @@ private async Task<UpdateDefinition<LianeDb>> GetTripUpdate(DateTime departureTi

var liane = await MapEntity(lianeDb);
var initialTripDuration = liane.WayPoints.TotalDuration();
if (filter.TargetTime.Direction == Direction.Arrival && lianeDb.DepartureTime.AddSeconds(initialTripDuration) > filter.TargetTime.DateTime)
if (filter.TargetTime.Direction == Direction.Arrival && lianeDb.DepartureTime.AddSeconds(initialTripDuration) > filter.TargetTime.At)
{
// For filters on arrival types, filter here using trip duration
return null;
Expand Down
13 changes: 6 additions & 7 deletions back/src/Liane/Liane.Test/Integration/LianeStorageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace Liane.Test.Integration;

[TestFixture(Category = "Integration")]
public class LianeStorageTest: BaseIntegrationTest
public class LianeStorageTest : BaseIntegrationTest
{
private ITripService tripService = null!;
private IPostgisService postgisService = null!;
Expand All @@ -22,7 +22,7 @@ protected override void Setup(IMongoDatabase db)
tripService = ServiceProvider.GetRequiredService<ITripService>();
postgisService = ServiceProvider.GetRequiredService<IPostgisService>();
}

private async Task<ImmutableList<Api.Trip.Trip>> CreateLianes(string creatorId)
{
var tomorrow = DateTime.Now.AddDays(1);
Expand All @@ -37,7 +37,7 @@ protected override void Setup(IMongoDatabase db)
var requests = new TripRequest[baseLianes.Length];
for (var i = 0; i < baseLianes.Length; i++)
{
var lianeRequest = Fakers.LianeRequestFaker.Generate() with { From = baseLianes[i].From, To = baseLianes[i].To, DepartureTime = tomorrow, AvailableSeats = 2 };
var lianeRequest = Fakers.LianeRequestFaker.Generate() with { From = baseLianes[i].From, To = baseLianes[i].To, ArriveAt = tomorrow, AvailableSeats = 2 };
requests[i] = lianeRequest;
}

Expand All @@ -55,16 +55,15 @@ public async Task ShouldSyncDatabases()
{
var userA = Fakers.FakeDbUsers[0].Id;
var createdLianes = await CreateLianes(userA);

var searchable = await postgisService.ListSearchableLianes();

Assert.AreEqual(createdLianes.Count, searchable.Count);

await tripService.ForceSyncDatabase();

var updatedSearchable = await postgisService.ListSearchableLianes();

CollectionAssert.AreEquivalent(searchable, updatedSearchable);

CollectionAssert.AreEquivalent(searchable, updatedSearchable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ public async Task ReverseTripShouldReturnNull()
var departureTime = DateTime.Parse("2023-03-02T08:00:00+01:00");

RouteSegment driver = (LabeledPositions.SaintEnimieParking, LabeledPositions.Mende);
var result = await tested.GetTrip(departureTime, driver, new[] { (RouteSegment)(LabeledPositions.Mende, LabeledPositions.SaintEnimieParking) });
var result = await tested.GetTrip(departureTime, driver, [(LabeledPositions.Mende, LabeledPositions.SaintEnimieParking)]);
Assert.Null(result);

var result2 = await tested.GetTrip(departureTime, driver, new[]
{
(RouteSegment)(LabeledPositions.ChamperbouxEglise, LabeledPositions.SaintEnimieParking),
var result2 = await tested.GetTrip(departureTime, driver, [
(LabeledPositions.ChamperbouxEglise, LabeledPositions.SaintEnimieParking),
(LabeledPositions.Mende, LabeledPositions.ChamperbouxEglise)
});
]);
Assert.Null(result2);
}

Expand Down
Loading

0 comments on commit fae1604

Please sign in to comment.