Skip to content

Commit

Permalink
Merge pull request #15 from naviqore/feature/NAV-17-simple-algorithm-…
Browse files Browse the repository at this point in the history
…implementation

Feature/nav 17 simple algorithm implementation
  • Loading branch information
clukas1 authored May 23, 2024
2 parents a54369c + a793177 commit c60524e
Show file tree
Hide file tree
Showing 26 changed files with 1,708 additions and 343 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ch.naviqore.gtfs.schedule.type.RouteType;
import ch.naviqore.gtfs.schedule.type.ServiceDayTime;
import ch.naviqore.gtfs.schedule.type.TransferType;
import ch.naviqore.utils.cache.ValueObjectCache;
import ch.naviqore.utils.spatial.GeoCoordinate;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand All @@ -15,7 +16,6 @@
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Implements a builder pattern for constructing instances of {@link GtfsSchedule}. This builder helps assemble a GTFS
Expand All @@ -30,7 +30,8 @@
@Log4j2
public class GtfsScheduleBuilder {

private final Cache cache = new Cache();
private final ValueObjectCache<LocalDate> localDateCache = new ValueObjectCache<>();
private final ValueObjectCache<ServiceDayTime> serviceDayTimeCache = new ValueObjectCache<>();
private final Map<String, Agency> agencies = new HashMap<>();
private final Map<String, Calendar> calendars = new HashMap<>();
private final Map<String, Stop> stops = new HashMap<>();
Expand Down Expand Up @@ -80,7 +81,8 @@ public GtfsScheduleBuilder addCalendar(String id, EnumSet<DayOfWeek> serviceDays
throw new IllegalArgumentException("Calendar " + id + " already exists");
}
log.debug("Adding calendar {}", id);
calendars.put(id, new Calendar(id, serviceDays, cache.getOrAdd(startDate), cache.getOrAdd(endDate)));
calendars.put(id,
new Calendar(id, serviceDays, localDateCache.getOrAdd(startDate), localDateCache.getOrAdd(endDate)));
return this;
}

Expand All @@ -91,7 +93,7 @@ public GtfsScheduleBuilder addCalendarDate(String calendarId, LocalDate date, Ex
throw new IllegalArgumentException("Calendar " + calendarId + " does not exist");
}
log.debug("Adding calendar {}-{}", calendarId, date);
CalendarDate calendarDate = new CalendarDate(calendar, cache.getOrAdd(date), type);
CalendarDate calendarDate = new CalendarDate(calendar, localDateCache.getOrAdd(date), type);
calendar.addCalendarDate(calendarDate);
return this;
}
Expand Down Expand Up @@ -129,7 +131,8 @@ public GtfsScheduleBuilder addStopTime(String tripId, String stopId, ServiceDayT
throw new IllegalArgumentException("Stop " + stopId + " does not exist");
}
log.debug("Adding stop time at {} to trip {} ({}-{})", stopId, tripId, arrival, departure);
StopTime stopTime = new StopTime(stop, trip, cache.getOrAdd(arrival), cache.getOrAdd(departure));
StopTime stopTime = new StopTime(stop, trip, serviceDayTimeCache.getOrAdd(arrival),
serviceDayTimeCache.getOrAdd(departure));
stop.addStopTime(stopTime);
trip.addStopTime(stopTime);
return this;
Expand Down Expand Up @@ -187,39 +190,19 @@ public void reset() {
}

private void clear() {
log.debug("Clearing maps and cache of the builder");
log.debug("Clearing cache and maps of the builder");
localDateCache.clear();
serviceDayTimeCache.clear();
agencies.clear();
calendars.clear();
stops.clear();
routes.clear();
trips.clear();
cache.clear();
}

private void checkNotBuilt() {
if (built) {
throw new IllegalStateException("Cannot modify builder after build() has been called.");
}
}

/**
* Cache for value objects
*/
static class Cache {
private final Map<LocalDate, LocalDate> localDates = new ConcurrentHashMap<>();
private final Map<ServiceDayTime, ServiceDayTime> serviceDayTimes = new ConcurrentHashMap<>();

public LocalDate getOrAdd(LocalDate value) {
return localDates.computeIfAbsent(value, k -> value);
}

public ServiceDayTime getOrAdd(ServiceDayTime value) {
return serviceDayTimes.computeIfAbsent(value, k -> value);
}

public void clear() {
localDates.clear();
serviceDayTimes.clear();
}
}
}
46 changes: 42 additions & 4 deletions src/main/java/ch/naviqore/gtfs/schedule/type/ServiceDayTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

/**
* Service day time
* <p>
Expand All @@ -14,14 +18,33 @@
@EqualsAndHashCode
@Getter
public final class ServiceDayTime implements Comparable<ServiceDayTime> {

public static final int HOURS_IN_DAY = 24;
public static final int MINUTES_IN_HOUR = 60;
public static final int SECONDS_IN_MINUTE = 60;
public static final int SECONDS_IN_HOUR = MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
public static final int SECONDS_IN_DAY = HOURS_IN_DAY * SECONDS_IN_HOUR;

private final int totalSeconds;

public ServiceDayTime(int seconds) {
if (seconds < 0) {
throw new IllegalArgumentException("Seconds cannot be negative.");
}
this.totalSeconds = seconds;
}

public ServiceDayTime(int hours, int minutes, int seconds) {
this.totalSeconds = seconds + 60 * minutes + 3600 * hours;
if (hours < 0) {
throw new IllegalArgumentException("Hours cannot be negative.");
}
if (minutes < 0 || minutes >= MINUTES_IN_HOUR) {
throw new IllegalArgumentException("Minutes must be between 0 and 59 inclusive");
}
if (seconds < 0 || seconds >= SECONDS_IN_MINUTE) {
throw new IllegalArgumentException("Seconds must be between 0 and 59 inclusive");
}
this.totalSeconds = seconds + SECONDS_IN_MINUTE * minutes + SECONDS_IN_HOUR * hours;
}

public static ServiceDayTime parse(String timeString) {
Expand All @@ -32,16 +55,31 @@ public static ServiceDayTime parse(String timeString) {
return new ServiceDayTime(hours, minutes, seconds);
}

public LocalTime toLocalTime() {
int hours = totalSeconds / SECONDS_IN_HOUR;
int minutes = (totalSeconds % SECONDS_IN_HOUR) / SECONDS_IN_MINUTE;
int seconds = totalSeconds % SECONDS_IN_MINUTE;
return LocalTime.of(hours % HOURS_IN_DAY, minutes, seconds);
}

public LocalDateTime toLocalDateTime(LocalDate date) {
LocalTime localTime = this.toLocalTime();
int hours = totalSeconds / SECONDS_IN_HOUR;
LocalDate adjustedDate = date.plusDays(hours / HOURS_IN_DAY);
return LocalDateTime.of(adjustedDate, localTime);
}

@Override
public int compareTo(ServiceDayTime o) {
return Integer.compare(totalSeconds, o.totalSeconds);
}

@Override
public String toString() {
int hours = totalSeconds / 3600;
int minutes = (totalSeconds % 3600) / 60;
int seconds = totalSeconds % 60;
int hours = totalSeconds / SECONDS_IN_HOUR;
int minutes = (totalSeconds % SECONDS_IN_HOUR) / SECONDS_IN_MINUTE;
int seconds = totalSeconds % SECONDS_IN_MINUTE;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}

}
48 changes: 25 additions & 23 deletions src/main/java/ch/naviqore/raptor/GtfsToRaptorConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
@Log4j2
public class GtfsToRaptorConverter {

private final Set<GtfsRoutePartitioner.SubRoute> subRoutes = new HashSet<>();
private final Set<Stop> stops = new HashSet<>();
private final Set<GtfsRoutePartitioner.SubRoute> addedSubRoutes = new HashSet<>();
private final Set<String> addedStops = new HashSet<>();
private final RaptorBuilder builder = Raptor.builder();
private final GtfsRoutePartitioner partitioner;
private final GtfsSchedule schedule;
Expand All @@ -42,15 +42,29 @@ public Raptor convert(LocalDate date) {
for (Trip trip : activeTrips) {
GtfsRoutePartitioner.SubRoute subRoute = partitioner.getSubRoute(trip);

if (!subRoutes.contains(subRoute)) {
subRoutes.add(subRoute);
builder.addRoute(subRoute.getId());
addRouteStops(trip, subRoute);
// add route if not already
if (!addedSubRoutes.contains(subRoute)) {
List<String> stopIds = subRoute.getStopsSequence().stream().map(Stop::getId).toList();

// add stops of that are not already added
for (String stopId : stopIds) {
if (!addedStops.contains(stopId)) {
builder.addStop(stopId);
addedStops.add(stopId);
}
}

builder.addRoute(subRoute.getId(), stopIds);
addedSubRoutes.add(subRoute);
}

for (StopTime stopTime : trip.getStopTimes()) {
builder.addStopTime(stopTime.stop().getId(), subRoute.getId(), stopTime.arrival().getTotalSeconds(),
stopTime.departure().getTotalSeconds());
// add current trip
builder.addTrip(trip.getId(), subRoute.getId());
List<StopTime> stopTimes = trip.getStopTimes();
for (int i = 0; i < stopTimes.size(); i++) {
StopTime stopTime = stopTimes.get(i);
builder.addStopTime(subRoute.getId(), trip.getId(), i, stopTime.stop().getId(),
stopTime.arrival().getTotalSeconds(), stopTime.departure().getTotalSeconds());
}
}

Expand All @@ -59,21 +73,9 @@ public Raptor convert(LocalDate date) {
return builder.build();
}

private void addRouteStops(Trip trip, GtfsRoutePartitioner.SubRoute subRoute) {
for (StopTime stopTime : trip.getStopTimes()) {
Stop stop = stopTime.stop();

if (!stops.contains(stop)) {
stops.add(stop);
builder.addStop(stop.getId());
}

builder.addRouteStop(stop.getId(), subRoute.getId());
}
}

private void addTransfers() {
for (Stop stop : stops) {
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()
.isPresent()) {
Expand Down
122 changes: 122 additions & 0 deletions src/main/java/ch/naviqore/raptor/model/Connection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package ch.naviqore.raptor.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* A connection is a sequence of legs to travel from an origin stop to destination stop.
*/
@NoArgsConstructor
@Getter
@ToString
public class Connection implements Comparable<Connection> {

private List<Leg> legs = new ArrayList<>();

private static void validateLegOrder(Leg current, Leg next) {
if (!current.toStopId.equals(next.fromStopId)) {
throw new IllegalStateException("Legs are not connected: " + current + " -> " + next);
}
if (current.arrivalTime < current.departureTime) {
throw new IllegalStateException("Arrival time must be after departure time: " + current);
}
if (current.arrivalTime > next.departureTime) {
throw new IllegalStateException(
"Arrival time must be before next departure time: " + current + " -> " + next);
}
}

void addLeg(Leg leg) {
this.legs.add(leg);
}

void initialize() {
// sort legs by departure time
Collections.sort(legs);
// make sure that the legs are connected and times are consistent
for (int i = 0; i < legs.size() - 1; i++) {
Leg current = legs.get(i);
Leg next = legs.get(i + 1);
validateLegOrder(current, next);
}
// make legs immutable and remove unnecessary allocated memory
this.legs = List.copyOf(legs);
}

@Override
public int compareTo(@NotNull Connection other) {
return Integer.compare(this.getArrivalTime(), other.getArrivalTime());
}

public int getDepartureTime() {
return legs.getFirst().departureTime;
}

public int getArrivalTime() {
return legs.getLast().arrivalTime;
}

public String getFromStopId() {
return legs.getFirst().fromStopId;
}

public String getToStopId() {
return legs.getLast().toStopId;
}

public int getDuration() {
return getArrivalTime() - getDepartureTime();
}

public int getNumFootPathTransfers() {
return (int) legs.stream().filter(l -> l.type == LegType.FOOTPATH).count();
}

public int getNumSameStationTransfers() {
int transferCounter = 0;
for (int i = 0; i < legs.size() - 1; i++) {
Leg current = legs.get(i);
Leg next = legs.get(i + 1);
if (current.type == LegType.ROUTE && next.type == LegType.ROUTE) {
transferCounter++;
}
}
return transferCounter;
}

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

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

/**
* Types of legs in a connection.
*/
public enum LegType {
FOOTPATH,
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> {

@Override
public int compareTo(@NotNull Connection.Leg other) {
return Integer.compare(this.departureTime, other.departureTime);
}

}

}
2 changes: 1 addition & 1 deletion src/main/java/ch/naviqore/raptor/model/Lookup.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

import java.util.Map;

public record Lookup(Map<String, Integer> stops, Map<String, Integer> routes) {
record Lookup(Map<String, Integer> stops, Map<String, Integer> routes) {
}
Loading

0 comments on commit c60524e

Please sign in to comment.