Skip to content

Commit

Permalink
Merge pull request #51 from naviqore/feature/NAV-64-enrich-raptor-res…
Browse files Browse the repository at this point in the history
…ults-inside-of-raptor-to-include-trips-ids-etc

Feature/nav 64 enrich raptor results inside of raptor to include trips ids etc
  • Loading branch information
clukas1 authored Jun 5, 2024
2 parents 4ec44c4 + beec21f commit ae03ed7
Show file tree
Hide file tree
Showing 20 changed files with 234 additions and 105 deletions.
4 changes: 2 additions & 2 deletions src/main/java/ch/naviqore/app/dto/DtoDummyData.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private static Leg buildFootpathLeg(Stop from, Stop to, LocalDateTime departureT
int footpathSpeed = 100; // meters per minute
int footpathTravelTime = footpathDistance / footpathSpeed; // in minutes

return new Leg(from.getCoordinates(), to.getCoordinates(), from, to, LegType.WALK, departureTime,
return new Leg(LegType.WALK, from.getCoordinates(), to.getCoordinates(), from, to, departureTime,
departureTime.plusMinutes(footpathTravelTime), null);
}

Expand Down Expand Up @@ -268,7 +268,7 @@ private static Leg buildTripDummyLeg(Stop from, Stop to, LocalDateTime departure

Trip trip = new Trip(to.getName(), route, stopTimes);

return new Leg(from.getCoordinates(), to.getCoordinates(), from, to, LegType.ROUTE, tripDepartureTime,
return new Leg(LegType.ROUTE, from.getCoordinates(), to.getCoordinates(), from, to, tripDepartureTime,
tripArrivalTime, trip);
}

Expand Down
42 changes: 24 additions & 18 deletions src/main/java/ch/naviqore/app/dto/DtoMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,37 +53,43 @@ public static Connection map(ch.naviqore.service.Connection connection) {
}

public static EarliestArrival map(ch.naviqore.service.Stop stop, ch.naviqore.service.Connection connection) {
return new EarliestArrival(map(stop), connection.getLegs().getLast().getArrivalTime(), map(connection));
return new EarliestArrival(map(stop), connection.getArrivalTime(), map(connection));
}

private static class LegVisitorImpl implements LegVisitor<Leg> {
@Override
public Leg visit(PublicTransitLeg publicTransitLeg) {
// TODO: Trip id in raptor has to be passed first.
return new Leg(
// map(publicTransitLeg.getDeparture().getStop().getLocation()),
// map(publicTransitLeg.getArrival().getStop().getLocation()),
null, null,
// map(publicTransitLeg.getDeparture().getStop()),
// map(publicTransitLeg.getArrival().getStop()),
null, null, LegType.ROUTE,
// publicTransitLeg.getDeparture().getDepartureTime(),
// publicTransitLeg.getArrival().getArrivalTime(),
null, null,
// map(publicTransitLeg.getTrip())
null);
return new Leg(LegType.ROUTE, publicTransitLeg.getDeparture().getStop().getLocation(),
publicTransitLeg.getArrival().getStop().getLocation(),
map(publicTransitLeg.getDeparture().getStop()), map(publicTransitLeg.getArrival().getStop()),
publicTransitLeg.getDeparture().getDepartureTime(), publicTransitLeg.getArrival().getArrivalTime(),
map(publicTransitLeg.getTrip()));
}

@Override
public Leg visit(Transfer transfer) {
return new Leg(transfer.getSourceStop().getLocation(), transfer.getTargetStop().getLocation(),
map(transfer.getSourceStop()), map(transfer.getTargetStop()), LegType.WALK,
transfer.getDepartureTime(), transfer.getArrivalTime(), null);
return new Leg(LegType.WALK, transfer.getSourceStop().getLocation(), transfer.getTargetStop().getLocation(),
map(transfer.getSourceStop()), map(transfer.getTargetStop()), transfer.getDepartureTime(),
transfer.getArrivalTime(), null);
}

@Override
public Leg visit(Walk walk) {
return new Leg(walk.getSourceLocation(), walk.getTargetLocation(), null, null, LegType.WALK,
Stop sourceStop = null;
Stop targetStop = null;

// set stop depending on walk type
if (walk.getStop().isPresent()) {
switch (walk.getWalkType()) {
case WalkType.LAST_MILE -> sourceStop = map(walk.getStop().get());
case WalkType.FIRST_MILE -> targetStop = map(walk.getStop().get());
// a walk between two stations is a TransferLeg and not a Walk, therefore this case should not occur
case DIRECT -> throw new IllegalStateException(
"No stop should be present in a direct walk between two location.");
}
}

return new Leg(LegType.WALK, walk.getSourceLocation(), walk.getTargetLocation(), sourceStop, targetStop,
walk.getDepartureTime(), walk.getArrivalTime(), null);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ch/naviqore/app/dto/Leg.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
@Getter
public class Leg {

private final LegType type;
private final GeoCoordinate from;
private final GeoCoordinate to;
private final Stop fromStop;
private final Stop toStop;
private final LegType type;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private final LocalDateTime departureTime;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
Expand Down
25 changes: 13 additions & 12 deletions src/main/java/ch/naviqore/raptor/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -74,11 +75,15 @@ public int getDuration() {
return getArrivalTime() - getDepartureTime();
}

public int getNumFootPathTransfers() {
return (int) legs.stream().filter(l -> l.type == LegType.FOOTPATH).count();
public List<Leg> getWalkTransfers() {
return legs.stream().filter(l -> l.type == LegType.WALK_TRANSFER).toList();
}

public int getNumSameStationTransfers() {
public List<Leg> getRouteLegs() {
return legs.stream().filter(l -> l.type == LegType.ROUTE).toList();
}

public int getNumberOfSameStationTransfers() {
int transferCounter = 0;
for (int i = 0; i < legs.size() - 1; i++) {
Leg current = legs.get(i);
Expand All @@ -90,27 +95,23 @@ public int getNumSameStationTransfers() {
return transferCounter;
}

public int getNumTransfers() {
return getNumFootPathTransfers() + getNumSameStationTransfers();
}

public int getNumRouteLegs() {
return (int) legs.stream().filter(l -> l.type == LegType.ROUTE).count();
public int getNumberOfTotalTransfers() {
return getWalkTransfers().size() + getNumberOfSameStationTransfers();
}

/**
* Types of legs in a connection.
*/
public enum LegType {
FOOTPATH,
WALK_TRANSFER,
ROUTE
}

/**
* A leg is a part of a connection that is travelled on the same route and transport mode, without a transfer.
*/
public record Leg(String routeId, String fromStopId, String toStopId, int departureTime, int arrivalTime,
LegType type) implements Comparable<Leg> {
public record Leg(String routeId, @Nullable String tripId, String fromStopId, String toStopId, int departureTime,
int arrivalTime, LegType type) implements Comparable<Leg> {

@Override
public int compareTo(@NotNull Connection.Leg other) {
Expand Down
88 changes: 68 additions & 20 deletions src/main/java/ch/naviqore/raptor/Raptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public List<Connection> routeEarliestArrival(String sourceStopId, String targetS
return routeEarliestArrival(createStopMap(sourceStopId, departureTime), createStopMap(targetStopId, 0));
}

// TODO: Do we still need this? There are no usages...
public List<Connection> routeEarliestArrival(Collection<String> sourceStopIds, Collection<String> targetStopIds,
int departureTime) {
Map<String, Integer> sourceStops = createStopMap(sourceStopIds, departureTime);
Expand All @@ -65,6 +66,7 @@ private Map<String, Integer> createStopMap(Collection<String> stopIds, int value
return stopMap;
}

// TODO: Do we still need this? There are no usages...
private Map<String, Integer> createStopMap(List<String> stopIds, List<Integer> values) {
if (stopIds.size() != values.size()) {
throw new IllegalArgumentException("Stop IDs and values must have the same size.");
Expand Down Expand Up @@ -158,7 +160,7 @@ private List<Leg[]> spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId
// subtract same stop transfer time, as this will be added by default before scanning routes
earliestArrivals[sourceStopIdxs[i]] = departureTimes[i] - SAME_STOP_TRANSFER_TIME;
earliestArrivalsPerRound.getFirst()[sourceStopIdxs[i]] = new Leg(0, departureTimes[i], ArrivalType.INITIAL,
NO_INDEX, sourceStopIdxs[i], null);
NO_INDEX, NO_INDEX, sourceStopIdxs[i], null);
markedStops.add(sourceStopIdxs[i]);
}

Expand Down Expand Up @@ -247,9 +249,10 @@ private List<Leg[]> spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId
continue;
}

// create a route leg
earliestArrivals[stopIdx] = stopTime.arrival();
earliestArrivalsThisRound[stopIdx] = new Leg(tripEntryTime, stopTime.arrival(),
ArrivalType.ROUTE, currentRouteIdx, stopIdx, enteredAtArrival);
ArrivalType.ROUTE, currentRouteIdx, tripOffset, stopIdx, enteredAtArrival);
// mark stop improvement for next round
markedStopsNext.add(stopIdx);
// check if this was a target stop
Expand Down Expand Up @@ -315,7 +318,7 @@ private List<Leg[]> spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId
stops[stopIdx].id());
earliestArrivals[transfer.targetStopIdx()] = newTargetStopArrivalTime;
earliestArrivalsThisRound[transfer.targetStopIdx()] = new Leg(earliestArrivals[stopIdx],
newTargetStopArrivalTime, ArrivalType.TRANSFER, i, transfer.targetStopIdx(),
newTargetStopArrivalTime, ArrivalType.TRANSFER, i, NO_INDEX, transfer.targetStopIdx(),
earliestArrivalsThisRound[stopIdx]);
newStops.add(transfer.targetStopIdx());
}
Expand Down Expand Up @@ -374,25 +377,36 @@ private List<Connection> reconstructParetoOptimalSolutions(List<Leg[]> earliestA

private @Nullable Connection reconstructConnectionFromLeg(Leg leg) {
Connection connection = new Connection();

// start from destination leg and follow legs back until the initial leg is reached
while (leg.type != ArrivalType.INITIAL) {
String id;
String routeId;
String tripId = null;
assert leg.previous != null;
String fromStopId = stops[leg.previous.stopIdx].id();
String toStopId = stops[leg.stopIdx].id();
Connection.LegType type;
int departureTime = leg.departureTime;
int arrivalTime = leg.arrivalTime;

if (leg.type == ArrivalType.ROUTE) {
id = routes[leg.routeOrTransferIdx].id();
Route route = routes[leg.routeOrTransferIdx];
routeId = route.id();
tripId = route.tripIds()[leg.tripOffset];
type = Connection.LegType.ROUTE;

} else if (leg.type == ArrivalType.TRANSFER) {
id = String.format("transfer_%s_%s", fromStopId, toStopId);
type = Connection.LegType.FOOTPATH;
routeId = String.format("transfer_%s_%s", fromStopId, toStopId);
type = Connection.LegType.WALK_TRANSFER;
// include same stop transfer time (which is subtracted before scanning routes)
arrivalTime += SAME_STOP_TRANSFER_TIME;

} else {
throw new IllegalStateException("Unknown arrival type");
}
connection.addLeg(new Connection.Leg(id, fromStopId, toStopId, departureTime, arrivalTime, type));

connection.addLeg(
new Connection.Leg(routeId, tripId, fromStopId, toStopId, departureTime, arrivalTime, type));
leg = leg.previous;
}

Expand All @@ -411,6 +425,7 @@ private void expandFootpathsForSourceStop(int[] earliestArrivals, List<Leg[]> ea
if (stops[sourceStopIdx].numberOfTransfers() == 0) {
return;
}

// mark all transfer stops, no checks needed for since all transfers will improve arrival time and can be
// marked
Stop sourceStop = stops[sourceStopIdx];
Expand All @@ -422,20 +437,45 @@ private void expandFootpathsForSourceStop(int[] earliestArrivals, List<Leg[]> ea
}
earliestArrivals[transfer.targetStopIdx()] = newTargetStopArrivalTime;
earliestArrivalsPerRound.getFirst()[transfer.targetStopIdx()] = new Leg(departureTime,
newTargetStopArrivalTime, ArrivalType.TRANSFER, i, transfer.targetStopIdx(),
newTargetStopArrivalTime, ArrivalType.TRANSFER, i, NO_INDEX, transfer.targetStopIdx(),
earliestArrivalsPerRound.getFirst()[sourceStopIdx]);
markedStops.add(transfer.targetStopIdx());
}
}

/**
* Arrival type of the leg.
*/
private enum ArrivalType {

/**
* First leg in the connection, so there is no previous leg set.
*/
INITIAL,
/**
* A route leg uses a public transit trip in the network.
*/
ROUTE,
/**
* Uses a transfer between station (no same station transfers).
*/
TRANSFER

}

private record Leg(int departureTime, int arrivalTime, ArrivalType type, int routeOrTransferIdx, int stopIdx,
Leg previous) {
/**
* A leg is a part of a connection in the same mode (PT or walk).
*
* @param departureTime the departure time of the leg in seconds after midnight.
* @param arrivalTime the arrival time of the leg in seconds after midnight.
* @param type the type of the leg, can be INITIAL, ROUTE or TRANSFER.
* @param routeOrTransferIdx the index of the route or of the transfer, see arrival type (or NO_INDEX).
* @param tripOffset the trip offset on the current route (or NO_INDEX).
* @param stopIdx the arrival stop of the leg.
* @param previous the previous leg, null if it is the previous leg.
*/
private record Leg(int departureTime, int arrivalTime, ArrivalType type, int routeOrTransferIdx, int tripOffset,
int stopIdx, @Nullable Leg previous) {
}

/**
Expand All @@ -444,6 +484,7 @@ private record Leg(int departureTime, int arrivalTime, ArrivalType type, int rou
private class InputValidator {
private static final int MIN_DEPARTURE_TIME = 0;
private static final int MAX_DEPARTURE_TIME = 48 * 60 * 60; // 48 hours
private static final int MIN_WALKING_TIME_TO_TARGET = 0;

private static void validateStopPermutations(Map<String, Integer> sourceStops,
Map<String, Integer> targetStops) {
Expand All @@ -453,14 +494,17 @@ private static void validateStopPermutations(Map<String, Integer> sourceStops,
if (targetStops.isEmpty()) {
throw new IllegalArgumentException("At least one target stop must be provided.");
}

sourceStops.values().forEach(InputValidator::validateDepartureTime);
targetStops.values().forEach(InputValidator::validateWalkingTimeToTarget);
Set<String> targetStopIds = targetStops.keySet();
for (String sourceStopId : sourceStops.keySet()) {
if (targetStopIds.contains(sourceStopId)) {
throw new IllegalArgumentException("Source and target stop IDs must not be the same.");
}

// ensure departure and arrival stops are not the same
Set<String> intersection = new HashSet<>(sourceStops.keySet());
intersection.retainAll(targetStops.keySet());
if (!intersection.isEmpty()) {
throw new IllegalArgumentException("Source and target stop IDs must not be the same.");
}

}

private static void validateDepartureTime(int departureTime) {
Expand All @@ -471,8 +515,9 @@ private static void validateDepartureTime(int departureTime) {
}

private static void validateWalkingTimeToTarget(int walkingDurationToTarget) {
if (walkingDurationToTarget < 0) {
throw new IllegalArgumentException("Walking duration to target must be greater or equal to 0.");
if (walkingDurationToTarget < MIN_WALKING_TIME_TO_TARGET) {
throw new IllegalArgumentException(
"Walking duration to target must be greater or equal to " + MIN_WALKING_TIME_TO_TARGET + "seconds.");
}
}

Expand All @@ -489,16 +534,19 @@ private Map<Integer, Integer> validateStops(Map<String, Integer> stops) {
if (stops.isEmpty()) {
throw new IllegalArgumentException("At least one stop ID must be provided.");
}
// Loop over all stop pairs

// loop over all stop pairs and check if stop exists in raptor, then validate departure time
Map<Integer, Integer> validStopIds = new HashMap<>();
for (Map.Entry<String, Integer> entry : stops.entrySet()) {
String stopId = entry.getKey();
int time = entry.getValue();

if (stopsToIdx.containsKey(stopId)) {
validateDepartureTime(time);
validStopIds.put(stopsToIdx.get(stopId), time);
} else {
log.warn("Stop ID {} not found in lookup removing from query.", entry.getKey());
}
log.warn("Stop ID {} not found in lookup removing from query.", entry.getKey());
}

if (validStopIds.isEmpty()) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ch/naviqore/raptor/RaptorBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ private RouteTraversal buildRouteTraversal(List<RouteBuilder.RouteContainer> rou
// add route entry to route array
final int numberOfStops = routeContainer.stopSequence().size();
final int numberOfTrips = routeContainer.trips().size();
routeArr[routeIdx] = new Route(routeContainer.id(), routeStopCnt, numberOfStops, stopTimeCnt,
numberOfTrips);
routeArr[routeIdx] = new Route(routeContainer.id(), routeStopCnt, numberOfStops, stopTimeCnt, numberOfTrips,
routeContainer.trips().keySet().toArray(new String[0]));

// add stops to route stop array
Map<Integer, String> stopSequence = routeContainer.stopSequence();
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/ch/naviqore/raptor/Route.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package ch.naviqore.raptor;

record Route(String id, int firstRouteStopIdx, int numberOfStops, int firstStopTimeIdx, int numberOfTrips) {
record Route(String id, int firstRouteStopIdx, int numberOfStops, int firstStopTimeIdx, int numberOfTrips,
String[] tripIds) {
}
5 changes: 5 additions & 0 deletions src/main/java/ch/naviqore/service/Connection.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.naviqore.service;

import java.time.LocalDateTime;
import java.util.List;

/**
Expand All @@ -9,4 +10,8 @@ public interface Connection {

List<Leg> getLegs();

LocalDateTime getDepartureTime();

LocalDateTime getArrivalTime();

}
Loading

0 comments on commit ae03ed7

Please sign in to comment.