/** * Copyright (C) 2015 University of South Florida * * 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.utility; import java.util.Arrays; /** * Transit-specific methods to support searching for deviations (produced from real-time * predictions) for a given stop. Interpolation behavior is consistent * with the GTFS-realtime spec (https://developers.google.com/transit/gtfs-realtime/) when * using the {@link EInRangeStrategy.PREVIOUS_VALUE} and {@link EOutOfRangeStrategy.LAST_VALUE} * strategies - in particular, this applies to the propagation of delays downstream in a trip. * The {@link EInRangeStrategy.INTERPOLATE} and {@link EOutOfRangeStrategy.INTERPOLATE} * strategies have behavior consistent with the normal {@link InterpolationLibrary}, which * do not conform to the GTFS-rt spec. * */ public class TransitInterpolationLibrary { private static final String OUT_OF_RANGE = "no values provided"; public static Double interpolate(double[] keys, double[] values, double target, EOutOfRangeStrategy outOfRangeStrategy) { return interpolate(keys, values, target, outOfRangeStrategy, null); } /** * Find the deviation that should be used for a particular stop, given sorted keys (arrival times) * and values (deviations) arrays. The {@code target} is the arrival time for the stop * we're searching for. Delay propagation is consistent with the GTFS-realtime spec * (https://developers.google.com/transit/gtfs-realtime/) when using the * {@link EInRangeStrategy.PREVIOUS_VALUE} and {@link EOutOfRangeStrategy.LAST_VALUE} strategies. If * {@link EOutOfRangeStrategy.LAST_VALUE} is provided and all deviations are downstream from the target stop, * null will be returned to indicate that no real-time information is available for the target stop. * If {@link EInRangeStrategy.INTERPOLATE} is provided, this method will interpolate using * linear interpolation and produce a value for a target key within the key-range of the map. * For a key outside the range of the keys of the map, the {@code outOfRange} {@link EOutOfRangeStrategy} * strategy will determine the interpolation behavior. {@link EOutOfRangeStrategy.INTERPOLATE} * will linearly extrapolate the value. * * @param keys sorted array of keys (the scheduled arrival time of the stop) * @param values sorted arrays of values (the list of real-time deviations for the provided stops) * @param target the target key used to interpolate a value (the scheduled arrival time of the stop) * @param outOfRangeStrategy the strategy to use for a target key that outside * the key-range of the value map (use {@link EOutOfRangeStrategy.LAST_VALUE} for GTFS-rt behavior) * @param inRangeStrategy the strategy to use for a target key that inside * the key-range of the value map (use {@link EInRangeStrategy.PREVIOUS_VALUE} for GTFS-rt behavior) * @return an interpolated value (deviation) for the target key, or null if the target is upstream of the deviations */ public static Double interpolate(double[] keys, double[] values, double target, EOutOfRangeStrategy outOfRangeStrategy, EInRangeStrategy inRangeStrategy) { if (values.length == 0) throw new IndexOutOfBoundsException(OUT_OF_RANGE); int index = Arrays.binarySearch(keys, target); if (index >= 0) { // There is a real-time prediction provided for this stop - return it return values[index]; } // If we get this far, the target value wasn't contained in the keys. Convert the returned index into the insertion // index for target, which is the index of the first element greater than the target key (see Arrays.binarySearch()). index = -(index + 1); if (index == values.length) { // We're searching for a stop that is downstream of the predictions switch (outOfRangeStrategy) { case INTERPOLATE: if (values.length > 1) return InterpolationLibrary.interpolatePair(keys[index - 2], values[index - 2], keys[index - 1], values[index - 1], target); return values[index - 1]; case LAST_VALUE: // Return the closest upstream deviation (i.e., propagate the last deviation in values downstream) return values[index - 1]; case EXCEPTION: throw new IndexOutOfBoundsException(OUT_OF_RANGE); } } if (index == 0) { // We're searching for a stop that is upstream of the predictions switch (outOfRangeStrategy) { case INTERPOLATE: if (values.length > 1) return InterpolationLibrary.interpolatePair(keys[0], values[0], keys[1], values[1], target); return values[0]; case LAST_VALUE: // We shouldn't propagate deviations upstream, so return null to indicate no prediction // should be used, and schedule data should be used instead. return null; case EXCEPTION: throw new IndexOutOfBoundsException(OUT_OF_RANGE); } } if (inRangeStrategy == null) { inRangeStrategy = EInRangeStrategy.INTERPOLATE; } // We're searching for a stop that is within the window of predictions, but no prediction is provided for // the target stop switch (inRangeStrategy) { case PREVIOUS_VALUE: // Return the closest upstream deviation (i.e., propagate the closest deviation in values downstream) return values[index - 1]; default: return InterpolationLibrary.interpolatePair(keys[index - 1], values[index - 1], keys[index], values[index], target); } } }