use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class TimetableSnapshotSourceTest method testHandleAddedTrip.
@Test
public void testHandleAddedTrip() throws ParseException {
// GIVEN
// Get service date of today because old dates will be purged after applying updates
final ServiceDate serviceDate = new ServiceDate(Calendar.getInstance());
final String addedTripId = "added_trip";
TripUpdate tripUpdate;
{
final TripDescriptor.Builder tripDescriptorBuilder = TripDescriptor.newBuilder();
tripDescriptorBuilder.setTripId(addedTripId);
tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.ADDED);
tripDescriptorBuilder.setStartDate(serviceDate.getAsString());
final Calendar calendar = serviceDate.getAsCalendar(graph.getTimeZone());
final long midnightSecondsSinceEpoch = calendar.getTimeInMillis() / 1000;
final TripUpdate.Builder tripUpdateBuilder = TripUpdate.newBuilder();
tripUpdateBuilder.setTrip(tripDescriptorBuilder);
{
// Stop A
final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder();
stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
stopTimeUpdateBuilder.setStopId("A");
{
// Arrival
final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
arrivalBuilder.setTime(midnightSecondsSinceEpoch + (8 * 3600) + (30 * 60));
arrivalBuilder.setDelay(0);
}
{
// Departure
final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
departureBuilder.setTime(midnightSecondsSinceEpoch + (8 * 3600) + (30 * 60));
departureBuilder.setDelay(0);
}
}
{
// Stop C
final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder();
stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
stopTimeUpdateBuilder.setStopId("C");
{
// Arrival
final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
arrivalBuilder.setTime(midnightSecondsSinceEpoch + (8 * 3600) + (40 * 60));
arrivalBuilder.setDelay(0);
}
{
// Departure
final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
departureBuilder.setTime(midnightSecondsSinceEpoch + (8 * 3600) + (45 * 60));
departureBuilder.setDelay(0);
}
}
{
// Stop E
final StopTimeUpdate.Builder stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder();
stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
stopTimeUpdateBuilder.setStopId("E");
{
// Arrival
final StopTimeEvent.Builder arrivalBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
arrivalBuilder.setTime(midnightSecondsSinceEpoch + (8 * 3600) + (55 * 60));
arrivalBuilder.setDelay(0);
}
{
// Departure
final StopTimeEvent.Builder departureBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
departureBuilder.setTime(midnightSecondsSinceEpoch + (8 * 3600) + (55 * 60));
departureBuilder.setDelay(0);
}
}
tripUpdate = tripUpdateBuilder.build();
}
// WHEN
updater.applyTripUpdates(graph, fullDataset, Arrays.asList(tripUpdate), feedId);
// THEN
// Find new pattern in graph starting from stop A
Stop stopA = graph.index.stopForId.get(new AgencyAndId(feedId, "A"));
TransitStopDepart transitStopDepartA = graph.index.stopVertexForStop.get(stopA).departVertex;
// Get trip pattern of last (most recently added) outgoing edge
final List<Edge> outgoingEdges = (List<Edge>) transitStopDepartA.getOutgoing();
final TripPattern tripPattern = ((TransitBoardAlight) outgoingEdges.get(outgoingEdges.size() - 1)).getPattern();
assertNotNull("Added trip pattern should be found", tripPattern);
final TimetableSnapshot snapshot = updater.getTimetableSnapshot();
final Timetable forToday = snapshot.resolve(tripPattern, serviceDate);
final Timetable schedule = snapshot.resolve(tripPattern, null);
assertNotSame(forToday, schedule);
final int forTodayAddedTripIndex = forToday.getTripIndex(addedTripId);
assertTrue("Added trip should be found in time table for service date", forTodayAddedTripIndex > -1);
assertEquals(RealTimeState.ADDED, forToday.getTripTimes(forTodayAddedTripIndex).getRealTimeState());
final int scheduleTripIndex = schedule.getTripIndex(addedTripId);
assertEquals("Added trip should not be found in scheduled time table", -1, scheduleTripIndex);
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class ConvertToFrequency method apply.
public void apply(List<FrequencyEntry> frequencyEntries, List<TripTimes> scheduledTrips, Graph graph, BitSet servicesRunning, RaptorWorkerTimetable.BoardingAssumption assumption) {
// preserve existing frequency entries
this.frequencyEntries.addAll(frequencyEntries);
Set<String> routeIds = new HashSet<>();
if (routeId != null)
Stream.of(routeId).forEach(routeIds::add);
// loop over scheduled trips and figure out what to do with them
for (TripTimes tt : scheduledTrips) {
if (routeId == null || routeIds.contains(tt.trip.getRoute().getId().getId())) {
// put this in the appropriate group for frequency conversion
String key;
switch(groupBy) {
case ROUTE_DIRECTION:
key = tt.trip.getRoute().getId().getId() + "_" + tt.trip.getDirectionId();
break;
case ROUTE:
key = tt.trip.getRoute().getId().getId();
break;
case PATTERN:
key = graph.index.patternForTrip.get(tt.trip).getExemplar().getId().getId();
break;
default:
throw new RuntimeException("Unrecognized group by value");
}
tripsToConvert.put(key, tt);
} else {
// don't touch this trip
this.scheduledTrips.add(tt);
}
}
// loop over all the groups and create frequency entries
GROUPS: for (Map.Entry<String, Collection<TripTimes>> e : tripsToConvert.asMap().entrySet()) {
// get just the running services
List<TripTimes> group = e.getValue().stream().filter(tt -> servicesRunning.get(tt.serviceCode)).filter(tt -> windowStart < tt.getDepartureTime(0) && tt.getDepartureTime(0) < windowEnd).collect(Collectors.toList());
if (group.isEmpty())
continue GROUPS;
if (group.size() == 1) {
group.stream().forEach(scheduledTrips::add);
continue GROUPS;
}
// find the dominant pattern
TObjectIntMap<TripPattern> patternCount = new TObjectIntHashMap<>(5, 0.75f, 0);
group.forEach(tt -> patternCount.adjustOrPutValue(graph.index.patternForTrip.get(tt.trip), 1, 1));
int maxCount = 0;
TripPattern tripPattern = null;
for (TObjectIntIterator<TripPattern> it = patternCount.iterator(); it.hasNext(); ) {
it.advance();
if (it.value() > maxCount) {
maxCount = it.value();
tripPattern = it.key();
}
}
// find a stop that is common to all trip patterns. Sort the list so that the same common stop is always returned
NavigableSet<Stop> stops = new TreeSet<>((s1, s2) -> s1.getId().compareTo(s2.getId()));
stops.addAll(tripPattern.getStops());
patternCount.keySet().stream().forEach(p -> stops.retainAll(p.getStops()));
if (stops.isEmpty()) {
LOG.warn("Unable to find common stop for key {}, not converting to frequencies", e.getKey());
scheduledTrips.addAll(e.getValue());
continue GROUPS;
}
Stop stop = stops.stream().findFirst().get();
// determine the median frequency at this stop
// use a set to handle duplicated trips
TIntSet arrivalTimes = new TIntHashSet();
for (boolean filter : new boolean[] { true, false }) {
for (TripTimes tt : group) {
TripPattern tp = graph.index.patternForTrip.get(tt.trip);
int arrivalTime = tt.getArrivalTime(tp.getStops().indexOf(stop));
// however, if we apply the filter and end up with no trips at this stop, re-run with the filter disabled
if (windowStart < arrivalTime && arrivalTime < windowEnd || !filter)
arrivalTimes.add(arrivalTime);
}
// if we didn't find stops, continue, which will turn off the filter
if (arrivalTimes.size() > 1)
break;
}
// now convert to elapsed times
int[] arrivalTimeArray = arrivalTimes.toArray();
Arrays.sort(arrivalTimeArray);
int[] headway = new int[arrivalTimeArray.length - 1];
for (int i = 1; i < arrivalTimeArray.length; i++) {
headway[i - 1] = arrivalTimeArray[i] - arrivalTimeArray[i - 1];
}
Arrays.sort(headway);
// the headway that we will use
int aggregateHeadway;
if (assumption == RaptorWorkerTimetable.BoardingAssumption.WORST_CASE)
// simple: worst case analysis should use the worst case headway
aggregateHeadway = Ints.max(headway);
else {
// we want the average headway, but we we want the average of the headways weighted
// by themselves as if there is a two minute headway then a twenty-minute headway,
// customers are ten times as likely to experience the twenty minute headway
// (we want the average from the user's perspective, not the vehicle's perspective)
// This is a weighted average where the weight is the same as the headway so it simplifies
// to sum (headway^2) / sum(headway)
aggregateHeadway = IntStream.of(headway).map(h -> h * h).sum() / IntStream.of(headway).sum();
}
LOG.info("Headway for route {} ({}) in direction {}: {}min", tripPattern.route.getShortName(), tripPattern.route.getId().getId(), tripPattern.directionId, aggregateHeadway / 60);
// figure out running/dwell times based on the trips on this pattern
final TripPattern chosenTp = tripPattern;
List<TripTimes> candidates = group.stream().filter(tt -> graph.index.patternForTrip.get(tt.trip) == chosenTp).collect(Collectors.toList());
// transposed from what you'd expect: stops on the rows
int[][] hopTimes = new int[tripPattern.getStops().size() - 1][candidates.size()];
int[][] dwellTimes = new int[tripPattern.getStops().size()][candidates.size()];
int tripIndex = 0;
for (TripTimes tt : candidates) {
for (int stopIndex = 0; stopIndex < tripPattern.getStops().size(); stopIndex++) {
dwellTimes[stopIndex][tripIndex] = tt.getDwellTime(stopIndex);
if (stopIndex > 0)
hopTimes[stopIndex - 1][tripIndex] = tt.getArrivalTime(stopIndex) - tt.getDepartureTime(stopIndex - 1);
}
tripIndex++;
}
// collapse it down
int[] meanHopTimes = new int[tripPattern.getStops().size() - 1];
int hopIndex = 0;
for (int[] hop : hopTimes) {
meanHopTimes[hopIndex++] = IntStream.of(hop).sum() / hop.length;
}
int[] meanDwellTimes = new int[tripPattern.getStops().size()];
int dwellIndex = 0;
for (int[] dwell : dwellTimes) {
meanDwellTimes[dwellIndex++] = IntStream.of(dwell).sum() / dwell.length;
}
// phew! now let's make a frequency entry
TripTimes tt = new TripTimes(candidates.get(0));
int cumulative = 0;
for (int i = 0; i < tt.getNumStops(); i++) {
tt.updateArrivalTime(i, cumulative);
cumulative += meanDwellTimes[i];
tt.updateDepartureTime(i, cumulative);
if (i + 1 < tt.getNumStops())
cumulative += meanHopTimes[i];
}
FrequencyEntry fe = new FrequencyEntry(windowStart - 60 * 60 * 3, windowEnd + 60 * 60 * 3, aggregateHeadway, false, tt);
this.frequencyEntries.add(fe);
}
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class Graph method index.
/**
* Perform indexing on vertices, edges, and timetables, and create transient data structures.
* This used to be done in readObject methods upon deserialization, but stand-alone mode now
* allows passing graphs from graphbuilder to server in memory, without a round trip through
* serialization.
* TODO: do we really need a factory for different street vertex indexes?
*/
public void index(StreetVertexIndexFactory indexFactory) {
streetIndex = indexFactory.newIndex(this);
LOG.debug("street index built.");
LOG.debug("Rebuilding edge and vertex indices.");
rebuildVertexAndEdgeIndices();
Set<TripPattern> tableTripPatterns = Sets.newHashSet();
for (PatternArriveVertex pav : Iterables.filter(this.getVertices(), PatternArriveVertex.class)) {
tableTripPatterns.add(pav.getTripPattern());
}
for (TripPattern ttp : tableTripPatterns) {
// skip frequency-based patterns with no table (null)
if (ttp != null)
ttp.scheduledTimetable.finish();
}
// TODO: Move this ^ stuff into the graph index
this.index = new GraphIndex(this);
}
Aggregations