Interesting thread guys, made me realize that my GTFS implementation was not interpolating arrival or departure times. This hasn't been an issue with the 4 feeds I have so far consumed as they all fully specify arrival and departure times. I presume there are others out there though that don't so I updated the implementation to handle it.
The discussions here and the one linked to above on github about timepoint and arrival/departure time specification are quite abstract at times. Anyway from an implementation perspective I looked at what the intended aim is and basically I store the timepoint value if specified but otherwise don't use it. If a stop time entry for a trip does not have either an arrival or departure time I'll work back through the stops for the trip and use the last one specified. It is crude as the times will be the same but it is still better than no time at all.
If I were to use the timepoint going forward to indicate to a commuter that the scheduled time is not timed, then I would only flag this if neither an arrival or departure time was entered in the stop time entry or if at least one of them was entered and a timepoint value of 0 was explicitly specified.
I currently load data from stop_times into an equivalent database table and run a cleansing method which implements this. The cleansing code is below, I won't know if it is bug free until I come across a feed where interpolation is needed. If yous do however notice any major flaws with the underlying logic please do let me know.
Thanks Paul.
public void start(int feedId) throws SQLException {
int stopTimesId, count;
int countAddZeroArr, countAddZeroDep, countCopyDeparturetoArrival, countCopyArrivalToDeparture, countNoTimepointArr, countNoTimepointDep;
boolean updateNeeded;
int numRecords;
String tripId, arrivalTime, departureTime;
String lastSpecifiedArrivalTime, lastSpecifiedDepartureTime, lastTripId;
logger.log(Level.INFO, String.format("For feed id %d, converting arrival and departure times such as 6:45:00 to 06:45:00 and interpolating empty arrival/departure times", feedId));
logger.log(Level.INFO, " Setting autocommit to false");
db.setAutoCommit(false);
long startTableLoad = System.currentTimeMillis();
try (Statement statement = db.createStatement();
PreparedStatement update = db.prepareStatement(ST_UPDATE)) {
ResultSet rs = statement.executeQuery(String.format("select * from stop_times where feed_id=%d order by trip_id, feed_id, stop_sequence", feedId));
lastSpecifiedArrivalTime = lastSpecifiedDepartureTime = lastTripId = "";
numRecords = countAddZeroArr = countAddZeroDep = countCopyDeparturetoArrival = countCopyArrivalToDeparture = countNoTimepointArr = countNoTimepointDep = 0;
while (rs.next()) {
tripId = rs.getString("trip_id");
stopTimesId = rs.getInt("stop_times_id");
arrivalTime = rs.getString("arrival_time").trim();
departureTime = rs.getString("departure_time").trim();
updateNeeded = false;
if (!tripId.equals(lastTripId)) {
lastSpecifiedArrivalTime = lastSpecifiedDepartureTime = "";
}
if (arrivalTime.length() == 7) {
arrivalTime = "0" + arrivalTime;
countAddZeroArr++;
updateNeeded = true;
}
if (departureTime.length() == 7) {
departureTime = "0" + departureTime;
countAddZeroDep++;
updateNeeded = true;
}
if (arrivalTime.length() < 8 && departureTime.length() == 8) {
arrivalTime = departureTime;
countCopyDeparturetoArrival++;
updateNeeded = true;
}
if (departureTime.length() < 8 && arrivalTime.length() == 8) {
departureTime = arrivalTime;
countCopyArrivalToDeparture++;
updateNeeded = true;
}
if (arrivalTime.length() < 8 && lastSpecifiedArrivalTime.length() == 8) {
arrivalTime = lastSpecifiedArrivalTime;
countNoTimepointArr++;
updateNeeded = true;
}
if (departureTime.length() < 8 && lastSpecifiedDepartureTime.length() == 8) {
departureTime = lastSpecifiedDepartureTime;
countNoTimepointDep++;
updateNeeded = true;
}
if (updateNeeded) {
update.setString(1, arrivalTime);
update.setString(2, departureTime);
update.setInt(3, stopTimesId);
try {
count = update.executeUpdate();
}
catch (SQLException e) {
throw new SQLException(String.format("StopTimesId=%s ArrivalTime=%s DepartureTime=%s caused an exception [%s]",
stopTimesId, arrivalTime, departureTime, e.getMessage()));
}
if (count != 1) {
logger.log(Level.WARNING, String.format(" Error updating stop_times with [StopTimesId=%s ArrivalTime=%s DepartureTime=%s]",
stopTimesId, arrivalTime, departureTime));
}
else {
numRecords++;
if (numRecords%100 == 0) {
// Commit every 100 records
db.commit();
}
}
}
lastTripId = tripId;
lastSpecifiedArrivalTime = arrivalTime;
lastSpecifiedDepartureTime = departureTime;
}
long endTableLoad = System.currentTimeMillis();
float tableLoadTime = endTableLoad-startTableLoad;
tableLoadTime = tableLoadTime/1000;
logger.log(Level.INFO, String.format(" All records for feed checked, %d converted, process time %.2f seconds",
numRecords, tableLoadTime));
logger.log(Level.INFO, String.format(" %d arrivals with \"0\" prepended, %d departures with \"0\" prepended", countAddZeroArr, countAddZeroDep));
logger.log(Level.INFO, String.format(" %d non timepoint arrivals, %d non timepoint departures", countNoTimepointArr, countNoTimepointDep));
logger.log(Level.INFO, String.format(" %d departures copied to arrivals, %d arrivals copied to departures", countCopyDeparturetoArrival, countCopyArrivalToDeparture));
}
finally {
db.commit();
logger.log(Level.INFO, " Setting autocommit back to true");
db.setAutoCommit(true);
}
}