/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.transit_data_federation.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.PriorityQueue; import org.onebusaway.collections.Range; import org.onebusaway.collections.tuple.Pair; import org.onebusaway.collections.tuple.Tuples; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.calendar.ServiceDate; import org.onebusaway.gtfs.model.calendar.ServiceInterval; import org.onebusaway.transit_data_federation.impl.blocks.IndexAdapters; import org.onebusaway.transit_data_federation.impl.time.GenericBinarySearch; import org.onebusaway.transit_data_federation.impl.time.GenericBinarySearch.IndexAdapter; import org.onebusaway.transit_data_federation.services.ExtendedCalendarService; import org.onebusaway.transit_data_federation.services.StopTimeService; import org.onebusaway.transit_data_federation.services.blocks.AbstractBlockStopTimeIndex; import org.onebusaway.transit_data_federation.services.blocks.BlockIndexService; import org.onebusaway.transit_data_federation.services.blocks.BlockStopSequenceIndex; import org.onebusaway.transit_data_federation.services.blocks.BlockStopTimeIndex; import org.onebusaway.transit_data_federation.services.blocks.FrequencyBlockStopTimeIndex; import org.onebusaway.transit_data_federation.services.blocks.FrequencyStopTripIndex; import org.onebusaway.transit_data_federation.services.blocks.HasIndexedBlockStopTimes; import org.onebusaway.transit_data_federation.services.blocks.HasIndexedFrequencyBlockTrips; import org.onebusaway.transit_data_federation.services.blocks.InstanceState; import org.onebusaway.transit_data_federation.services.transit_graph.BlockStopTimeEntry; import org.onebusaway.transit_data_federation.services.transit_graph.FrequencyBlockStopTimeEntry; import org.onebusaway.transit_data_federation.services.transit_graph.FrequencyEntry; import org.onebusaway.transit_data_federation.services.transit_graph.ServiceIdActivation; import org.onebusaway.transit_data_federation.services.transit_graph.StopEntry; import org.onebusaway.transit_data_federation.services.transit_graph.TransitGraphDao; import org.onebusaway.transit_data_federation.services.tripplanner.StopTimeInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component class StopTimeServiceImpl implements StopTimeService { private static final FirstDepartureTimeComparator _firstDepartureComparator = new FirstDepartureTimeComparator(); private static final LastArrivalTimeComparator _lastArrivalComparator = new LastArrivalTimeComparator(); private TransitGraphDao _graph; private ExtendedCalendarService _calendarService; private BlockIndexService _blockIndexService; @Autowired public void setTransitGraphDao(TransitGraphDao graph) { _graph = graph; } @Autowired public void setCalendarService(ExtendedCalendarService calendarService) { _calendarService = calendarService; } @Autowired public void setBlockIndexService(BlockIndexService blockIndexService) { _blockIndexService = blockIndexService; } @Override public List<StopTimeInstance> getStopTimeInstancesInTimeRange( AgencyAndId stopId, Date from, Date to) { StopEntry stopEntry = _graph.getStopEntryForId(stopId, true); return getStopTimeInstancesInTimeRange(stopEntry, from, to, EFrequencyStopTimeBehavior.INCLUDE_UNSPECIFIED); } @Override public List<StopTimeInstance> getStopTimeInstancesInTimeRange( StopEntry stopEntry, Date from, Date to, EFrequencyStopTimeBehavior frequencyBehavior) { List<StopTimeInstance> stopTimeInstances = new ArrayList<StopTimeInstance>(); for (BlockStopTimeIndex index : _blockIndexService.getStopTimeIndicesForStop(stopEntry)) { Collection<Date> serviceDates = _calendarService.getServiceDatesWithinRange( index.getServiceIds(), index.getServiceInterval(), from, to); for (Date serviceDate : serviceDates) { getStopTimesForStopAndServiceDateAndTimeRange(index, serviceDate, from, to, stopTimeInstances); } } List<FrequencyStopTripIndex> frequencyStopTripIndices = _blockIndexService.getFrequencyStopTripIndicesForStop(stopEntry); for (FrequencyStopTripIndex index : frequencyStopTripIndices) { Collection<Date> serviceDates = _calendarService.getServiceDatesWithinRange( index.getServiceIds(), index.getServiceInterval(), from, to); for (Date serviceDate : serviceDates) { getFrequenciesForStopAndServiceIdsAndTimeRange(index, serviceDate, from, to, stopTimeInstances, frequencyBehavior); } } return stopTimeInstances; } @Override public Range getDepartureForStopAndServiceDate(AgencyAndId stopId, ServiceDate serviceDate) { StopEntry stop = _graph.getStopEntryForId(stopId, true); List<BlockStopTimeIndex> indices = _blockIndexService.getStopTimeIndicesForStop(stop); Range interval = new Range(); for (BlockStopTimeIndex index : indices) { extendIntervalWithIndex(serviceDate, interval, index); } List<FrequencyBlockStopTimeIndex> freqIndices = _blockIndexService.getFrequencyStopTimeIndicesForStop(stop); for (FrequencyBlockStopTimeIndex index : freqIndices) extendIntervalWithIndex(serviceDate, interval, index); return interval; } @Override public List<StopTimeInstance> getNextBlockSequenceDeparturesForStop( StopEntry stopEntry, long time, boolean includePrivateSerivce) { List<StopTimeInstance> stopTimeInstances = new ArrayList<StopTimeInstance>(); List<BlockStopSequenceIndex> blockStopTripIndices = _blockIndexService.getStopSequenceIndicesForStop(stopEntry); for (BlockStopSequenceIndex index : blockStopTripIndices) { List<Date> serviceDates = _calendarService.getNextServiceDatesForDepartureInterval( index.getServiceIds(), index.getServiceInterval(), time); for (Date serviceDate : serviceDates) { int relativeFrom = effectiveTime(serviceDate.getTime(), time); int fromIndex = GenericBinarySearch.search(index, index.size(), relativeFrom, IndexAdapters.BLOCK_STOP_TIME_DEPARTURE_INSTANCE); if (fromIndex < index.size()) { BlockStopTimeEntry blockStopTime = index.getBlockStopTimeForIndex(fromIndex); InstanceState state = new InstanceState(serviceDate.getTime()); StopTimeInstance sti = new StopTimeInstance(blockStopTime, state); stopTimeInstances.add(sti); } } } List<FrequencyBlockStopTimeIndex> frequencyIndices = _blockIndexService.getFrequencyStopTimeIndicesForStop(stopEntry); for (FrequencyBlockStopTimeIndex index : frequencyIndices) { List<Date> serviceDates = _calendarService.getNextServiceDatesForDepartureInterval( index.getServiceIds(), index.getServiceInterval(), time); for (Date serviceDate : serviceDates) { int relativeFrom = effectiveTime(serviceDate.getTime(), time); int fromIndex = GenericBinarySearch.search(index, index.size(), relativeFrom, IndexAdapters.FREQUENCY_END_TIME_INSTANCE); List<FrequencyBlockStopTimeEntry> frequencyStopTimes = index.getFrequencyStopTimes(); if (fromIndex < index.size()) { FrequencyBlockStopTimeEntry entry = frequencyStopTimes.get(fromIndex); BlockStopTimeEntry bst = entry.getStopTime(); FrequencyEntry frequency = entry.getFrequency(); InstanceState state = new InstanceState(serviceDate.getTime(), frequency); int stopTimeOffset = entry.getStopTimeOffset(); int frequencyOffset = computeFrequencyOffset(relativeFrom, bst, frequency, stopTimeOffset, true); StopTimeInstance sti = new StopTimeInstance(bst, state, frequencyOffset); stopTimeInstances.add(sti); } } } return stopTimeInstances; } @Override public List<Pair<StopTimeInstance>> getNextDeparturesBetweenStopPair( StopEntry fromStop, StopEntry toStop, Date fromTime, int runningEarlySlack, int runningLateSlack, int resultCount, boolean includePrivateService) { if (resultCount == 0) return Collections.emptyList(); PriorityQueue<Pair<StopTimeInstance>> nBestQueue = new PriorityQueue<Pair<StopTimeInstance>>( resultCount, _lastArrivalComparator); PriorityQueue<Pair<StopTimeInstance>> resultQueue = new PriorityQueue<Pair<StopTimeInstance>>( resultCount, _lastArrivalComparator); getDeparturesAndArrivalsBetweenStopPair(fromStop, toStop, fromTime, runningEarlySlack, runningLateSlack, resultCount, nBestQueue, resultQueue, true, includePrivateService); getFrequencyDeparturesAndArrivalsBetweenStopPair(fromStop, toStop, fromTime, runningEarlySlack, runningLateSlack, resultCount, nBestQueue, resultQueue, true); List<Pair<StopTimeInstance>> results = new ArrayList<Pair<StopTimeInstance>>(); results.addAll(resultQueue); Collections.sort(results, _firstDepartureComparator); return results; } @Override public List<Pair<StopTimeInstance>> getPreviousArrivalsBetweenStopPair( StopEntry fromStop, StopEntry toStop, Date toTime, int runningEarlySlack, int runningLateSlack, int resultCount, boolean includePrivateService) { if (resultCount == 0) return Collections.emptyList(); PriorityQueue<Pair<StopTimeInstance>> nBestQueue = new PriorityQueue<Pair<StopTimeInstance>>( resultCount, _firstDepartureComparator); PriorityQueue<Pair<StopTimeInstance>> resultQueue = new PriorityQueue<Pair<StopTimeInstance>>( resultCount, _firstDepartureComparator); getDeparturesAndArrivalsBetweenStopPair(fromStop, toStop, toTime, runningEarlySlack, runningLateSlack, resultCount, nBestQueue, resultQueue, false, includePrivateService); getFrequencyDeparturesAndArrivalsBetweenStopPair(fromStop, toStop, toTime, runningEarlySlack, runningLateSlack, resultCount, nBestQueue, resultQueue, false); List<Pair<StopTimeInstance>> results = new ArrayList<Pair<StopTimeInstance>>(); results.addAll(resultQueue); Collections.sort(results, _lastArrivalComparator); return results; } /**** * Private Methods * * @param includePrivateService TODO ****/ private void getDeparturesAndArrivalsBetweenStopPair(StopEntry fromStop, StopEntry toStop, Date tTime, int runningEarlySlack, int runningLateSlack, int resultCount, PriorityQueue<Pair<StopTimeInstance>> nBestQueue, PriorityQueue<Pair<StopTimeInstance>> resultQueue, boolean findDepartures, boolean includePrivateService) { List<Pair<BlockStopSequenceIndex>> indexPairs = _blockIndexService.getBlockSequenceIndicesBetweenStops( fromStop, toStop); long targetTime = tTime.getTime(); long slackAdjustedTime = targetTime; if (findDepartures) slackAdjustedTime -= runningLateSlack * 1000; else slackAdjustedTime += runningEarlySlack * 1000; long slack = (runningLateSlack + runningEarlySlack) * 1000; for (Pair<BlockStopSequenceIndex> pair : indexPairs) { BlockStopSequenceIndex sourceStopIndex = findDepartures ? pair.getFirst() : pair.getSecond(); BlockStopSequenceIndex destStopIndex = findDepartures ? pair.getSecond() : pair.getFirst(); if (!includePrivateService && sourceStopIndex.getIndex().isPrivateService()) continue; List<BlockStopTimeEntry> destStopTimes = destStopIndex.getStopTimes(); List<Date> serviceDates = _calendarService.getServiceDatesForInterval( sourceStopIndex.getServiceIds(), sourceStopIndex.getServiceInterval(), slackAdjustedTime, findDepartures); for (Date serviceDate : serviceDates) { ServiceInterval destServiceInterval = destStopIndex.getServiceInterval(); if (serviceDateIsBeyondRangeOfQueue(nBestQueue, serviceDate, destServiceInterval, resultCount, findDepartures, slack)) { /** * The service date is beyond our worst departure-arrival, so we break */ break; } int relativeTime = effectiveTime(serviceDate.getTime(), slackAdjustedTime); IndexAdapter<HasIndexedBlockStopTimes> adapter = findDepartures ? IndexAdapters.BLOCK_STOP_TIME_DEPARTURE_INSTANCE : IndexAdapters.BLOCK_STOP_TIME_ARRIVAL_INSTANCE; int sourceStopIndexSize = sourceStopIndex.size(); int sourceIndex = GenericBinarySearch.search(sourceStopIndex, sourceStopIndexSize, relativeTime, adapter); /** * When searching for arrival times, the index is an upper bound, so we * have to decrement to find the first good stop index */ if (!findDepartures) sourceIndex--; InstanceState state = new InstanceState(serviceDate.getTime()); while (0 <= sourceIndex && sourceIndex < sourceStopIndexSize) { BlockStopTimeEntry stopTimeSource = sourceStopIndex.getBlockStopTimeForIndex(sourceIndex); StopTimeInstance stiSource = new StopTimeInstance(stopTimeSource, state); stiSource.setBlockSequence(sourceStopIndex.getBlockSequenceForIndex(sourceIndex)); BlockStopTimeEntry stopTimeDest = destStopTimes.get(sourceIndex); StopTimeInstance stiDest = new StopTimeInstance(stopTimeDest, state); stiDest.setBlockSequence(destStopIndex.getBlockSequenceForIndex(sourceIndex)); if (stopTimeIsBeyondRangeOfQueue(nBestQueue, stiDest, resultCount, findDepartures, slack)) { break; } Pair<StopTimeInstance> stiPair = findDepartures ? Tuples.pair( stiSource, stiDest) : Tuples.pair(stiDest, stiSource); /** * We only add to the n-best queue if the arrival-departure is beyond * the target time, as opposed to time adjusted by slack */ if (isStopTimeInstanceBeyondTargetTime(stiSource, targetTime, findDepartures)) { nBestQueue.add(stiPair); } while (nBestQueue.size() > resultCount) nBestQueue.poll(); /** * We always add to the result queue */ resultQueue.add(stiPair); while (!resultQueue.isEmpty()) { Pair<StopTimeInstance> r = resultQueue.peek(); StopTimeInstance sti = findDepartures ? r.getSecond() : r.getFirst(); if (stopTimeIsBeyondRangeOfQueue(nBestQueue, sti, resultCount, findDepartures, slack)) resultQueue.poll(); else break; } if (findDepartures) sourceIndex++; else sourceIndex--; } } } } private void getFrequencyDeparturesAndArrivalsBetweenStopPair( StopEntry fromStop, StopEntry toStop, Date tTime, int runningEarlySlack, int runningLateSlack, int resultCount, PriorityQueue<Pair<StopTimeInstance>> nBestQueue, PriorityQueue<Pair<StopTimeInstance>> resultQueue, boolean findDepartures) { List<Pair<FrequencyStopTripIndex>> indexPairs = _blockIndexService.getFrequencyIndicesBetweenStops( fromStop, toStop); long targetTime = tTime.getTime(); long slackAdjustedTime = targetTime; if (findDepartures) slackAdjustedTime -= runningEarlySlack * 1000; else slackAdjustedTime += runningLateSlack * 1000; long slack = (runningLateSlack + runningEarlySlack) * 1000; for (Pair<FrequencyStopTripIndex> pair : indexPairs) { FrequencyStopTripIndex sourceStopIndex = findDepartures ? pair.getFirst() : pair.getSecond(); FrequencyStopTripIndex destStopIndex = findDepartures ? pair.getSecond() : pair.getFirst(); List<FrequencyBlockStopTimeEntry> sourceStopTimes = sourceStopIndex.getFrequencyStopTimes(); List<FrequencyBlockStopTimeEntry> destStopTimes = destStopIndex.getFrequencyStopTimes(); List<Date> serviceDates = _calendarService.getServiceDatesForInterval( sourceStopIndex.getServiceIds(), sourceStopIndex.getServiceInterval(), slackAdjustedTime, findDepartures); for (Date serviceDate : serviceDates) { ServiceInterval destServiceInterval = destStopIndex.getServiceInterval(); if (serviceDateIsBeyondRangeOfQueue(nBestQueue, serviceDate, destServiceInterval, resultCount, findDepartures, slack)) { /** * The service date is beyond our worst departure-arrival, so we break */ break; } int relativeTime = effectiveTime(serviceDate.getTime(), slackAdjustedTime); IndexAdapter<HasIndexedFrequencyBlockTrips> adapter = findDepartures ? IndexAdapters.FREQUENCY_END_TIME_INSTANCE : IndexAdapters.FREQUENCY_START_TIME_INSTANCE; int sourceIndex = GenericBinarySearch.search(sourceStopIndex, sourceStopIndex.size(), relativeTime, adapter); /** * When searching for arrival times, the index is an upper bound, so we * have to decrement to find the first good stop index */ if (!findDepartures) sourceIndex--; if (0 <= sourceIndex && sourceIndex < sourceStopIndex.size()) { FrequencyBlockStopTimeEntry sourceEntry = sourceStopTimes.get(sourceIndex); BlockStopTimeEntry sourceBst = sourceEntry.getStopTime(); FrequencyEntry frequency = sourceEntry.getFrequency(); InstanceState state = new InstanceState(serviceDate.getTime(), frequency); int stopTimeOffset = sourceEntry.getStopTimeOffset(); int frequencyOffset = computeFrequencyOffset(relativeTime, sourceBst, frequency, stopTimeOffset, findDepartures); StopTimeInstance stiSource = new StopTimeInstance(sourceBst, state, frequencyOffset); FrequencyBlockStopTimeEntry toEntry = destStopTimes.get(sourceIndex); BlockStopTimeEntry stopTimeTo = toEntry.getStopTime(); StopTimeInstance stiDest = new StopTimeInstance(stopTimeTo, state, frequencyOffset); /** * There's a chance the frequency-based departure+arrival pair could * extend out the front of the frequency range, at which point we * discard it. */ if (!findDepartures && stiDest.getDepartureTime() < serviceDate.getTime() + frequency.getStartTime() * 1000) { break; } if (stopTimeIsBeyondRangeOfQueue(nBestQueue, stiDest, resultCount, findDepartures, slack)) break; Pair<StopTimeInstance> stiPair = findDepartures ? Tuples.pair( stiSource, stiDest) : Tuples.pair(stiDest, stiSource); /** * We only add to the n-best queue if the arrival-departure is beyond * the target time, as opposed to time adjusted by slack */ if (isStopTimeInstanceBeyondTargetTime(stiSource, targetTime, findDepartures)) { nBestQueue.add(stiPair); } while (nBestQueue.size() > resultCount) nBestQueue.poll(); /** * We always add to the result queue */ resultQueue.add(stiPair); while (!resultQueue.isEmpty()) { Pair<StopTimeInstance> r = resultQueue.peek(); StopTimeInstance sti = findDepartures ? r.getSecond() : r.getFirst(); if (stopTimeIsBeyondRangeOfQueue(nBestQueue, sti, resultCount, findDepartures, slack)) resultQueue.poll(); else break; } } } } } private int computeFrequencyOffset(int relativeTime, BlockStopTimeEntry sourceBst, FrequencyEntry frequency, int stopTimeOffset, boolean findDepartures) { int t = Math.max(relativeTime, frequency.getStartTime()); t = Math.min(t, frequency.getEndTime()); t = snapToFrequencyStopTime(frequency, t, stopTimeOffset, findDepartures); return t - sourceBst.getStopTime().getDepartureTime(); } private boolean serviceDateIsBeyondRangeOfQueue( PriorityQueue<Pair<StopTimeInstance>> queue, Date serviceDate, ServiceInterval interval, int resultCount, boolean findDepartures, long slack) { if (queue.size() != resultCount) return false; Pair<StopTimeInstance> stiPair = queue.peek(); if (findDepartures) { /** * If we're looking for departures, then our queue is sorted by arrival * time at the toStop. Thus, if the latest arrival time in the queue is * less than the serviceDate, we return true. */ return stiPair.getSecond().getArrivalTime() + slack < serviceDate.getTime() + interval.getMinArrival() * 1000; } else { /** * If we're looking for arrivals, then our queue is sorted by departure * time at the fromStop. Thus, if the earliest departure time in the queue * is more than the serviceDate, we return true. */ return stiPair.getFirst().getDepartureTime() - slack > serviceDate.getTime() + interval.getMaxDeparture() * 1000; } } private boolean stopTimeIsBeyondRangeOfQueue( PriorityQueue<Pair<StopTimeInstance>> queue, StopTimeInstance sti, int resultCount, boolean findDepartures, long slack) { if (queue.size() != resultCount) return false; Pair<StopTimeInstance> stiPair = queue.peek(); if (findDepartures) { /** * If we're looking for departures, then our queue is sorted by arrival * time at the toStop. Thus, if the latest arrival time in the queue is * less than the sti arrival time, we return true. */ return stiPair.getSecond().getArrivalTime() + slack < sti.getArrivalTime(); } else { /** * If we're looking for arrivals, then our queue is sorted by departure * time at the fromStop. Thus, if the earliest departure time in the queue * is more than the sti departure time, we return true. */ return stiPair.getFirst().getDepartureTime() - slack > sti.getDepartureTime(); } } private boolean isStopTimeInstanceBeyondTargetTime( StopTimeInstance stiSource, long targetTime, boolean findDepartures) { if (findDepartures) return targetTime <= stiSource.getDepartureTime(); else return targetTime >= stiSource.getArrivalTime(); } private int getStopTimesForStopAndServiceDateAndTimeRange( HasIndexedBlockStopTimes index, Date serviceDate, Date from, Date to, List<StopTimeInstance> instances) { List<BlockStopTimeEntry> blockStopTimes = index.getStopTimes(); int relativeFrom = effectiveTime(serviceDate, from); int relativeTo = effectiveTime(serviceDate, to); int fromIndex = GenericBinarySearch.search(index, blockStopTimes.size(), relativeFrom, IndexAdapters.BLOCK_STOP_TIME_DEPARTURE_INSTANCE); int toIndex = GenericBinarySearch.search(index, blockStopTimes.size(), relativeTo, IndexAdapters.BLOCK_STOP_TIME_ARRIVAL_INSTANCE); InstanceState state = new InstanceState(serviceDate.getTime()); for (int in = fromIndex; in < toIndex; in++) { BlockStopTimeEntry blockStopTime = blockStopTimes.get(in); instances.add(new StopTimeInstance(blockStopTime, state)); } return fromIndex; } private List<Integer> getFrequenciesForStopAndServiceIdsAndTimeRange( FrequencyStopTripIndex index, Date serviceDate, Date from, Date to, List<StopTimeInstance> stopTimeInstances, EFrequencyStopTimeBehavior frequencyBehavior) { int relativeFrom = effectiveTime(serviceDate, from); int relativeTo = effectiveTime(serviceDate, to); int fromIndex = GenericBinarySearch.search(index, index.size(), relativeFrom, IndexAdapters.FREQUENCY_END_TIME_INSTANCE); int toIndex = GenericBinarySearch.search(index, index.size(), relativeTo, IndexAdapters.FREQUENCY_START_TIME_INSTANCE); List<FrequencyBlockStopTimeEntry> frequencyStopTimes = index.getFrequencyStopTimes(); List<Integer> offsetsIntoIndex = new ArrayList<Integer>(); for (int in = fromIndex; in < toIndex; in++) { FrequencyBlockStopTimeEntry entry = frequencyStopTimes.get(in); BlockStopTimeEntry bst = entry.getStopTime(); FrequencyEntry frequency = entry.getFrequency(); InstanceState state = new InstanceState(serviceDate.getTime(), frequency); switch (frequencyBehavior) { case INCLUDE_UNSPECIFIED: { stopTimeInstances.add(new StopTimeInstance(bst, state)); offsetsIntoIndex.add(in); break; } case INCLUDE_INTERPOLATED: { int stopTimeOffset = entry.getStopTimeOffset(); int tFrom = Math.max(relativeFrom, frequency.getStartTime()); int tTo = Math.min(relativeTo, frequency.getEndTime()); tFrom = snapToFrequencyStopTime(frequency, tFrom, stopTimeOffset, true); tTo = snapToFrequencyStopTime(frequency, tTo, stopTimeOffset, false); for (int t = tFrom; t <= tTo; t += frequency.getHeadwaySecs()) { int frequencyOffset = t - bst.getStopTime().getDepartureTime(); stopTimeInstances.add(new StopTimeInstance(bst, state, frequencyOffset)); offsetsIntoIndex.add(in); } break; } } } return offsetsIntoIndex; } private int snapToFrequencyStopTime(FrequencyEntry frequency, int timeToSnap, int stopTimeOffset, boolean isLowerBound) { int offset = timeToSnap - frequency.getStartTime(); int headway = frequency.getHeadwaySecs(); int snappedToHeadway = (offset / headway) * headway; int snapped = snappedToHeadway + frequency.getStartTime() + stopTimeOffset; if (isLowerBound) { if (snapped < timeToSnap) snapped += headway; } else { if (snapped > timeToSnap) snapped -= headway; } return snapped; } private void extendIntervalWithIndex(ServiceDate serviceDate, Range interval, AbstractBlockStopTimeIndex index) { ServiceIdActivation serviceIds = index.getServiceIds(); Date date = serviceDate.getAsDate(serviceIds.getTimeZone()); if (_calendarService.areServiceIdsActiveOnServiceDate(serviceIds, date)) { ServiceInterval in = index.getServiceInterval(); long tFrom = date.getTime() + in.getMinDeparture() * 1000; long tTo = date.getTime() + in.getMaxDeparture() * 1000; interval.addValue(tFrom); interval.addValue(tTo); } } private static final int effectiveTime(Date serviceDate, Date targetTime) { return effectiveTime(serviceDate.getTime(), targetTime.getTime()); } private static final int effectiveTime(long serviceDate, long targetTime) { return (int) ((targetTime - serviceDate) / 1000); } private static class FirstDepartureTimeComparator implements Comparator<Pair<StopTimeInstance>> { @Override public int compare(Pair<StopTimeInstance> o1, Pair<StopTimeInstance> o2) { long t1 = o1.getFirst().getDepartureTime(); long t2 = o2.getFirst().getDepartureTime(); return t1 == t2 ? 0 : (t1 < t2 ? -1 : 1); } } private static class LastArrivalTimeComparator implements Comparator<Pair<StopTimeInstance>> { @Override public int compare(Pair<StopTimeInstance> o1, Pair<StopTimeInstance> o2) { long t1 = o1.getSecond().getArrivalTime(); long t2 = o2.getSecond().getArrivalTime(); return t1 == t2 ? 0 : (t1 < t2 ? 1 : -1); } } }