Package org.onebusaway.transit_data_federation.impl

Source Code of org.onebusaway.transit_data_federation.impl.ArrivalAndDepartureServiceImpl

/**
* 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.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.onebusaway.collections.CollectionsLibrary;
import org.onebusaway.collections.FactoryMap;
import org.onebusaway.collections.Min;
import org.onebusaway.collections.tuple.Pair;
import org.onebusaway.collections.tuple.Tuples;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.transit_data.model.TimeIntervalBean;
import org.onebusaway.transit_data_federation.model.TargetTime;
import org.onebusaway.transit_data_federation.services.ArrivalAndDeparturePairQuery;
import org.onebusaway.transit_data_federation.services.ArrivalAndDepartureQuery;
import org.onebusaway.transit_data_federation.services.ArrivalAndDepartureService;
import org.onebusaway.transit_data_federation.services.StopTimeService;
import org.onebusaway.transit_data_federation.services.StopTimeService.EFrequencyStopTimeBehavior;
import org.onebusaway.transit_data_federation.services.blocks.BlockInstance;
import org.onebusaway.transit_data_federation.services.blocks.BlockStatusService;
import org.onebusaway.transit_data_federation.services.blocks.BlockTripInstance;
import org.onebusaway.transit_data_federation.services.blocks.BlockTripInstanceLibrary;
import org.onebusaway.transit_data_federation.services.blocks.InstanceState;
import org.onebusaway.transit_data_federation.services.realtime.ArrivalAndDepartureInstance;
import org.onebusaway.transit_data_federation.services.realtime.ArrivalAndDepartureTime;
import org.onebusaway.transit_data_federation.services.realtime.BlockLocation;
import org.onebusaway.transit_data_federation.services.realtime.BlockLocationService;
import org.onebusaway.transit_data_federation.services.realtime.ScheduleDeviationSamples;
import org.onebusaway.transit_data_federation.services.transit_graph.BlockConfigurationEntry;
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.StopEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.StopTimeEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry;
import org.onebusaway.transit_data_federation.services.tripplanner.StopTimeInstance;
import org.onebusaway.transit_data_federation.services.tripplanner.StopTransfer;
import org.onebusaway.transit_data_federation.services.tripplanner.StopTransferService;
import org.onebusaway.utility.EOutOfRangeStrategy;
import org.onebusaway.utility.InterpolationLibrary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
class ArrivalAndDepartureServiceImpl implements ArrivalAndDepartureService {

  private StopTimeService _stopTimeService;

  private BlockLocationService _blockLocationService;

  private BlockStatusService _blockStatusService;

  private StopTransferService _stopTransferService;

  @Autowired
  public void setStopTimeService(StopTimeService stopTimeService) {
    _stopTimeService = stopTimeService;
  }

  @Autowired
  public void setBlockLocationService(BlockLocationService blockLocationService) {
    _blockLocationService = blockLocationService;
  }

  @Autowired
  public void setBlockStatusService(BlockStatusService blockStatusService) {
    _blockStatusService = blockStatusService;
  }

  @Autowired
  public void setStopTransferService(StopTransferService stopTransferService) {
    _stopTransferService = stopTransferService;
  }

  @Override
  public List<ArrivalAndDepartureInstance> getArrivalsAndDeparturesForStopInTimeRange(
      StopEntry stop, TargetTime targetTime, long fromTime, long toTime) {

    // We add a buffer before and after to catch late and early buses
    Date fromTimeBuffered = new Date(fromTime - _blockStatusService.getRunningLateWindow() * 1000);
    Date toTimeBuffered = new Date(toTime + _blockStatusService.getRunningEarlyWindow() * 1000);

    List<StopTimeInstance> stis = _stopTimeService.getStopTimeInstancesInTimeRange(
        stop, fromTimeBuffered, toTimeBuffered,
        EFrequencyStopTimeBehavior.INCLUDE_UNSPECIFIED);

    long frequencyOffsetTime = Math.max(targetTime.getTargetTime(), fromTime);

    Map<BlockInstance, List<StopTimeInstance>> stisByBlockId = getStopTimeInstancesByBlockInstance(stis);

    List<ArrivalAndDepartureInstance> instances = new ArrayList<ArrivalAndDepartureInstance>();

    for (Map.Entry<BlockInstance, List<StopTimeInstance>> entry : stisByBlockId.entrySet()) {

      BlockInstance blockInstance = entry.getKey();
      List<BlockLocation> locations = _blockLocationService.getLocationsForBlockInstance(
          blockInstance, targetTime);

      List<StopTimeInstance> stisForBlock = entry.getValue();

      for (StopTimeInstance sti : stisForBlock) {

        applyRealTimeToStopTimeInstance(sti, targetTime, fromTime, toTime,
            frequencyOffsetTime, blockInstance, locations, instances);
      }
    }

    return instances;
  }

  @Override
  public List<ArrivalAndDepartureInstance> getScheduledArrivalsAndDeparturesForStopInTimeRange(
      StopEntry stop, long currentTime, long fromTime, long toTime) {

    List<StopTimeInstance> stis = _stopTimeService.getStopTimeInstancesInTimeRange(
        stop, new Date(fromTime), new Date(toTime),
        EFrequencyStopTimeBehavior.INCLUDE_UNSPECIFIED);

    List<ArrivalAndDepartureInstance> instances = new ArrayList<ArrivalAndDepartureInstance>();

    long prevFrequencyTime = Math.max(currentTime, fromTime);

    for (StopTimeInstance sti : stis) {

      BlockInstance blockInstance = sti.getBlockInstance();

      ArrivalAndDepartureInstance instance = createArrivalAndDepartureForStopTimeInstance(
          sti, prevFrequencyTime);

      if (sti.getFrequency() == null) {

        /**
         * We don't need to get the scheduled location of a vehicle unless its
         * in our arrival window
         */
        if (isArrivalAndDepartureBeanInRange(instance, fromTime, toTime)) {

          BlockLocation scheduledLocation = _blockLocationService.getScheduledLocationForBlockInstance(
              blockInstance, currentTime);
          if (scheduledLocation != null)
            applyBlockLocationToInstance(instance, scheduledLocation,
                currentTime);

          instances.add(instance);
        }

      } else {
        if (isFrequencyBasedArrivalInRange(blockInstance, sti.getFrequency(),
            fromTime, toTime)) {
          instances.add(instance);
        }
      }
    }

    return instances;
  }

  @Override
  public List<ArrivalAndDepartureInstance> getNextScheduledBlockTripDeparturesForStop(
      StopEntry stop, long time, boolean includePrivateService) {

    List<StopTimeInstance> stopTimes = _stopTimeService.getNextBlockSequenceDeparturesForStop(
        stop, time, includePrivateService);

    List<ArrivalAndDepartureInstance> instances = new ArrayList<ArrivalAndDepartureInstance>();

    for (StopTimeInstance sti : stopTimes) {
      ArrivalAndDepartureInstance instance = createArrivalAndDepartureForStopTimeInstance(
          sti, time);
      instances.add(instance);
    }

    return instances;
  }

  @Override
  public ArrivalAndDepartureInstance getArrivalAndDepartureForStop(
      ArrivalAndDepartureQuery query) {

    StopEntry stop = query.getStop();
    int stopSequence = query.getStopSequence();
    TripEntry trip = query.getTrip();
    long serviceDate = query.getServiceDate();
    AgencyAndId vehicleId = query.getVehicleId();
    long time = query.getTime();

    Map<BlockInstance, List<BlockLocation>> locationsByInstance = _blockStatusService.getBlocks(
        trip.getBlock().getId(), serviceDate, vehicleId, time);

    if (locationsByInstance.isEmpty())
      return null;

    Map.Entry<BlockInstance, List<BlockLocation>> entry = locationsByInstance.entrySet().iterator().next();

    BlockInstance blockInstance = entry.getKey();
    List<BlockLocation> locations = entry.getValue();

    int timeOfServiceDate = (int) ((time - serviceDate) / 1000);

    ArrivalAndDepartureInstance instance = createArrivalAndDeparture(
        blockInstance, trip.getId(), stop.getId(), stopSequence, serviceDate,
        timeOfServiceDate, time);

    if (!locations.isEmpty()) {

      /**
       * What if there are multiple locations? Pick the first?
       */
      BlockLocation location = locations.get(0);
      applyBlockLocationToInstance(instance, location, time);
    }

    return instance;
  }

  @Override
  public ArrivalAndDepartureInstance getPreviousStopArrivalAndDeparture(
      ArrivalAndDepartureInstance instance) {

    BlockStopTimeEntry stopTime = instance.getBlockStopTime();
    BlockTripEntry trip = stopTime.getTrip();
    BlockConfigurationEntry blockConfig = trip.getBlockConfiguration();
    List<BlockStopTimeEntry> stopTimes = blockConfig.getStopTimes();

    int index = stopTime.getBlockSequence() - 1;
    if (index < 0)
      return null;

    BlockStopTimeEntry prevStopTime = stopTimes.get(index);
    InstanceState state = instance.getStopTimeInstance().getState();
    ArrivalAndDepartureTime scheduledTime = ArrivalAndDepartureTime.getScheduledTime(
        state, prevStopTime);

    if (instance.getFrequency() != null) {

      StopTimeEntry pStopTime = prevStopTime.getStopTime();

      int betweenStopDelta = stopTime.getStopTime().getArrivalTime()
          - pStopTime.getDepartureTime();
      int atStopDelta = pStopTime.getDepartureTime()
          - pStopTime.getArrivalTime();

      long scheduledDepartureTime = instance.getScheduledArrivalTime()
          - betweenStopDelta * 1000;
      long scheduledArrivalTime = scheduledDepartureTime - atStopDelta * 1000;

      scheduledTime.setArrivalTime(scheduledArrivalTime);
      scheduledTime.setDepartureTime(scheduledDepartureTime);
    }

    StopTimeInstance prevStopTimeInstance = new StopTimeInstance(prevStopTime,
        state);
    ArrivalAndDepartureInstance prevInstance = new ArrivalAndDepartureInstance(
        prevStopTimeInstance, scheduledTime);

    if (instance.isPredictedArrivalTimeSet()) {

      int scheduledDeviation = (int) ((instance.getPredictedArrivalTime() - instance.getScheduledArrivalTime()) / 1000);

      int departureDeviation = propagateScheduleDeviationBackwardBetweenStops(
          prevStopTime, stopTime, scheduledDeviation);
      int arrivalDeviation = propagateScheduleDeviationBackwardAcrossStop(
          prevStopTime, departureDeviation);

      setPredictedArrivalTimeForInstance(prevInstance,
          prevInstance.getScheduledArrivalTime() + arrivalDeviation * 1000);
      setPredictedDepartureTimeForInstance(prevInstance,
          prevInstance.getScheduledDepartureTime() + departureDeviation * 1000);
    }

    return prevInstance;
  }

  @Override
  public ArrivalAndDepartureInstance getNextStopArrivalAndDeparture(
      ArrivalAndDepartureInstance instance) {

    BlockStopTimeEntry stopTime = instance.getBlockStopTime();
    BlockTripEntry trip = stopTime.getTrip();
    BlockConfigurationEntry blockConfig = trip.getBlockConfiguration();
    List<BlockStopTimeEntry> stopTimes = blockConfig.getStopTimes();

    int index = stopTime.getBlockSequence() + 1;
    if (index >= stopTimes.size())
      return null;

    BlockStopTimeEntry nextStopTime = stopTimes.get(index);
    InstanceState state = instance.getStopTimeInstance().getState();

    ArrivalAndDepartureTime scheduledTime = ArrivalAndDepartureTime.getScheduledTime(
        state, nextStopTime);

    if (state.getFrequency() != null) {

      StopTimeEntry nStopTime = nextStopTime.getStopTime();

      int betweenStopDelta = nStopTime.getArrivalTime()
          - stopTime.getStopTime().getDepartureTime();
      int atStopDelta = nStopTime.getDepartureTime()
          - nStopTime.getArrivalTime();

      long scheduledArrivalTime = instance.getScheduledDepartureTime()
          + betweenStopDelta * 1000;
      long scheduledDepartureTime = scheduledArrivalTime + atStopDelta * 1000;

      scheduledTime.setArrivalTime(scheduledArrivalTime);
      scheduledTime.setDepartureTime(scheduledDepartureTime);
    }

    StopTimeInstance nextStopTimeInstance = new StopTimeInstance(stopTime,
        state);
    ArrivalAndDepartureInstance nextInstance = new ArrivalAndDepartureInstance(
        nextStopTimeInstance, scheduledTime);

    if (instance.isPredictedDepartureTimeSet()) {

      int scheduledDeviation = (int) ((instance.getPredictedDepartureTime() - instance.getScheduledDepartureTime()) / 1000);

      int arrivalDeviation = propagateScheduleDeviationForwardBetweenStops(
          stopTime, nextStopTime, scheduledDeviation);
      int departureDeviation = propagateScheduleDeviationForwardAcrossStop(
          nextStopTime, arrivalDeviation);

      setPredictedArrivalTimeForInstance(nextInstance,
          nextInstance.getScheduledArrivalTime() + arrivalDeviation * 1000);
      setPredictedDepartureTimeForInstance(nextInstance,
          nextInstance.getScheduledDepartureTime() + departureDeviation * 1000);
    }

    return nextInstance;
  }

  @Override
  public ArrivalAndDepartureInstance getNextTransferStopArrivalAndDeparture(
      ArrivalAndDepartureInstance instance) {

    BlockStopTimeEntry blockStopTime = instance.getBlockStopTime();
    BlockTripEntry trip = blockStopTime.getTrip();
    BlockConfigurationEntry blockConfig = trip.getBlockConfiguration();
    List<BlockStopTimeEntry> stopTimes = blockConfig.getStopTimes();

    int index = blockStopTime.getBlockSequence() + 1;

    while (true) {

      if (index >= stopTimes.size())
        return null;

      BlockStopTimeEntry nextBlockStopTime = stopTimes.get(index);

      StopTimeEntry nextStopTime = nextBlockStopTime.getStopTime();
      StopEntry nextStop = nextStopTime.getStop();

      List<StopTransfer> transfers = _stopTransferService.getTransfersFromStop(nextStop);

      if (!transfers.isEmpty()) {
        InstanceState state = instance.getStopTimeInstance().getState();
        StopTimeInstance nextStopTimeInstance = new StopTimeInstance(
            nextBlockStopTime, state);
        ArrivalAndDepartureTime nextScheduledTime = ArrivalAndDepartureTime.getScheduledTime(
            state, nextBlockStopTime);

        ArrivalAndDepartureInstance nextInstance = new ArrivalAndDepartureInstance(
            nextStopTimeInstance, nextScheduledTime);

        if (state.getFrequency() != null) {

          int betweenStopDelta = nextStopTime.getArrivalTime()
              - blockStopTime.getStopTime().getDepartureTime();
          int atStopDelta = nextStopTime.getDepartureTime()
              - nextStopTime.getArrivalTime();

          long scheduledArrivalTime = instance.getScheduledDepartureTime()
              + betweenStopDelta * 1000;
          long scheduledDepartureTime = scheduledArrivalTime + atStopDelta
              * 1000;

          nextInstance.setScheduledArrivalTime(scheduledArrivalTime);
          nextInstance.setScheduledDepartureTime(scheduledDepartureTime);
        }

        return nextInstance;
      }

      index++;
    }
  }

  @Override
  public List<Pair<ArrivalAndDepartureInstance>> getNextDeparturesForStopPair(
      StopEntry fromStop, StopEntry toStop, TargetTime targetTime,
      ArrivalAndDeparturePairQuery query) {

    Date tFrom = new Date(targetTime.getTargetTime());
    boolean applyRealTime = query.isApplyRealTime();
    int lookaheadTime = query.getLookaheadTime();
    int resultCount = query.getResultCount();
    boolean includePrivateService = query.isIncludePrivateService();

    int runningEarlySlack = applyRealTime ? _blockStatusService.getRunningEarlyWindow() : 0;
    int runningLateSlack = (applyRealTime ? _blockStatusService.getRunningLateWindow() : 0)
        + lookaheadTime;

    List<Pair<StopTimeInstance>> pairs = _stopTimeService.getNextDeparturesBetweenStopPair(
        fromStop, toStop, tFrom, runningEarlySlack, runningLateSlack,
        resultCount, includePrivateService);

    Date tShifted = new Date(targetTime.getTargetTime() - lookaheadTime * 1000);

    return getArrivalsAndDeparturesFromStopTimeInstancePairs(targetTime, pairs,
        tShifted, null, applyRealTime, true, false);
  }

  @Override
  public List<Pair<ArrivalAndDepartureInstance>> getPreviousArrivalsForStopPair(
      StopEntry fromStop, StopEntry toStop, TargetTime targetTime,
      ArrivalAndDeparturePairQuery query) {

    Date tTo = new Date(targetTime.getTargetTime());
    boolean applyRealTime = query.isApplyRealTime();
    int resultCount = query.getResultCount();
    boolean includePrivateService = query.isIncludePrivateService();

    int runningEarlySlack = applyRealTime ? _blockStatusService.getRunningEarlyWindow() : 0;
    int runningLateSlack = applyRealTime ? _blockStatusService.getRunningLateWindow() : 0;

    List<Pair<StopTimeInstance>> pairs = _stopTimeService.getPreviousArrivalsBetweenStopPair(
        fromStop, toStop, tTo, runningEarlySlack, runningLateSlack,
        resultCount, includePrivateService);

    return getArrivalsAndDeparturesFromStopTimeInstancePairs(targetTime, pairs,
        null, tTo, applyRealTime, false, false);
  }

  /****
   * Private Methods
   ****/

  private List<Pair<ArrivalAndDepartureInstance>> getArrivalsAndDeparturesFromStopTimeInstancePairs(
      TargetTime targetTime, List<Pair<StopTimeInstance>> pairs, Date tFrom,
      Date tTo, boolean applyRealTime, boolean findDepartures,
      boolean fillBlockLocations) {

    long frequencyOffsetTime = Math.max(targetTime.getTargetTime(),
        targetTime.getCurrentTime());

    List<Pair<ArrivalAndDepartureInstance>> results = new ArrayList<Pair<ArrivalAndDepartureInstance>>();

    Map<BlockInstance, List<BlockLocation>> blockLocationsByBlockInstance = getBlockLocationInformationForPairs(
        pairs, targetTime, applyRealTime);

    for (Pair<StopTimeInstance> pair : pairs) {

      StopTimeInstance stiFrom = pair.getFirst();
      StopTimeInstance stiTo = pair.getSecond();

      BlockInstance blockInstance = stiFrom.getBlockInstance();
      List<BlockLocation> locations = blockLocationsByBlockInstance.get(blockInstance);

      applyRealTimeToStopTimeInstancePair(stiFrom, stiTo, targetTime, tFrom,
          tTo, frequencyOffsetTime, blockInstance, locations, results,
          findDepartures, fillBlockLocations);
    }

    return results;
  }

  private Map<BlockInstance, List<BlockLocation>> getBlockLocationInformationForPairs(
      List<Pair<StopTimeInstance>> pairs, TargetTime targetTime,
      boolean applyRealTime) {

    if (!applyRealTime)
      return Collections.emptyMap();

    Set<BlockInstance> blockInstances = new HashSet<BlockInstance>();
    for (Pair<StopTimeInstance> pair : pairs)
      blockInstances.add(pair.getFirst().getBlockInstance());

    Map<BlockInstance, List<BlockLocation>> blockLocationsByBlockInstance = new HashMap<BlockInstance, List<BlockLocation>>();

    for (BlockInstance blockInstance : blockInstances) {
      List<BlockLocation> locations = _blockLocationService.getLocationsForBlockInstance(
          blockInstance, targetTime);
      blockLocationsByBlockInstance.put(blockInstance, locations);
    }
    return blockLocationsByBlockInstance;
  }

  private Map<BlockInstance, List<StopTimeInstance>> getStopTimeInstancesByBlockInstance(
      List<StopTimeInstance> stopTimes) {

    Map<BlockInstance, List<StopTimeInstance>> r = new FactoryMap<BlockInstance, List<StopTimeInstance>>(
        new ArrayList<StopTimeInstance>());

    for (StopTimeInstance stopTime : stopTimes) {
      BlockStopTimeEntry blockStopTime = stopTime.getStopTime();
      BlockTripEntry blockTrip = blockStopTime.getTrip();
      BlockConfigurationEntry blockConfiguration = blockTrip.getBlockConfiguration();
      long serviceDate = stopTime.getServiceDate();
      BlockInstance blockInstance = new BlockInstance(blockConfiguration,
          serviceDate, stopTime.getFrequency());
      r.get(blockInstance).add(stopTime);
    }

    return r;
  }

  private void applyRealTimeToStopTimeInstance(StopTimeInstance sti,
      TargetTime targetTime, long fromTime, long toTime,
      long frequencyOffsetTime, BlockInstance blockInstance,
      List<BlockLocation> locations, List<ArrivalAndDepartureInstance> results) {

    for (BlockLocation location : locations) {

      ArrivalAndDepartureInstance instance = createArrivalAndDepartureForStopTimeInstance(
          sti, frequencyOffsetTime);
      applyBlockLocationToInstance(instance, location,
          targetTime.getTargetTime());

      if (isArrivalAndDepartureBeanInRange(instance, fromTime, toTime))
        results.add(instance);
    }

    if (locations.isEmpty()) {

      ArrivalAndDepartureInstance instance = createArrivalAndDepartureForStopTimeInstance(
          sti, frequencyOffsetTime);

      if (sti.getFrequency() == null) {

        /**
         * We don't need to get the scheduled location of a vehicle unless its
         * in our arrival window
         */
        if (isArrivalAndDepartureBeanInRange(instance, fromTime, toTime)) {

          BlockLocation scheduledLocation = _blockLocationService.getScheduledLocationForBlockInstance(
              blockInstance, targetTime.getTargetTime());

          if (scheduledLocation != null)
            applyBlockLocationToInstance(instance, scheduledLocation,
                targetTime.getTargetTime());

          results.add(instance);
        }

      } else {
        if (isFrequencyBasedArrivalInRange(blockInstance, sti.getFrequency(),
            fromTime, toTime)) {
          results.add(instance);
        }
      }
    }
  }

  private void applyRealTimeToStopTimeInstancePair(StopTimeInstance stiFrom,
      StopTimeInstance stiTo, TargetTime targetTime, Date fromTime,
      Date toTime, long frequencyOffsetTime, BlockInstance blockInstance,
      List<BlockLocation> locations,
      List<Pair<ArrivalAndDepartureInstance>> results, boolean findDepartures,
      boolean fillBlockLocations) {

    if (CollectionsLibrary.isEmpty(locations)) {

      ArrivalAndDepartureInstance instanceFrom = createArrivalAndDepartureForStopTimeInstance(
          stiFrom, frequencyOffsetTime);
      ArrivalAndDepartureInstance instanceTo = createArrivalAndDepartureForStopTimeInstance(
          stiTo, frequencyOffsetTime);

      /**
       * We don't need to get the scheduled location of a vehicle unless its in
       * our arrival window
       */
      if (isArrivalAndDeparturePairInRange(instanceFrom, instanceTo, fromTime,
          toTime, findDepartures)) {

        if (fillBlockLocations) {
          BlockLocation scheduledLocation = _blockLocationService.getScheduledLocationForBlockInstance(
              blockInstance, targetTime.getTargetTime());

          if (scheduledLocation != null) {
            applyBlockLocationToInstance(instanceFrom, scheduledLocation,
                targetTime.getTargetTime());
            applyBlockLocationToInstance(instanceTo, scheduledLocation,
                targetTime.getTargetTime());
          }
        }

        results.add(Tuples.pair(instanceFrom, instanceTo));
      }

    } else {

      for (BlockLocation location : locations) {

        ArrivalAndDepartureInstance instanceFrom = createArrivalAndDepartureForStopTimeInstance(
            stiFrom, frequencyOffsetTime);
        ArrivalAndDepartureInstance instanceTo = createArrivalAndDepartureForStopTimeInstance(
            stiTo, frequencyOffsetTime);

        applyBlockLocationToInstance(instanceFrom, location,
            targetTime.getTargetTime());
        applyBlockLocationToInstance(instanceTo, location,
            targetTime.getTargetTime());

        if (isArrivalAndDeparturePairInRange(instanceFrom, instanceTo,
            fromTime, toTime, findDepartures))
          results.add(Tuples.pair(instanceFrom, instanceTo));
      }
    }
  }

  private void applyBlockLocationToInstance(
      ArrivalAndDepartureInstance instance, BlockLocation blockLocation,
      long targetTime) {

    instance.setBlockLocation(blockLocation);

    if (blockLocation.isScheduleDeviationSet()
        || blockLocation.areScheduleDeviationsSet()) {

      int scheduleDeviation = getBestScheduleDeviation(instance, blockLocation);
      setPredictedTimesFromScheduleDeviation(instance, blockLocation,
          scheduleDeviation, targetTime);
    }
  }

  private int getBestScheduleDeviation(ArrivalAndDepartureInstance instance,
      BlockLocation blockLocation) {

    ScheduleDeviationSamples scheduleDeviations = blockLocation.getScheduleDeviations();

    if (scheduleDeviations != null && !scheduleDeviations.isEmpty()) {
      int arrivalTime = instance.getBlockStopTime().getStopTime().getArrivalTime();
      return (int) InterpolationLibrary.interpolate(
          scheduleDeviations.getScheduleTimes(),
          scheduleDeviations.getScheduleDeviationMus(), arrivalTime,
          EOutOfRangeStrategy.LAST_VALUE);
    } else if (blockLocation.isScheduleDeviationSet()) {
      return (int) blockLocation.getScheduleDeviation();
    } else {
      return 0;
    }
  }

  private void setPredictedTimesFromScheduleDeviation(
      ArrivalAndDepartureInstance instance, BlockLocation blockLocation,
      int scheduleDeviation, long targetTime) {

    BlockStopTimeEntry blockStopTime = instance.getBlockStopTime();

    int effectiveScheduleTime = (int) (((targetTime - instance.getServiceDate()) / 1000) - scheduleDeviation);

    int arrivalDeviation = calculateArrivalDeviation(
        blockLocation.getNextStop(), blockStopTime, effectiveScheduleTime,
        scheduleDeviation);

    int departureDeviation = calculateDepartureDeviation(
        blockLocation.getNextStop(), blockStopTime, effectiveScheduleTime,
        scheduleDeviation);

    /**
     * Why don't we use the ArrivalAndDepartureTime scheduled arrival and
     * departures here? Because they may have been artificially shifted for a
     * frequency-based method
     */
    InstanceState state = instance.getStopTimeInstance().getState();
    ArrivalAndDepartureTime schedule = ArrivalAndDepartureTime.getScheduledTime(
        state, instance.getBlockStopTime());

    long arrivalTime = schedule.getArrivalTime() + arrivalDeviation * 1000;
    setPredictedArrivalTimeForInstance(instance, arrivalTime);

    TimeIntervalBean predictedArrivalTimeInterval = computePredictedArrivalTimeInterval(
        instance, blockLocation, targetTime);
    instance.setPredictedArrivalInterval(predictedArrivalTimeInterval);

    long departureTime = schedule.getDepartureTime() + departureDeviation
        * 1000;
    setPredictedDepartureTimeForInstance(instance, departureTime);

    TimeIntervalBean predictedDepartureTimeInterval = computePredictedDepartureTimeInterval(
        instance, blockLocation, targetTime);
    instance.setPredictedDepartureInterval(predictedDepartureTimeInterval);

  }

  /**
   * This method both sets the predicted arrival time for an instance, but also
   * updates the scheduled arrival time for a frequency-based instance
   *
   * @param instance
   * @param arrivalTime
   */
  private void setPredictedArrivalTimeForInstance(
      ArrivalAndDepartureInstance instance, long arrivalTime) {

    instance.setPredictedArrivalTime(arrivalTime);

    if (instance.getFrequency() != null)
      instance.setScheduledArrivalTime(arrivalTime);
  }

  /**
   * This method both sets the predicted departure time for an instance, but
   * also updates the scheduled departure time for a frequency-based instance
   *
   * @param instance
   * @param departureTime
   */
  private void setPredictedDepartureTimeForInstance(
      ArrivalAndDepartureInstance instance, long departureTime) {

    instance.setPredictedDepartureTime(departureTime);

    if (instance.getFrequency() != null)
      instance.setScheduledDepartureTime(departureTime);
  }

  private int calculateArrivalDeviation(BlockStopTimeEntry nextBlockStopTime,
      BlockStopTimeEntry targetBlockStopTime, int effectiveScheduleTime,
      int scheduleDeviation) {

    // TargetStopTime

    if (nextBlockStopTime == null
        || nextBlockStopTime.getBlockSequence() > targetBlockStopTime.getBlockSequence()) {
      return scheduleDeviation;
    }

    int a = targetBlockStopTime.getAccumulatedSlackTime();
    int b = nextBlockStopTime.getAccumulatedSlackTime();
    double slack = a - b;

    StopTimeEntry nextStopTime = nextBlockStopTime.getStopTime();

    if (nextStopTime.getArrivalTime() <= effectiveScheduleTime
        && effectiveScheduleTime <= nextStopTime.getDepartureTime()) {
      slack -= (effectiveScheduleTime - nextStopTime.getArrivalTime());
    }

    slack = Math.max(slack, 0);

    if (slack > 0 && scheduleDeviation > 0)
      scheduleDeviation -= Math.min(scheduleDeviation, slack);

    return scheduleDeviation;
  }

  private int calculateDepartureDeviation(BlockStopTimeEntry nextBlockStopTime,
      BlockStopTimeEntry targetBlockStopTime, int effectiveScheduleTime,
      int scheduleDeviation) {

    // TargetStopTime
    if (nextBlockStopTime == null
        || nextBlockStopTime.getBlockSequence() > targetBlockStopTime.getBlockSequence()) {
      return scheduleDeviation;
    }

    StopTimeEntry nextStopTime = nextBlockStopTime.getStopTime();
    StopTimeEntry targetStopTime = targetBlockStopTime.getStopTime();

    double slack = targetBlockStopTime.getAccumulatedSlackTime()
        - nextBlockStopTime.getAccumulatedSlackTime();

    slack += targetStopTime.getSlackTime();

    if (nextStopTime.getArrivalTime() <= effectiveScheduleTime
        && effectiveScheduleTime <= nextStopTime.getDepartureTime()) {
      slack -= (effectiveScheduleTime - nextStopTime.getArrivalTime());
    }

    slack = Math.max(slack, 0);

    if (slack > 0 && scheduleDeviation > 0)
      scheduleDeviation -= Math.min(scheduleDeviation, slack);

    return scheduleDeviation;
  }

  private int propagateScheduleDeviationForwardBetweenStops(
      BlockStopTimeEntry prevStopTime, BlockStopTimeEntry nextStopTime,
      int scheduleDeviation) {

    int slack = nextStopTime.getAccumulatedSlackTime()
        - prevStopTime.getAccumulatedSlackTime();

    slack -= prevStopTime.getStopTime().getSlackTime();

    return propagateScheduleDeviationForwardWithSlack(scheduleDeviation, slack);
  }

  private int propagateScheduleDeviationForwardAcrossStop(
      BlockStopTimeEntry stopTime, int scheduleDeviation) {

    int slack = stopTime.getStopTime().getSlackTime();

    return propagateScheduleDeviationForwardWithSlack(scheduleDeviation, slack);
  }

  private int propagateScheduleDeviationBackwardBetweenStops(
      BlockStopTimeEntry prevStopTime, BlockStopTimeEntry nextStopTime,
      int scheduleDeviation) {

    // TODO: Need to think about this

    return scheduleDeviation;
  }

  private int propagateScheduleDeviationBackwardAcrossStop(
      BlockStopTimeEntry stopTime, int scheduleDeviation) {

    return scheduleDeviation;
  }

  private int propagateScheduleDeviationForwardWithSlack(int scheduleDeviation,
      int slack) {
    /**
     * If the vehicle is running early and there is slack built into the
     * schedule, we guess that the vehicle will take that opportunity to pause
     * and let the schedule catch back up. If there is no slack, assume we'll
     * continue to run early.
     */
    if (scheduleDeviation < 0) {
      if (slack > 0)
        return 0;
      return scheduleDeviation;
    }

    /**
     * If we're running behind schedule, we allow any slack to eat up part of
     * our delay.
     */
    return Math.max(0, scheduleDeviation - slack);
  }

  private TimeIntervalBean computePredictedArrivalTimeInterval(
      ArrivalAndDepartureInstance instance, BlockLocation blockLocation,
      long targetTime) {

    BlockStopTimeEntry blockStopTime = instance.getBlockStopTime();
    StopTimeEntry stopTime = blockStopTime.getStopTime();

    // If the vehicle has already passed the stop, then there is no prediction
    // interval
    if (stopTime.getArrivalTime() <= blockLocation.getEffectiveScheduleTime())
      return null;

    ScheduleDeviationSamples samples = blockLocation.getScheduleDeviations();

    if (samples == null || samples.isEmpty())
      return null;

    double mu = InterpolationLibrary.interpolate(samples.getScheduleTimes(),
        samples.getScheduleDeviationMus(), stopTime.getArrivalTime(),
        EOutOfRangeStrategy.LAST_VALUE);
    double sigma = InterpolationLibrary.interpolate(samples.getScheduleTimes(),
        samples.getScheduleDeviationSigmas(), stopTime.getArrivalTime(),
        EOutOfRangeStrategy.LAST_VALUE);

    long from = (long) (instance.getScheduledArrivalTime() + (mu - sigma) * 1000);
    long to = (long) (instance.getScheduledArrivalTime() + (mu + sigma) * 1000);

    return new TimeIntervalBean(from, to);
  }

  private TimeIntervalBean computePredictedDepartureTimeInterval(
      ArrivalAndDepartureInstance instance, BlockLocation blockLocation,
      long targetTime) {

    BlockStopTimeEntry blockStopTime = instance.getBlockStopTime();
    StopTimeEntry stopTime = blockStopTime.getStopTime();

    // If the vehicle has already passed the stop, then there is no prediction
    // interval
    if (stopTime.getDepartureTime() <= blockLocation.getEffectiveScheduleTime())
      return null;

    ScheduleDeviationSamples samples = blockLocation.getScheduleDeviations();

    if (samples == null || samples.isEmpty())
      return null;

    double mu = InterpolationLibrary.interpolate(samples.getScheduleTimes(),
        samples.getScheduleDeviationMus(), stopTime.getDepartureTime(),
        EOutOfRangeStrategy.LAST_VALUE);
    double sigma = InterpolationLibrary.interpolate(samples.getScheduleTimes(),
        samples.getScheduleDeviationSigmas(), stopTime.getDepartureTime(),
        EOutOfRangeStrategy.LAST_VALUE);

    long from = (long) (instance.getScheduledDepartureTime() + (mu - sigma) * 1000);
    long to = (long) (instance.getScheduledDepartureTime() + (mu + sigma) * 1000);

    return new TimeIntervalBean(from, to);
  }

  private boolean isArrivalAndDepartureBeanInRange(
      ArrivalAndDepartureInstance instance, long timeFrom, long timeTo) {
    if (timeFrom <= instance.getScheduledArrivalTime()
        && instance.getScheduledArrivalTime() <= timeTo)
      return true;
    if (timeFrom <= instance.getScheduledDepartureTime()
        && instance.getScheduledDepartureTime() <= timeTo)
      return true;
    if (instance.isPredictedArrivalTimeSet()
        && timeFrom <= instance.getPredictedArrivalTime()
        && instance.getPredictedArrivalTime() <= timeTo)
      return true;
    if (instance.isPredictedDepartureTimeSet()
        && timeFrom <= instance.getPredictedDepartureTime()
        && instance.getPredictedDepartureTime() <= timeTo)
      return true;
    return false;
  }

  private boolean isFrequencyBasedArrivalInRange(BlockInstance blockInstance,
      FrequencyEntry frequency, long fromReduced, long toReduced) {
    long startTime = blockInstance.getServiceDate() + frequency.getStartTime()
        * 1000;
    long endTime = blockInstance.getServiceDate() + frequency.getEndTime()
        * 1000;
    return fromReduced <= endTime && startTime <= toReduced;
  }

  private boolean isArrivalAndDeparturePairInRange(
      ArrivalAndDepartureInstance instanceFrom,
      ArrivalAndDepartureInstance instanceTo, Date timeFrom, Date timeTo,
      boolean findDepartures) {

    ArrivalAndDepartureInstance instance = findDepartures ? instanceFrom
        : instanceTo;

    if (timeFrom != null) {
      boolean schedInRange = instance.getScheduledDepartureTime() >= timeFrom.getTime();
      boolean realTimeInRange = true;
      if (instance.isPredictedDepartureTimeSet())
        realTimeInRange = instance.getPredictedDepartureTime() >= timeFrom.getTime();
      if (!(schedInRange || realTimeInRange))
        return false;
    }

    if (timeTo != null) {
      boolean schedInRange = instance.getScheduledArrivalTime() <= timeTo.getTime();
      boolean realTimeInRange = true;
      if (instance.isPredictedArrivalTimeSet())
        realTimeInRange = instance.getPredictedArrivalTime() <= timeTo.getTime();
      if (!(schedInRange || realTimeInRange))
        return false;
    }

    return true;
  }

  private ArrivalAndDepartureInstance createArrivalAndDepartureForStopTimeInstance(
      StopTimeInstance sti, long prevFrequencyTime) {

    ArrivalAndDepartureInstance instance = createArrivalAndDeparture(sti,
        prevFrequencyTime, sti.getFrequencyOffset());

    instance.setBlockSequence(sti.getBlockSequence());

    return instance;
  }

  private ArrivalAndDepartureInstance createArrivalAndDeparture(
      BlockInstance blockInstance, AgencyAndId tripId, AgencyAndId stopId,
      int stopSequence, long serviceDate, int timeOfServiceDate,
      long prevFrequencyTime) {

    BlockTripInstance blockTripInstance = BlockTripInstanceLibrary.getBlockTripInstance(
        blockInstance, tripId);

    if (blockTripInstance == null)
      return null;

    BlockStopTimeEntry blockStopTime = getBlockStopTime(blockTripInstance,
        stopId, stopSequence, timeOfServiceDate);
    StopTimeInstance stopTimeInstance = new StopTimeInstance(blockStopTime,
        blockTripInstance.getState());

    return createArrivalAndDeparture(stopTimeInstance, prevFrequencyTime,
        StopTimeInstance.UNSPECIFIED_FREQUENCY_OFFSET);
  }

  private BlockStopTimeEntry getBlockStopTime(
      BlockTripInstance blockTripInstance, AgencyAndId stopId,
      int stopSequence, int timeOfServiceDate) {

    /**
     * We don't iterate over block stop times directly because there is
     * performance penalty with instantiating each. Also note that this will
     * currently miss the case where a stop is visited twice in the same trip.
     */
    BlockTripEntry blockTrip = blockTripInstance.getBlockTrip();
    TripEntry trip = blockTrip.getTrip();
    List<StopTimeEntry> stopTimes = trip.getStopTimes();

    if (stopSequence > -1) {

      /**
       * If a stop sequence has been specified, we start our search at the
       * specified index, expanding our search until we find the target stop. We
       * allow this flexibility in the case of a bookmarked arrival-departure
       * where the stop sequence has changed slightly due to the addition or
       * subtraction of a previous stop.
       */
      int offset = 0;
      while (true) {
        int before = stopSequence - offset;
        if (isMatch(stopTimes, stopId, before)) {
          return blockTrip.getStopTimes().get(before);
        }
        int after = stopSequence + offset;
        if (isMatch(stopTimes, stopId, after)) {
          return blockTrip.getStopTimes().get(after);
        }

        if (before < 0 && after >= stopTimes.size())
          return null;

        offset++;

      }
    } else {

      Min<BlockStopTimeEntry> m = new Min<BlockStopTimeEntry>();
      int index = 0;

      for (StopTimeEntry stopTime : stopTimes) {
        if (stopTime.getStop().getId().equals(stopId)) {
          int a = Math.abs(timeOfServiceDate - stopTime.getArrivalTime());
          int b = Math.abs(timeOfServiceDate - stopTime.getDepartureTime());
          int delta = Math.min(a, b);
          m.add(delta, blockTrip.getStopTimes().get(index));
        }
        index++;
      }

      if (m.isEmpty())
        return null;

      return m.getMinElement();
    }
  }

  private boolean isMatch(List<StopTimeEntry> stopTimes, AgencyAndId stopId,
      int index) {
    if (index < 0 || index >= stopTimes.size())
      return false;
    StopTimeEntry stopTime = stopTimes.get(index);
    StopEntry stop = stopTime.getStop();
    return stop.getId().equals(stopId);
  }

  private ArrivalAndDepartureInstance createArrivalAndDeparture(
      StopTimeInstance stopTimeInstance, long prevFrequencyTime,
      int frequencyOffset) {

    ArrivalAndDepartureTime scheduledTime = getScheduledTime(stopTimeInstance,
        prevFrequencyTime, frequencyOffset);

    return new ArrivalAndDepartureInstance(stopTimeInstance, scheduledTime);
  }

  /**
   * Constructs an {@link ArrivalAndDepartureTime} object for the specified
   * {@link BlockInstance} and {@link BlockStopTimeEntry}.
   *
   * For frequency-based trips, the calculation is a bit complicated.
   *
   *
   * @param blockInstance
   * @param blockStopTime
   * @param prevFrequencyTime
   * @param frequencyOffset
   * @return
   */
  private ArrivalAndDepartureTime getScheduledTime(
      StopTimeInstance stopTimeInstance, long prevFrequencyTime,
      int frequencyOffset) {

    FrequencyEntry frequency = stopTimeInstance.getFrequency();

    if (frequency == null) {

      return ArrivalAndDepartureTime.getScheduledTime(stopTimeInstance);

    } else if (StopTimeInstance.isFrequencyOffsetSpecified(frequencyOffset)) {

      return ArrivalAndDepartureTime.getScheduledTime(
          stopTimeInstance.getServiceDate(), stopTimeInstance.getStopTime(),
          frequencyOffset);

    } else {

      long departureTime = prevFrequencyTime + frequency.getHeadwaySecs()
          * 1000 / 2;

      long freqStartTime = stopTimeInstance.getServiceDate()
          + frequency.getStartTime() * 1000;
      long freqEndTime = stopTimeInstance.getServiceDate()
          + frequency.getEndTime() * 1000;

      if (departureTime < freqStartTime)
        departureTime = freqStartTime;
      if (departureTime > freqEndTime)
        departureTime = freqEndTime;

      /**
       * We need to make sure the arrival time is adjusted relative to the
       * departure time and the layover at the stop.
       */
      BlockStopTimeEntry blockStopTime = stopTimeInstance.getStopTime();
      StopTimeEntry stopTime = blockStopTime.getStopTime();
      int delta = stopTime.getDepartureTime() - stopTime.getArrivalTime();

      long arrivalTime = departureTime - delta * 1000;

      return new ArrivalAndDepartureTime(arrivalTime, departureTime);
    }
  }
}
TOP

Related Classes of org.onebusaway.transit_data_federation.impl.ArrivalAndDepartureServiceImpl

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.