/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * Copyright (C) 2011 Google, Inc. * * 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.transit_graph; import java.io.Serializable; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; import org.onebusaway.transit_data_federation.services.transit_graph.BlockConfigurationEntry; import org.onebusaway.transit_data_federation.services.transit_graph.BlockEntry; import org.onebusaway.transit_data_federation.services.transit_graph.BlockStopTimeEntry; import org.onebusaway.transit_data_federation.services.transit_graph.BlockTripEntry; 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.StopTimeEntry; import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry; public class BlockConfigurationEntryImpl implements BlockConfigurationEntry, Serializable { private static final long serialVersionUID = 1L; private final BlockEntry block; private final ServiceIdActivation serviceIds; private final List<BlockTripEntry> trips; private final double totalBlockDistance; private final BlockStopTimeList blockStopTimes = new BlockStopTimeList(); private final int[] tripIndices; private final int[] accumulatedStopTimeIndices; /** * We make this one field non-final because it makes it easier to add * Frequency information after the fact. */ private List<FrequencyEntry> frequencies; private BlockConfigurationEntryImpl(Builder builder) { this.block = builder.block; this.serviceIds = builder.serviceIds; this.trips = builder.computeBlockTrips(this); this.frequencies = builder.frequencies; this.totalBlockDistance = builder.computeTotalBlockDistance(); this.tripIndices = builder.computeTripIndices(); this.accumulatedStopTimeIndices = builder.computeAccumulatedStopTimeIndices(); } public static Builder builder() { return new Builder(); } public void setFrequencies(List<FrequencyEntry> frequencies) { this.frequencies = frequencies; } /**** * {@link BlockConfigurationEntry} Interface ****/ @Override public BlockEntry getBlock() { return block; } @Override public ServiceIdActivation getServiceIds() { return serviceIds; } @Override public List<BlockTripEntry> getTrips() { return trips; } @Override public List<FrequencyEntry> getFrequencies() { return frequencies; } @Override public List<BlockStopTimeEntry> getStopTimes() { return blockStopTimes; } @Override public double getTotalBlockDistance() { return totalBlockDistance; } @Override public int getArrivalTimeForIndex(int index) { StopTimeEntry stopTime = getStopTimeForIndex(index); return stopTime.getArrivalTime(); } @Override public int getDepartureTimeForIndex(int index) { StopTimeEntry stopTime = getStopTimeForIndex(index); return stopTime.getDepartureTime(); } @Override public double getDistanceAlongBlockForIndex(int index) { int tripIndex = tripIndices[index]; BlockTripEntry blockTrip = trips.get(tripIndex); TripEntry trip = blockTrip.getTrip(); List<StopTimeEntry> stopTimes = trip.getStopTimes(); int stopTimeIndex = index - accumulatedStopTimeIndices[tripIndex]; StopTimeEntry stopTime = stopTimes.get(stopTimeIndex); return blockTrip.getDistanceAlongBlock() + stopTime.getShapeDistTraveled(); } @Override public String toString() { return "BlockConfiguration [block=" + block.getId() + " serviceIds=" + serviceIds + "]"; } public static class Builder { private BlockEntry block; private ServiceIdActivation serviceIds; private List<TripEntry> trips; private List<FrequencyEntry> frequencies; private double[] tripGapDistances; private Builder() { } public void setBlock(BlockEntry block) { this.block = block; } public void setServiceIds(ServiceIdActivation serviceIds) { this.serviceIds = serviceIds; } public List<TripEntry> getTrips() { return trips; } public void setTrips(List<TripEntry> trips) { this.trips = trips; } public List<FrequencyEntry> getFrequencies() { return frequencies; } public void setFrequencies(List<FrequencyEntry> frequencies) { this.frequencies = frequencies; } public void setTripGapDistances(double[] tripGapDistances) { this.tripGapDistances = tripGapDistances; } public BlockConfigurationEntry create() { return new BlockConfigurationEntryImpl(this); } private double computeTotalBlockDistance() { double distance = 0; for (int i = 0; i < trips.size(); i++) { TripEntry trip = trips.get(i); distance += trip.getTotalTripDistance() + tripGapDistances[i]; } return distance; } private int[] computeTripIndices() { int n = 0; for (TripEntry trip : trips) n += trip.getStopTimes().size(); int[] tripIndices = new int[n]; int index = 0; for (int tripIndex = 0; tripIndex < trips.size(); tripIndex++) { TripEntry trip = trips.get(tripIndex); for (int i = 0; i < trip.getStopTimes().size(); i++) tripIndices[index++] = tripIndex; } return tripIndices; } private int[] computeAccumulatedStopTimeIndices() { int[] accumulatedStopTimeIndices = new int[trips.size()]; int n = 0; for (int i = 0; i < trips.size(); i++) { accumulatedStopTimeIndices[i] = n; n += trips.get(i).getStopTimes().size(); } return accumulatedStopTimeIndices; } private List<BlockTripEntry> computeBlockTrips( BlockConfigurationEntryImpl blockConfiguration) { ArrayList<BlockTripEntry> blockTrips = new ArrayList<BlockTripEntry>(); short accumulatedStopTimeIndex = 0; int accumulatedSlackTime = 0; double distanceAlongBlock = 0; BlockTripEntryImpl prevTrip = null; StopTimeEntry prevTripStopTime = null; double prevTripAvgVelocity = 0; for (short i = 0; i < trips.size(); i++) { TripEntry tripEntry = trips.get(i); List<StopTimeEntry> stopTimes = tripEntry.getStopTimes(); /** * See if there is any slack time in the schedule in the transition * between the two trips. We take the distance between the last stop of * the previous trip and the first stop of the next trip, along with the * average travel velocity from the previous trip, and compute the * estimated travel time. Any time that's left over is slack. */ if (prevTripStopTime != null) { StopTimeEntry nextStopTime = stopTimes.get(0); int slackTime = nextStopTime.getArrivalTime() - prevTripStopTime.getDepartureTime(); double distance = (distanceAlongBlock - (prevTrip.getDistanceAlongBlock() + prevTripStopTime.getShapeDistTraveled())) + nextStopTime.getShapeDistTraveled(); if (prevTripAvgVelocity > 0) { int timeToTravel = (int) (distance / prevTripAvgVelocity); slackTime -= Math.min(timeToTravel, slackTime); } accumulatedSlackTime += slackTime; } BlockTripEntryImpl blockTripEntry = new BlockTripEntryImpl(); blockTripEntry.setTrip(tripEntry); blockTripEntry.setBlockConfiguration(blockConfiguration); blockTripEntry.setSequence(i); blockTripEntry.setAccumulatedStopTimeIndex(accumulatedStopTimeIndex); blockTripEntry.setAccumulatedSlackTime(accumulatedSlackTime); blockTripEntry.setDistanceAlongBlock(distanceAlongBlock); if (prevTrip != null) { prevTrip.setNextTrip(blockTripEntry); blockTripEntry.setPreviousTrip(prevTrip); } blockTrips.add(blockTripEntry); accumulatedStopTimeIndex += stopTimes.size(); if (accumulatedSlackTime < 0) throw new IllegalStateException( "I didn't think this was possible, but the number of stop times in a particular block exceeded " + Short.MAX_VALUE + " causing a wrap-around in the accumulated stop time index: blockId=" + blockConfiguration.getBlock().getId()); for (StopTimeEntry stopTime : stopTimes) accumulatedSlackTime += stopTime.getSlackTime(); prevTripAvgVelocity = computeAverageTripTravelVelocity(stopTimes); distanceAlongBlock += tripEntry.getTotalTripDistance() + tripGapDistances[i]; prevTrip = blockTripEntry; prevTripStopTime = stopTimes.get(stopTimes.size() - 1); } blockTrips.trimToSize(); return blockTrips; } private double computeAverageTripTravelVelocity( List<StopTimeEntry> stopTimes) { int accumulatedTravelTime = 0; double accumulatedTravelDistance = 0; StopTimeEntry prevStopTime = null; for (StopTimeEntry stopTime : stopTimes) { if (prevStopTime != null) { accumulatedTravelTime += stopTime.getArrivalTime() - prevStopTime.getDepartureTime(); accumulatedTravelDistance += stopTime.getShapeDistTraveled() - prevStopTime.getShapeDistTraveled(); } prevStopTime = stopTime; } if (accumulatedTravelTime == 0) return 0; return accumulatedTravelDistance / accumulatedTravelTime; } } /***** * Private Methods ****/ private StopTimeEntry getStopTimeForIndex(int index) { int tripIndex = tripIndices[index]; BlockTripEntry blockTrip = trips.get(tripIndex); TripEntry trip = blockTrip.getTrip(); List<StopTimeEntry> stopTimes = trip.getStopTimes(); int stopTimeIndex = index - accumulatedStopTimeIndices[tripIndex]; StopTimeEntry stopTime = stopTimes.get(stopTimeIndex); return stopTime; } private class BlockStopTimeList extends AbstractList<BlockStopTimeEntry> implements Serializable { private static final long serialVersionUID = 1L; @Override public int size() { return tripIndices.length; } @Override public BlockStopTimeEntry get(int index) { int tripIndex = tripIndices[index]; BlockTripEntry blockTrip = trips.get(tripIndex); TripEntry trip = blockTrip.getTrip(); List<StopTimeEntry> stopTimes = trip.getStopTimes(); int stopTimeIndex = index - accumulatedStopTimeIndices[tripIndex]; StopTimeEntry stopTime = stopTimes.get(stopTimeIndex); boolean hasNextStop = index + 1 < tripIndices.length; return new BlockStopTimeEntryImpl(stopTime, index, blockTrip, hasNextStop); } } }