/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
* Copyright (C) 2011 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.blocks;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.onebusaway.collections.Min;
import org.onebusaway.container.cache.Cacheable;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.calendar.ServiceInterval;
import org.onebusaway.transit_data_federation.services.ExtendedCalendarService;
import org.onebusaway.transit_data_federation.services.blocks.BlockCalendarService;
import org.onebusaway.transit_data_federation.services.blocks.BlockIndexService;
import org.onebusaway.transit_data_federation.services.blocks.BlockInstance;
import org.onebusaway.transit_data_federation.services.blocks.BlockLayoverIndex;
import org.onebusaway.transit_data_federation.services.blocks.BlockTripIndex;
import org.onebusaway.transit_data_federation.services.blocks.FrequencyBlockTripIndex;
import org.onebusaway.transit_data_federation.services.blocks.FrequencyServiceIntervalBlock;
import org.onebusaway.transit_data_federation.services.blocks.InstanceState;
import org.onebusaway.transit_data_federation.services.blocks.LayoverIntervalBlock;
import org.onebusaway.transit_data_federation.services.blocks.ServiceIntervalBlock;
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.BlockTripEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.FrequencyEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.TransitGraphDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
class BlockCalendarServiceImpl implements BlockCalendarService {
private ExtendedCalendarService _calendarService;
private BlockIndexService _blockIndexService;
private TransitGraphDao _transitGraphDao;
@Autowired
public void setCalendarService(ExtendedCalendarService calendarService) {
_calendarService = calendarService;
}
@Autowired
public void setBlockIndexService(BlockIndexService blockIndexService) {
_blockIndexService = blockIndexService;
}
@Autowired
public void setTransitGraphDao(TransitGraphDao transitGraphDao) {
_transitGraphDao = transitGraphDao;
}
/****
* {@link BlockCalendarService} Interface
****/
@Cacheable(isValueSerializable = false)
@Override
public BlockInstance getBlockInstance(AgencyAndId blockId, long serviceDate) {
BlockEntry block = _transitGraphDao.getBlockEntryForId(blockId);
if (block == null)
throw new IllegalArgumentException("unknown block: " + blockId);
List<BlockConfigurationEntry> configurations = block.getConfigurations();
int index = 0;
Date date = new Date(serviceDate);
InstanceState state = new InstanceState(serviceDate);
/**
* See the specific contract for {@link BlockEntry#getConfigurations()}
* about the sort order of configurations
*/
for (BlockConfigurationEntry configuration : configurations) {
if (allServiceIdsAreActiveForServiceDate(configuration, date)) {
return new BlockInstance(configuration, state);
}
index++;
}
return null;
}
@Override
public List<BlockInstance> getActiveBlocks(AgencyAndId blockId,
long timeFrom, long timeTo) {
List<BlockTripIndex> indices = _blockIndexService.getBlockTripIndicesForBlock(blockId);
List<BlockLayoverIndex> layoverIndices = _blockIndexService.getBlockLayoverIndicesForBlock(blockId);
List<FrequencyBlockTripIndex> frequencyIndices = _blockIndexService.getFrequencyBlockTripIndicesForBlock(blockId);
return getActiveBlocksInTimeRange(indices, layoverIndices,
frequencyIndices, timeFrom, timeTo);
}
@Override
public List<BlockInstance> getClosestActiveBlocks(AgencyAndId blockId,
long time) {
Date timeAsDate = new Date(time);
Min<BlockInstance> m = new Min<BlockInstance>();
BlockEntry blockEntry = _transitGraphDao.getBlockEntryForId(blockId);
for (BlockConfigurationEntry blockConfig : blockEntry.getConfigurations()) {
List<Date> serviceDates = _calendarService.getDatesForServiceIdsAsOrderedList(blockConfig.getServiceIds());
int index = index(Collections.binarySearch(serviceDates, timeAsDate));
if (index > 0) {
BlockInstance instance = new BlockInstance(blockConfig,
serviceDates.get(index - 1).getTime());
long delta = getTimeToBlockInstance(instance, time);
m.add(delta, instance);
}
if (index < serviceDates.size()) {
BlockInstance instance = new BlockInstance(blockConfig,
serviceDates.get(index).getTime());
long delta = getTimeToBlockInstance(instance, time);
m.add(delta, instance);
}
}
return m.getMinElements();
}
@Override
public List<BlockInstance> getActiveBlocksInTimeRange(long timeFrom,
long timeTo) {
List<BlockTripIndex> indices = _blockIndexService.getBlockTripIndices();
List<BlockLayoverIndex> layoverIndices = _blockIndexService.getBlockLayoverIndices();
List<FrequencyBlockTripIndex> frequencyIndices = _blockIndexService.getFrequencyBlockTripIndices();
return getActiveBlocksInTimeRange(indices, layoverIndices,
frequencyIndices, timeFrom, timeTo);
}
@Override
public List<BlockInstance> getActiveBlocksForAgencyInTimeRange(
String agencyId, long timeFrom, long timeTo) {
List<BlockTripIndex> indices = _blockIndexService.getBlockTripIndicesForAgencyId(agencyId);
List<BlockLayoverIndex> layoverIndices = _blockIndexService.getBlockLayoverIndicesForAgencyId(agencyId);
List<FrequencyBlockTripIndex> frequencyIndices = _blockIndexService.getFrequencyBlockTripIndicesForAgencyId(agencyId);
return getActiveBlocksInTimeRange(indices, layoverIndices,
frequencyIndices, timeFrom, timeTo);
}
@Override
public List<BlockInstance> getActiveBlocksForRouteInTimeRange(
AgencyAndId routeId, long timeFrom, long timeTo) {
List<BlockTripIndex> indices = _blockIndexService.getBlockTripIndicesForRouteCollectionId(routeId);
List<BlockLayoverIndex> layoverIndices = _blockIndexService.getBlockLayoverIndicesForRouteCollectionId(routeId);
List<FrequencyBlockTripIndex> frequencyIndices = _blockIndexService.getFrequencyBlockTripIndicesForRouteCollectionId(routeId);
return getActiveBlocksInTimeRange(indices, layoverIndices,
frequencyIndices, timeFrom, timeTo);
}
@Override
public List<BlockInstance> getActiveBlocksInTimeRange(
Iterable<BlockTripIndex> indices,
Iterable<BlockLayoverIndex> layoverIndices,
Iterable<FrequencyBlockTripIndex> frequencyIndices, long timeFrom,
long timeTo) {
Set<BlockInstance> instances = new HashSet<BlockInstance>();
for (BlockTripIndex index : indices)
getActiveBlocksInTimeRange(index, timeFrom, timeTo, instances);
for (BlockLayoverIndex index : layoverIndices)
getActiveLayoversInTimeRange(index, timeFrom, timeTo, instances);
for (FrequencyBlockTripIndex index : frequencyIndices)
getActiveFrequencyBlocksInTimeRange(index, timeFrom, timeTo, instances);
return new ArrayList<BlockInstance>(instances);
}
/****
* Private Methods
****/
private boolean allServiceIdsAreActiveForServiceDate(
BlockConfigurationEntry configuration, Date serviceDate) {
Set<Date> serviceDates = _calendarService.getDatesForServiceIds(configuration.getServiceIds());
return serviceDates.contains(serviceDate);
}
/****
*
****/
private void getActiveBlocksInTimeRange(BlockTripIndex index, long timeFrom,
long timeTo, Collection<BlockInstance> results) {
Date dateFrom = new Date(timeFrom);
Date dateTo = new Date(timeTo);
handleBlockIndex(index, dateFrom, dateTo, results);
}
private Collection<BlockInstance> handleBlockIndex(BlockTripIndex index,
Date timeFrom, Date timeTo, Collection<BlockInstance> instances) {
List<BlockTripEntry> trips = index.getTrips();
ServiceIntervalBlock serviceIntervalBlock = index.getServiceIntervalBlock();
ServiceInterval serviceInterval = serviceIntervalBlock.getRange();
Collection<Date> serviceDates = _calendarService.getServiceDatesWithinRange(
index.getServiceIds(), serviceInterval, timeFrom, timeTo);
for (Date serviceDate : serviceDates) {
findBlockTripsInRange(serviceIntervalBlock, serviceDate, timeFrom,
timeTo, trips, instances);
}
return instances;
}
private void findBlockTripsInRange(ServiceIntervalBlock intervals,
Date serviceDate, Date timeFrom, Date timeTo, List<BlockTripEntry> trips,
Collection<BlockInstance> instances) {
int scheduledTimeFrom = (int) ((timeFrom.getTime() - serviceDate.getTime()) / 1000);
int scheduledTimeTo = (int) ((timeTo.getTime() - serviceDate.getTime()) / 1000);
int indexFrom = index(Arrays.binarySearch(intervals.getMaxDepartures(),
scheduledTimeFrom));
int indexTo = index(Arrays.binarySearch(intervals.getMinArrivals(),
scheduledTimeTo));
InstanceState state = new InstanceState(serviceDate.getTime());
for (int in = indexFrom; in < indexTo; in++) {
BlockTripEntry trip = trips.get(in);
BlockConfigurationEntry block = trip.getBlockConfiguration();
BlockInstance instance = new BlockInstance(block, state);
instances.add(instance);
}
}
/****
*
****/
private void getActiveLayoversInTimeRange(BlockLayoverIndex index,
long timeFrom, long timeTo, Collection<BlockInstance> results) {
Date dateFrom = new Date(timeFrom);
Date dateTo = new Date(timeTo);
handleLayoverIndex(index, dateFrom, dateTo, results);
}
private Collection<BlockInstance> handleLayoverIndex(BlockLayoverIndex index,
Date timeFrom, Date timeTo, Collection<BlockInstance> instances) {
List<BlockTripEntry> trips = index.getTrips();
LayoverIntervalBlock layoverIntervalBlock = index.getLayoverIntervalBlock();
ServiceInterval serviceInterval = layoverIntervalBlock.getRange();
Collection<Date> serviceDates = _calendarService.getServiceDatesWithinRange(
index.getServiceIds(), serviceInterval, timeFrom, timeTo);
for (Date serviceDate : serviceDates) {
findBlockLayoversInRange(layoverIntervalBlock, serviceDate, timeFrom,
timeTo, trips, instances);
}
return instances;
}
private void findBlockLayoversInRange(LayoverIntervalBlock intervals,
Date serviceDate, Date timeFrom, Date timeTo, List<BlockTripEntry> trips,
Collection<BlockInstance> instances) {
int scheduledTimeFrom = (int) ((timeFrom.getTime() - serviceDate.getTime()) / 1000);
int scheduledTimeTo = (int) ((timeTo.getTime() - serviceDate.getTime()) / 1000);
int indexFrom = index(Arrays.binarySearch(intervals.getEndTimes(),
scheduledTimeFrom));
int indexTo = index(Arrays.binarySearch(intervals.getStartTimes(),
scheduledTimeTo));
InstanceState state = new InstanceState(serviceDate.getTime());
for (int in = indexFrom; in < indexTo; in++) {
BlockTripEntry trip = trips.get(in);
BlockConfigurationEntry block = trip.getBlockConfiguration();
BlockInstance instance = new BlockInstance(block, state);
instances.add(instance);
}
}
/****
* Frequency Block Indices
****/
private void getActiveFrequencyBlocksInTimeRange(
FrequencyBlockTripIndex index, long timeFrom, long timeTo,
Collection<BlockInstance> results) {
Date dateFrom = new Date(timeFrom);
Date dateTo = new Date(timeTo);
handleFrequencyBlockIndex(index, dateFrom, dateTo, results);
}
private Collection<BlockInstance> handleFrequencyBlockIndex(
FrequencyBlockTripIndex index, Date timeFrom, Date timeTo,
Collection<BlockInstance> instances) {
List<BlockTripEntry> trips = index.getTrips();
List<FrequencyEntry> frequencies = index.getFrequencies();
FrequencyServiceIntervalBlock serviceIntervalBlock = index.getServiceIntervalBlock();
ServiceInterval serviceInterval = serviceIntervalBlock.getRange();
Collection<Date> serviceDates = _calendarService.getServiceDatesWithinRange(
index.getServiceIds(), serviceInterval, timeFrom, timeTo);
for (Date serviceDate : serviceDates) {
findFrequencyBlockTripsInRange(serviceIntervalBlock, serviceDate,
timeFrom, timeTo, trips, frequencies, instances);
}
return instances;
}
private void findFrequencyBlockTripsInRange(
FrequencyServiceIntervalBlock serviceIntervalIndex, Date serviceDate,
Date timeFrom, Date timeTo, List<BlockTripEntry> trips,
List<FrequencyEntry> frequencies, Collection<BlockInstance> instances) {
int scheduledTimeFrom = (int) ((timeFrom.getTime() - serviceDate.getTime()) / 1000);
int scheduledTimeTo = (int) ((timeTo.getTime() - serviceDate.getTime()) / 1000);
int indexFrom = index(Arrays.binarySearch(
serviceIntervalIndex.getEndTimes(), scheduledTimeFrom));
int indexTo = index(Arrays.binarySearch(
serviceIntervalIndex.getStartTimes(), scheduledTimeTo));
for (int in = indexFrom; in < indexTo; in++) {
BlockTripEntry trip = trips.get(in);
BlockConfigurationEntry block = trip.getBlockConfiguration();
FrequencyEntry frequency = frequencies.get(in);
InstanceState state = new InstanceState(serviceDate.getTime(), frequency);
BlockInstance instance = new BlockInstance(block, state);
instances.add(instance);
}
}
/****
*
****/
private long getTimeToBlockInstance(BlockInstance instance, long time) {
long serviceDate = instance.getServiceDate();
BlockConfigurationEntry blockConfig = instance.getBlock();
int n = blockConfig.getStopTimes().size();
long from = serviceDate + blockConfig.getArrivalTimeForIndex(0) * 1000;
long to = serviceDate + blockConfig.getDepartureTimeForIndex(n - 1) * 1000;
return Math.abs((from + to) / 2 - time);
}
/****
*
****/
private int index(int index) {
if (index < 0)
return -(index + 1);
return index;
}
}