/** * 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); } } }