All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onebusaway.gtfs_transformer.updates.InterpolationLibrary Maven / Gradle / Ivy

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