Package org.onebusaway.transit_data_federation.bundle.tasks.transit_graph

Source Code of org.onebusaway.transit_data_federation.bundle.tasks.transit_graph.StopTimeEntriesFactory

/**
* 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.bundle.tasks.transit_graph;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import org.onebusaway.geospatial.model.CoordinatePoint;
import org.onebusaway.geospatial.services.SphericalGeometryLibrary;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.model.StopTime;
import org.onebusaway.transit_data_federation.bundle.tasks.transit_graph.DistanceAlongShapeLibrary.DistanceAlongShapeException;
import org.onebusaway.transit_data_federation.bundle.tasks.transit_graph.DistanceAlongShapeLibrary.StopIsTooFarFromShapeException;
import org.onebusaway.transit_data_federation.impl.shapes.PointAndIndex;
import org.onebusaway.transit_data_federation.impl.transit_graph.StopEntryImpl;
import org.onebusaway.transit_data_federation.impl.transit_graph.StopTimeEntryImpl;
import org.onebusaway.transit_data_federation.impl.transit_graph.TransitGraphImpl;
import org.onebusaway.transit_data_federation.impl.transit_graph.TripEntryImpl;
import org.onebusaway.transit_data_federation.model.ShapePoints;
import org.onebusaway.transit_data_federation.services.transit_graph.StopEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.StopTimeEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry;
import org.onebusaway.utility.InterpolationLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class StopTimeEntriesFactory {

  private Logger _log = LoggerFactory.getLogger(StopTimeEntriesFactory.class);

  private DistanceAlongShapeLibrary _distanceAlongShapeLibrary;

  private long _invalidStopToShapeMappingExceptionCount;

  @Autowired
  public void setDistanceAlongShapeLibrary(
      DistanceAlongShapeLibrary distanceAlongShapeLibrary) {
    _distanceAlongShapeLibrary = distanceAlongShapeLibrary;
  }

  public long getInvalidStopToShapeMappingExceptionCount() {
    return _invalidStopToShapeMappingExceptionCount;
  }

  public List<StopTimeEntryImpl> processStopTimes(TransitGraphImpl graph,
      List<StopTime> stopTimes, TripEntryImpl tripEntry, ShapePoints shapePoints) {

    // In case the list is unmodifiable
    stopTimes = new ArrayList<StopTime>(stopTimes);

    Collections.sort(stopTimes, new StopTimeComparator());

    List<StopTimeEntryImpl> stopTimeEntries = createInitialStopTimeEntries(
        graph, stopTimes);

    for (StopTimeEntryImpl stopTime : stopTimeEntries)
      stopTime.setTrip(tripEntry);

    ensureStopTimesHaveShapeDistanceTraveledSet(stopTimeEntries, shapePoints);

    removeDuplicateStopTimes(stopTimes);
    ensureStopTimesHaveTimesSet(stopTimes, stopTimeEntries);

    return stopTimeEntries;
  }

  private void removeDuplicateStopTimes(List<StopTime> stopTimes) {
    Collections.sort(stopTimes, new StopTimeComparator());

    boolean stopTimeWasModified = false;
    StopTime lastUnmodifiedStopTime = null;

    for (StopTime stopTime : stopTimes) {
      stopTimeWasModified = false;

      if (lastUnmodifiedStopTime == null) {
        lastUnmodifiedStopTime = stopTime;
        continue;
      }

      if (lastUnmodifiedStopTime.isArrivalTimeSet()
          && stopTime.isArrivalTimeSet()) {
        if (stopTime.getArrivalTime() == lastUnmodifiedStopTime.getArrivalTime()) {
          stopTime.clearArrivalTime();
          stopTimeWasModified = true;
        }
      }

      if (lastUnmodifiedStopTime.isDepartureTimeSet()
          && stopTime.isDepartureTimeSet()) {
        if (stopTime.getDepartureTime() == lastUnmodifiedStopTime.getDepartureTime()) {
          stopTime.clearDepartureTime();
          stopTimeWasModified = true;
        }
      }

      // always compare to the last stop time we didn't change
      if (!stopTimeWasModified) {
        lastUnmodifiedStopTime = stopTime;
      }
    }
  }

  private List<StopTimeEntryImpl> createInitialStopTimeEntries(
      TransitGraphImpl graph, List<StopTime> stopTimes) {

    List<StopTimeEntryImpl> stopTimeEntries = new ArrayList<StopTimeEntryImpl>(
        stopTimes.size());
    int sequence = 0;

    for (StopTime stopTime : stopTimes) {

      Stop stop = stopTime.getStop();
      AgencyAndId stopId = stop.getId();
      StopEntryImpl stopEntry = graph.getStopEntryForId(stopId);

      StopTimeEntryImpl stopTimeEntry = new StopTimeEntryImpl();

      stopTimeEntry.setId(stopTime.getId());
      stopTimeEntry.setSequence(sequence);
      stopTimeEntry.setDropOffType(stopTime.getDropOffType());
      stopTimeEntry.setPickupType(stopTime.getPickupType());
      stopTimeEntry.setStop(stopEntry);

      stopTimeEntries.add(stopTimeEntry);
      sequence++;
    }

    return stopTimeEntries;
  }

  /**
   * We have to make sure shape distance traveled is set, even if we don't have
   * shape information
   *
   * @param stopTimes
   * @param shapePoints potentially null
   */
  private void ensureStopTimesHaveShapeDistanceTraveledSet(
      List<StopTimeEntryImpl> stopTimes, ShapePoints shapePoints) {

    boolean distanceTraveledSet = false;

    // Do we have shape information?
    if (shapePoints != null) {

      try {
        PointAndIndex[] stopTimePoints = _distanceAlongShapeLibrary.getDistancesAlongShape(
            shapePoints, stopTimes);
        for (int i = 0; i < stopTimePoints.length; i++) {
          PointAndIndex pindex = stopTimePoints[i];
          StopTimeEntryImpl stopTime = stopTimes.get(i);
          stopTime.setShapePointIndex(pindex.index);
          stopTime.setShapeDistTraveled(pindex.distanceAlongShape);
        }

        distanceTraveledSet = true;
      } catch (StopIsTooFarFromShapeException ex) {
        StopTimeEntry stopTime = ex.getStopTime();
        TripEntry trip = stopTime.getTrip();
        StopEntry stop = stopTime.getStop();
        AgencyAndId shapeId = trip.getShapeId();
        CoordinatePoint point = ex.getPoint();
        PointAndIndex pindex = ex.getPointAndIndex();

        _log.warn("Stop is too far from shape: trip=" + trip.getId() + " stop="
            + stop.getId() + " stopLat=" + stop.getStopLat() + " stopLon="
            + stop.getStopLon() + " shapeId=" + shapeId + " shapePoint="
            + point + " index=" + pindex.index + " distance="
            + pindex.distanceFromTarget);
      } catch (DistanceAlongShapeException ex) {
        _invalidStopToShapeMappingExceptionCount++;
      }
    }

    if (!distanceTraveledSet) {

      // Make do without
      double d = 0;
      StopTimeEntryImpl prev = null;
      for (StopTimeEntryImpl stopTime : stopTimes) {
        if (prev != null) {
          CoordinatePoint from = prev.getStop().getStopLocation();
          CoordinatePoint to = stopTime.getStop().getStopLocation();
          d += SphericalGeometryLibrary.distance(from, to);
        }
        stopTime.setShapeDistTraveled(d);
        prev = stopTime;
      }
    }
  }

  private void ensureStopTimesHaveTimesSet(List<StopTime> stopTimes,
      List<StopTimeEntryImpl> stopTimeEntries) {

    double[] distanceTraveled = getDistanceTraveledForStopTimes(stopTimeEntries);

    int[] arrivalTimes = new int[stopTimes.size()];
    int[] departureTimes = new int[stopTimes.size()];

    interpolateArrivalAndDepartureTimes(stopTimes, distanceTraveled,
        arrivalTimes, departureTimes);

    int sequence = 0;
    int accumulatedSlackTime = 0;
    StopTimeEntryImpl prevStopTimeEntry = null;

    for (StopTimeEntryImpl stopTimeEntry : stopTimeEntries) {

      int arrivalTime = arrivalTimes[sequence];
      int departureTime = departureTimes[sequence];

      stopTimeEntry.setArrivalTime(arrivalTime);
      stopTimeEntry.setDepartureTime(departureTime);

      stopTimeEntry.setAccumulatedSlackTime(accumulatedSlackTime);
      accumulatedSlackTime += stopTimeEntry.getDepartureTime()
          - stopTimeEntry.getArrivalTime();

      if (prevStopTimeEntry != null) {

        int duration = stopTimeEntry.getArrivalTime()
            - prevStopTimeEntry.getDepartureTime();

        if (duration < 0) {
          throw new IllegalStateException();
        }
      }

      prevStopTimeEntry = stopTimeEntry;

      sequence++;
    }
  }

  /**
   * The {@link StopTime#getArrivalTime()} and
   * {@link StopTime#getDepartureTime()} properties are optional. This method
   * takes charge of interpolating the arrival and departure time for any
   * StopTime where they are missing. The interpolation is based on the distance
   * traveled along the current trip/block.
   *
   * @param stopTimes
   * @param distanceTraveled
   * @param arrivalTimes
   * @param departureTimes
   */
  private void interpolateArrivalAndDepartureTimes(List<StopTime> stopTimes,
      double[] distanceTraveled, int[] arrivalTimes, int[] departureTimes) {

    SortedMap<Double, Integer> scheduleTimesByDistanceTraveled = new TreeMap<Double, Integer>();

    populateArrivalAndDepartureTimesByDistanceTravelledForStopTimes(stopTimes,
        distanceTraveled, scheduleTimesByDistanceTraveled);

    for (int i = 0; i < stopTimes.size(); i++) {

      StopTime stopTime = stopTimes.get(i);

      double d = distanceTraveled[i];

      boolean hasDeparture = stopTime.isDepartureTimeSet();
      boolean hasArrival = stopTime.isArrivalTimeSet();

      int departureTime = stopTime.getDepartureTime();
      int arrivalTime = stopTime.getArrivalTime();

      if (hasDeparture && !hasArrival) {
        arrivalTime = departureTime;
      } else if (hasArrival && !hasDeparture) {
        departureTime = arrivalTime;
      } else if (!hasArrival && !hasDeparture) {
        int t = departureTimes[i] = (int) InterpolationLibrary.interpolate(
            scheduleTimesByDistanceTraveled, d);
        arrivalTime = t;
        departureTime = t;
      }

      departureTimes[i] = departureTime;
      arrivalTimes[i] = arrivalTime;

      if (departureTimes[i] < arrivalTimes[i])
        throw new IllegalStateException(
            "departure time is less than arrival time for stop time with trip_id="
                + stopTime.getTrip().getId() + " stop_sequence="
                + stopTime.getStopSequence());

      if (i > 0 && arrivalTimes[i] < departureTimes[i - 1]) {

        /**
         * The previous stop time's departure time comes AFTER this stop time's
         * arrival time. That's bad.
         */
        StopTime prevStopTime = stopTimes.get(i - 1);
        Stop prevStop = prevStopTime.getStop();
        Stop stop = stopTime.getStop();

        if (prevStop.equals(stop)
            && arrivalTimes[i] == departureTimes[i - 1] - 1) {
          _log.info("fixing decreasing passingTimes: stopTimeA="
              + prevStopTime.getId() + " stopTimeB=" + stopTime.getId());
          arrivalTimes[i] = departureTimes[i - 1];
          if (departureTimes[i] < arrivalTimes[i])
            departureTimes[i] = arrivalTimes[i];
        } else {
          for (int x = 0; x < stopTimes.size(); x++) {
            StopTime st = stopTimes.get(x);
            System.err.println(x + " " + st.getId() + " " + arrivalTimes[x]
                + " " + departureTimes[x]);
          }
          throw new IllegalStateException(
              "arrival time is less than previous departure time for stop time with trip_id="
                  + stopTime.getTrip().getId() + " stop_sequence="
                  + stopTime.getStopSequence());
        }
      }
    }
  }

  private double[] getDistanceTraveledForStopTimes(
      List<StopTimeEntryImpl> stopTimeEntries) {

    double[] distances = new double[stopTimeEntries.size()];

    for (int i = 0; i < stopTimeEntries.size(); i++) {
      StopTimeEntryImpl stopTime = stopTimeEntries.get(i);
      distances[i] = stopTime.getShapeDistTraveled();
    }

    return distances;
  }

  /**
   * We have a list of StopTimes, along with their distance traveled along their
   * trip/block. For any StopTime that has either an arrival or a departure
   * time, we add it to the SortedMaps of arrival and departure times by
   * distance traveled.
   *
   * @param stopTimes
   * @param distances
   * @param arrivalTimesByDistanceTraveled
   */
  private void populateArrivalAndDepartureTimesByDistanceTravelledForStopTimes(
      List<StopTime> stopTimes, double[] distances,
      SortedMap<Double, Integer> scheduleTimesByDistanceTraveled) {

    for (int i = 0; i < stopTimes.size(); i++) {

      StopTime stopTime = stopTimes.get(i);
      double d = distances[i];

      // We introduce distinct arrival and departure distances so that our
      // scheduleTimes map might have entries for arrival and departure times
      // that are not the same at a given stop
      double arrivalDistance = d;
      double departureDistance = d + 1e-6;

      /**
       * For StopTime's that have the same distance travelled, we keep the min
       * arrival time and max departure time
       */
      if (stopTime.getArrivalTime() >= 0) {
        if (!scheduleTimesByDistanceTraveled.containsKey(arrivalDistance)
            || scheduleTimesByDistanceTraveled.get(arrivalDistance) > stopTime.getArrivalTime())
          scheduleTimesByDistanceTraveled.put(arrivalDistance,
              stopTime.getArrivalTime());
      }

      if (stopTime.getDepartureTime() >= 0)
        if (!scheduleTimesByDistanceTraveled.containsKey(departureDistance)
            || scheduleTimesByDistanceTraveled.get(departureDistance) < stopTime.getDepartureTime())
          scheduleTimesByDistanceTraveled.put(departureDistance,
              stopTime.getDepartureTime());
    }
  }

  private static class StopTimeComparator implements Comparator<StopTime> {

    public int compare(StopTime o1, StopTime o2) {
      return o1.getStopSequence() - o2.getStopSequence();
    }
  }

}
TOP

Related Classes of org.onebusaway.transit_data_federation.bundle.tasks.transit_graph.StopTimeEntriesFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.