Package org.onebusaway.transit_data_federation.impl.realtime

Source Code of org.onebusaway.transit_data_federation.impl.realtime.BlockLocationServiceImpl

/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
* Copyright (C) 2011 <inf71391@gmail.com>
* Copyright (C) 2012 Google, Inc.
*
* 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.realtime;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.onebusaway.collections.CollectionsLibrary;
import org.onebusaway.collections.FactoryMap;
import org.onebusaway.collections.Min;
import org.onebusaway.collections.Range;
import org.onebusaway.container.ConfigurationParameter;
import org.onebusaway.geospatial.model.CoordinatePoint;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.realtime.api.TimepointPredictionRecord;
import org.onebusaway.realtime.api.VehicleLocationRecord;
import org.onebusaway.transit_data_federation.model.TargetTime;
import org.onebusaway.transit_data_federation.services.blocks.BlockCalendarService;
import org.onebusaway.transit_data_federation.services.blocks.BlockInstance;
import org.onebusaway.transit_data_federation.services.blocks.BlockVehicleLocationListener;
import org.onebusaway.transit_data_federation.services.blocks.ScheduledBlockLocation;
import org.onebusaway.transit_data_federation.services.blocks.ScheduledBlockLocationService;
import org.onebusaway.transit_data_federation.services.realtime.BlockLocation;
import org.onebusaway.transit_data_federation.services.realtime.BlockLocationListener;
import org.onebusaway.transit_data_federation.services.realtime.BlockLocationService;
import org.onebusaway.transit_data_federation.services.realtime.RealTimeHistoryService;
import org.onebusaway.transit_data_federation.services.realtime.ScheduleDeviationSamples;
import org.onebusaway.transit_data_federation.services.realtime.VehicleLocationCacheElement;
import org.onebusaway.transit_data_federation.services.realtime.VehicleLocationCacheElements;
import org.onebusaway.transit_data_federation.services.realtime.VehicleLocationRecordCache;
import org.onebusaway.transit_data_federation.services.transit_graph.BlockConfigurationEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.BlockEntry;
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.StopEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.StopTimeEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.TransitGraphDao;
import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;

/**
* Implementation for {@link BlockLocationService}. Keeps a recent cache of
* {@link BlockLocationRecord} records for current queries and can access
* database persisted records for queries in the past.
*
* @author bdferris
* @see BlockLocationService
*/
@Component
@ManagedResource("org.onebusaway.transit_data_federation.impl.realtime:name=BlockLocationServiceImpl")
public class BlockLocationServiceImpl implements BlockLocationService,
    BlockVehicleLocationListener {

  private static Logger _log = LoggerFactory.getLogger(BlockLocationServiceImpl.class);

  private VehicleLocationRecordCache _cache;

  private BlockLocationRecordDao _blockLocationRecordDao;

  private TransitGraphDao _transitGraphDao;

  private ScheduledBlockLocationService _scheduledBlockLocationService;

  private BlockCalendarService _blockCalendarService;

  private RealTimeHistoryService _realTimeHistoryService;

  private List<BlockLocationListener> _blockLocationListeners = Collections.emptyList();

  /**
   * By default, we keep around 20 minutes of cache entries
   */
  private int _blockLocationRecordCacheWindowSize = 20 * 60;

  private int _predictionCacheMaxOffset = 5 * 60;

  private int _blockInstanceMatchingWindow = 60 * 60 * 1000;

  /**
   * When true, we will interpolate the current location of a transit vehicle
   * based on estimated schedule deviation information. If false, we will use
   * the last known location of the bus as the position.
   */
  private boolean _locationInterpolation = true;

  /**
   * When true, we attempt to interpolate the current location of the vehicle
   * given the most recent distance-along-block update. This parameter has been
   * deprecated in favor of the more-general {@link #_locationInterpolation}
   * parameter.
   */
  @Deprecated
  private boolean _distanceAlongBlockLocationInterpolation = false;

  /**
   * Should block location records be stored to the database?
   */
  private boolean _persistBlockLocationRecords = false;

  /**
   * We queue up block location records so they can be bulk persisted to the
   * database
   */
  private List<BlockLocationRecord> _recordPersistenceQueue = new ArrayList<BlockLocationRecord>();

  /**
   * Used to schedule periodic flushes to the database of the block location
   * records queue
   */
  private ScheduledExecutorService _executor = Executors.newSingleThreadScheduledExecutor();

  /**
   * Block location record persistence stats - last record insert duration
   */
  private volatile long _lastInsertDuration = 0;

  /**
   * Block location record persistence stats - last record insert count
   */
  private volatile long _lastInsertCount = 0;

  /**
   * Records the number of times block location record cache requests fall
   * through to the database
   */
  private AtomicInteger _blockLocationRecordPersistentStoreAccessCount = new AtomicInteger();

  @Autowired
  public void setVehicleLocationRecordCache(VehicleLocationRecordCache cache) {
    _cache = cache;
  }

  @Autowired
  public void setBlockLocationRecordDao(
      BlockLocationRecordDao blockLocationRecordDao) {
    _blockLocationRecordDao = blockLocationRecordDao;
  }

  @Autowired
  public void setTransitGraphDao(TransitGraphDao transitGraphDao) {
    _transitGraphDao = transitGraphDao;
  }

  @Autowired
  public void setScheduledBlockLocationService(
      ScheduledBlockLocationService scheduleBlockLocationService) {
    _scheduledBlockLocationService = scheduleBlockLocationService;
  }

  @Autowired
  public void setBlockCalendarService(BlockCalendarService blockCalendarService) {
    _blockCalendarService = blockCalendarService;
  }

  @Autowired
  public void setRealTimeHistoryService(
      RealTimeHistoryService realTimeHistoryService) {
    _realTimeHistoryService = realTimeHistoryService;
  }

  @Autowired
  public void setBlockLocationListeners(List<BlockLocationListener> listeners) {
    _blockLocationListeners = listeners;
  }

  /**
   * Controls how far back in time we include records in the
   * {@link BlockLocationRecordCollection} for each active trip.
   *
   * @param windowSize in seconds
   */
  @ConfigurationParameter
  public void setBlockLocationRecordCacheWindowSize(int windowSize) {
    _blockLocationRecordCacheWindowSize = windowSize;
  }

  /**
   * Should we persist {@link BlockLocationRecord} records to an underlying
   * datastore. Useful if you wish to query trip status for historic analysis.
   *
   * @param persistTripTimePredictions
   */
  @ConfigurationParameter
  public void setPersistBlockLocationRecords(boolean persistBlockLocationRecords) {
    _persistBlockLocationRecords = persistBlockLocationRecords;
  }

  /**
   * When true, we will interpolate the current location of a transit vehicle
   * based on the last know location of the bus and the schedule deviation of
   * the bus at the time. If false, we will use the last known location of the
   * bus as the current location.
   *
   * @param locationInterpolation
   */
  @ConfigurationParameter
  public void setLocationInterpolation(boolean locationInterpolation) {
    _locationInterpolation = locationInterpolation;
  }

  /**
   * @param distanceAlongBlockLocationInterpolation
   *
   * @deprecated in favor of the more general
   *             {@link #setLocationInterpolation(boolean)} configuration
   *             method.
   */
  @Deprecated
  public void setDistanceAlongBlockLocationInterpolation(
      boolean distanceAlongBlockLocationInterpolation) {
    _distanceAlongBlockLocationInterpolation = distanceAlongBlockLocationInterpolation;
  }

  /****
   * JMX Attributes
   ****/

  @ManagedAttribute
  public long getLastInsertDuration() {
    return _lastInsertDuration;
  }

  @ManagedAttribute
  public long getLastInsertCount() {
    return _lastInsertCount;
  }

  @ManagedAttribute
  public long getBlockLocationRecordPersistentStoreAccessCount() {
    return _blockLocationRecordPersistentStoreAccessCount.get();
  }

  /****
   * Setup and Teardown
   ****/

  @PostConstruct
  public void start() {
    if (_persistBlockLocationRecords)
      _executor.scheduleAtFixedRate(new PredictionWriter(), 0, 1,
          TimeUnit.SECONDS);
  }

  @PreDestroy
  public void stop() {
    _executor.shutdownNow();
  }

  /****
   * {@link BlockVehicleLocationListener} Interface
   ****/

  @Override
  public void handleVehicleLocationRecord(VehicleLocationRecord record) {

    BlockInstance instance = getVehicleLocationRecordAsBlockInstance(record);

    if (instance != null) {

      ScheduledBlockLocation scheduledBlockLocation = getScheduledBlockLocationForVehicleLocationRecord(
          record, instance);

      if (!record.isScheduleDeviationSet()) {
        int deviation = (int) ((record.getTimeOfRecord() - record.getServiceDate()) / 1000 - scheduledBlockLocation.getScheduledTime());
        record.setScheduleDeviation(deviation);
      }

      ScheduleDeviationSamples samples = _realTimeHistoryService.sampleScheduleDeviationsForVehicle(
          instance, record, scheduledBlockLocation);

      putBlockLocationRecord(instance, record, scheduledBlockLocation, samples);
    }
  }

  @Override
  public void resetVehicleLocation(AgencyAndId vehicleId) {
    _cache.clearRecordsForVehicleId(vehicleId);
  }

  /****
   * {@link BlockLocationService} Interface
   ****/

  @Override
  public BlockLocation getLocationForBlockInstance(BlockInstance blockInstance,
      TargetTime time) {

    List<VehicleLocationCacheElements> records = getBlockLocationRecordCollectionForBlock(
        blockInstance, time);

    VehicleLocationCacheElements record = null;
    if (!records.isEmpty())
      record = records.get(0);

    // TODO : find a better way to pick?
    return getBlockLocation(blockInstance, record, null, time.getTargetTime());
  }

  @Override
  public BlockLocation getLocationForBlockInstanceAndScheduledBlockLocation(
      BlockInstance blockInstance, ScheduledBlockLocation scheduledLocation,
      long targetTime) {
    return getBlockLocation(blockInstance, null, scheduledLocation, targetTime);
  }

  @Override
  public List<BlockLocation> getLocationsForBlockInstance(
      BlockInstance blockInstance, TargetTime time) {

    List<VehicleLocationCacheElements> records = getBlockLocationRecordCollectionForBlock(
        blockInstance, time);

    List<BlockLocation> locations = new ArrayList<BlockLocation>();
    for (VehicleLocationCacheElements cacheRecord : records) {
      BlockLocation location = getBlockLocation(blockInstance, cacheRecord,
          null, time.getTargetTime());
      if (location != null)
        locations.add(location);
    }

    return locations;
  }

  @Override
  public Map<AgencyAndId, List<BlockLocation>> getLocationsForBlockInstance(
      BlockInstance blockInstance, List<Date> times, long currentTime) {

    Map<AgencyAndId, List<BlockLocation>> locationsByVehicleId = new FactoryMap<AgencyAndId, List<BlockLocation>>(
        new ArrayList<BlockLocation>());

    for (Date time : times) {
      TargetTime tt = new TargetTime(time.getTime(), currentTime);
      List<VehicleLocationCacheElements> records = getBlockLocationRecordCollectionForBlock(
          blockInstance, tt);
      for (VehicleLocationCacheElements cacheRecord : records) {
        BlockLocation location = getBlockLocation(blockInstance, cacheRecord,
            null, time.getTime());
        if (location != null) {
          locationsByVehicleId.get(location.getVehicleId()).add(location);
        }
      }
    }

    return locationsByVehicleId;
  }

  @Override
  public BlockLocation getScheduledLocationForBlockInstance(
      BlockInstance blockInstance, long targetTime) {
    return getBlockLocation(blockInstance, null, null, targetTime);
  }

  @Override
  public BlockLocation getLocationForVehicleAndTime(AgencyAndId vehicleId,
      TargetTime targetTime) {

    List<VehicleLocationCacheElements> cacheRecords = getBlockLocationRecordCollectionForVehicle(
        vehicleId, targetTime);

    // TODO : We might take a bit more care in picking the collection if
    // multiple collections are returned
    for (VehicleLocationCacheElements cacheRecord : cacheRecords) {
      BlockInstance blockInstance = cacheRecord.getBlockInstance();
      BlockLocation location = getBlockLocation(blockInstance, cacheRecord,
          null, targetTime.getTargetTime());
      if (location != null)
        return location;
    }

    return null;
  }

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

  private BlockInstance getVehicleLocationRecordAsBlockInstance(
      VehicleLocationRecord record) {

    AgencyAndId blockId = record.getBlockId();

    if (blockId == null) {
      AgencyAndId tripId = record.getTripId();
      if (tripId == null)
        throw new IllegalArgumentException(
            "at least one of blockId or tripId must be specified for VehicleLocationRecord");
      TripEntry tripEntry = _transitGraphDao.getTripEntryForId(tripId);
      if (tripEntry == null)
        throw new IllegalArgumentException("trip not found with id=" + tripId);
      BlockEntry block = tripEntry.getBlock();
      blockId = block.getId();
    }

    if (record.getServiceDate() == 0)
      throw new IllegalArgumentException("you must specify a serviceDate");

    if (record.getTimeOfRecord() == 0)
      throw new IllegalArgumentException("you must specify a record time");

    BlockInstance blockInstance = getBestBlockForRecord(blockId,
        record.getServiceDate(), record.getTimeOfRecord());

    return blockInstance;
  }

  private BlockInstance getBestBlockForRecord(AgencyAndId blockId,
      long serviceDate, long timeOfRecord) {

    long timeFrom = timeOfRecord - _blockInstanceMatchingWindow;
    long timeTo = timeOfRecord + _blockInstanceMatchingWindow;

    List<BlockInstance> blocks = _blockCalendarService.getActiveBlocks(blockId,
        timeFrom, timeTo);

    if (blocks.isEmpty())
      return null;
    else if (blocks.size() == 1)
      return blocks.get(0);

    Min<BlockInstance> m = new Min<BlockInstance>();

    for (BlockInstance block : blocks) {
      long delta = Math.abs(block.getServiceDate() - serviceDate);
      m.add(delta, block);
    }

    return m.getMinElement();
  }

  /**
   * We add the {@link BlockPositionRecord} to the local cache and persist it to
   * a back-end data-store if necessary
   *
   * @param scheduledBlockLocation TODO
   * @param samples
   */
  private void putBlockLocationRecord(BlockInstance blockInstance,
      VehicleLocationRecord record,
      ScheduledBlockLocation scheduledBlockLocation,
      ScheduleDeviationSamples samples) {

    // Cache the result
    VehicleLocationCacheElements elements = _cache.addRecord(blockInstance,
        record, scheduledBlockLocation, samples);

    if (!CollectionsLibrary.isEmpty(_blockLocationListeners)) {

      /**
       * We only fill in the block location if we have listeners
       */
      BlockLocation location = getBlockLocation(blockInstance, elements,
          scheduledBlockLocation, record.getTimeOfRecord());

      if (location != null) {
        for (BlockLocationListener listener : _blockLocationListeners) {
          listener.handleBlockLocation(location);
        }
      }
    }

    if (_persistBlockLocationRecords) {
      List<BlockLocationRecord> blockLocationRecords = getVehicleLocationRecordAsBlockLocationRecord(
          blockInstance, record, scheduledBlockLocation);
      addPredictionToPersistenceQueue(blockLocationRecords);
    }
  }

  /**
   *
   * @param blockInstance
   * @param scheduledBlockLocation TODO
   * @param targetTime
   * @param record
   * @return null if the effective scheduled block location cannot be determined
   */
  private BlockLocation getBlockLocation(BlockInstance blockInstance,
      VehicleLocationCacheElements cacheElements,
      ScheduledBlockLocation scheduledLocation, long targetTime) {

    BlockLocation location = new BlockLocation();
    location.setTime(targetTime);

    location.setBlockInstance(blockInstance);

    VehicleLocationCacheElement cacheElement = null;
    if (cacheElements != null)
      cacheElement = cacheElements.getElementForTimestamp(targetTime);

    if (cacheElement != null) {

      VehicleLocationRecord record = cacheElement.getRecord();

      if (scheduledLocation == null)
        scheduledLocation = getScheduledBlockLocationForVehicleLocationCacheRecord(
            blockInstance, cacheElement, targetTime);

      if (scheduledLocation != null) {
        location.setEffectiveScheduleTime(scheduledLocation.getScheduledTime());
        location.setDistanceAlongBlock(scheduledLocation.getDistanceAlongBlock());

      }

      location.setPredicted(true);
      location.setLastUpdateTime(record.getTimeOfRecord());
      location.setLastLocationUpdateTime(record.getTimeOfLocationUpdate());
      location.setScheduleDeviation(record.getScheduleDeviation());
      location.setScheduleDeviations(cacheElement.getScheduleDeviations());

      if (record.isCurrentLocationSet()) {
        CoordinatePoint p = new CoordinatePoint(record.getCurrentLocationLat(),
            record.getCurrentLocationLon());
        location.setLastKnownLocation(p);
      }
      location.setOrientation(record.getCurrentOrientation());
      location.setPhase(record.getPhase());
      location.setStatus(record.getStatus());
      location.setVehicleId(record.getVehicleId());

      List<TimepointPredictionRecord> timepointPredictions = record.getTimepointPredictions();
      if (timepointPredictions != null && !timepointPredictions.isEmpty()) {

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

        BlockConfigurationEntry blockConfig = blockInstance.getBlock();

        for (TimepointPredictionRecord tpr : timepointPredictions) {
          AgencyAndId stopId = tpr.getTimepointId();
          long predictedTime = tpr.getTimepointPredictedTime();
          if (stopId == null || predictedTime == 0)
            continue;

          for (BlockStopTimeEntry blockStopTime : blockConfig.getStopTimes()) {
            StopTimeEntry stopTime = blockStopTime.getStopTime();
            StopEntry stop = stopTime.getStop();
            if (stopId.equals(stop.getId())) {
              int arrivalTime = stopTime.getArrivalTime();
              int deviation = (int) ((tpr.getTimepointPredictedTime() - blockInstance.getServiceDate()) / 1000 - arrivalTime);
              scheduleDeviations.put(arrivalTime, (double) deviation);
            }
          }
        }

        double[] scheduleTimes = new double[scheduleDeviations.size()];
        double[] scheduleDeviationMus = new double[scheduleDeviations.size()];
        double[] scheduleDeviationSigmas = new double[scheduleDeviations.size()];

        int index = 0;
        for (Map.Entry<Integer, Double> entry : scheduleDeviations.entrySet()) {
          scheduleTimes[index] = entry.getKey();
          scheduleDeviationMus[index] = entry.getValue();
          index++;
        }

        ScheduleDeviationSamples samples = new ScheduleDeviationSamples(
            scheduleTimes, scheduleDeviationMus, scheduleDeviationSigmas);
        location.setScheduleDeviations(samples);
      }

    } else {
      if (scheduledLocation == null)
        scheduledLocation = getScheduledBlockLocationForBlockInstance(
            blockInstance, targetTime);
    }

    /**
     * Will be null in the following cases:
     *
     * 1) When the effective schedule time is beyond the last scheduled stop
     * time for the block.
     *
     * 2) When the effective distance along block is outside the range of the
     * block's shape.
     */
    if (scheduledLocation == null)
      return null;

    location.setInService(scheduledLocation.isInService());
    location.setActiveTrip(scheduledLocation.getActiveTrip());
    location.setLocation(scheduledLocation.getLocation());
    location.setOrientation(scheduledLocation.getOrientation());
    location.setScheduledDistanceAlongBlock(scheduledLocation.getDistanceAlongBlock());
    location.setClosestStop(scheduledLocation.getClosestStop());
    location.setClosestStopTimeOffset(scheduledLocation.getClosestStopTimeOffset());
    location.setNextStop(scheduledLocation.getNextStop());
    location.setNextStopTimeOffset(scheduledLocation.getNextStopTimeOffset());
    location.setPreviousStop(scheduledLocation.getPreviousStop());

    return location;
  }

  /****
   * {@link ScheduledBlockLocation} Methods
   ****/

  private ScheduledBlockLocation getScheduledBlockLocationForVehicleLocationRecord(
      VehicleLocationRecord record, BlockInstance blockInstance) {

    BlockConfigurationEntry blockConfig = blockInstance.getBlock();
    long serviceDate = blockInstance.getServiceDate();

    long targetTime = record.getTimeOfRecord();

    int scheduledTime = (int) ((targetTime - serviceDate) / 1000);

    if (record.isScheduleDeviationSet()) {

      /**
       * Effective scheduled time is the point that a transit vehicle is at on
       * its schedule, with schedule deviation taken into account. So if it's
       * 100 minutes into the current service date and the bus is running 10
       * minutes late, it's actually at the 90 minute point in its scheduled
       * operation.
       */
      int effectiveScheduledTime = (int) (scheduledTime - record.getScheduleDeviation());

      return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
          blockConfig, effectiveScheduledTime);
    }

    if (record.isDistanceAlongBlockSet()) {
      return _scheduledBlockLocationService.getScheduledBlockLocationFromDistanceAlongBlock(
          blockConfig, record.getDistanceAlongBlock());
    }

    return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
        blockConfig, scheduledTime);
  }

  private ScheduledBlockLocation getScheduledBlockLocationForBlockInstance(
      BlockInstance blockInstance, long targetTime) {

    BlockConfigurationEntry blockConfig = blockInstance.getBlock();
    long serviceDate = blockInstance.getServiceDate();

    int scheduledTime = (int) ((targetTime - serviceDate) / 1000);

    return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
        blockConfig, scheduledTime);
  }

  private ScheduledBlockLocation getScheduledBlockLocationForVehicleLocationCacheRecord(
      BlockInstance blockInstance, VehicleLocationCacheElement cacheElement,
      long targetTime) {

    VehicleLocationRecord record = cacheElement.getRecord();
    ScheduledBlockLocation scheduledBlockLocation = cacheElement.getScheduledBlockLocation();

    BlockConfigurationEntry blockConfig = blockInstance.getBlock();
    long serviceDate = blockInstance.getServiceDate();

    int scheduledTime = (int) ((targetTime - serviceDate) / 1000);

    /**
     * If location interpolation has been turned off, then we assume the vehicle
     * is at its last known location, so we return that if it's been stored with
     * the cache element.
     */
    if (!_locationInterpolation && scheduledBlockLocation != null) {
      return scheduledBlockLocation;
    }

    if (record.isScheduleDeviationSet()) {

      /**
       * Effective scheduled time is the point that a transit vehicle is at on
       * its schedule, with schedule deviation taken into account. So if it's
       * 100 minutes into the current service date and the bus is running 10
       * minutes late, it's actually at the 90 minute point in its scheduled
       * operation.
       */
      int effectiveScheduledTime = (int) (scheduledTime - record.getScheduleDeviation());

      if (scheduledBlockLocation != null
          && scheduledBlockLocation.getScheduledTime() <= effectiveScheduledTime) {

        return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
            scheduledBlockLocation, effectiveScheduledTime);
      }

      return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
          blockConfig, effectiveScheduledTime);
    }

    if (record.isDistanceAlongBlockSet()) {

      if ((_locationInterpolation || _distanceAlongBlockLocationInterpolation)
          && scheduledBlockLocation != null
          && scheduledBlockLocation.getDistanceAlongBlock() <= record.getDistanceAlongBlock()) {

        int ellapsedTime = (int) ((targetTime - record.getTimeOfRecord()) / 1000);

        if (ellapsedTime >= 0) {

          int effectiveScheduledTime = scheduledBlockLocation.getScheduledTime()
              + ellapsedTime;

          return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
              blockConfig, effectiveScheduledTime);
        }

        return _scheduledBlockLocationService.getScheduledBlockLocationFromDistanceAlongBlock(
            scheduledBlockLocation, record.getDistanceAlongBlock());
      }

      return _scheduledBlockLocationService.getScheduledBlockLocationFromDistanceAlongBlock(
          blockConfig, record.getDistanceAlongBlock());
    }

    return _scheduledBlockLocationService.getScheduledBlockLocationFromScheduledTime(
        blockConfig, scheduledTime);
  }

  private List<VehicleLocationCacheElements> getBlockLocationRecordCollectionForBlock(
      BlockInstance blockInstance, TargetTime time) {
    return getBlockLocationRecordCollections(new BlockInstanceStrategy(
        blockInstance), time);
  }

  private List<VehicleLocationCacheElements> getBlockLocationRecordCollectionForVehicle(
      AgencyAndId vehicleId, TargetTime time) {
    return getBlockLocationRecordCollections(new VehicleIdRecordStrategy(
        vehicleId), time);
  }

  private List<VehicleLocationCacheElements> getBlockLocationRecordCollections(
      RecordStrategy strategy, TargetTime time) {

    List<VehicleLocationCacheElements> entries = strategy.getRecordsFromCache();

    if (!entries.isEmpty()) {
      List<VehicleLocationCacheElements> inRange = new ArrayList<VehicleLocationCacheElements>();
      long offset = _predictionCacheMaxOffset * 1000;
      for (VehicleLocationCacheElements elements : entries) {
        if (elements.isEmpty())
          continue;
        Range range = elements.getTimeRange();
        long tFrom = (long) (range.getMin() - offset);
        long tTo = (long) (range.getMax() + offset);
        if (tFrom <= time.getCurrentTime() && time.getCurrentTime() <= tTo)
          inRange.add(elements);
      }
      if (!inRange.isEmpty())
        return inRange;
    }

    long offset = _blockLocationRecordCacheWindowSize * 1000 / 2;

    // We only consult persisted cache entries if the requested target time is
    // not within our current cache window
    boolean outOfRange = time.getTargetTime() + offset < time.getCurrentTime()
        || time.getCurrentTime() < time.getTargetTime() - offset;

    if (outOfRange && _persistBlockLocationRecords) {

      _blockLocationRecordPersistentStoreAccessCount.incrementAndGet();

      long fromTime = time.getTargetTime() - offset;
      long toTime = time.getTargetTime() + offset;

      List<BlockLocationRecord> predictions = strategy.getRecordsFromDao(
          fromTime, toTime);

      if (!predictions.isEmpty()) {

        Map<BlockLocationRecordKey, List<BlockLocationRecord>> recordsByKey = groupRecord(predictions);

        List<VehicleLocationCacheElements> allCollections = new ArrayList<VehicleLocationCacheElements>();
        for (Map.Entry<BlockLocationRecordKey, List<BlockLocationRecord>> entry : recordsByKey.entrySet()) {
          BlockLocationRecordKey key = entry.getKey();
          List<BlockLocationRecord> blockLocationRecords = entry.getValue();
          List<VehicleLocationCacheElements> someRecords = getBlockLocationRecordsAsVehicleLocationRecords(
              key.getBlockInstance(), blockLocationRecords);
          allCollections.addAll(someRecords);
        }

        return allCollections;
      }
    }

    return Collections.emptyList();
  }

  private List<BlockLocationRecord> getVehicleLocationRecordAsBlockLocationRecord(
      BlockInstance blockInstance, VehicleLocationRecord record,
      ScheduledBlockLocation scheduledBlockLocation) {

    BlockLocationRecord.Builder builder = BlockLocationRecord.builder();

    if (scheduledBlockLocation != null) {

      BlockTripEntry activeTrip = scheduledBlockLocation.getActiveTrip();
      builder.setTripId(activeTrip.getTrip().getId());
      builder.setBlockId(activeTrip.getBlockConfiguration().getBlock().getId());

      double distanceAlongBlock = scheduledBlockLocation.getDistanceAlongBlock();
      builder.setDistanceAlongBlock(distanceAlongBlock);

      double distanceAlongTrip = distanceAlongBlock
          - activeTrip.getDistanceAlongBlock();
      builder.setDistanceAlongTrip(distanceAlongTrip);
    }

    if (record.getBlockId() != null)
      builder.setBlockId(record.getBlockId());
    if (record.getTripId() != null)
      builder.setTripId(record.getTripId());
    builder.setTime(record.getTimeOfRecord());
    builder.setServiceDate(record.getServiceDate());

    if (record.isScheduleDeviationSet())
      builder.setScheduleDeviation(record.getScheduleDeviation());

    if (record.isDistanceAlongBlockSet()) {
      double distanceAlongBlock = record.getDistanceAlongBlock();
      builder.setDistanceAlongBlock(distanceAlongBlock);
      AgencyAndId tripId = record.getTripId();
      if (tripId != null) {
        BlockConfigurationEntry block = blockInstance.getBlock();
        for (BlockTripEntry blockTrip : block.getTrips()) {
          TripEntry trip = blockTrip.getTrip();
          if (trip.getId().equals(tripId)) {
            double distanceAlongTrip = distanceAlongBlock
                - blockTrip.getDistanceAlongBlock();
            builder.setDistanceAlongTrip(distanceAlongTrip);
          }
        }
      }
    }

    if (record.isCurrentLocationSet()) {
      builder.setLocationLat(record.getCurrentLocationLat());
      builder.setLocationLon(record.getCurrentLocationLon());
    }

    if (record.isCurrentOrientationSet())
      builder.setOrientation(record.getCurrentOrientation());

    builder.setPhase(record.getPhase());
    builder.setStatus(record.getStatus());
    builder.setVehicleId(record.getVehicleId());

    List<TimepointPredictionRecord> predictions = record.getTimepointPredictions();
    if (predictions == null || predictions.isEmpty())
      return Arrays.asList(builder.create());

    List<BlockLocationRecord> results = new ArrayList<BlockLocationRecord>();
    for (TimepointPredictionRecord tpr : predictions) {
      builder.setTimepointId(tpr.getTimepointId());
      builder.setTimepointScheduledTime(tpr.getTimepointScheduledTime());
      builder.setTimepointPredictedTime(tpr.getTimepointPredictedTime());
      results.add(builder.create());
    }
    return results;
  }

  private List<VehicleLocationCacheElements> getBlockLocationRecordsAsVehicleLocationRecords(
      BlockInstance blockInstance, List<BlockLocationRecord> records) {

    List<VehicleLocationCacheElements> results = new ArrayList<VehicleLocationCacheElements>();

    for (BlockLocationRecord record : records) {
      VehicleLocationRecord vlr = new VehicleLocationRecord();
      vlr.setBlockId(blockInstance.getBlock().getBlock().getId());
      if (record.isLocationSet()) {
        vlr.setCurrentLocationLat(record.getLocationLat());
        vlr.setCurrentLocationLon(record.getLocationLon());
      }
      if (record.isOrientationSet())
        vlr.setCurrentOrientation(record.getOrientation());
      if (record.isDistanceAlongBlockSet())
        vlr.setDistanceAlongBlock(record.getDistanceAlongBlock());
      vlr.setPhase(record.getPhase());
      if (record.isScheduleDeviationSet())
        vlr.setScheduleDeviation(record.getScheduleDeviation());
      vlr.setServiceDate(record.getServiceDate());
      vlr.setStatus(record.getStatus());
      vlr.setTimeOfRecord(record.getTime());
      vlr.setVehicleId(record.getVehicleId());

      VehicleLocationCacheElement element = new VehicleLocationCacheElement(
          vlr, null, null);
      VehicleLocationCacheElements elements = new VehicleLocationCacheElements(
          blockInstance, element);
      results.add(elements);
    }

    return results;
  }

  private Map<BlockLocationRecordKey, List<BlockLocationRecord>> groupRecord(
      List<BlockLocationRecord> predictions) {

    Map<BlockLocationRecordKey, List<BlockLocationRecord>> recordsByKey = new FactoryMap<BlockLocationRecordKey, List<BlockLocationRecord>>(
        new ArrayList<BlockLocationRecord>());

    for (BlockLocationRecord record : predictions) {
      AgencyAndId blockId = record.getBlockId();
      long serviceDate = record.getServiceDate();
      BlockInstance blockInstance = _blockCalendarService.getBlockInstance(
          blockId, serviceDate);
      if (blockInstance != null) {
        BlockLocationRecordKey key = new BlockLocationRecordKey(blockInstance,
            record.getVehicleId());
        recordsByKey.get(key).add(record);
      }
    }

    return recordsByKey;
  }

  private void addPredictionToPersistenceQueue(List<BlockLocationRecord> records) {
    synchronized (_recordPersistenceQueue) {
      _recordPersistenceQueue.addAll(records);
    }
  }

  private List<BlockLocationRecord> getPredictionPersistenceQueue() {
    synchronized (_recordPersistenceQueue) {
      List<BlockLocationRecord> queue = new ArrayList<BlockLocationRecord>(
          _recordPersistenceQueue);
      _recordPersistenceQueue.clear();
      return queue;
    }
  }

  private class PredictionWriter implements Runnable {

    @Override
    public void run() {

      try {
        List<BlockLocationRecord> queue = getPredictionPersistenceQueue();

        if (queue.isEmpty())
          return;

        long t1 = System.currentTimeMillis();
        _blockLocationRecordDao.saveBlockLocationRecords(queue);
        long t2 = System.currentTimeMillis();
        _lastInsertDuration = t2 - t1;
        _lastInsertCount = queue.size();
      } catch (Throwable ex) {
        _log.error("error writing block location records to dao", ex);
      }
    }
  }

  private interface RecordStrategy {

    public List<VehicleLocationCacheElements> getRecordsFromCache();

    public List<BlockLocationRecord> getRecordsFromDao(long fromTime,
        long toTime);
  }

  private class BlockInstanceStrategy implements RecordStrategy {

    private BlockInstance _blockInstance;

    public BlockInstanceStrategy(BlockInstance blockInstance) {
      _blockInstance = blockInstance;
    }

    @Override
    public List<VehicleLocationCacheElements> getRecordsFromCache() {
      return _cache.getRecordsForBlockInstance(_blockInstance);
    }

    @Override
    public List<BlockLocationRecord> getRecordsFromDao(long fromTime,
        long toTime) {
      BlockConfigurationEntry blockConfig = _blockInstance.getBlock();
      BlockEntry block = blockConfig.getBlock();
      return _blockLocationRecordDao.getBlockLocationRecordsForBlockServiceDateAndTimeRange(
          block.getId(), _blockInstance.getServiceDate(), fromTime, toTime);
    }
  }

  private class VehicleIdRecordStrategy implements RecordStrategy {

    private AgencyAndId _vehicleId;

    public VehicleIdRecordStrategy(AgencyAndId vehicleId) {
      _vehicleId = vehicleId;
    }

    public List<VehicleLocationCacheElements> getRecordsFromCache() {
      VehicleLocationCacheElements elementsForVehicleId = _cache.getRecordForVehicleId(_vehicleId);
      if (elementsForVehicleId == null)
        return Collections.emptyList();
      return Arrays.asList(elementsForVehicleId);
    }

    public List<BlockLocationRecord> getRecordsFromDao(long fromTime,
        long toTime) {
      return _blockLocationRecordDao.getBlockLocationRecordsForVehicleAndTimeRange(
          _vehicleId, fromTime, toTime);
    }
  }
}
TOP

Related Classes of org.onebusaway.transit_data_federation.impl.realtime.BlockLocationServiceImpl

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.