org.onebusaway.gtfs_transformer.updates.InterpolationLibrary Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of onebusaway-gtfs-transformer Show documentation
Show all versions of onebusaway-gtfs-transformer Show documentation
A Java library for transforming Google Transit Feed Spec feeds
The newest version!
/**
* Copyright (C) 2011 Brian Ferris
* 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.gtfs_transformer.updates;
import java.util.Arrays;
import java.util.Iterator;
import java.util.SortedMap;
/**
* Generic methods to support interpolation of values against a sorted key-value
* map given a new target key.
*
* Note that these interpolation methods do not conform to the GTFS-rt spec. For GTFS-rt
* compliant interpolation/extrapolation.
*
* Ported from onebusaway-application-modules.
*
* @author bdferris
*/
public class InterpolationLibrary {
private static final String OUT_OF_RANGE = "attempt to interpolate key outside range of key-value data";
private static final NumberInterpolationStrategy _numberInterpolation = new NumberInterpolationStrategy();
public interface InterpolationStrategy {
public VALUE interpolate(KEY prevKey, VALUE prevValue, KEY nextKey,
VALUE nextValue, double ratio);
}
public enum EOutOfRangeStrategy {
/**
* As long as two key-values are present in the map, we we will attempt to
* interpolate the value for a key that is outside the key range of the
* key-value map. If only one key-value pair is present in the map, that value
* will be used.
*/
INTERPOLATE,
/**
* The closest key-value pair to target key outside the range of the key-value
* map will be used. This usually corresponds to the first or last key-value
* pair in the map depending on which end of the key-range the target key is
* out-of-bounds.
*/
LAST_VALUE,
/**
* An exception will be thrown when attempting to interpolate a key that is
* outside the key range of the key-value map
*/
EXCEPTION;
}
public enum EInRangeStrategy {
/**
* As long as two key-values are present in the map, we we will attempt to
* interpolate the value for a key that is inside the key range of the
* key-value map. If only one key-value pair is present in the map, that value
* will be used.
*/
INTERPOLATE,
/**
* Returns the value in the key-value map closest to the target value, where the
* index for the returned value is less than the index of the target value.
*/
PREVIOUS_VALUE;
}
/**
* Same behavior as
* {@link #interpolate(SortedMap, Number, EOutOfRangeStrategy)} but with a
* default {@link EOutOfRangeStrategy} of
* {@link EOutOfRangeStrategy#INTERPOLATE}.
*
* @param values a sorted-map of key-value number pairs
* @param target the target key used to interpolate a value
* @return an interpolated value for the target key
*/
public static double interpolate(
SortedMap values, K target) {
return interpolate(values, target, EOutOfRangeStrategy.INTERPOLATE);
}
/**
* Given a {@link SortedMap} with key-values that all extend from
* {@link Number}, interpolate using linear interpolation 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.
*
* @param values a sorted-map of key-value number pairs
* @param target the target key used to interpolate a value
* @param outOfRangeStrategy the strategy to use for a target key that outside
* the key-range of the value map
* @return an interpolated value for the target key
*/
public static double interpolate(
SortedMap values, K target, EOutOfRangeStrategy outOfRangeStrategy) {
Number result = interpolate(_numberInterpolation, outOfRangeStrategy,
values, target);
return result.doubleValue();
}
public static double interpolate(double[] keys, double[] values,
double target, EOutOfRangeStrategy outOfRangeStrategy) {
return interpolate(keys, values, target, outOfRangeStrategy, null);
}
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)
return values[index];
index = -(index + 1);
if (index == values.length) {
switch (outOfRangeStrategy) {
case INTERPOLATE:
if (values.length > 1)
return interpolatePair(keys[index - 2], values[index - 2],
keys[index - 1], values[index - 1], target);
return values[index - 1];
case LAST_VALUE:
return values[index - 1];
case EXCEPTION:
throw new IndexOutOfBoundsException(OUT_OF_RANGE);
}
}
if (index == 0) {
switch (outOfRangeStrategy) {
case INTERPOLATE:
if (values.length > 1)
return interpolatePair(keys[0], values[0], keys[1], values[1],
target);
return values[0];
case LAST_VALUE:
return values[0];
case EXCEPTION:
throw new IndexOutOfBoundsException(OUT_OF_RANGE);
}
}
if (inRangeStrategy == null) {
inRangeStrategy = EInRangeStrategy.INTERPOLATE;
}
switch (inRangeStrategy) {
case PREVIOUS_VALUE:
return values[index - 1];
default:
return interpolatePair(keys[index - 1], values[index - 1], keys[index],
values[index], target);
}
}
/**
* Simple numeric interpolation between two double values using the equation
* {@code ratio * (toValue - fromValue) + fromValue}
*
* @param fromValue
* @param toValue
* @param ratio
* @return {@code ratio * (toValue - fromValue) + fromValue}
*/
public static double interpolatePair(double fromValue, double toValue,
double ratio) {
return ratio * (toValue - fromValue) + fromValue;
}
/**
* Simple numeric interpolation between two pairs of key-values and a third
* key. Here, {@code ratio = (targetKey - keyA) / (keyB - keyA)} and the
* result is {@code ratio * (valueB - valueA) + valueA}.
*
* @return {@code ratio * (toValue - fromValue) + fromValue}
*/
public static double interpolatePair(double keyA, double valueA, double keyB,
double valueB, double targetKey) {
double ratio = (targetKey - keyA) / (keyB - keyA);
return interpolatePair(valueA, valueB, ratio);
}
/**
* Given a {@link SortedMap} with key-values that all extend from
* {@link Number}, interpolate using linear interpolation 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.
*
* @param values
* @param target the target key used to interpolate a value
* @param outOfRangeStrategy the strategy to use for a target key that outside
* the key-range of the value map
* @return an interpolated value for the target key
*/
/**
*
* Given a {@link SortedMap} with key-values that of arbitrary type and a
* {@link InterpolationStrategy} to define interpolation over those types,
* interpolate 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.
*
* @param interpolationStrategy the interpolation strategy used to perform
* interpolation between key-value pairs of arbitrary type
* @param outOfRangeStrategy the strategy to use for a target key that outside
* the key-range of the value map
* @param values a sorted-map of key-value pairs
* @param target the target key used to interpolate a value
* @return an interpolated value for the target key
*/
public static VALUE interpolate(
InterpolationStrategy interpolationStrategy,
EOutOfRangeStrategy outOfRangeStrategy,
SortedMap values, ANY_KEY target) {
if (values.containsKey(target))
return values.get(target);
SortedMap before = values.headMap(target);
SortedMap after = values.tailMap(target);
ANY_KEY prevKey = null;
ANY_KEY nextKey = null;
if (before.isEmpty()) {
if (after.isEmpty())
throw new IndexOutOfBoundsException(OUT_OF_RANGE);
switch (outOfRangeStrategy) {
case INTERPOLATE:
if (after.size() == 1)
return after.get(after.firstKey());
Iterator it = after.keySet().iterator();
prevKey = it.next();
nextKey = it.next();
break;
case LAST_VALUE:
return after.get(after.firstKey());
case EXCEPTION:
throw new IndexOutOfBoundsException(OUT_OF_RANGE);
}
} else if (after.isEmpty()) {
if (before.isEmpty())
throw new IndexOutOfBoundsException(OUT_OF_RANGE);
switch (outOfRangeStrategy) {
case INTERPOLATE:
if (before.size() == 1)
return before.get(before.lastKey());
nextKey = before.lastKey();
before = before.headMap(nextKey);
prevKey = before.lastKey();
break;
case LAST_VALUE:
return before.get(before.lastKey());
case EXCEPTION:
throw new IndexOutOfBoundsException(OUT_OF_RANGE);
}
} else {
prevKey = before.lastKey();
nextKey = after.firstKey();
}
VALUE prevValue = values.get(prevKey);
VALUE nextValue = values.get(nextKey);
double keyRatio = (target.doubleValue() - prevKey.doubleValue())
/ (nextKey.doubleValue() - prevKey.doubleValue());
VALUE result = interpolationStrategy.interpolate(prevKey, prevValue,
nextKey, nextValue, keyRatio);
return result;
}
private static class NumberInterpolationStrategy implements
InterpolationStrategy {
@Override
public Number interpolate(Number prevKey, Number prevValue, Number nextKey,
Number nextValue, double ratio) {
double result = interpolatePair(prevValue.doubleValue(),
nextValue.doubleValue(), ratio);
return new Double(result);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy