Skip to content

Commit

Permalink
REFACTOR: 158 - Draft based on query template and routing facade
Browse files Browse the repository at this point in the history
- Still has open TODOs and points to be discussed.
- Introduce routing query facade the hide complexity of routing from the service.
  • Loading branch information
munterfi committed Jan 14, 2025
1 parent ebea0b0 commit 0e4dcd5
Show file tree
Hide file tree
Showing 13 changed files with 693 additions and 482 deletions.
40 changes: 34 additions & 6 deletions src/main/java/ch/naviqore/gtfs/schedule/model/GtfsSchedule.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* General Transit Feed Specification (GTFS) schedule
Expand Down Expand Up @@ -83,10 +83,7 @@ public static GtfsScheduleBuilder builder() {
* @return A list of the next departures from the specified stop.
*/
public List<StopTime> getNextDepartures(String stopId, LocalDateTime dateTime, int limit) {
Stop stop = stops.get(stopId);
if (stop == null) {
throw new IllegalArgumentException("Stop " + stopId + " not found");
}
Stop stop = getStop(stopId);
return stop.getStopTimes()
.stream()
.filter(stopTime -> stopTime.departure().getTotalSeconds() >= dateTime.toLocalTime().toSecondOfDay())
Expand All @@ -106,7 +103,38 @@ public List<Trip> getActiveTrips(LocalDate date) {
.stream()
.filter(calendar -> calendar.isServiceAvailable(date))
.flatMap(calendar -> calendar.getTrips().stream())
.collect(Collectors.toList());
.toList();
}

/**
* Retrieves all stops for a given stop, including child stops if the stop has any.
*
* @param stopId the identifier of the stop.
* @return A list of stops including the stop itself and its children.
*/
public List<Stop> getRelatedStops(String stopId) {
Stop stop = getStop(stopId);

if (stop.getChildren().isEmpty()) {
// child stop; return itself
return List.of(stop);
} else {
// parent stop; return all children and itself (departures on parent are possible)
List<Stop> stops = new ArrayList<>();
stops.add(stop);
stops.addAll(stop.getChildren());

return stops;
}
}

private Stop getStop(String stopId) {
Stop stop = stops.get(stopId);
if (stop == null) {
throw new IllegalArgumentException("Stop " + stopId + " not found");
}

return stop;
}

}
491 changes: 16 additions & 475 deletions src/main/java/ch/naviqore/service/gtfs/raptor/GtfsRaptorService.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.List;

@NoArgsConstructor(access = AccessLevel.NONE)
final class TypeMapper {
public final class TypeMapper {

private static final int SECONDS_IN_DAY = 86400;

Expand Down Expand Up @@ -128,10 +128,12 @@ public static EnumSet<ch.naviqore.raptor.TravelMode> map(EnumSet<TravelMode> tra
if (travelModes == null || travelModes.isEmpty()) {
return EnumSet.allOf(ch.naviqore.raptor.TravelMode.class);
}

EnumSet<ch.naviqore.raptor.TravelMode> raptorTravelModes = EnumSet.noneOf(ch.naviqore.raptor.TravelMode.class);
for (TravelMode travelMode : travelModes) {
raptorTravelModes.add(ch.naviqore.raptor.TravelMode.valueOf(travelMode.name()));
}

return raptorTravelModes;
}

Expand Down Expand Up @@ -188,12 +190,14 @@ private static Leg createPublicTransitLeg(ch.naviqore.raptor.Leg leg, GtfsSchedu
private static LocalDate getServiceDay(ch.naviqore.raptor.Leg leg, ch.naviqore.gtfs.schedule.model.Trip trip) {
String StopId = leg.getFromStopId();
LocalTime departureTime = leg.getDepartureTime().toLocalTime();

for (ch.naviqore.gtfs.schedule.model.StopTime stopTime : trip.getStopTimes()) {
if (stopTime.stop().getId().equals(StopId) && stopTime.departure().toLocalTime().equals(departureTime)) {
int dayShift = stopTime.departure().getTotalSeconds() / SECONDS_IN_DAY;
return leg.getDepartureTime().toLocalDate().minusDays(dayShift);
}
}

throw new IllegalStateException("Could not find service day for leg");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ch.naviqore.service.gtfs.raptor.routing;

import ch.naviqore.service.Connection;
import ch.naviqore.service.TimeType;
import ch.naviqore.service.Walk;
import ch.naviqore.service.config.ConnectionQueryConfig;
import ch.naviqore.utils.spatial.GeoCoordinate;

import java.time.LocalDateTime;
import java.util.Map;

class ConnectionGeoToGeo extends ConnectionQueryTemplate<GeoCoordinate, GeoCoordinate> {

ConnectionGeoToGeo(LocalDateTime time, TimeType timeType, ConnectionQueryConfig queryConfig,
RoutingQueryUtils utils, GeoCoordinate source, GeoCoordinate target) {
super(time, timeType, queryConfig, utils, source, target);
}

@Override
protected Map<String, LocalDateTime> prepareSourceStops(GeoCoordinate source) {
return utils.getStopsWithWalkTimeFromLocation(source, time, timeType, queryConfig);
}

@Override
protected Map<String, Integer> prepareTargetStops(GeoCoordinate target) {
return utils.getStopsWithWalkTimeFromLocation(target, queryConfig);
}

@Override
protected Connection postprocessConnection(GeoCoordinate source, ch.naviqore.raptor.Connection connection,
GeoCoordinate target) {
LocalDateTime departureTime = connection.getDepartureTime();
Walk firstMile = utils.createFirstWalk(source, connection.getFromStopId(), departureTime);

LocalDateTime arrivalTime = connection.getArrivalTime();
Walk lastMile = utils.createLastWalk(target, connection.getToStopId(), arrivalTime);

// TODO: Add sourceStop as source for firstMile if sourceStop != null
// TODO: Add targetStop as target for lastMile if targetStop != null
// COMMENT: I think this is already handled inside 'utils.createFirstWalk' and 'utils.createLastWalk'

// TODO: Handle case where firstMile is not null and first leg is a transfer --> use walkCalculator?
// TODO: Handle case where lastMile is not null and last leg is a transfer --> use walkCalculator?
// COMMENT: This means we would currently have a walk form our coordinate to the next stop, but then from
// there another walk to another stop? Have we considered this case in the old version (non-refactored) version?

return utils.composeConnection(firstMile, connection, lastMile);
}

@Override
protected ConnectionQueryTemplate<GeoCoordinate, GeoCoordinate> swap(GeoCoordinate source, GeoCoordinate target) {
return new ConnectionGeoToGeo(time, timeType, queryConfig, utils, target, source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ch.naviqore.service.gtfs.raptor.routing;

import ch.naviqore.service.Connection;
import ch.naviqore.service.Stop;
import ch.naviqore.service.TimeType;
import ch.naviqore.service.Walk;
import ch.naviqore.service.config.ConnectionQueryConfig;
import ch.naviqore.utils.spatial.GeoCoordinate;

import java.time.LocalDateTime;
import java.util.Map;

class ConnectionGeoToStop extends ConnectionQueryTemplate<GeoCoordinate, Stop> {

ConnectionGeoToStop(LocalDateTime time, TimeType timeType, ConnectionQueryConfig queryConfig,
RoutingQueryUtils utils, GeoCoordinate source, Stop target) {
super(time, timeType, queryConfig, utils, source, target);
}

@Override
protected Map<String, LocalDateTime> prepareSourceStops(GeoCoordinate source) {
return utils.getStopsWithWalkTimeFromLocation(source, time, timeType, queryConfig);
}

@Override
protected Map<String, Integer> prepareTargetStops(Stop target) {
return utils.getAllChildStopsFromStop(target);
}

@Override
protected Connection postprocessConnection(GeoCoordinate source, ch.naviqore.raptor.Connection connection,
Stop target) {
LocalDateTime departureTime = connection.getDepartureTime();
Walk firstMile = utils.createLastWalk(source, connection.getFromStopId(), departureTime);
Walk lastMile = utils.createLastWalk(source, connection.getFromStopId(), departureTime);

// TODO: Handle case where firstMile is not null and first leg is a transfer --> use walkCalculator

return utils.composeConnection(firstMile, connection, lastMile);
}

@Override
protected ConnectionQueryTemplate<Stop, GeoCoordinate> swap(GeoCoordinate source, Stop target) {
return new ConnectionStopToGeo(time, timeType, queryConfig, utils, target, source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package ch.naviqore.service.gtfs.raptor.routing;

import ch.naviqore.raptor.RaptorAlgorithm;
import ch.naviqore.service.Connection;
import ch.naviqore.service.TimeType;
import ch.naviqore.service.config.ConnectionQueryConfig;
import ch.naviqore.service.exception.ConnectionRoutingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

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

@RequiredArgsConstructor
@Slf4j
abstract class ConnectionQueryTemplate<S, T> {

protected final LocalDateTime time;
protected final TimeType timeType;
protected final ConnectionQueryConfig queryConfig;
protected final RoutingQueryUtils utils;

private final S source;
private final T target;

protected abstract Map<String, LocalDateTime> prepareSourceStops(S source);

protected abstract Map<String, Integer> prepareTargetStops(T target);

protected abstract Connection postprocessConnection(S source, ch.naviqore.raptor.Connection connection, T target);

protected abstract ConnectionQueryTemplate<T, S> swap(S source, T target);

List<Connection> run() throws ConnectionRoutingException {
// TODO: / COMMENT: This is dangerous because if you run the query multiple times (e.g. when routing from
// location instead of stop due to InvalidStopException, the swap might occur twice resulting in incorrect
// results.

// swap source and target if time type is arrival, which means routing in the reverse time dimension
if (timeType == TimeType.ARRIVAL) {
return swap(source, target).process();
}

return process();
}

List<Connection> process() throws ConnectionRoutingException {

Map<String, LocalDateTime> sourceStops = prepareSourceStops(source);
Map<String, Integer> targetStops = prepareTargetStops(target);

// no source stop or target stop is within walkable distance, and therefore no connections are available
if (sourceStops.isEmpty() || targetStops.isEmpty()) {
return List.of();
}

// query connection from raptor
List<ch.naviqore.raptor.Connection> connections;
try {
connections = utils.routeConnections(sourceStops, targetStops, timeType, queryConfig);
} catch (RaptorAlgorithm.InvalidStopException e) {
log.debug("{}: {}", e.getClass().getSimpleName(), e.getMessage());
// TODO: Implement abstract handleInvalidStopException method?
return List.of();
} catch (IllegalArgumentException e) {
throw new ConnectionRoutingException(e);
}

// assemble connection results
List<Connection> result = new ArrayList<>();
for (ch.naviqore.raptor.Connection raptorConnection : connections) {

Connection serviceConnection = postprocessConnection(source, raptorConnection, target);

if (utils.isBelowMaximumTravelTime(serviceConnection, queryConfig)) {
result.add(serviceConnection);
}
}

return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ch.naviqore.service.gtfs.raptor.routing;

import ch.naviqore.service.Connection;
import ch.naviqore.service.Stop;
import ch.naviqore.service.TimeType;
import ch.naviqore.service.Walk;
import ch.naviqore.service.config.ConnectionQueryConfig;
import ch.naviqore.utils.spatial.GeoCoordinate;

import java.time.LocalDateTime;
import java.util.Map;

class ConnectionStopToGeo extends ConnectionQueryTemplate<Stop, GeoCoordinate> {

ConnectionStopToGeo(LocalDateTime time, TimeType timeType, ConnectionQueryConfig queryConfig,
RoutingQueryUtils utils, Stop source, GeoCoordinate target) {
super(time, timeType, queryConfig, utils, source, target);
}

@Override
protected Map<String, LocalDateTime> prepareSourceStops(Stop source) {
return utils.getAllChildStopsFromStop(source, time);
}

@Override
protected Map<String, Integer> prepareTargetStops(GeoCoordinate target) {
return utils.getStopsWithWalkTimeFromLocation(target, queryConfig);
}

@Override
protected Connection postprocessConnection(Stop source, ch.naviqore.raptor.Connection connection,
GeoCoordinate target) {
LocalDateTime arrivalTime = connection.getArrivalTime();
Walk lastMile = utils.createLastWalk(target, connection.getToStopId(), arrivalTime);

return utils.composeConnection(connection, lastMile);
}

@Override
protected ConnectionQueryTemplate<GeoCoordinate, Stop> swap(Stop source, GeoCoordinate target) {
return new ConnectionGeoToStop(time, timeType, queryConfig, utils, target, source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ch.naviqore.service.gtfs.raptor.routing;

import ch.naviqore.service.Connection;
import ch.naviqore.service.Stop;
import ch.naviqore.service.TimeType;
import ch.naviqore.service.config.ConnectionQueryConfig;

import java.time.LocalDateTime;
import java.util.Map;

class ConnectionStopToStop extends ConnectionQueryTemplate<Stop, Stop> {

ConnectionStopToStop(LocalDateTime time, TimeType timeType, ConnectionQueryConfig queryConfig,
RoutingQueryUtils utils, Stop source, Stop target) {
super(time, timeType, queryConfig, utils, source, target);
}

@Override
protected Map<String, LocalDateTime> prepareSourceStops(Stop source) {
return utils.getAllChildStopsFromStop(source, time);
}

@Override
protected Map<String, Integer> prepareTargetStops(Stop target) {
return utils.getAllChildStopsFromStop(target);
}

@Override
protected Connection postprocessConnection(Stop source, ch.naviqore.raptor.Connection connection, Stop target) {
return utils.composeConnection(connection);
}

@Override
protected ConnectionQueryTemplate<Stop, Stop> swap(Stop source, Stop target) {
return new ConnectionStopToStop(time, timeType, queryConfig, utils, source, target);
}
}
Loading

0 comments on commit 0e4dcd5

Please sign in to comment.