-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from naviqore/feature/gtfs-schedule-parser
Feature/gtfs schedule parser
- Loading branch information
Showing
19 changed files
with
789 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
src/main/java/ch/naviqore/gtfs/schedule/GtfsScheduleParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package ch.naviqore.gtfs.schedule; | ||
|
||
import ch.naviqore.gtfs.schedule.model.GtfsScheduleBuilder; | ||
import ch.naviqore.gtfs.schedule.type.ExceptionType; | ||
import ch.naviqore.gtfs.schedule.type.RouteType; | ||
import ch.naviqore.gtfs.schedule.type.ServiceDayTime; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.log4j.Log4j2; | ||
import org.apache.commons.csv.CSVRecord; | ||
|
||
import java.time.DayOfWeek; | ||
import java.time.LocalDate; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.EnumSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/** | ||
* GTFS CSV Records Parser | ||
* | ||
* @author munterfi | ||
*/ | ||
@RequiredArgsConstructor | ||
@Log4j2 | ||
class GtfsScheduleParser { | ||
|
||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); | ||
private static final Map<String, DayOfWeek> DAY_MAPPINGS = Map.of("monday", DayOfWeek.MONDAY, "tuesday", | ||
DayOfWeek.TUESDAY, "wednesday", DayOfWeek.WEDNESDAY, "thursday", DayOfWeek.THURSDAY, "friday", | ||
DayOfWeek.FRIDAY, "saturday", DayOfWeek.SATURDAY, "sunday", DayOfWeek.SUNDAY); | ||
private final GtfsScheduleBuilder builder; | ||
|
||
void parseAgencies(List<CSVRecord> records) { | ||
log.info("Parsing {} agency records", records.size()); | ||
for (CSVRecord record : records) { | ||
builder.addAgency(record.get("agency_id"), record.get("agency_name"), record.get("agency_url"), | ||
record.get("agency_timezone")); | ||
} | ||
} | ||
|
||
void parseCalendars(List<CSVRecord> records) { | ||
log.info("Parsing {} calendar records", records.size()); | ||
for (CSVRecord record : records) { | ||
EnumSet<DayOfWeek> serviceDays = EnumSet.noneOf(DayOfWeek.class); | ||
DAY_MAPPINGS.forEach((key, value) -> { | ||
if ("1".equals(record.get(key))) { | ||
serviceDays.add(value); | ||
} | ||
}); | ||
builder.addCalendar(record.get("service_id"), serviceDays, | ||
LocalDate.parse(record.get("start_date"), DATE_FORMATTER), | ||
LocalDate.parse(record.get("end_date"), DATE_FORMATTER)); | ||
} | ||
} | ||
|
||
void parseCalendarDates(List<CSVRecord> records) { | ||
log.info("Parsing {} calendar date records", records.size()); | ||
for (CSVRecord record : records) { | ||
builder.addCalendarDate(record.get("service_id"), LocalDate.parse(record.get("date"), DATE_FORMATTER), | ||
ExceptionType.parse(record.get("exception_type"))); | ||
} | ||
} | ||
|
||
void parseStops(List<CSVRecord> records) { | ||
log.info("Parsing {} stop records", records.size()); | ||
for (CSVRecord record : records) { | ||
builder.addStop(record.get("stop_id"), record.get("stop_name"), Double.parseDouble(record.get("stop_lat")), | ||
Double.parseDouble(record.get("stop_lon"))); | ||
} | ||
} | ||
|
||
void parseRoutes(List<CSVRecord> records) { | ||
log.info("Parsing {} route records", records.size()); | ||
for (CSVRecord record : records) { | ||
// TODO: Route types are not standardized in any way. | ||
// RouteType.parse(record.get("route_type")) | ||
builder.addRoute(record.get("route_id"), record.get("agency_id"), record.get("route_short_name"), | ||
record.get("route_long_name"), RouteType.RAIL); | ||
} | ||
} | ||
|
||
void parseTrips(List<CSVRecord> records) { | ||
log.info("Parsing {} trip records", records.size()); | ||
for (CSVRecord record : records) { | ||
builder.addTrip(record.get("trip_id"), record.get("route_id"), record.get("service_id")); | ||
} | ||
} | ||
|
||
void parseStopTimes(List<CSVRecord> records) { | ||
log.info("Parsing {} stop time records", records.size()); | ||
for (CSVRecord record : records) { | ||
builder.addStopTime(record.get("trip_id"), record.get("stop_id"), | ||
ServiceDayTime.parse(record.get("arrival_time")), | ||
ServiceDayTime.parse(record.get("departure_time"))); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package ch.naviqore.gtfs.schedule; | ||
|
||
import ch.naviqore.gtfs.schedule.model.GtfsSchedule; | ||
import ch.naviqore.gtfs.schedule.model.GtfsScheduleDay; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDate; | ||
|
||
public class RunExample { | ||
private static final String GTFS_FILE = "/Users/munterfi/Downloads/gtfs_fp2024_2024-04-11_09-11.zip"; | ||
|
||
public static void main(String[] args) throws IOException, InterruptedException { | ||
GtfsSchedule schedule = new GtfsScheduleReader().read(GTFS_FILE); | ||
GtfsScheduleDay scheduleDay = schedule.getScheduleForDay(LocalDate.now()); | ||
System.gc(); | ||
Thread.sleep(30000); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package ch.naviqore.gtfs.schedule.model; | ||
|
||
public record Agency(String agency, String name, String url, String timezone) { | ||
} |
64 changes: 64 additions & 0 deletions
64
src/main/java/ch/naviqore/gtfs/schedule/model/Calendar.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package ch.naviqore.gtfs.schedule.model; | ||
|
||
import ch.naviqore.gtfs.schedule.type.ExceptionType; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
import java.time.DayOfWeek; | ||
import java.time.LocalDate; | ||
import java.util.EnumSet; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) | ||
@Getter | ||
public final class Calendar { | ||
private final String id; | ||
private final EnumSet<DayOfWeek> serviceDays; | ||
private final LocalDate startDate; | ||
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); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (obj == this) return true; | ||
if (obj == null || obj.getClass() != this.getClass()) return false; | ||
var that = (Calendar) obj; | ||
return Objects.equals(this.id, that.id); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(id); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Calendar[" + "id=" + id + ", " + "serviceDays=" + serviceDays + ", " + "startDate=" + startDate + ", " + "endDate=" + endDate + ']'; | ||
} | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/ch/naviqore/gtfs/schedule/model/CalendarDate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package ch.naviqore.gtfs.schedule.model; | ||
|
||
import ch.naviqore.gtfs.schedule.type.ExceptionType; | ||
|
||
import java.time.LocalDate; | ||
|
||
public record CalendarDate(Calendar calendar, LocalDate date, ExceptionType type) implements Comparable<CalendarDate> { | ||
@Override | ||
public int compareTo(CalendarDate o) { | ||
return this.date.compareTo(o.date); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/main/java/ch/naviqore/gtfs/schedule/model/GtfsSchedule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package ch.naviqore.gtfs.schedule.model; | ||
|
||
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; | ||
|
||
/** | ||
* 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); | ||
} | ||
} |
Oops, something went wrong.