/**
* 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.beans;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.transit_data.model.ArrivalAndDepartureBean;
import org.onebusaway.transit_data.model.ArrivalsAndDeparturesQueryBean;
import org.onebusaway.transit_data.model.StopBean;
import org.onebusaway.transit_data.model.realtime.HistogramBean;
import org.onebusaway.transit_data.model.schedule.FrequencyBean;
import org.onebusaway.transit_data.model.service_alerts.ServiceAlertBean;
import org.onebusaway.transit_data.model.trips.TripBean;
import org.onebusaway.transit_data.model.trips.TripStatusBean;
import org.onebusaway.transit_data_federation.model.TargetTime;
import org.onebusaway.transit_data_federation.model.narrative.StopTimeNarrative;
import org.onebusaway.transit_data_federation.services.AgencyAndIdLibrary;
import org.onebusaway.transit_data_federation.services.ArrivalAndDepartureQuery;
import org.onebusaway.transit_data_federation.services.ArrivalAndDepartureService;
import org.onebusaway.transit_data_federation.services.beans.ArrivalsAndDeparturesBeanService;
import org.onebusaway.transit_data_federation.services.beans.ServiceAlertsBeanService;
import org.onebusaway.transit_data_federation.services.beans.StopBeanService;
import org.onebusaway.transit_data_federation.services.beans.TripBeanService;
import org.onebusaway.transit_data_federation.services.beans.TripDetailsBeanService;
import org.onebusaway.transit_data_federation.services.blocks.BlockInstance;
import org.onebusaway.transit_data_federation.services.narrative.NarrativeService;
import org.onebusaway.transit_data_federation.services.realtime.ArrivalAndDepartureInstance;
import org.onebusaway.transit_data_federation.services.realtime.BlockLocation;
import org.onebusaway.transit_data_federation.services.realtime.RealTimeHistoryService;
import org.onebusaway.transit_data_federation.services.realtime.ScheduleDeviationHistogram;
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.TransitGraphDao;
import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry;
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;
@Component
@ManagedResource("org.onebusaway.transit_data_federation.impl.beans:name=ArrivalsAndDeparturesBeanServiceImpl")
public class ArrivalsAndDeparturesBeanServiceImpl implements
ArrivalsAndDeparturesBeanService {
private TransitGraphDao _transitGraphDao;
private ArrivalAndDepartureService _arrivalAndDepartureService;
private NarrativeService _narrativeService;
private TripBeanService _tripBeanService;
private StopBeanService _stopBeanService;
private TripDetailsBeanService _tripDetailsBeanService;
private ServiceAlertsBeanService _serviceAlertsBeanService;
private RealTimeHistoryService _realTimeHistoryService;
@Autowired
public void setTransitGraphDao(TransitGraphDao transitGraphDao) {
_transitGraphDao = transitGraphDao;
}
@Autowired
public void setArrivalAndDepartureService(
ArrivalAndDepartureService arrivalAndDepartureService) {
_arrivalAndDepartureService = arrivalAndDepartureService;
}
@Autowired
public void setNarrativeService(NarrativeService narrativeService) {
_narrativeService = narrativeService;
}
@Autowired
public void setTripBeanService(TripBeanService tripBeanService) {
_tripBeanService = tripBeanService;
}
@Autowired
public void setStopBeanService(StopBeanService stopBeanService) {
_stopBeanService = stopBeanService;
}
@Autowired
public void setTripDetailsBeanService(
TripDetailsBeanService tripDetailsBeanService) {
_tripDetailsBeanService = tripDetailsBeanService;
}
@Autowired
public void setServiceAlertsBeanService(
ServiceAlertsBeanService serviceAlertsBeanService) {
_serviceAlertsBeanService = serviceAlertsBeanService;
}
@Autowired
public void setRealTimeHistoryService(
RealTimeHistoryService realTimeHistoryService) {
_realTimeHistoryService = realTimeHistoryService;;
}
private AtomicInteger _stopTimesTotal = new AtomicInteger();
private AtomicInteger _stopTimesWithPredictions = new AtomicInteger();
@ManagedAttribute()
public int getStopTimesTotal() {
return _stopTimesTotal.intValue();
}
@ManagedAttribute
public int getStopTimesWithPredictions() {
return _stopTimesWithPredictions.intValue();
}
private boolean useScheduleDeviationHistory = true;
/**
* Optionally turn off schedule deviation history support.
* @param useScheduleDeviationHistory flag to use/disable deviation history
*/
public void setScheduleDeviationHistory(boolean useScheduleDeviationHistory) {
this.useScheduleDeviationHistory = useScheduleDeviationHistory;
}
/****
* {@link ArrivalsAndDeparturesBeanService} Interface
****/
@Override
public List<ArrivalAndDepartureBean> getArrivalsAndDeparturesByStopId(
AgencyAndId stopId, ArrivalsAndDeparturesQueryBean query) {
StopEntry stop = _transitGraphDao.getStopEntryForId(stopId, true);
long time = query.getTime();
int minutesBefore = Math.max(query.getMinutesBefore(),
query.getFrequencyMinutesBefore());
int minutesAfter = Math.max(query.getMinutesAfter(),
query.getFrequencyMinutesAfter());
long fromTime = time - minutesBefore * 60 * 1000;
long toTime = time + minutesAfter * 60 * 1000;
long nonFrequencyFromTime = time - query.getMinutesBefore() * 60 * 1000;
long nonFrequencyToTime = time + query.getMinutesAfter() * 60 * 1000;
long frequencyFromTime = time - query.getFrequencyMinutesBefore() * 60
* 1000;
long frequencyToTime = time + query.getFrequencyMinutesAfter() * 60 * 1000;
TargetTime target = new TargetTime(time, time);
List<ArrivalAndDepartureInstance> instances = _arrivalAndDepartureService.getArrivalsAndDeparturesForStopInTimeRange(
stop, target, fromTime, toTime);
List<ArrivalAndDepartureBean> beans = new ArrayList<ArrivalAndDepartureBean>();
Map<AgencyAndId, StopBean> stopBeanCache = new HashMap<AgencyAndId, StopBean>();
for (ArrivalAndDepartureInstance instance : instances) {
FrequencyEntry frequency = instance.getFrequency();
long from = frequency != null ? frequencyFromTime : nonFrequencyFromTime;
long to = frequency != null ? frequencyToTime : nonFrequencyToTime;
if (!isArrivalAndDepartureInRange(instance, from, to))
continue;
ArrivalAndDepartureBean bean = getStopTimeInstanceAsBean(time, instance,
stopBeanCache);
applyBlockLocationToBean(instance, bean, time);
applySituationsToBean(time, instance, bean);
beans.add(bean);
}
Collections.sort(beans, new ArrivalAndDepartureComparator());
return beans;
}
@Override
public ArrivalAndDepartureBean getArrivalAndDepartureForStop(
ArrivalAndDepartureQuery query) {
long time = query.getTime();
ArrivalAndDepartureInstance instance = _arrivalAndDepartureService.getArrivalAndDepartureForStop(query);
if (instance == null) {
return null;
}
ArrivalAndDepartureBean bean = getStopTimeInstanceAsBean(time, instance,
new HashMap<AgencyAndId, StopBean>());
applyBlockLocationToBean(instance, bean, time);
applySituationsToBean(time, instance, bean);
if (!this.useScheduleDeviationHistory) {
return bean;
}
int step = 120;
ScheduleDeviationHistogram histo = _realTimeHistoryService.getScheduleDeviationHistogramForArrivalAndDepartureInstance(
instance, step);
if (histo != null) {
int[] sds = histo.getScheduleDeviations();
double[] values = new double[sds.length];
String[] labels = new String[sds.length];
for (int i = 0; i < sds.length; i++) {
int sd = sds[i];
values[i] = sd;
labels[i] = Integer.toString(sd / 60);
}
HistogramBean hb = new HistogramBean();
hb.setValues(values);
hb.setCounts(histo.getCounts());
hb.setLabels(labels);
bean.setScheduleDeviationHistogram(hb);
}
return bean;
}
/****
* Private Methods
****/
private ArrivalAndDepartureBean getStopTimeInstanceAsBean(long time,
ArrivalAndDepartureInstance instance,
Map<AgencyAndId, StopBean> stopBeanCache) {
ArrivalAndDepartureBean pab = new ArrivalAndDepartureBean();
pab.setServiceDate(instance.getServiceDate());
BlockStopTimeEntry blockStopTime = instance.getBlockStopTime();
BlockTripEntry blockTrip = blockStopTime.getTrip();
BlockConfigurationEntry blockConfig = blockTrip.getBlockConfiguration();
StopTimeEntry stopTime = blockStopTime.getStopTime();
StopEntry stop = stopTime.getStop();
TripEntry trip = stopTime.getTrip();
TripBean tripBean = _tripBeanService.getTripForId(trip.getId());
pab.setTrip(tripBean);
pab.setBlockTripSequence(blockTrip.getSequence());
pab.setArrivalEnabled(blockStopTime.getBlockSequence() > 0);
pab.setDepartureEnabled(blockStopTime.getBlockSequence() + 1 < blockConfig.getStopTimes().size());
StopTimeNarrative stopTimeNarrative = _narrativeService.getStopTimeForEntry(stopTime);
pab.setRouteShortName(stopTimeNarrative.getRouteShortName());
pab.setTripHeadsign(stopTimeNarrative.getStopHeadsign());
StopBean stopBean = stopBeanCache.get(stop.getId());
if (stopBean == null) {
stopBean = _stopBeanService.getStopForId(stop.getId());
stopBeanCache.put(stop.getId(), stopBean);
}
pab.setStop(stopBean);
pab.setStopSequence(stopTime.getSequence());
pab.setStatus("default");
pab.setScheduledArrivalTime(instance.getScheduledArrivalTime());
pab.setScheduledDepartureTime(instance.getScheduledDepartureTime());
FrequencyEntry frequency = instance.getFrequencyLabel();
pab.setFrequency(null);
if (frequency != null) {
FrequencyBean fb = FrequencyBeanLibrary.getBeanForFrequency(
instance.getServiceDate(), frequency);
pab.setFrequency(fb);
}
return pab;
}
private void applyBlockLocationToBean(ArrivalAndDepartureInstance instance,
ArrivalAndDepartureBean bean, long targetTime) {
boolean hasFrequency = instance.getFrequency() != null;
if (instance.isPredictedArrivalTimeSet()) {
bean.setPredictedArrivalTime(instance.getPredictedArrivalTime());
if (hasFrequency)
bean.setScheduledArrivalTime(bean.getPredictedArrivalTime());
}
if (instance.isPredictedDepartureTimeSet()) {
bean.setPredictedDepartureTime(instance.getPredictedDepartureTime());
if (hasFrequency)
bean.setScheduledDepartureTime(bean.getPredictedDepartureTime());
}
BlockStopTimeEntry stopTime = instance.getBlockStopTime();
BlockLocation blockLocation = instance.getBlockLocation();
if (blockLocation == null)
return;
bean.setPredicted(blockLocation.isPredicted());
// Distance from stop
if (blockLocation.isDistanceAlongBlockSet()) {
double distanceFromStop = stopTime.getDistanceAlongBlock()
- blockLocation.getDistanceAlongBlock();
bean.setDistanceFromStop(distanceFromStop);
} else {
double distanceFromStop = stopTime.getDistanceAlongBlock()
- blockLocation.getScheduledDistanceAlongBlock();
bean.setDistanceFromStop(distanceFromStop);
}
// Number of stops away
if (blockLocation.getNextStop() != null) {
BlockStopTimeEntry nextStopTime = blockLocation.getNextStop();
bean.setNumberOfStopsAway(stopTime.getBlockSequence()
- nextStopTime.getBlockSequence());
}
if (blockLocation.getLastUpdateTime() > 0)
bean.setLastUpdateTime(blockLocation.getLastUpdateTime());
if (blockLocation.getVehicleId() != null)
bean.setVehicleId(AgencyAndIdLibrary.convertToString(blockLocation.getVehicleId()));
TripStatusBean tripStatusBean = _tripDetailsBeanService.getBlockLocationAsStatusBean(
blockLocation, targetTime);
bean.setTripStatus(tripStatusBean);
}
private void applySituationsToBean(long time,
ArrivalAndDepartureInstance instance, ArrivalAndDepartureBean bean) {
BlockInstance blockInstance = instance.getBlockInstance();
AgencyAndId vehicleId = null;
BlockLocation blockLocation = instance.getBlockLocation();
if (blockLocation != null)
vehicleId = blockLocation.getVehicleId();
List<ServiceAlertBean> situations = _serviceAlertsBeanService.getServiceAlertsForStopCall(
time, blockInstance, instance.getBlockStopTime(), vehicleId);
if (!situations.isEmpty())
bean.setSituations(situations);
}
private boolean isArrivalAndDepartureInRange(
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 static class ArrivalAndDepartureComparator implements
Comparator<ArrivalAndDepartureBean> {
public int compare(ArrivalAndDepartureBean o1, ArrivalAndDepartureBean o2) {
long t1 = o1.getScheduledArrivalTime();
if (o1.hasPredictedArrivalTime())
t1 = o1.getPredictedArrivalTime();
long t2 = o2.getScheduledArrivalTime();
if (o2.hasPredictedArrivalTime())
t2 = o2.getPredictedArrivalTime();
return (int) (t1 - t2);
}
}
}