Skip to content

Commit

Permalink
Merge pull request #56 from naviqore/NAV-59-Add-support-for-user-defi…
Browse files Browse the repository at this point in the history
…ned-query-params

NAV-49 (Raptor Tests), NAV-40 (Transfers), NAV-59 (Query Config)
  • Loading branch information
munterfi authored Jun 10, 2024
2 parents 168591c + c3cfca6 commit e4f9e83
Show file tree
Hide file tree
Showing 13 changed files with 916 additions and 255 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ public class ServiceConfigParser {

public ServiceConfigParser(@Value("${gtfs.static.url}") String gtfsUrl,
@Value("${transfer.time.minimum:120}") int minimumTransferTime,
@Value("${transfer.defaultSameStationTransferTime:120}") int sameStationTransferTime,
@Value("${walking.distance.maximum:500}") int maxWalkingDistance,
@Value("${walking.speed:3500}") int walkingSpeed,
@Value("${walking.calculator.type:BEE_LINE_DISTANCE}") String walkCalculatorTypeStr) {
ServiceConfig.WalkCalculatorType walkCalculatorType = ServiceConfig.WalkCalculatorType.valueOf(
walkCalculatorTypeStr.toUpperCase());
this.serviceConfig = new ServiceConfig(gtfsUrl, minimumTransferTime, maxWalkingDistance, walkingSpeed,
walkCalculatorType);
sameStationTransferTime, walkCalculatorType);
}

}
57 changes: 57 additions & 0 deletions src/main/java/ch/naviqore/raptor/QueryConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ch.naviqore.raptor;

import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* Configuration to query connections or iso-lines. Default values are set to infinity or zero, which means that no
* restrictions are set.
*/
@NoArgsConstructor
@Getter
public class QueryConfig {

private static final int INFINITY = Integer.MAX_VALUE;

private int maximumWalkingDuration = INFINITY;
private int minimumTransferDuration = 0;
private int maximumTransferNumber = INFINITY;
private int maximumTravelTime = INFINITY;

public QueryConfig(int maximumWalkingDuration, int minimumTransferDuration, int maximumTransferNumber,
int maximumTravelTime) {
this.setMaximumWalkingDuration(maximumWalkingDuration);
this.setMinimumTransferDuration(minimumTransferDuration);
this.setMaximumTransferNumber(maximumTransferNumber);
this.setMaximumTravelTime(maximumTravelTime);
}

public void setMaximumWalkingDuration(int maximumWalkingDuration) {
if (maximumWalkingDuration < 0) {
throw new IllegalArgumentException("Maximum walking duration must be greater than or equal to 0.");
}
this.maximumWalkingDuration = maximumWalkingDuration;
}

public void setMinimumTransferDuration(int minimumTransferDuration) {
if (minimumTransferDuration < 0) {
throw new IllegalArgumentException("Minimum transfer duration must be greater than or equal to 0.");
}
this.minimumTransferDuration = minimumTransferDuration;
}

public void setMaximumTransferNumber(int maximumTransferNumber) {
if (maximumTransferNumber < 0) {
throw new IllegalArgumentException("Maximum transfer number must be greater than or equal to 0.");
}
this.maximumTransferNumber = maximumTransferNumber;
}

public void setMaximumTravelTime(int maximumTravelTime) {
if (maximumTravelTime <= 0) {
throw new IllegalArgumentException("Maximum transfer number must be greater than 0.");
}
this.maximumTravelTime = maximumTravelTime;
}

}
223 changes: 120 additions & 103 deletions src/main/java/ch/naviqore/raptor/Raptor.java

Large diffs are not rendered by default.

18 changes: 14 additions & 4 deletions src/main/java/ch/naviqore/raptor/RaptorBuilder.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package ch.naviqore.raptor;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;

Expand All @@ -20,19 +18,24 @@
*
* @author munterfi
*/
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@Log4j2
public class RaptorBuilder {

private final int defaultSameStationTransferTime;
private final Map<String, Integer> stops = new HashMap<>();
private final Map<String, RouteBuilder> routeBuilders = new HashMap<>();
private final Map<String, List<Transfer>> transfers = new HashMap<>();
private final Map<String, Integer> sameStationTransfers = new HashMap<>();
private final Map<String, Set<String>> stopRoutes = new HashMap<>();

int stopTimeSize = 0;
int routeStopSize = 0;
int transferSize = 0;

RaptorBuilder(int defaultSameStationTransferTime) {
this.defaultSameStationTransferTime = defaultSameStationTransferTime;
}

public RaptorBuilder addStop(String id) {
if (stops.containsKey(id)) {
throw new IllegalArgumentException("Stop " + id + " already exists");
Expand Down Expand Up @@ -90,6 +93,11 @@ public RaptorBuilder addTransfer(String sourceStopId, String targetStopId, int d
throw new IllegalArgumentException("Target stop " + targetStopId + " does not exist");
}

if (sourceStopId.equals(targetStopId)) {
sameStationTransfers.put(sourceStopId, duration);
return this;
}

transfers.computeIfAbsent(sourceStopId, k -> new ArrayList<>())
.add(new Transfer(stops.get(targetStopId), duration));
transferSize++;
Expand Down Expand Up @@ -152,8 +160,10 @@ private StopContext buildStopContext(Lookup lookup) {
List<Transfer> currentTransfers = transfers.get(stopId);
int numberOfTransfers = currentTransfers == null ? 0 : currentTransfers.size();

int sameStopTransferTime = sameStationTransfers.getOrDefault(stopId, defaultSameStationTransferTime);

// add stop entry to stop array
stopArr[stopIdx] = new Stop(stopId, stopRouteIdx, currentStopRoutes.size(),
stopArr[stopIdx] = new Stop(stopId, stopRouteIdx, currentStopRoutes.size(), sameStopTransferTime,
numberOfTransfers == 0 ? Raptor.NO_INDEX : transferIdx, numberOfTransfers);

// add transfer entry to transfer array if there are any
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/ch/naviqore/raptor/Stop.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package ch.naviqore.raptor;

record Stop(String id, int stopRouteIdx, int numberOfRoutes, int transferIdx, int numberOfTransfers) {
record Stop(String id, int stopRouteIdx, int numberOfRoutes, int sameStationTransferTime, int transferIdx,
int numberOfTransfers) {
}
23 changes: 21 additions & 2 deletions src/main/java/ch/naviqore/service/config/ServiceConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,42 @@ public class ServiceConfig {
private final int minimumTransferTime;
private final int maxWalkingDistance;
private final int walkingSpeed;
private final int sameStationTransferTime;
private final WalkCalculatorType walkCalculatorType;
private final String gtfsUrl;

public ServiceConfig(String gtfsUrl, int minimumTransferTime, int maxWalkingDistance, int walkingSpeed,
WalkCalculatorType walkCalculatorType) {
int sameStationTransferTime, WalkCalculatorType walkCalculatorType) {
this.gtfsUrl = gtfsUrl;

if (minimumTransferTime < 0) {
throw new IllegalArgumentException("Minimum transfer time must be greater than or equal to 0.");
}
this.minimumTransferTime = minimumTransferTime;

if (maxWalkingDistance < 0) {
throw new IllegalArgumentException("Maximum walking distance must be greater than or equal to 0.");
}
this.maxWalkingDistance = maxWalkingDistance;

if (walkingSpeed <= 0) {
throw new IllegalArgumentException("Walking speed must be greater than to 0.");
}
this.walkingSpeed = walkingSpeed;

if (sameStationTransferTime < 0) {
throw new IllegalArgumentException("Same station transfer time must be greater than or equal to 0.");
}
this.sameStationTransferTime = sameStationTransferTime;

this.walkCalculatorType = walkCalculatorType;
}

/**
* Constructor with defaults
*/
public ServiceConfig(String gtfsUrl) {
this(gtfsUrl, 120, 500, 3500, WalkCalculatorType.BEE_LINE_DISTANCE);
this(gtfsUrl, 120, 500, 3500, 120, WalkCalculatorType.BEE_LINE_DISTANCE);
}

public enum WalkCalculatorType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
Expand Down Expand Up @@ -187,22 +188,29 @@ private List<Connection> getConnections(@Nullable ch.naviqore.gtfs.schedule.mode
if (sourceStop != null) {
sourceStops = getAllChildStopsFromStop(map(sourceStop), departureTime);
} else if (sourceLocation != null) {
sourceStops = getStopsWithWalkTimeFromLocation(sourceLocation, departureTime);
sourceStops = getStopsWithWalkTimeFromLocation(sourceLocation, departureTime,
config.getMaximumWalkingDuration());
} else {
throw new IllegalArgumentException("Either sourceStop or sourceLocation must be provided.");
}

if (targetStop != null) {
targetStops = getAllChildStopsFromStop(map(targetStop));
} else if (targetLocation != null) {
targetStops = getStopsWithWalkTimeFromLocation(targetLocation);
targetStops = getStopsWithWalkTimeFromLocation(targetLocation, config.getMaximumWalkingDuration());
} else {
throw new IllegalArgumentException("Either targetStop or targetLocation must be provided.");
}

// In this case either no source stop or target stop is within walkable distance
if (sourceStops.isEmpty() || targetStops.isEmpty()) {
return List.of();
}

// query connection from raptor
Raptor raptor = cache.getRaptor(time.toLocalDate());
List<ch.naviqore.raptor.Connection> connections = raptor.routeEarliestArrival(sourceStops, targetStops);
List<ch.naviqore.raptor.Connection> connections = raptor.routeEarliestArrival(sourceStops, targetStops,
map(config));

List<Connection> result = new ArrayList<>();

Expand All @@ -219,17 +227,24 @@ private List<Connection> getConnections(@Nullable ch.naviqore.gtfs.schedule.mode
lastMile = getLastWalk(targetLocation, connection.getToStopId(), arrivalTime, targetStops);
}

result.add(map(connection, firstMile, lastMile, time.toLocalDate(), schedule));
Connection serviceConnection = map(connection, firstMile, lastMile, time.toLocalDate(), schedule);

// Filter needed because the raptor algorithm does not consider the firstMile and lastMile walk time
if (Duration.between(serviceConnection.getDepartureTime(), serviceConnection.getArrivalTime())
.getSeconds() <= config.getMaximumTravelTime()) {
result.add(serviceConnection);
}
}

return result;
}

public Map<String, Integer> getStopsWithWalkTimeFromLocation(GeoCoordinate location) {
return getStopsWithWalkTimeFromLocation(location, 0);
public Map<String, Integer> getStopsWithWalkTimeFromLocation(GeoCoordinate location, int maxWalkDuration) {
return getStopsWithWalkTimeFromLocation(location, 0, maxWalkDuration);
}

public Map<String, Integer> getStopsWithWalkTimeFromLocation(GeoCoordinate location, int startTimeInSeconds) {
public Map<String, Integer> getStopsWithWalkTimeFromLocation(GeoCoordinate location, int startTimeInSeconds,
int maxWalkDuration) {
// TODO: Make configurable
int maxSearchRadius = 500;
List<ch.naviqore.gtfs.schedule.model.Stop> nearestStops = new ArrayList<>(
Expand All @@ -241,8 +256,10 @@ public Map<String, Integer> getStopsWithWalkTimeFromLocation(GeoCoordinate locat

Map<String, Integer> stopsWithWalkTime = new HashMap<>();
for (ch.naviqore.gtfs.schedule.model.Stop stop : nearestStops) {
stopsWithWalkTime.put(stop.getId(),
startTimeInSeconds + walkCalculator.calculateWalk(location, stop.getCoordinate()).duration());
int walkDuration = walkCalculator.calculateWalk(location, stop.getCoordinate()).duration();
if (walkDuration <= maxWalkDuration) {
stopsWithWalkTime.put(stop.getId(), startTimeInSeconds + walkDuration);
}
}
return stopsWithWalkTime;
}
Expand All @@ -264,11 +281,12 @@ public Map<String, Integer> getAllChildStopsFromStop(Stop stop, int startTimeInS
public Map<Stop, Connection> getIsolines(GeoCoordinate source, LocalDateTime departureTime,
ConnectionQueryConfig config) {
Map<String, Integer> sourceStops = getStopsWithWalkTimeFromLocation(source,
departureTime.toLocalTime().toSecondOfDay());
departureTime.toLocalTime().toSecondOfDay(), config.getMaximumWalkingDuration());

Raptor raptor = cache.getRaptor(departureTime.toLocalDate());

return mapToStopConnectionMap(raptor.getIsoLines(sourceStops), sourceStops, source, departureTime);
return mapToStopConnectionMap(raptor.getIsoLines(sourceStops, map(config)), sourceStops, source, departureTime,
config);
}

@Override
Expand All @@ -278,12 +296,14 @@ public Map<Stop, Connection> getIsolines(Stop source, LocalDateTime departureTim

Raptor raptor = cache.getRaptor(departureTime.toLocalDate());

return mapToStopConnectionMap(raptor.getIsoLines(sourceStops), sourceStops, null, departureTime);
return mapToStopConnectionMap(raptor.getIsoLines(sourceStops, map(config)), sourceStops, null, departureTime,
config);
}

private Map<Stop, Connection> mapToStopConnectionMap(Map<String, ch.naviqore.raptor.Connection> isoLines,
Map<String, Integer> sourceStops,
@Nullable GeoCoordinate source, LocalDateTime departureTime) {
@Nullable GeoCoordinate source, LocalDateTime departureTime,
ConnectionQueryConfig config) {
Map<Stop, Connection> result = new HashMap<>();

for (Map.Entry<String, ch.naviqore.raptor.Connection> entry : isoLines.entrySet()) {
Expand All @@ -294,8 +314,15 @@ private Map<Stop, Connection> mapToStopConnectionMap(Map<String, ch.naviqore.rap
firstMile = getFirstWalk(source, connection.getFromStopId(), departureTime, sourceStops);
}

ch.naviqore.gtfs.schedule.model.Stop stop = schedule.getStops().get(entry.getKey());
result.put(map(stop), map(connection, firstMile, null, departureTime.toLocalDate(), schedule));
Stop stop = map(schedule.getStops().get(entry.getKey()));
Connection serviceConnection = map(connection, firstMile, null, departureTime.toLocalDate(), schedule);

// The raptor algorithm does not consider the firstMile walk time, so we need to filter out connections
// that exceed the maximum travel time here
if (Duration.between(serviceConnection.getArrivalTime(), serviceConnection.getDepartureTime())
.getSeconds() <= config.getMaximumTravelTime()) {
result.put(stop, serviceConnection);
}
}

return result;
Expand Down Expand Up @@ -466,7 +493,8 @@ private Raptor getRaptor(LocalDate date) {
Set<ch.naviqore.gtfs.schedule.model.Calendar> activeServices = this.activeServices.computeIfAbsent(date,
() -> getActiveServices(date));
return raptorCache.computeIfAbsent(activeServices,
() -> new GtfsToRaptorConverter(schedule, additionalTransfers).convert(date));
() -> new GtfsToRaptorConverter(schedule, additionalTransfers,
config.getSameStationTransferTime()).convert(date));
}

// get all active calendars form the gtfs for given date, serves as key for caching raptor instances
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/ch/naviqore/service/impl/TypeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import ch.naviqore.gtfs.schedule.model.GtfsSchedule;
import ch.naviqore.gtfs.schedule.type.ServiceDayTime;
import ch.naviqore.raptor.QueryConfig;
import ch.naviqore.service.*;
import ch.naviqore.service.config.ConnectionQueryConfig;
import ch.naviqore.utils.search.SearchIndex;
import ch.naviqore.utils.spatial.GeoCoordinate;
import lombok.AccessLevel;
Expand Down Expand Up @@ -101,6 +103,11 @@ public static Leg map(ch.naviqore.raptor.Connection.Leg leg, LocalDate date, Gtf
};
}

public static QueryConfig map(ConnectionQueryConfig config) {
return new QueryConfig(config.getMaximumWalkingDuration(), config.getMinimumTransferDuration(),
config.getMaximumTransferNumber(), config.getMaximumTravelTime());
}

private static Leg createPublicTransitLeg(ch.naviqore.raptor.Connection.Leg leg, LocalDate date,
GtfsSchedule schedule, int distance) {
int duration = leg.arrivalTime() - leg.departureTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,20 @@ public class GtfsToRaptorConverter {

private final Set<GtfsRoutePartitioner.SubRoute> addedSubRoutes = new HashSet<>();
private final Set<String> addedStops = new HashSet<>();
private final RaptorBuilder builder = Raptor.builder();
private final RaptorBuilder builder;
private final GtfsRoutePartitioner partitioner;
private final List<TransferGenerator.Transfer> additionalTransfers;
private final GtfsSchedule schedule;

public GtfsToRaptorConverter(GtfsSchedule schedule) {
this(schedule, List.of());
public GtfsToRaptorConverter(GtfsSchedule schedule, int sameStationTransferTime) {
this(schedule, List.of(), sameStationTransferTime);
}

public GtfsToRaptorConverter(GtfsSchedule schedule, List<TransferGenerator.Transfer> additionalTransfers) {
public GtfsToRaptorConverter(GtfsSchedule schedule, List<TransferGenerator.Transfer> additionalTransfers, int sameStationTransferTime) {
this.partitioner = new GtfsRoutePartitioner(schedule);
this.additionalTransfers = additionalTransfers;
this.schedule = schedule;
this.builder = Raptor.builder(sameStationTransferTime);
}

public Raptor convert(LocalDate date) {
Expand Down Expand Up @@ -84,7 +85,7 @@ private void addTransfers() {
for (String stopId : addedStops) {
Stop stop = schedule.getStops().get(stopId);
for (Transfer transfer : stop.getTransfers()) {
if (transfer.getTransferType() == TransferType.MINIMUM_TIME && stop != transfer.getToStop() && transfer.getMinTransferTime()
if (transfer.getTransferType() == TransferType.MINIMUM_TIME && transfer.getMinTransferTime()
.isPresent()) {
try {
builder.addTransfer(stop.getId(), transfer.getToStop().getId(),
Expand All @@ -102,14 +103,6 @@ private void addTransfers() {

for (TransferGenerator.Transfer transfer : additionalTransfers) {

if (transfer.from() == transfer.to()) {
// TODO: Make Raptor handle same station transfers correctly. This is a workaround to avoid adding
// transfers between the same station, as not implemented yet.
log.warn("Omit adding transfer from {} 2to {} with duration {} as it is the same stop",
transfer.from().getId(), transfer.to().getId(), transfer.duration());
continue;
}

if (schedule.getStops()
.get(transfer.from().getId())
.getTransfers()
Expand Down
Loading

0 comments on commit e4f9e83

Please sign in to comment.