Skip to content

Commit

Permalink
Add GTFS schedule service day
Browse files Browse the repository at this point in the history
- Extract a service day from the schedule.
- Introduce Initializable interface to mark objects that need construction.
  • Loading branch information
munterfi committed Apr 13, 2024
1 parent 70d8c1b commit e2fd4da
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 12 deletions.
19 changes: 19 additions & 0 deletions src/main/java/ch/naviqore/gtfs/schedule/model/Calendar.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.naviqore.gtfs.schedule.model;

import ch.naviqore.gtfs.schedule.type.ExceptionType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,6 +21,24 @@ public final class Calendar {
private final LocalDate endDate;
private final Map<LocalDate, CalendarDate> calendarDates = new HashMap<>();

/**
* Determines if the service is operational on a specific day, considering both regular service days and
* exceptions.
*
* @param date the date to check for service availability
* @return true if the service is operational on the given date, false otherwise
*/
public boolean isServiceAvailable(LocalDate date) {
if (date.isBefore(startDate) || date.isAfter(endDate)) {
return false;
}
CalendarDate exception = calendarDates.get(date);
if (exception != null) {
return exception.type() == ExceptionType.ADDED;
}
return serviceDays.contains(date.getDayOfWeek());
}

void addCalendarDate(CalendarDate calendarDate) {
calendarDates.put(calendarDate.date(), calendarDate);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@

import java.time.LocalDate;

public record CalendarDate(Calendar calendar, LocalDate date, ExceptionType type) {
public record CalendarDate(Calendar calendar, LocalDate date, ExceptionType type) implements Comparable<CalendarDate> {
@Override
public int compareTo(CalendarDate o) {
return this.date.compareTo(o.date);
}
}
43 changes: 41 additions & 2 deletions src/main/java/ch/naviqore/gtfs/schedule/model/GtfsSchedule.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,53 @@
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.time.LocalDate;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class GtfsSchedule {

private final Map<String, Agency> agencies;
private final Map<String, Calendar> calendars;
private final Map<String, Stop> stops;
private final Map<String, Route> routes;
private final Map<String, Trip> trips;

public Agency getAgency(String id) {
return agencies.get(id);
/**
* Retrieves a snapshot of the GTFS schedule active on a specific date.
*
* @param date the date for which the active schedule is requested.
* @return GtfsScheduleDay containing only the active routes, stops, and trips for the specified date.
*/
public GtfsScheduleDay getScheduleForDay(LocalDate date) {
Map<String, Trip> activeTrips = trips.entrySet().stream()
.filter(entry -> entry.getValue().getCalendar().isServiceAvailable(date))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// TODO: Implement efficiently without copying.
// return new GtfsScheduleDay(date, activeStops, activeRoutes, activeTrips);
return null;
}

public Map<String, Agency> getAgencies() {
return Collections.unmodifiableMap(agencies);
}

public Map<String, Calendar> getCalendars() {
return Collections.unmodifiableMap(calendars);
}

public Map<String, Stop> getStops() {
return Collections.unmodifiableMap(stops);
}

public Map<String, Route> getRoutes() {
return Collections.unmodifiableMap(routes);
}

public Map<String, Trip> getTrips() {
return Collections.unmodifiableMap(trips);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,27 @@
import java.util.HashMap;
import java.util.Map;

/**
* General Transit Feed Specification (GTFS) schedule builder
* <p>
* Provides a builder pattern implementation for constructing a {@link GtfsSchedule} instance. This class encapsulates
* the complexity of assembling a GTFS schedule by incrementally adding components such as agencies, stops, routes,
* trips, and calendars. The builder ensures that all components are added in a controlled manner and that the resulting
* schedule is consistent and ready for use.
*
* <p>Instances of this class should be obtained through the static {@code builder()} method. This class uses
* a private constructor to enforce the use of the builder pattern.</p>
*
* @author munterfi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Log4j2
public class GtfsScheduleBuilder {

private final Map<String, Agency> agencies = new HashMap<>();
private final Map<String, Calendar> calendars = new HashMap<>();
private final Map<String, Stop> stops = new HashMap<>();
private final Map<String, Route> routes = new HashMap<>();
private final Map<String, Calendar> calendars = new HashMap<>();
private final Map<String, Trip> trips = new HashMap<>();

public static GtfsScheduleBuilder builder() {
Expand Down Expand Up @@ -108,12 +121,16 @@ public GtfsScheduleBuilder addStopTime(String tripId, String stopId, ServiceDayT
}
log.debug("Adding stop {} to trip {} ({}-{})", stopId, tripId, arrival, departure);
StopTime stopTime = new StopTime(stop, trip, arrival, departure);
stop.addStopTime(stopTime);
trip.addStopTime(stopTime);
// TODO: Add stop time to stop
return this;
}

public GtfsSchedule build() {
return new GtfsSchedule(agencies);
log.info("Building schedule with {} stops, {} routes and {} trips", stops.size(), routes.size(), trips.size());
trips.values().parallelStream().forEach(Trip::initialize);
stops.values().parallelStream().forEach(Stop::initialize);
routes.values().parallelStream().forEach(Route::initialize);
return new GtfsSchedule(agencies, calendars, stops, routes, trips);
}
}
36 changes: 36 additions & 0 deletions src/main/java/ch/naviqore/gtfs/schedule/model/GtfsScheduleDay.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ch.naviqore.gtfs.schedule.model;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.time.LocalDate;
import java.util.Collections;
import java.util.Map;

/**
* GTFS Schedule Service Day
* <p>
* Represents a daily snapshot of the GTFS schedule, containing only the active services on a specific date.
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class GtfsScheduleDay {

@Getter
private final LocalDate date;
private final Map<String, Stop> stops;
private final Map<String, Route> routes;
private final Map<String, Trip> trips;

public Map<String, Stop> getStops() {
return Collections.unmodifiableMap(stops);
}

public Map<String, Route> getRoutes() {
return Collections.unmodifiableMap(routes);
}

public Map<String, Trip> getTrips() {
return Collections.unmodifiableMap(trips);
}
}
19 changes: 19 additions & 0 deletions src/main/java/ch/naviqore/gtfs/schedule/model/Initializable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ch.naviqore.gtfs.schedule.model;

/**
* Initializable class
* <p>
* This internal interface should be implemented by classes that require initialization steps to be executed before they
* are considered fully ready and operational. This interface is designed to enforce a consistent initialization pattern
* across different components of the GTFS (General Transit Feed Specification) schedule model.
*
* @author munterfi
*/
interface Initializable {

/**
* Initializes the implementing object.
*/
void initialize();

}
9 changes: 8 additions & 1 deletion src/main/java/ch/naviqore/gtfs/schedule/model/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import lombok.RequiredArgsConstructor;

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

@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
public final class Route {
public final class Route implements Initializable {
private final String id;
private final Agency agency;
private final String shortName;
Expand All @@ -23,6 +24,12 @@ void addTrip(Trip trip) {
trips.add(trip);
}

@Override
public void initialize() {
Collections.sort(trips);

}

@Override
public boolean equals(Object obj) {
if (obj == this) return true;
Expand Down
45 changes: 44 additions & 1 deletion src/main/java/ch/naviqore/gtfs/schedule/model/Stop.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,47 @@
package ch.naviqore.gtfs.schedule.model;

public record Stop(String id, String name, double lat, double lon) {
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

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

@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
public final class Stop implements Initializable {
private final String id;
private final String name;
private final double lat;
private final double lon;
private final List<StopTime> stopTimes = new ArrayList<>();

void addStopTime(StopTime stopTime) {
stopTimes.add(stopTime);
}

@Override
public void initialize() {
Collections.sort(stopTimes);
}

@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Stop) obj;
return Objects.equals(this.id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}

@Override
public String toString() {
return "Stop[" + "id=" + id + ", " + "name=" + name + ", " + "lat=" + lat + ", " + "lon=" + lon + ']';
}
}
18 changes: 17 additions & 1 deletion src/main/java/ch/naviqore/gtfs/schedule/model/StopTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,21 @@

import ch.naviqore.gtfs.schedule.type.ServiceDayTime;

public record StopTime(Stop stop, Trip trip, ServiceDayTime arrival, ServiceDayTime departure) {
public record StopTime(Stop stop, Trip trip, ServiceDayTime arrival,
ServiceDayTime departure) implements Comparable<StopTime> {

public StopTime {
if (arrival.compareTo(departure) > 0) {
throw new IllegalArgumentException("Arrival time must be before departure time.");
}
}

/**
* StopTimes are compared based on departures.
*/
@Override
public int compareTo(StopTime o) {
return this.departure.compareTo(o.departure);
}
}

12 changes: 9 additions & 3 deletions src/main/java/ch/naviqore/gtfs/schedule/model/Trip.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import lombok.RequiredArgsConstructor;

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

@RequiredArgsConstructor
@Getter
public final class Trip implements Comparable<Trip> {
public final class Trip implements Comparable<Trip>, Initializable {
private final String id;
private final Route route;
private final Calendar calendar;
Expand All @@ -19,10 +20,14 @@ void addStopTime(StopTime stopTime) {
stopTimes.add(stopTime);
}

@Override
public void initialize() {
Collections.sort(stopTimes);
}

@Override
public int compareTo(Trip o) {
// TODO: Sort stopTimes, then return first stop.
return 0;
return this.stopTimes.getFirst().compareTo(o.stopTimes.getFirst());
}

@Override
Expand All @@ -42,4 +47,5 @@ public int hashCode() {
public String toString() {
return "Trip[" + "id=" + id + ", " + "route=" + route + ", " + "calendar=" + calendar + ']';
}

}

0 comments on commit e2fd4da

Please sign in to comment.