diff --git a/.travis.yml b/.travis.yml index 2e151380bd..4040c87a12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,4 @@ language: java sudo: false cache: directories: - - $HOME/.m2 \ No newline at end of file + - $HOME/.m2 diff --git a/onebusaway-api-webapp/src/main/java/org/onebusaway/api/actions/api/gtfs_realtime/TripUpdatesForAgencyAction.java b/onebusaway-api-webapp/src/main/java/org/onebusaway/api/actions/api/gtfs_realtime/TripUpdatesForAgencyAction.java index f86b8eea66..8efdcd0d75 100644 --- a/onebusaway-api-webapp/src/main/java/org/onebusaway/api/actions/api/gtfs_realtime/TripUpdatesForAgencyAction.java +++ b/onebusaway-api-webapp/src/main/java/org/onebusaway/api/actions/api/gtfs_realtime/TripUpdatesForAgencyAction.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2013 Google, Inc. + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +28,11 @@ import com.google.transit.realtime.GtfsRealtime.TripDescriptor; import com.google.transit.realtime.GtfsRealtime.TripUpdate; import com.google.transit.realtime.GtfsRealtime.VehicleDescriptor; +import org.onebusaway.transit_data.model.trips.TimepointPredictionBean; public class TripUpdatesForAgencyAction extends GtfsRealtimeActionSupport { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; @Override protected void fillFeedMessage(FeedMessage.Builder feed, String agencyId, @@ -58,16 +60,36 @@ protected void fillFeedMessage(FeedMessage.Builder feed, String agencyId, VehicleDescriptor.Builder vehicleDesc = tripUpdate.getVehicleBuilder(); vehicleDesc.setId(normalizeId(vehicle.getVehicleId())); - StopBean nextStop = tripStatus.getNextStop(); - if (nextStop != null) { - TripUpdate.StopTimeUpdate.Builder stopTimeUpdate = tripUpdate.addStopTimeUpdateBuilder(); - stopTimeUpdate.setStopId(normalizeId(nextStop.getId())); - TripUpdate.StopTimeEvent.Builder departure = stopTimeUpdate.getDepartureBuilder(); - departure.setTime(timestamp / 1000 + tripStatus.getNextStopTimeOffset()); - } + if (tripStatus.getTimepointPredictions() != null && tripStatus.getTimepointPredictions().size() > 0) { + for (TimepointPredictionBean timepointPrediction: tripStatus.getTimepointPredictions()) { + TripUpdate.StopTimeUpdate.Builder stopTimeUpdate = tripUpdate.addStopTimeUpdateBuilder(); + stopTimeUpdate.setStopId(normalizeId(timepointPrediction.getTimepointId())); + TripUpdate.StopTimeEvent.Builder arrival = stopTimeUpdate.getArrivalBuilder(); + if (timepointPrediction.getTimepointPredictedArrivalTime() != -1) { + arrival.setTime(timepointPrediction.getTimepointPredictedArrivalTime()); + } + + TripUpdate.StopTimeEvent.Builder departure = stopTimeUpdate.getDepartureBuilder(); + if (timepointPrediction.getTimepointPredictedDepartureTime() != -1) { + departure.setTime(timepointPrediction.getTimepointPredictedDepartureTime()); + } - tripUpdate.setTimestamp(vehicle.getLastUpdateTime() / 1000); + + } + + tripUpdate.setTimestamp(vehicle.getLastUpdateTime() / 1000); + } else { + StopBean nextStop = tripStatus.getNextStop(); + if (nextStop != null) { + TripUpdate.StopTimeUpdate.Builder stopTimeUpdate = tripUpdate.addStopTimeUpdateBuilder(); + stopTimeUpdate.setStopId(normalizeId(nextStop.getId())); + TripUpdate.StopTimeEvent.Builder departure = stopTimeUpdate.getDepartureBuilder(); + departure.setTime(timestamp / 1000 + tripStatus.getNextStopTimeOffset()); + } + + } tripUpdate.setDelay((int) tripStatus.getScheduleDeviation()); + tripUpdate.setTimestamp(vehicle.getLastUpdateTime() / 1000); } } } diff --git a/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java b/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java new file mode 100644 index 0000000000..46d5c40d49 --- /dev/null +++ b/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 University of South Florida + * + * 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.utility; + +/** + * Defines the strategy to use when interpolating a key that is inside the key + * range of the key-value map + */ +public enum EInRangeStrategy { + + /** + * As long as two key-values are present in the map, we we will attempt to + * interpolate the value for a key that is inside the key range of the + * key-value map. If only one key-value pair is present in the map, that value + * will be used. + */ + INTERPOLATE, + + /** + * Returns the value in the key-value map closest to the target value, where the + * index for the returned value is less than the index of the target value. + */ + PREVIOUS_VALUE; +} \ No newline at end of file diff --git a/onebusaway-core/src/main/java/org/onebusaway/utility/InterpolationLibrary.java b/onebusaway-core/src/main/java/org/onebusaway/utility/InterpolationLibrary.java index 86ecb5eb3b..846f0ffd10 100644 --- a/onebusaway-core/src/main/java/org/onebusaway/utility/InterpolationLibrary.java +++ b/onebusaway-core/src/main/java/org/onebusaway/utility/InterpolationLibrary.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +24,9 @@ * Generic methods to support interpolation of values against a sorted key-value * map given a new target key. * + * Note that these interpolation methods do not conform to the GTFS-rt spec. For GTFS-rt + * compliant interpolation/extrapolation, see {@link TransitInterpolationLibrary}. + * * @author bdferris */ public class InterpolationLibrary { @@ -91,7 +95,12 @@ public static V nearestNeighbor(SortedMap values, } public static double interpolate(double[] keys, double[] values, - double target, EOutOfRangeStrategy outOfRangeStrategy) { + double target, EOutOfRangeStrategy outOfRangeStrategy) { + return interpolate(keys, values, target, outOfRangeStrategy, null); + } + + public static double interpolate(double[] keys, double[] values, + double target, EOutOfRangeStrategy outOfRangeStrategy, EInRangeStrategy inRangeStrategy) { if (values.length == 0) throw new IndexOutOfBoundsException(OUT_OF_RANGE); @@ -130,8 +139,17 @@ public static double interpolate(double[] keys, double[] values, } } - return interpolatePair(keys[index - 1], values[index - 1], keys[index], - values[index], target); + if (inRangeStrategy == null) { + inRangeStrategy = EInRangeStrategy.INTERPOLATE; + } + + switch (inRangeStrategy) { + case PREVIOUS_VALUE: + return values[index - 1]; + default: + return interpolatePair(keys[index - 1], values[index - 1], keys[index], + values[index], target); + } } /** diff --git a/onebusaway-core/src/main/java/org/onebusaway/utility/TransitInterpolationLibrary.java b/onebusaway-core/src/main/java/org/onebusaway/utility/TransitInterpolationLibrary.java new file mode 100644 index 0000000000..4093d46a23 --- /dev/null +++ b/onebusaway-core/src/main/java/org/onebusaway/utility/TransitInterpolationLibrary.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2015 University of South Florida + * + * 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.utility; + +import java.util.Arrays; + +/** + * Transit-specific methods to support searching for deviations (produced from real-time + * predictions) for a given stop. Interpolation behavior is consistent + * with the GTFS-realtime spec (https://developers.google.com/transit/gtfs-realtime/) when + * using the {@link EInRangeStrategy.PREVIOUS_VALUE} and {@link EOutOfRangeStrategy.LAST_VALUE} + * strategies - in particular, this applies to the propagation of delays downstream in a trip. + * The {@link EInRangeStrategy.INTERPOLATE} and {@link EOutOfRangeStrategy.INTERPOLATE} + * strategies have behavior consistent with the normal {@link InterpolationLibrary}, which + * do not conform to the GTFS-rt spec. + * + */ +public class TransitInterpolationLibrary { + + private static final String OUT_OF_RANGE = "no values provided"; + + public static Double interpolate(double[] keys, double[] values, + double target, EOutOfRangeStrategy outOfRangeStrategy) { + return interpolate(keys, values, target, outOfRangeStrategy, null); + } + + /** + * Find the deviation that should be used for a particular stop, given sorted keys (arrival times) + * and values (deviations) arrays. The {@code target} is the arrival time for the stop + * we're searching for. Delay propagation is consistent with the GTFS-realtime spec + * (https://developers.google.com/transit/gtfs-realtime/) when using the + * {@link EInRangeStrategy.PREVIOUS_VALUE} and {@link EOutOfRangeStrategy.LAST_VALUE} strategies. If + * {@link EOutOfRangeStrategy.LAST_VALUE} is provided and all deviations are downstream from the target stop, + * null will be returned to indicate that no real-time information is available for the target stop. + * If {@link EInRangeStrategy.INTERPOLATE} is provided, this method will interpolate using + * linear interpolation and produce a value for a target key within the key-range of the map. + * For a key outside the range of the keys of the map, the {@code outOfRange} {@link EOutOfRangeStrategy} + * strategy will determine the interpolation behavior. {@link EOutOfRangeStrategy.INTERPOLATE} + * will linearly extrapolate the value. + * + * @param keys sorted array of keys (the scheduled arrival time of the stop) + * @param values sorted arrays of values (the list of real-time deviations for the provided stops) + * @param target the target key used to interpolate a value (the scheduled arrival time of the stop) + * @param outOfRangeStrategy the strategy to use for a target key that outside + * the key-range of the value map (use {@link EOutOfRangeStrategy.LAST_VALUE} for GTFS-rt behavior) + * @param inRangeStrategy the strategy to use for a target key that inside + * the key-range of the value map (use {@link EInRangeStrategy.PREVIOUS_VALUE} for GTFS-rt behavior) + * @return an interpolated value (deviation) for the target key, or null if the target is upstream of the deviations + */ + public static Double interpolate(double[] keys, double[] values, + double target, EOutOfRangeStrategy outOfRangeStrategy, + EInRangeStrategy inRangeStrategy) { + + if (values.length == 0) + throw new IndexOutOfBoundsException(OUT_OF_RANGE); + + int index = Arrays.binarySearch(keys, target); + if (index >= 0) { + // There is a real-time prediction provided for this stop - return it + return values[index]; + } + + // If we get this far, the target value wasn't contained in the keys. Convert the returned index into the insertion + // index for target, which is the index of the first element greater than the target key (see Arrays.binarySearch()). + index = -(index + 1); + + if (index == values.length) { + // We're searching for a stop that is downstream of the predictions + switch (outOfRangeStrategy) { + case INTERPOLATE: + if (values.length > 1) + return InterpolationLibrary.interpolatePair(keys[index - 2], + values[index - 2], keys[index - 1], values[index - 1], target); + return values[index - 1]; + case LAST_VALUE: + // Return the closest upstream deviation (i.e., propagate the last deviation in values downstream) + return values[index - 1]; + case EXCEPTION: + throw new IndexOutOfBoundsException(OUT_OF_RANGE); + } + } + + if (index == 0) { + // We're searching for a stop that is upstream of the predictions + switch (outOfRangeStrategy) { + case INTERPOLATE: + if (values.length > 1) + return InterpolationLibrary.interpolatePair(keys[0], values[0], + keys[1], values[1], target); + return values[0]; + case LAST_VALUE: + // We shouldn't propagate deviations upstream, so return null to indicate no prediction + // should be used, and schedule data should be used instead. + return null; + case EXCEPTION: + throw new IndexOutOfBoundsException(OUT_OF_RANGE); + } + } + + if (inRangeStrategy == null) { + inRangeStrategy = EInRangeStrategy.INTERPOLATE; + } + + // We're searching for a stop that is within the window of predictions, but no prediction is provided for + // the target stop + switch (inRangeStrategy) { + case PREVIOUS_VALUE: + // Return the closest upstream deviation (i.e., propagate the closest deviation in values downstream) + return values[index - 1]; + default: + return InterpolationLibrary.interpolatePair(keys[index - 1], + values[index - 1], keys[index], values[index], target); + } + } + +} diff --git a/onebusaway-realtime-api/src/main/java/org/onebusaway/realtime/api/TimepointPredictionRecord.java b/onebusaway-realtime-api/src/main/java/org/onebusaway/realtime/api/TimepointPredictionRecord.java index e73bd94d38..4d26539b85 100644 --- a/onebusaway-realtime-api/src/main/java/org/onebusaway/realtime/api/TimepointPredictionRecord.java +++ b/onebusaway-realtime-api/src/main/java/org/onebusaway/realtime/api/TimepointPredictionRecord.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,16 +22,22 @@ public class TimepointPredictionRecord implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** * */ private AgencyAndId timepointId; + + private AgencyAndId tripId; + + private int stopSequence = -1; private long timepointScheduledTime; - private long timepointPredictedTime; + private long timepointPredictedArrivalTime = -1; + + private long timepointPredictedDepartureTime = -1; public TimepointPredictionRecord() { @@ -38,7 +45,10 @@ public TimepointPredictionRecord() { public TimepointPredictionRecord(TimepointPredictionRecord r) { this.timepointId = r.timepointId; - this.timepointPredictedTime = r.timepointPredictedTime; + this.tripId = r.tripId; + this.stopSequence = r.stopSequence; + this.timepointPredictedArrivalTime = r.timepointPredictedArrivalTime; + this.timepointPredictedDepartureTime = r.timepointPredictedDepartureTime; this.timepointScheduledTime = r.timepointScheduledTime; } @@ -58,11 +68,36 @@ public void setTimepointScheduledTime(long timepointScheduledTime) { this.timepointScheduledTime = timepointScheduledTime; } - public long getTimepointPredictedTime() { - return timepointPredictedTime; + public AgencyAndId getTripId() { + return tripId; + } + + public void setTripId(AgencyAndId tripId) { + this.tripId = tripId; + } + + public int getStopSequence() { + return stopSequence; + } + + public void setStopSequence(int stopSequence) { + this.stopSequence = stopSequence; + } + + public long getTimepointPredictedArrivalTime() { + return timepointPredictedArrivalTime; + } + + public void setTimepointPredictedArrivalTime(long timepointPredictedArrivalTime) { + this.timepointPredictedArrivalTime = timepointPredictedArrivalTime; + } + + public long getTimepointPredictedDepartureTime() { + return timepointPredictedDepartureTime; } - public void setTimepointPredictedTime(long timepointPredictedTime) { - this.timepointPredictedTime = timepointPredictedTime; + public void setTimepointPredictedDepartureTime( + long timepointPredictedDepartureTime) { + this.timepointPredictedDepartureTime = timepointPredictedDepartureTime; } } diff --git a/onebusaway-transit-data-federation-builder/src/main/java/org/onebusaway/transit_data_federation/bundle/tasks/transit_graph/StopTimeEntriesFactory.java b/onebusaway-transit-data-federation-builder/src/main/java/org/onebusaway/transit_data_federation/bundle/tasks/transit_graph/StopTimeEntriesFactory.java index 5d46a87857..cfaba5c24d 100644 --- a/onebusaway-transit-data-federation-builder/src/main/java/org/onebusaway/transit_data_federation/bundle/tasks/transit_graph/StopTimeEntriesFactory.java +++ b/onebusaway-transit-data-federation-builder/src/main/java/org/onebusaway/transit_data_federation/bundle/tasks/transit_graph/StopTimeEntriesFactory.java @@ -1,4 +1,5 @@ /** + * Copyright (C) 2013 Kurt Raschke * Copyright (C) 2011 Brian Ferris * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -139,6 +140,7 @@ private List createInitialStopTimeEntries( stopTimeEntry.setId(stopTime.getId()); stopTimeEntry.setSequence(sequence); + stopTimeEntry.setGtfsSequence(stopTime.getStopSequence()); stopTimeEntry.setDropOffType(stopTime.getDropOffType()); stopTimeEntry.setPickupType(stopTime.getPickupType()); stopTimeEntry.setStop(stopEntry); diff --git a/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/BoundingBoxController.java b/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/BoundingBoxController.java new file mode 100644 index 0000000000..47ae87fbba --- /dev/null +++ b/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/BoundingBoxController.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2013 Kurt Raschke + * + * 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_webapp.controllers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.MultiPolygon; +import com.vividsolutions.jts.geom.Polygon; + +import org.onebusaway.geospatial.model.CoordinateBounds; +import org.onebusaway.transit_data_federation.services.AgencyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +@Controller +@RequestMapping("/bounding-box.action") +public class BoundingBoxController { + + @Autowired + private AgencyService _agencyService; + + @RequestMapping() + public ModelAndView index() { + + GeometryFactory gf = new GeometryFactory(); + List polygons = new ArrayList(); + + Map agencies = _agencyService.getAgencyIdsAndCoverageAreas(); + + for (CoordinateBounds cb: agencies.values()) { + Envelope e = new Envelope(cb.getMinLon(), cb.getMaxLon(), cb.getMinLat(), cb.getMaxLat()); + Polygon p = (Polygon) gf.toGeometry(e); + polygons.add(p); + } + + MultiPolygon mp = gf.createMultiPolygon(polygons.toArray(new Polygon[0])); + + Geometry hull = mp.convexHull(); + Envelope env = hull.getEnvelopeInternal(); + + ModelAndView mv = new ModelAndView("bounding-box.jspx"); + mv.addObject("minY", env.getMinY()); + mv.addObject("minX", env.getMinX()); + mv.addObject("maxY", env.getMaxY()); + mv.addObject("maxX", env.getMaxX()); + mv.addObject("hullWKT", hull.toText()); + return mv; + } +} diff --git a/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/gtfs_realtime/GtfsRealtimeController.java b/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/gtfs_realtime/GtfsRealtimeController.java index 7aa00f27ae..e21dce9165 100644 --- a/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/gtfs_realtime/GtfsRealtimeController.java +++ b/onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/gtfs_realtime/GtfsRealtimeController.java @@ -1,4 +1,5 @@ /** + * Copyright (C) 2013 Kurt Raschke * Copyright (C) 2011 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +17,8 @@ package org.onebusaway.transit_data_federation_webapp.controllers.gtfs_realtime; import java.io.IOException; -import java.io.OutputStream; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletResponse; import org.onebusaway.transit_data_federation.impl.realtime.gtfs_realtime.GtfsRealtimeService; import org.springframework.beans.factory.annotation.Autowired; @@ -36,20 +38,32 @@ public void setGtfsRealtimeService(GtfsRealtimeService gtfsRealtimeService) { } @RequestMapping(value = "/gtfs-realtime/trip-updates.action") - public void tripUpdates(OutputStream out) throws IOException { + public void tripUpdates(ServletRequest request, HttpServletResponse response) throws IOException { FeedMessage tripUpdates = _gtfsRealtimeService.getTripUpdates(); - tripUpdates.writeTo(out); + render(request, response, tripUpdates); } @RequestMapping(value = "/gtfs-realtime/vehicle-positions.action") - public void vehiclePositions(OutputStream out) throws IOException { + public void vehiclePositions(ServletRequest request, HttpServletResponse response) throws IOException { FeedMessage vehiclePositions = _gtfsRealtimeService.getVehiclePositions(); - vehiclePositions.writeTo(out); + render(request, response, vehiclePositions); } @RequestMapping(value = "/gtfs-realtime/alerts.action") - public void alerts(OutputStream out) throws IOException { + public void alerts(ServletRequest request, HttpServletResponse response) throws IOException { FeedMessage alerts = _gtfsRealtimeService.getAlerts(); - alerts.writeTo(out); + render(request, response, alerts); } + + private void render(ServletRequest request, HttpServletResponse response, + FeedMessage message) throws IOException { + if (request.getParameter("debug") != null) { + response.setContentType("text/plain"); + response.getWriter().write(message.toString()); + } else { + response.setContentType("application/x-google-protobuf"); + message.writeTo(response.getOutputStream()); + } + } + } \ No newline at end of file diff --git a/onebusaway-transit-data-federation-webapp/src/main/webapp/WEB-INF/jsp/bounding-box.jspx b/onebusaway-transit-data-federation-webapp/src/main/webapp/WEB-INF/jsp/bounding-box.jspx new file mode 100644 index 0000000000..fab8a77d7c --- /dev/null +++ b/onebusaway-transit-data-federation-webapp/src/main/webapp/WEB-INF/jsp/bounding-box.jspx @@ -0,0 +1,40 @@ + + + + + +Bounding Box + + +

Bundle Bounding Box

+
+
minLat
+
+
minLon
+
+
maxLat
+
+
maxLon
+
+
Filter WKT
+
+
+ + \ No newline at end of file diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImpl.java index 8e1f4cd2e4..32d6fd724f 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImpl.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +16,6 @@ */ 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; @@ -57,11 +49,23 @@ 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.EInRangeStrategy; import org.onebusaway.utility.EOutOfRangeStrategy; import org.onebusaway.utility.InterpolationLibrary; +import org.onebusaway.utility.TransitInterpolationLibrary; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +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; + @Component class ArrivalAndDepartureServiceImpl implements ArrivalAndDepartureService { @@ -640,27 +644,45 @@ private void applyBlockLocationToInstance( if (blockLocation.isScheduleDeviationSet() || blockLocation.areScheduleDeviationsSet()) { - int scheduleDeviation = getBestScheduleDeviation(instance, blockLocation); - setPredictedTimesFromScheduleDeviation(instance, blockLocation, - scheduleDeviation, targetTime); + Double scheduleDeviation = getBestScheduleDeviation(instance, blockLocation); + if (scheduleDeviation != null) { + setPredictedTimesFromScheduleDeviation(instance, blockLocation, + scheduleDeviation.intValue(), targetTime); + } } } - private int getBestScheduleDeviation(ArrivalAndDepartureInstance instance, + /** + * Returns the best schedule deviation for this stop, given the scheduleDeviations + * stored in blockLocation. {@link TransitInterpolationLibrary} is used to find + * the best deviation, which interpolates/extrapolates values consistent with the + * GTFS-realtime spec (https://developers.google.com/transit/gtfs-realtime/) when + * using the {@link EInRangeStrategy.PREVIOUS_VALUE} and {@link EOutOfRangeStrategy.LAST_VALUE} + * strategies. null is returned if no real-time deviations were found and the scheduled + * arrival time should be used. + * @param instance + * @param blockLocation + * @return the best deviation for this stop, or null if no real-time deviations were found + * and the scheduled arrival time should be used. + */ + private Double getBestScheduleDeviation(ArrivalAndDepartureInstance instance, BlockLocation blockLocation) { ScheduleDeviationSamples scheduleDeviations = blockLocation.getScheduleDeviations(); if (scheduleDeviations != null && !scheduleDeviations.isEmpty()) { - int arrivalTime = instance.getBlockStopTime().getStopTime().getArrivalTime(); - return (int) InterpolationLibrary.interpolate( + // We currently use the scheduled arrival time of the stop as the search index + // This MUST be consistent with the index set in BlockLocationServiceImpl.getBlockLocation() + Integer arrivalTime = instance.getBlockStopTime().getStopTime().getArrivalTime(); + // Determine which real-time deviation should be used for this stop, if any + return TransitInterpolationLibrary.interpolate( scheduleDeviations.getScheduleTimes(), scheduleDeviations.getScheduleDeviationMus(), arrivalTime, - EOutOfRangeStrategy.LAST_VALUE); + EOutOfRangeStrategy.LAST_VALUE, EInRangeStrategy.PREVIOUS_VALUE); } else if (blockLocation.isScheduleDeviationSet()) { - return (int) blockLocation.getScheduleDeviation(); + return blockLocation.getScheduleDeviation(); } else { - return 0; + return 0.0; } } @@ -874,10 +896,10 @@ private TimeIntervalBean computePredictedArrivalTimeInterval( double mu = InterpolationLibrary.interpolate(samples.getScheduleTimes(), samples.getScheduleDeviationMus(), stopTime.getArrivalTime(), - EOutOfRangeStrategy.LAST_VALUE); + EOutOfRangeStrategy.LAST_VALUE, EInRangeStrategy.INTERPOLATE); double sigma = InterpolationLibrary.interpolate(samples.getScheduleTimes(), samples.getScheduleDeviationSigmas(), stopTime.getArrivalTime(), - EOutOfRangeStrategy.LAST_VALUE); + EOutOfRangeStrategy.LAST_VALUE, EInRangeStrategy.INTERPOLATE); long from = (long) (instance.getScheduledArrivalTime() + (mu - sigma) * 1000); long to = (long) (instance.getScheduledArrivalTime() + (mu + sigma) * 1000); @@ -904,10 +926,10 @@ private TimeIntervalBean computePredictedDepartureTimeInterval( double mu = InterpolationLibrary.interpolate(samples.getScheduleTimes(), samples.getScheduleDeviationMus(), stopTime.getDepartureTime(), - EOutOfRangeStrategy.LAST_VALUE); + EOutOfRangeStrategy.LAST_VALUE, EInRangeStrategy.INTERPOLATE); double sigma = InterpolationLibrary.interpolate(samples.getScheduleTimes(), samples.getScheduleDeviationSigmas(), stopTime.getDepartureTime(), - EOutOfRangeStrategy.LAST_VALUE); + EOutOfRangeStrategy.LAST_VALUE, EInRangeStrategy.INTERPOLATE); long from = (long) (instance.getScheduledDepartureTime() + (mu - sigma) * 1000); long to = (long) (instance.getScheduledDepartureTime() + (mu + sigma) * 1000); diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/TripStatusBeanServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/TripStatusBeanServiceImpl.java index d6f5ec0399..137485b82e 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/TripStatusBeanServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/beans/TripStatusBeanServiceImpl.java @@ -1,6 +1,7 @@ /** * Copyright (C) 2011 Brian Ferris * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +23,13 @@ import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.realtime.api.EVehiclePhase; +import org.onebusaway.realtime.api.TimepointPredictionRecord; import org.onebusaway.transit_data.model.ListBean; import org.onebusaway.transit_data.model.StopBean; import org.onebusaway.transit_data.model.TripStopTimesBean; import org.onebusaway.transit_data.model.schedule.FrequencyBean; import org.onebusaway.transit_data.model.service_alerts.ServiceAlertBean; +import org.onebusaway.transit_data.model.trips.TimepointPredictionBean; import org.onebusaway.transit_data.model.trips.TripBean; import org.onebusaway.transit_data.model.trips.TripDetailsBean; import org.onebusaway.transit_data.model.trips.TripDetailsInclusionBean; @@ -323,6 +326,20 @@ public TripStatusBean getBlockLocationAsStatusBean( bean.setSituations(situations); } + if (blockLocation.getTimepointPredictions() != null && blockLocation.getTimepointPredictions().size() > 0) { + List timepointPredictions = new ArrayList(); + for (TimepointPredictionRecord tpr: blockLocation.getTimepointPredictions()) { + TimepointPredictionBean tpb = new TimepointPredictionBean(); + tpb.setTimepointId(tpr.getTimepointId().toString()); + tpb.setTripId(tpr.getTripId().toString()); + tpb.setStopSequence(tpr.getStopSequence()); + tpb.setTimepointPredictedArrivalTime(tpr.getTimepointPredictedArrivalTime()); + tpb.setTimepointPredictedDepartureTime(tpr.getTimepointPredictedDepartureTime()); + timepointPredictions.add(tpb); + } + bean.setTimepointPredictions(timepointPredictions); + } + return bean; } @@ -415,4 +432,4 @@ private TripDetailsBean getTripEntryAndBlockLocationAsTripDetails( bean.setSituations(situations); return bean; } -} \ No newline at end of file +} diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/blocks/ScheduledBlockLocationServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/blocks/ScheduledBlockLocationServiceImpl.java index 21013b041a..5a9c9562bd 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/blocks/ScheduledBlockLocationServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/blocks/ScheduledBlockLocationServiceImpl.java @@ -39,7 +39,7 @@ import org.springframework.stereotype.Component; @Component -class ScheduledBlockLocationServiceImpl implements +public class ScheduledBlockLocationServiceImpl implements ScheduledBlockLocationService { private ShapePointService _shapePointService; @@ -571,4 +571,4 @@ private PointAndOrientation getLocationAlongShape( distanceAlongTrip, shapePointIndexFrom, shapePointIndexTo); return shapePointIndexMethod.getPointAndOrientation(shapePoints); } -} \ No newline at end of file +} diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/federated/TransitDataServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/federated/TransitDataServiceImpl.java index e4a6e6b9dc..61bec2afeb 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/federated/TransitDataServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/federated/TransitDataServiceImpl.java @@ -1,6 +1,7 @@ /** * Copyright (C) 2011 Brian Ferris * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2014 Kurt Raschke * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ import java.util.Set; import org.onebusaway.exceptions.NoSuchTripServiceException; +import org.onebusaway.exceptions.OutOfServiceAreaServiceException; import org.onebusaway.exceptions.ServiceException; import org.onebusaway.federations.annotations.FederatedByAgencyIdMethod; import org.onebusaway.federations.annotations.FederatedByEntityIdMethod; @@ -267,6 +269,7 @@ public StopScheduleBean getScheduleForStop(String stopId, Date date) @Override public StopsBean getStops(SearchQueryBean query) throws ServiceException { + checkBounds(query.getBounds()); return _stopsBeanService.getStops(query); } @@ -366,6 +369,7 @@ public ListBean getTripDetails(TripDetailsQueryBean query) @Override public ListBean getTripsForBounds( TripsForBoundsQueryBean query) { + checkBounds(query.getBounds()); return _tripDetailsBeanService.getTripsForBounds(query); } @@ -430,6 +434,7 @@ public TripDetailsBean getTripDetailsForVehicleAndTime( @Override public RoutesBean getRoutes(SearchQueryBean query) throws ServiceException { + checkBounds(query.getBounds()); return _routesBeanService.getRoutesForQuery(query); } @@ -706,5 +711,21 @@ private ArrivalAndDepartureQuery createArrivalAndDepartureQuery( return adQuery; } + + private void checkBounds(CoordinateBounds cb) { + if (cb == null) { + return; + } + + Collection allAgencyBounds = _agencyService.getAgencyIdsAndCoverageAreas().values(); + + for (CoordinateBounds agencyBounds : allAgencyBounds) { + if (agencyBounds.intersects(cb)) { + return; + } + } + + throw new OutOfServiceAreaServiceException(); + } } diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationRecord.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationRecord.java index faa0cce54a..2466eeb268 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationRecord.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationRecord.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,7 +101,9 @@ public class BlockLocationRecord { private final long timepointScheduledTime; - private final long timepointPredictedTime; + private final long timepointPredictedArrivalTime; + + private final long timepointPredictedDepartureTime; /** * Custom Hibernate mapping so that the vehicle phase enum gets mapped to a @@ -137,7 +140,8 @@ public BlockLocationRecord() { orientation = null; timepointId = null; timepointScheduledTime = 0; - timepointPredictedTime = 0; + timepointPredictedArrivalTime = -1; + timepointPredictedDepartureTime = -1; phase = null; status = null; vehicleId = null; @@ -156,7 +160,8 @@ private BlockLocationRecord(Builder builder) { this.orientation = builder.orientation; this.timepointId = builder.timepointId; this.timepointScheduledTime = builder.timepointScheduledTime; - this.timepointPredictedTime = builder.timepointPredictedTime; + this.timepointPredictedArrivalTime = builder.timepointPredictedArrivalTime; + this.timepointPredictedDepartureTime = builder.timepointPredictedDepartureTime; this.phase = builder.phase; this.status = builder.status; this.vehicleId = builder.vehicleId; @@ -261,8 +266,12 @@ public long getTimepointScheduledTime() { return timepointScheduledTime; } - public long getTimepointPredictedTime() { - return timepointPredictedTime; + public long getTimepointPredictedArrivalTime() { + return timepointPredictedArrivalTime; + } + + public long getTimepointPredictedDepartureTime() { + return timepointPredictedDepartureTime; } public EVehiclePhase getPhase() { @@ -316,7 +325,9 @@ public static class Builder { private long timepointScheduledTime; - private long timepointPredictedTime; + private long timepointPredictedArrivalTime; + + private long timepointPredictedDepartureTime; private EVehiclePhase phase; @@ -382,8 +393,12 @@ public void setTimepointScheduledTime(long timepointScheduledTime) { this.timepointScheduledTime = timepointScheduledTime; } - public void setTimepointPredictedTime(long timepointPredictedTime) { - this.timepointPredictedTime = timepointPredictedTime; + public void setTimepointPredictedArrivalTime(long timepointPredictedArrivalTime) { + this.timepointPredictedArrivalTime = timepointPredictedArrivalTime; + } + + public void setTimepointPredictedDepartureTime(long timepointPredictedDepartureTime) { + this.timepointPredictedDepartureTime = timepointPredictedDepartureTime; } public void setPhase(EVehiclePhase phase) { diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationServiceImpl.java index d2c2fb5db2..4a33d9f0f0 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/BlockLocationServiceImpl.java @@ -2,6 +2,7 @@ * Copyright (C) 2011 Brian Ferris * Copyright (C) 2011 * Copyright (C) 2012 Google, Inc. + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +65,7 @@ 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; @@ -550,6 +552,7 @@ private BlockLocation getBlockLocation(BlockInstance blockInstance, location.setVehicleId(record.getVehicleId()); List timepointPredictions = record.getTimepointPredictions(); + location.setTimepointPredictions(timepointPredictions); if (timepointPredictions != null && !timepointPredictions.isEmpty()) { SortedMap scheduleDeviations = new TreeMap(); @@ -558,17 +561,33 @@ private BlockLocation getBlockLocation(BlockInstance blockInstance, for (TimepointPredictionRecord tpr : timepointPredictions) { AgencyAndId stopId = tpr.getTimepointId(); - long predictedTime = tpr.getTimepointPredictedTime(); + long predictedTime; + if (tpr.getTimepointPredictedDepartureTime() != -1) { + predictedTime = tpr.getTimepointPredictedDepartureTime(); + } else { + predictedTime = tpr.getTimepointPredictedArrivalTime(); + } 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); + // StopSequence equals to -1 when there is no stop sequence in the GTFS-rt + if (stopId.equals(stop.getId()) && stopTime.getTrip().getId().equals(tpr.getTripId()) && + (tpr.getStopSequence() == -1 || stopTime.getSequence() == tpr.getStopSequence())) { + int arrivalOrDepartureTime; + // We currently use the scheduled arrival time of the stop as the search index + // This MUST be consistent with the index search in ArrivalAndSepartureServiceImpl.getBestScheduleDeviation() + int index = stopTime.getArrivalTime(); + if (tpr.getTimepointPredictedDepartureTime() != -1) { + // Prefer departure time, because if both exist departure deviations should be the ones propagated downstream + arrivalOrDepartureTime = stopTime.getDepartureTime(); + } else { + arrivalOrDepartureTime = stopTime.getArrivalTime(); + } + int deviation = (int) ((predictedTime - blockInstance.getServiceDate()) / 1000 - arrivalOrDepartureTime); + scheduleDeviations.put(index, (double) deviation); } } } @@ -880,7 +899,8 @@ private List getVehicleLocationRecordAsBlockLocationRecord( for (TimepointPredictionRecord tpr : predictions) { builder.setTimepointId(tpr.getTimepointId()); builder.setTimepointScheduledTime(tpr.getTimepointScheduledTime()); - builder.setTimepointPredictedTime(tpr.getTimepointPredictedTime()); + builder.setTimepointPredictedArrivalTime(tpr.getTimepointPredictedArrivalTime()); + builder.setTimepointPredictedDepartureTime(tpr.getTimepointPredictedDepartureTime()); results.add(builder.create()); } return results; diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/VehicleLocationRecordCacheImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/VehicleLocationRecordCacheImpl.java index 06d08dd3e4..168f892519 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/VehicleLocationRecordCacheImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/VehicleLocationRecordCacheImpl.java @@ -52,7 +52,7 @@ * @param record record to add */ @Component -class VehicleLocationRecordCacheImpl implements VehicleLocationRecordCache { +public class VehicleLocationRecordCacheImpl implements VehicleLocationRecordCache { private static Logger _log = LoggerFactory.getLogger(VehicleLocationRecordCacheImpl.class); diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeServiceImpl.java index edce10c863..5cb4e3d649 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeServiceImpl.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +20,7 @@ import java.util.List; import org.onebusaway.gtfs.model.AgencyAndId; +import org.onebusaway.realtime.api.TimepointPredictionRecord; import org.onebusaway.realtime.api.VehicleLocationRecord; import org.onebusaway.transit_data_federation.services.blocks.BlockInstance; import org.onebusaway.transit_data_federation.services.blocks.BlockStatusService; @@ -101,25 +103,52 @@ public FeedMessage getTripUpdates() { if (!activeBlock.isScheduleDeviationSet()) continue; - // No matter what our active trip is, we let our current trip be the the - // trip of our next stop + TripUpdate.Builder tripUpdate = TripUpdate.newBuilder(); BlockTripEntry activeBlockTrip = nextBlockStop.getTrip(); TripEntry activeTrip = activeBlockTrip.getTrip(); - StopTimeEntry nextStopTime = nextBlockStop.getStopTime(); - StopEntry stop = nextStopTime.getStop(); - - TripUpdate.Builder tripUpdate = TripUpdate.newBuilder(); - - StopTimeUpdate.Builder stopTimeUpdate = StopTimeUpdate.newBuilder(); - stopTimeUpdate.setStopId(AgencyAndId.convertToString(stop.getId())); - stopTimeUpdate.setStopSequence(nextStopTime.getSequence()); - stopTimeUpdate.setScheduleRelationship(com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SCHEDULED); - tripUpdate.addStopTimeUpdate(stopTimeUpdate); - - StopTimeEvent.Builder stopTimeEvent = StopTimeEvent.newBuilder(); - stopTimeEvent.setDelay((int) activeBlock.getScheduleDeviation()); - stopTimeUpdate.setDeparture(stopTimeEvent); - + + if (activeBlock.getTimepointPredictions() != null && activeBlock.getTimepointPredictions().size() > 0) { + // If multiple stoptime predictions were originally obtained, + // pass them through as received + List timepointPredictions = activeBlock.getTimepointPredictions(); + + for (TimepointPredictionRecord tpr: timepointPredictions) { + StopTimeUpdate.Builder stopTimeUpdate = StopTimeUpdate.newBuilder(); + stopTimeUpdate.setStopId(AgencyAndId.convertToString(tpr.getTimepointId())); + stopTimeUpdate.setScheduleRelationship(com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SCHEDULED); + + if (tpr.getTimepointPredictedArrivalTime() != -1) { + StopTimeEvent.Builder arrivalStopTimeEvent = StopTimeEvent.newBuilder(); + arrivalStopTimeEvent.setTime(tpr.getTimepointPredictedArrivalTime()); + stopTimeUpdate.setArrival(arrivalStopTimeEvent); + } + + if (tpr.getTimepointPredictedDepartureTime() != -1) { + StopTimeEvent.Builder departureStopTimeEvent = StopTimeEvent.newBuilder(); + departureStopTimeEvent.setTime(tpr.getTimepointPredictedDepartureTime()); + stopTimeUpdate.setDeparture(departureStopTimeEvent); + } + + tripUpdate.addStopTimeUpdate(stopTimeUpdate); + } + } else { + // No matter what our active trip is, we let our current trip be the the + // trip of our next stop + StopTimeEntry nextStopTime = nextBlockStop.getStopTime(); + StopEntry stop = nextStopTime.getStop(); + + StopTimeUpdate.Builder stopTimeUpdate = StopTimeUpdate.newBuilder(); + stopTimeUpdate.setStopId(AgencyAndId.convertToString(stop.getId())); + stopTimeUpdate.setStopSequence(nextStopTime.getSequence()); + stopTimeUpdate.setScheduleRelationship(com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SCHEDULED); + + StopTimeEvent.Builder stopTimeEvent = StopTimeEvent.newBuilder(); + stopTimeEvent.setDelay((int) activeBlock.getScheduleDeviation()); + stopTimeUpdate.setDeparture(stopTimeEvent); + + tripUpdate.addStopTimeUpdate(stopTimeUpdate); + } + AgencyAndId routeId = activeTrip.getRouteCollection().getId(); AgencyAndId tripId = activeTrip.getId(); BlockInstance blockInstance = activeBlock.getBlockInstance(); diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeTripLibrary.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeTripLibrary.java index d88789cde0..be1b8346b8 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeTripLibrary.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeTripLibrary.java @@ -1,5 +1,7 @@ /** + * Copyright (C) 2013 Kurt Raschke * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +50,7 @@ import com.google.transit.realtime.GtfsRealtime.VehiclePosition; import com.google.transit.realtime.GtfsRealtimeOneBusAway; import com.google.transit.realtime.GtfsRealtimeOneBusAway.OneBusAwayTripUpdate; +import org.onebusaway.realtime.api.TimepointPredictionRecord; class GtfsRealtimeTripLibrary { @@ -361,10 +364,13 @@ private void applyTripUpdatesToRecord(MonitoredResult result, BlockDescriptor bl int currentTime = (int) ((t - instance.getServiceDate()) / 1000); BestScheduleDeviation best = new BestScheduleDeviation(); + List timepointPredictions = new ArrayList(); + for (BlockTripEntry blockTrip : blockTrips) { TripEntry trip = blockTrip.getTrip(); AgencyAndId tripId = trip.getId(); List updatesForTrip = tripUpdatesByTripId.get(tripId.getId()); + if (updatesForTrip != null) { for (TripUpdate tripUpdate : updatesForTrip) { @@ -407,18 +413,40 @@ private void applyTripUpdatesToRecord(MonitoredResult result, BlockDescriptor bl instance.getServiceDate()); if (blockStopTime == null) continue; + StopTimeEntry stopTime = blockStopTime.getStopTime(); + + TimepointPredictionRecord tpr = new TimepointPredictionRecord(); + tpr.setTimepointId(stopTime.getStop().getId()); + tpr.setTripId(stopTime.getTrip().getId()); + if (stopTimeUpdate.hasStopSequence()) { + tpr.setStopSequence(stopTimeUpdate.getStopSequence()); + } + int currentArrivalTime = computeArrivalTime(stopTime, stopTimeUpdate, instance.getServiceDate()); + int currentDepartureTime = computeDepartureTime(stopTime, + stopTimeUpdate, instance.getServiceDate()); + if (currentArrivalTime >= 0) { updateBestScheduleDeviation(currentTime, stopTime.getArrivalTime(), currentArrivalTime, best); - } - int currentDepartureTime = computeDepartureTime(stopTime, - stopTimeUpdate, instance.getServiceDate()); + + long timepointPredictedTime = instance.getServiceDate() + (currentArrivalTime * 1000L); + tpr.setTimepointPredictedArrivalTime(timepointPredictedTime); + } + if (currentDepartureTime >= 0) { updateBestScheduleDeviation(currentTime, stopTime.getDepartureTime(), currentDepartureTime, best); + + long timepointPredictedTime = instance.getServiceDate() + (currentDepartureTime * 1000L); + tpr.setTimepointPredictedDepartureTime(timepointPredictedTime); + } + + if (tpr.getTimepointPredictedArrivalTime() != -1 || + tpr.getTimepointPredictedDepartureTime() != -1) { + timepointPredictions.add(tpr); } } } @@ -430,6 +458,7 @@ private void applyTripUpdatesToRecord(MonitoredResult result, BlockDescriptor bl if (best.timestamp != 0) { record.setTimeOfRecord(best.timestamp); } + record.setTimepointPredictions(timepointPredictions); } private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( @@ -438,8 +467,11 @@ private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( if (stopTimeUpdate.hasStopSequence()) { int stopSequence = stopTimeUpdate.getStopSequence(); - if (0 <= stopSequence && stopSequence < stopTimes.size()) { - BlockStopTimeEntry blockStopTime = stopTimes.get(stopSequence); + + Map sequenceToStopTime = MappingLibrary.mapToValue(stopTimes, "stopTime.gtfsSequence"); + + if (sequenceToStopTime.containsKey(stopSequence)) { + BlockStopTimeEntry blockStopTime = sequenceToStopTime.get(stopSequence); if (!stopTimeUpdate.hasStopId()) { return blockStopTime; } @@ -451,7 +483,7 @@ private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( // match by stop id if possible } else { - _log.warn("StopTimeSequence is out of bounds: stopSequence=" + _log.warn("StopTimeSequence not found: stopSequence=" + stopSequence + " tripUpdate=\n" + tripUpdate); } } diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/mybus/TimepointPredictionServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/mybus/TimepointPredictionServiceImpl.java index 27b461711b..730f556de0 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/mybus/TimepointPredictionServiceImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/mybus/TimepointPredictionServiceImpl.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2011 Brian Ferris + * Copyright (C) 2015 University of South Florida * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -303,7 +304,7 @@ private VehicleLocationRecord getBestScheduleAdherenceRecord( if (_includeTimepointPredictionRecords) { TimepointPredictionRecord tpr = new TimepointPredictionRecord(); tpr.setTimepointId(best.getTimepointId()); - tpr.setTimepointPredictedTime(best.getTimepointPredictedTime()); + tpr.setTimepointPredictedArrivalTime(best.getTimepointPredictedTime()); tpr.setTimepointScheduledTime(best.getTimepointScheduledTime()); r.setTimepointPredictions(Arrays.asList(tpr)); } diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/transit_graph/StopTimeEntryImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/transit_graph/StopTimeEntryImpl.java index 4996b80482..80f3b3fc4f 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/transit_graph/StopTimeEntryImpl.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/transit_graph/StopTimeEntryImpl.java @@ -1,4 +1,5 @@ /** + * Copyright (C) 2013 Kurt Raschke * Copyright (C) 2011 Brian Ferris * Copyright (C) 2011 Google, Inc. * @@ -22,12 +23,13 @@ public class StopTimeEntryImpl implements StopTimeEntry, Serializable { - private static final long serialVersionUID = 5L; + private static final long serialVersionUID = 7L; private int _stopTimeId; private int _arrivalTime; private int _departureTime; private int _sequence; + private int _gtfsSequence; private int _dropOffType; private int _pickupType; private int _shapePointIndex = -1; @@ -60,6 +62,10 @@ public void setSequence(int sequence) { _sequence = sequence; } + public void setGtfsSequence(int gtfsSequence) { + _gtfsSequence = gtfsSequence; + } + public void setDropOffType(int dropOffType) { _dropOffType = dropOffType; } @@ -117,6 +123,11 @@ public int getSequence() { return _sequence; } + @Override + public int getGtfsSequence() { + return _gtfsSequence; + } + @Override public int getDropOffType() { return _dropOffType; diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/realtime/BlockLocation.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/realtime/BlockLocation.java index 99d6d178c5..fd0532eea1 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/realtime/BlockLocation.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/realtime/BlockLocation.java @@ -16,9 +16,11 @@ */ package org.onebusaway.transit_data_federation.services.realtime; +import java.util.List; import org.onebusaway.geospatial.model.CoordinatePoint; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.realtime.api.EVehiclePhase; +import org.onebusaway.realtime.api.TimepointPredictionRecord; import org.onebusaway.transit_data_federation.services.blocks.BlockInstance; import org.onebusaway.transit_data_federation.services.blocks.BlockTripInstance; import org.onebusaway.transit_data_federation.services.transit_graph.BlockStopTimeEntry; @@ -101,6 +103,8 @@ public class BlockLocation { private ScheduleDeviationSamples scheduleDeviations = null; private AgencyAndId vehicleId; + + private List timepointPredictions; public BlockLocation() { @@ -458,6 +462,14 @@ public void setVehicleId(AgencyAndId vehicleId) { this.vehicleId = vehicleId; } + public List getTimepointPredictions() { + return this.timepointPredictions; + } + + public void setTimepointPredictions(List timepointPredictions) { + this.timepointPredictions = timepointPredictions; + } + @Override public String toString() { StringBuilder b = new StringBuilder(); @@ -479,4 +491,4 @@ public String toString() { b.append(")"); return b.toString(); } -} \ No newline at end of file +} diff --git a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/transit_graph/StopTimeEntry.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/transit_graph/StopTimeEntry.java index e850e768fc..1791336e7a 100644 --- a/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/transit_graph/StopTimeEntry.java +++ b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/services/transit_graph/StopTimeEntry.java @@ -1,4 +1,5 @@ /** + * Copyright (C) 2013 Kurt Raschke * Copyright (C) 2011 Brian Ferris * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +22,16 @@ public interface StopTimeEntry { public TripEntry getTrip(); + /** + * @return stop sequence computed by OneBusAway + */ public int getSequence(); + /** + * @return stop sequence defined in GTFS + */ + public int getGtfsSequence(); + public StopEntry getStop(); /** diff --git a/onebusaway-transit-data-federation/src/test/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImplTest.java b/onebusaway-transit-data-federation/src/test/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImplTest.java new file mode 100644 index 0000000000..508d990099 --- /dev/null +++ b/onebusaway-transit-data-federation/src/test/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImplTest.java @@ -0,0 +1,760 @@ +/** + * Copyright (C) 2015 University of South Florida + * + * 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 static org.junit.Assert.assertEquals; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.block; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.blockConfiguration; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.dateAsLong; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.lsids; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.serviceIds; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.stop; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.stopTime; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.time; +import static org.onebusaway.transit_data_federation.testing.UnitTestingSupport.trip; + +import org.onebusaway.gtfs.model.AgencyAndId; +import org.onebusaway.realtime.api.TimepointPredictionRecord; +import org.onebusaway.realtime.api.VehicleLocationRecord; +import org.onebusaway.transit_data_federation.impl.blocks.BlockStatusServiceImpl; +import org.onebusaway.transit_data_federation.impl.blocks.ScheduledBlockLocationServiceImpl; +import org.onebusaway.transit_data_federation.impl.realtime.BlockLocationServiceImpl; +import org.onebusaway.transit_data_federation.impl.realtime.VehicleLocationRecordCacheImpl; +import org.onebusaway.transit_data_federation.impl.transit_graph.BlockEntryImpl; +import org.onebusaway.transit_data_federation.impl.transit_graph.StopEntryImpl; +import org.onebusaway.transit_data_federation.impl.transit_graph.TripEntryImpl; +import org.onebusaway.transit_data_federation.model.TargetTime; +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.ScheduledBlockLocation; +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.transit_graph.BlockConfigurationEntry; +import org.onebusaway.transit_data_federation.services.transit_graph.BlockStopTimeEntry; +import org.onebusaway.transit_data_federation.services.transit_graph.StopTimeEntry; +import org.onebusaway.transit_data_federation.services.tripplanner.StopTimeInstance; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Tests to see if the per stop time point predictions provided by a real-time + * feed are correctly applied to the scheduled time, so the correct predicted + * arrival times are produced. Behavior for propagating times is consistent with + * the GTFS-realtime spec + * (https://developers.google.com/transit/gtfs-realtime/). + * + * @author cetin + * @author barbeau + */ +public class ArrivalAndDepartureServiceImplTest { + + private ArrivalAndDepartureServiceImpl _service; + + private BlockStatusService _blockStatusService; + + private StopTimeService _stopTimeService; + + private BlockLocationServiceImpl _blockLocationService; + + // Setup current time + private long mCurrentTime = dateAsLong("2015-07-23 13:00"); + + private long mServiceDate = dateAsLong("2015-07-23 00:00"); + + // Stops + private StopEntryImpl mStopA = stop("stopA", 47.0, -122.0); + + private StopEntryImpl mStopB = stop("stopB", 47.0, -128.0); + + private TripEntryImpl mTripA = trip("tripA", "sA", 3000); + + @Before + public void setup() { + _service = new ArrivalAndDepartureServiceImpl(); + + _blockStatusService = new BlockStatusServiceImpl(); + _service.setBlockStatusService(_blockStatusService); + + _stopTimeService = Mockito.mock(StopTimeServiceImpl.class); + _service.setStopTimeService(_stopTimeService); + + _blockLocationService = new BlockLocationServiceImpl(); + _blockLocationService.setLocationInterpolation(false); + _service.setBlockLocationService(_blockLocationService); + } + + /** + * This method tests time point predictions upstream of a stop for *arrival* + * times. + * + * Test configuration: Time point predictions are upstream of the stop and + * include the given stop_ids, which means that the bus hasn't passed these + * bus stops yet. There are 2 bus stops which have the real time arrival times + * (time point predictions). In this case + * getArrivalsAndDeparturesForStopInTimeRange() should return the absolute + * time point prediction for particular stop that was provided by the feed, + * which replaces the scheduled time from GTFS for these stops. + * + * Current time = 13:00 + * Schedule time Real-time from feed + * Stop A 13:30 13:30 + * Stop B 13:40 13:50 + * + * When requesting arrival estimate for Stop B, result should be 13:50 (same + * as exact prediction from real-time feed). + */ + @Test + public void testGetArrivalsAndDeparturesForStopInTimeRange01() { + + // Set time point predictions for stop A + TimepointPredictionRecord tprA = new TimepointPredictionRecord(); + tprA.setTimepointId(mStopA.getId()); + long tprATime = createPredictedTime(time(13, 30)); + tprA.setTimepointPredictedArrivalTime(tprATime); + tprA.setTripId(mTripA.getId()); + + // Set time point predictions for stop B + TimepointPredictionRecord tprB = new TimepointPredictionRecord(); + tprB.setTimepointId(mStopB.getId()); + long tprBTime = createPredictedTime(time(13, 50)); + tprB.setTimepointPredictedArrivalTime(tprBTime); + tprB.setTripId(mTripA.getId()); + + // Call ArrivalsAndDeparturesForStopInTimeRange method in + // ArrivalAndDepartureServiceImpl + List arrivalsAndDepartures = getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord(Arrays.asList( + tprA, tprB)); + + long predictedArrivalTime = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + /** + * Check if the predictedArrivalTime is exactly the same as the + * TimepointPrediction. + */ + assertEquals(tprB.getTimepointPredictedArrivalTime(), predictedArrivalTime); + } + + /** + * This method tests upstream time point predictions for scheduled *arrival* + * times. + * + * Test configuration: Time point predictions are upstream of the current + * stop_id, which means that the bus hasn't passed the bus stop yet. A real + * time arrival time (time point prediction) is provided for only one bus stop + * (Stop A). In this case getArrivalsAndDeparturesForStopInTimeRange() should + * calculate a new arrival time for Stop B (based on the upstream prediction + * for Stop A), which is the scheduled arrival time + the upstream deviation. + * + * Current time = 13:00 + * Schedule time Real-time from feed + * Stop A 13:30 13:35 + * Stop B 13:40 --- + * + * We are requesting arrival time for Stop B, which should be propagated + * downstream from Stop A's prediction, which should be 13:45 (13:40 + 5 min + * deviation from Stop A). Stop A's predicted arrival and departure should + * also be the respective scheduled arrival and departure plus the 5 min + * deviation. + * + */ + @Test + public void testGetArrivalsAndDeparturesForStopInTimeRange02() { + + // Set time point predictions for stop A + TimepointPredictionRecord tprA = new TimepointPredictionRecord(); + tprA.setTimepointId(mStopA.getId()); + long tprATime = createPredictedTime(time(13, 35)); + tprA.setTimepointPredictedArrivalTime(tprATime); + tprA.setTripId(mTripA.getId()); + + // Call ArrivalsAndDeparturesForStopInTimeRange method in + // ArrivalAndDepartureServiceImpl + List arrivalsAndDepartures = getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord(Arrays.asList(tprA)); + + long predictedArrivalTimeStopA = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + long predictedDepartureTimeStopA = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + long predictedArrivalTimeStopB = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + long predictedDepartureTimeStopB = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + long scheduledArrivalTimeStopA = getScheduledArrivalTimeByStopId(mTripA, + mStopA.getId()); + long scheduledDepartureTimeStopA = getScheduledDepartureTimeByStopId( + mTripA, mStopA.getId()); + long scheduledArrivalTimeStopB = getScheduledArrivalTimeByStopId(mTripA, + mStopB.getId()); + long scheduledDepartureTimeStopB = getScheduledDepartureTimeByStopId( + mTripA, mStopB.getId()); + + // The time point prediction for Stop A was 5 min late, so this should be + // applied to Stop B scheduled arrival + long delta = TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopA) + - scheduledArrivalTimeStopA; + assertEquals(TimeUnit.MINUTES.toSeconds(5), delta); + + // Check if the predictedArrivalTimes and predictedDepartureTimes is the + // same as the scheduledArrivalTime plus the delta + assertEquals(TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopA), + scheduledArrivalTimeStopA + delta); + assertEquals(TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopA), + scheduledDepartureTimeStopA + delta); + assertEquals(TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopB), + scheduledArrivalTimeStopB + delta); + assertEquals(TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopB), + scheduledDepartureTimeStopB + delta); + } + + /** + * This method tests upstream time point predictions with only a predicted + * *departure* time. + * + * Test configuration: There is only one bus stop (Stop A) which has the real + * time departure time (time point prediction). In this case + * getArrivalsAndDeparturesForStopInTimeRange() should return the time point + * prediction for Stop A's departure time, which replaces the scheduled time + * from GTFS for this stop. For Stop B, the upstream departure prediction for + * Stop A should be propagated down to Stop B, and this deviation should be + * used to calculate Stop B's arrival and departure times. + * + * Current time = 13:00 + * Schedule Arrival time Schedule Departure time Real-time departure time + * Stop A 13:30 13:35 13:30 + * Stop B 13:45 13:50 ---- + * + * When requesting arrival estimate for Stop A, result should be 0 (note this + * isn't currently supported - see TODO in method body). + * + * When requesting departure estimate for Stop A, result should be exactly + * same with the real-time feed's departure time for Stop A. + * + * When requesting arrival and departure estimate for Stop B, the result + * should be 5 min less then the scheduled arrival and departure times. + * Because the upstream stop departs 5 min early, OBA should subtract this 5 + * min deviation from the downstream scheduled values. + */ + @Test + public void testGetArrivalsAndDeparturesForStopInTimeRange03() { + + // Set time point predictions for stop A + TimepointPredictionRecord tprA = new TimepointPredictionRecord(); + tprA.setTimepointId(mStopA.getId()); + long tprATime = createPredictedTime(time(13, 30)); + tprA.setTimepointPredictedDepartureTime(tprATime); + tprA.setTripId(mTripA.getId()); + + // Call ArrivalsAndDeparturesForStopInTimeRange method in + // ArrivalAndDepartureServiceImpl + List arrivalsAndDepartures = getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord(Arrays.asList(tprA)); + + long predictedArrivalTimeStopA = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + + long predictedDepartureTimeStopA = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + /** + * Check if the predictedDepartureTime is exactly the same with + * TimepointPrediction. + */ + assertEquals(tprA.getTimepointPredictedDepartureTime(), + predictedDepartureTimeStopA); + + /** + * TODO - Fully support both real-time arrival and departure times for each + * stop in OBA + * + * We're currently limited by OBA's internal data model which contains only + * one deviation per stop. By GTFS-rt spec, if no real-time arrival + * information is given for a stop, then the scheduled arrival should be + * used. In our case here, we're getting a real-time departure for Stop A + * (and no real-time arrival time for Stop A), but then we're showing the + * real-time departure info for Stop A as the real-time arrival time for + * Stop A. So, we're effectively propagating the real-time value backwards + * within the same stop. The correct value for predictedArrivalTimeStopA is + * actually 0, because we don't have any real-time arrival information for + * Stop A (or upstream of Stop A). + * + * So, the below assertion is currently commented out, as it fails. Future + * work should overhaul OBA's data model to support more than one real-time + * deviation per stop. When this is correctly implemented, the below + * assertion should be uncommented and it should pass. + */ + // assertEquals(0, predictedArrivalTimeStopA); + + /** + * Test for Stop B + */ + + long predictedArrivalTimeStopB = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + long predictedDepartureTimeStopB = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + long scheduledDepartureTimeForStopA = getScheduledDepartureTimeByStopId( + mTripA, mStopA.getId()); + long scheduledArrivalTimeForStopB = getScheduledArrivalTimeByStopId(mTripA, + mStopB.getId()); + long scheduledDepartureTimeForStopB = getScheduledDepartureTimeByStopId( + mTripA, mStopB.getId()); + + // Calculate the departure time difference from the upstream stop + long deltaB = (scheduledDepartureTimeForStopA - TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopA)); + + /** + * Check if the predictedArrivalTime is 5 min less then the scheduled + * arrival time for stop B. + */ + assertEquals(TimeUnit.MINUTES.toSeconds(5), deltaB); + assertEquals(scheduledArrivalTimeForStopB - deltaB, + TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopB)); + + /** + * Check if the predictedDepartureTime is 5 min less then the scheduled + * departure time for stop B. + */ + assertEquals(scheduledDepartureTimeForStopB - deltaB, + TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopB)); + } + + /** + * This method tests upstream time point predictions with both predicted + * arrival and departure times. + * + * Test configuration: Time point predictions are upstream and include the + * current stop_id, which means that the bus hasn't passed the bus stop yet. + * There is only one bus stop (Stop A) which has the real time arrival and + * departure times (time point prediction). In this case + * getArrivalsAndDeparturesForStopInTimeRange() should return absolute time + * point prediction for Stop A's departure time, which replaces the scheduled + * time from GTFS for this stop. Stop B's predictions should be derived from + * the upstream predictions provided for Stop A. + * + * Current time = 13:00 + * Schedule Arrival time Schedule Departure time Real-time arrival time Real-time departure time + * Stop A 13:30 13:35 13:20 13:30 + * Stop B 13:45 13:50 ----- ----- + * + * When requesting arrival estimate for Stop A, result should be 13:20 + * (predicted real-time arrival time). Note that this currently isn't support + * - see TODO statement in method body. + * + * When requesting departure estimate for Stop A, result should be 13:30 + * (predicted real-time departure time). + * + * When requesting arrival and departure estimates for Stop B, results should + * be 5 min less then the scheduled arrival and departure times. Because the + * upstream Stop A departs 5 min early, OBA should subtract this 5 min from + * the downstream estimates. + */ + @Test + public void testGetArrivalsAndDeparturesForStopInTimeRange04() { + + // Set time point predictions for stop A + TimepointPredictionRecord tprA = new TimepointPredictionRecord(); + tprA.setTimepointId(mStopA.getId()); + long tprADepartureTime = createPredictedTime(time(13, 30)); + tprA.setTimepointPredictedDepartureTime(tprADepartureTime); + long tprAArrivalTime = createPredictedTime(time(13, 20)); + tprA.setTimepointPredictedArrivalTime(tprAArrivalTime); + tprA.setTripId(mTripA.getId()); + + // Call ArrivalsAndDeparturesForStopInTimeRange method in + // ArrivalAndDepartureServiceImpl + List arrivalsAndDepartures = getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord(Arrays.asList(tprA)); + + long predictedDepartureTimeStopA = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + /** + * Check if the predictedDepartureTime is exactly the same with + * TimepointPrediction. + */ + assertEquals(tprA.getTimepointPredictedDepartureTime(), + predictedDepartureTimeStopA); + + /** + * TODO - Fully support both real-time arrival and departure times for each + * stop in OBA + * + * We're currently limited by OBA's internal data model which contains only + * one deviation per stop. By GTFS-rt spec, if real-time arrival information + * is given for a stop, then it should be used. In our case we expect to get + * 13:20 as predictedArrivalTime, as this is the predicted arrival time + * supplied in the real-time feed. However, we are getting 13:25 as + * predictedArrivalTime, which is actually the predictedDepartureTime for + * this stop. This is because OBA must prefer predicted departure times to + * arrival times when only one deviation per stop is supported, as the + * departure times are what should be propagated downstream. + * + * So, the below assertion is currently commented out, as it fails. Future + * work should overhaul OBA's data model to support more than one real-time + * deviation per stop. When this is correctly implemented, the below + * assertion should be uncommented and it should pass. + */ + + // long predictedArrivalTimeStopA = getPredictedArrivalTimeByStopId( + // arrivalsAndDepartures, stopA.getId()); + // + // assertEquals(TimeUnit.MILLISECONDS.toSeconds(tprA.getTimepointPredictedArrivalTime()), + // TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopA)); + + /** + * Test for Stop B + */ + + long scheduledDepartureTimeForStopA = getScheduledDepartureTimeByStopId( + mTripA, mStopA.getId()); + + long predictedArrivalTimeStopB = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + long predictedDepartureTimeStopB = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + long scheduledArrivalTimeForStopB = getScheduledArrivalTimeByStopId(mTripA, + mStopB.getId()); + long scheduledDepartureTimeForStopB = getScheduledDepartureTimeByStopId( + mTripA, mStopB.getId()); + + // Calculate the departure time difference from the upstream stop + long deltaB = scheduledDepartureTimeForStopA + - TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopA); + + /** + * Check if the predictedDepartureTime is 5 min less then the scheduled + * departure time for stop B. + */ + assertEquals(TimeUnit.MINUTES.toSeconds(5), deltaB); + assertEquals(scheduledDepartureTimeForStopB - deltaB, + TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopB)); + + /** + * Check if the predictedArrivalTime is 5 min less then the scheduled + * arrival time for stop B. + */ + assertEquals(scheduledArrivalTimeForStopB - deltaB, + TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopB)); + } + + /** + * This method tests a request for an arrival time for a stop, when the + * current time is greater than the arrival time prediction for that stop + * (Stop B). In other words, the bus is predicted to have already passed the + * stop (Stop B). + * + * Test configuration: There are 2 bus stops which have the real time arrival + * times (time point predictions) - Stop A and B. In this case + * getArrivalsAndDeparturesForStopInTimeRange() should return last received + * time point prediction for particular stop we're requesting information for. + * + * Current time = 14:00 + * Schedule time Real-time from feed + * Stop A 13:30 13:30 + * Stop B 13:40 13:50 + * + */ + @Test + public void testGetArrivalsAndDeparturesForStopInTimeRange05() { + // Override the current time with a later time than the time point + // predictions + mCurrentTime = dateAsLong("2015-07-23 14:00"); + + // Set time point predictions for stop A + TimepointPredictionRecord tprA = new TimepointPredictionRecord(); + tprA.setTimepointId(mStopA.getId()); + long tprATime = createPredictedTime(time(13, 30)); + tprA.setTimepointPredictedArrivalTime(tprATime); + tprA.setTripId(mTripA.getId()); + + // Set time point predictions for stop B + TimepointPredictionRecord tprB = new TimepointPredictionRecord(); + tprB.setTimepointId(mStopB.getId()); + long tprBTime = createPredictedTime(time(13, 50)); + tprB.setTimepointPredictedArrivalTime(tprBTime); + tprB.setTripId(mTripA.getId()); + + // Call ArrivalsAndDeparturesForStopInTimeRange method in + // ArrivalAndDepartureServiceImpl + List arrivalsAndDepartures = getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord(Arrays.asList( + tprA, tprB)); + + long predictedArrivalTimeStopA = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + long predictedArrivalTimeStopB = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + /** + * Check if the predictedArrivalTime is exactly the same as + * TimepointPrediction for both stops + */ + assertEquals(tprA.getTimepointPredictedArrivalTime(), + predictedArrivalTimeStopA); + assertEquals(tprB.getTimepointPredictedArrivalTime(), + predictedArrivalTimeStopB); + + /** + * Check if the predictedDepartureTimes and scheduledDepartureTimes have the + * same delta as arrival predictions and scheduled arrival times for both + * stops + */ + long predictedDepartureTimeStopA = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + long predictedDepartureTimeStopB = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + + long scheduledArrivalTimeForStopA = getScheduledArrivalTimeByStopId(mTripA, + mStopA.getId()); + long scheduledArrivalTimeForStopB = getScheduledArrivalTimeByStopId(mTripA, + mStopB.getId()); + long scheduledDepartureTimeForStopA = getScheduledDepartureTimeByStopId( + mTripA, mStopA.getId()); + long scheduledDepartureTimeForStopB = getScheduledDepartureTimeByStopId( + mTripA, mStopB.getId()); + + long deltaA = TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopA) + - scheduledArrivalTimeForStopA; + assertEquals(scheduledDepartureTimeForStopA + deltaA, + TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopA)); + + long deltaB = TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopB) + - scheduledArrivalTimeForStopB; + assertEquals(scheduledDepartureTimeForStopB + deltaB, + TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopB)); + } + + /** + * This method tests to make sure upstream propagation isn't happening. + * + * Test configuration: Time point predictions are downstream of Stop A, which + * means that the bus is predicted to have already passed the bus stop. There + * only one bus stop (Stop B) which has a real time arrival time (time point + * prediction). In this case getArrivalsAndDeparturesForStopInTimeRange() for + * Stop A should return a predicted arrival time = 0, indicating that no + * real-time information is available for Stop A. + * + * Current time = 14:00 + * Schedule time Real-time from feed + * Stop A 13:30 ----- + * Stop B 13:45 13:40 + * + * Since the bus already passed the bus stop A, and no real-time information + * is available for Stop A, OBA should NOT propagate arrival estimate for Stop + * B upstream to Stop A. + */ + @Test + public void testGetArrivalsAndDeparturesForStopInTimeRange06() { + // Override the current time with a later time than the time point + // predictions + mCurrentTime = dateAsLong("2015-07-23 14:00"); + + // Set time point predictions for stop B + TimepointPredictionRecord tprB = new TimepointPredictionRecord(); + tprB.setTimepointId(mStopB.getId()); + long tprBTime = createPredictedTime(time(13, 40)); + tprB.setTimepointPredictedArrivalTime(tprBTime); + tprB.setTripId(mTripA.getId()); + + // Call ArrivalsAndDeparturesForStopInTimeRange method in + // ArrivalAndDepartureServiceImpl + List arrivalsAndDepartures = getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord(Arrays.asList(tprB)); + + long predictedArrivalTimeStopB = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + /** + * Check if the predictedArrivalTime for stop B is exactly the same as + * TimepointPredictionRecord. + */ + assertEquals(tprB.getTimepointPredictedArrivalTime(), + predictedArrivalTimeStopB); + + /** + * Check predicted departure for Stop B too, to make sure its propagated + * from provided predicted arrival time + */ + long scheduledArrivalTimeForStopB = getScheduledArrivalTimeByStopId(mTripA, + mStopB.getId()); + long scheduledDepartureTimeForStopB = getScheduledDepartureTimeByStopId( + mTripA, mStopB.getId()); + long predictedDepartureTimeStopB = getPredictedDepartureTimeByStopId( + arrivalsAndDepartures, mStopB.getId()); + long deltaB = TimeUnit.MILLISECONDS.toSeconds(predictedArrivalTimeStopB) + - scheduledArrivalTimeForStopB; + assertEquals(scheduledDepartureTimeForStopB + deltaB, + TimeUnit.MILLISECONDS.toSeconds(predictedDepartureTimeStopB)); + + /** + * Make sure the predictedArrivalTime for stop A is equals to 0 - in other + * words, we should show no real-time information for this stop and use the + * scheduled time instead. + */ + + long predictedArrivalTimeA = getPredictedArrivalTimeByStopId( + arrivalsAndDepartures, mStopA.getId()); + assertEquals(0, predictedArrivalTimeA); + } + + /** + * Set up the BlockLocationServiceImpl for the test, using the given + * timepointPredictions + * + * @param timepointPredictions real-time predictions to apply to the + * BlockLocationServiceImpl + * @return a list of ArrivalAndDepartureInstances which is used to access + * predicted arrival/departure times for a stop, for comparison + * against the expected values + */ + private List getArrivalsAndDeparturesForStopInTimeRangeByTimepointPredictionRecord( + List timepointPredictions) { + TargetTime target = new TargetTime(mCurrentTime, mCurrentTime); + + // Setup block + BlockEntryImpl block = block("blockA"); + + stopTime(0, mStopA, mTripA, time(13, 30), time(13, 35), 1000); + stopTime(1, mStopB, mTripA, time(13, 45), time(13, 50), 2000); + + BlockConfigurationEntry blockConfig = blockConfiguration(block, + serviceIds(lsids("sA"), lsids()), mTripA); + BlockStopTimeEntry bstAA = blockConfig.getStopTimes().get(0); + BlockStopTimeEntry bstAB = blockConfig.getStopTimes().get(1); + BlockStopTimeEntry bstBA = blockConfig.getStopTimes().get(0); + + // Setup block location instance for trip B + BlockInstance blockInstance = new BlockInstance(blockConfig, mServiceDate); + BlockLocation blockLocationB = new BlockLocation(); + blockLocationB.setActiveTrip(bstBA.getTrip()); + blockLocationB.setBlockInstance(blockInstance); + blockLocationB.setClosestStop(bstBA); + blockLocationB.setDistanceAlongBlock(400); + blockLocationB.setInService(true); + blockLocationB.setNextStop(bstAA); + blockLocationB.setPredicted(false); + blockLocationB.setScheduledDistanceAlongBlock(400); + + blockLocationB.setTimepointPredictions(timepointPredictions); + + // Mock StopTimeInstance with time frame + long stopTimeFrom = dateAsLong("2015-07-23 00:00"); + long stopTimeTo = dateAsLong("2015-07-24 00:00"); + + StopTimeInstance sti1 = new StopTimeInstance(bstAB, + blockInstance.getState()); + ArrivalAndDepartureInstance in1 = new ArrivalAndDepartureInstance(sti1); + in1.setBlockLocation(blockLocationB); + in1.setPredictedArrivalTime((long) (in1.getScheduledArrivalTime())); + in1.setPredictedDepartureTime((long) (in1.getScheduledDepartureTime())); + + StopTimeInstance sti2 = new StopTimeInstance(bstBA, + blockInstance.getState()); + ArrivalAndDepartureInstance in2 = new ArrivalAndDepartureInstance(sti2); + in2.setBlockLocation(blockLocationB); + + Date fromTimeBuffered = new Date(stopTimeFrom + - _blockStatusService.getRunningLateWindow() * 1000); + Date toTimeBuffered = new Date(stopTimeTo + + _blockStatusService.getRunningEarlyWindow() * 1000); + + Mockito.when( + _stopTimeService.getStopTimeInstancesInTimeRange(mStopB, + fromTimeBuffered, toTimeBuffered, + EFrequencyStopTimeBehavior.INCLUDE_UNSPECIFIED)).thenReturn( + Arrays.asList(sti1, sti2)); + + // Create and add vehicle location record cache + VehicleLocationRecordCacheImpl _cache = new VehicleLocationRecordCacheImpl(); + VehicleLocationRecord vlr = new VehicleLocationRecord(); + vlr.setBlockId(blockLocationB.getBlockInstance().getBlock().getBlock().getId()); + vlr.setTripId(mTripA.getId()); + vlr.setTimepointPredictions(blockLocationB.getTimepointPredictions()); + vlr.setTimeOfRecord(mCurrentTime); + vlr.setVehicleId(new AgencyAndId("1", "123")); + + // Create ScheduledBlockLocation for cache + ScheduledBlockLocation sbl = new ScheduledBlockLocation(); + sbl.setActiveTrip(blockLocationB.getActiveTrip()); + + // Add data to cache + _cache.addRecord(blockInstance, vlr, sbl, null); + _blockLocationService.setVehicleLocationRecordCache(_cache); + ScheduledBlockLocationServiceImpl scheduledBlockLocationServiceImpl = new ScheduledBlockLocationServiceImpl(); + _blockLocationService.setScheduledBlockLocationService(scheduledBlockLocationServiceImpl); + + // Call ArrivalAndDepartureService + return _service.getArrivalsAndDeparturesForStopInTimeRange(mStopB, target, + stopTimeFrom, stopTimeTo); + } + + // + // Helper methods + // + + private long getPredictedArrivalTimeByStopId( + List arrivalsAndDepartures, + AgencyAndId stopId) { + for (ArrivalAndDepartureInstance adi : arrivalsAndDepartures) { + if (adi.getStop().getId().equals(stopId)) { + return adi.getPredictedArrivalTime(); + } + } + return 0; + } + + private long getPredictedDepartureTimeByStopId( + List arrivalsAndDepartures, + AgencyAndId stopId) { + for (ArrivalAndDepartureInstance adi : arrivalsAndDepartures) { + if (adi.getStop().getId().equals(stopId)) { + return adi.getPredictedDepartureTime(); + } + } + return 0; + } + + private long getScheduledArrivalTimeByStopId(TripEntryImpl trip, + AgencyAndId id) { + for (StopTimeEntry ste : trip.getStopTimes()) { + if (ste.getStop().getId().equals(id)) { + return ste.getArrivalTime() + mServiceDate / 1000; + } + } + return 0; + } + + private long getScheduledDepartureTimeByStopId(TripEntryImpl trip, + AgencyAndId id) { + for (StopTimeEntry ste : trip.getStopTimes()) { + if (ste.getStop().getId().equals(id)) { + return ste.getDepartureTime() + mServiceDate / 1000; + } + } + return 0; + } + + private long createPredictedTime(int time) { + return (mServiceDate / 1000 + time) * 1000; + } +} \ No newline at end of file diff --git a/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TimepointPredictionBean.java b/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TimepointPredictionBean.java new file mode 100644 index 0000000000..6e256c5033 --- /dev/null +++ b/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TimepointPredictionBean.java @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2013 Kurt Raschke + * Copyright (C) 2015 University of South Florida + * + * 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.model.trips; + +import java.io.Serializable; + +public class TimepointPredictionBean implements Serializable { + + private static final long serialVersionUID = 2L; + + private String timepointId; + + private String tripId; + + private int stopSequence = -1; + + private long timepointScheduledTime; + + private long timepointPredictedArrivalTime = -1; + + private long timepointPredictedDepartureTime = -1; + + public TimepointPredictionBean() { + + } + + public String getTimepointId() { + return timepointId; + } + + public void setTimepointId(String timepointId) { + this.timepointId = timepointId; + } + + public long getTimepointScheduledTime() { + return timepointScheduledTime; + } + + public void setTimepointScheduledTime(long timepointScheduledTime) { + this.timepointScheduledTime = timepointScheduledTime; + } + + public String getTripId() { + return tripId; + } + + public void setTripId(String tripId) { + this.tripId = tripId; + } + + public int getStopSequence() { + return stopSequence; + } + + public void setStopSequence(int stopSequence) { + this.stopSequence = stopSequence; + } + + public long getTimepointPredictedArrivalTime() { + return timepointPredictedArrivalTime; + } + + public void setTimepointPredictedArrivalTime(long timepointPredictedArrivalTime) { + this.timepointPredictedArrivalTime = timepointPredictedArrivalTime; + } + + public long getTimepointPredictedDepartureTime() { + return timepointPredictedDepartureTime; + } + + public void setTimepointPredictedDepartureTime( + long timepointPredictedDepartureTime) { + this.timepointPredictedDepartureTime = timepointPredictedDepartureTime; + } +} diff --git a/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TripStatusBean.java b/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TripStatusBean.java index 2260e45314..1459dbfae8 100644 --- a/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TripStatusBean.java +++ b/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TripStatusBean.java @@ -25,7 +25,7 @@ public final class TripStatusBean implements Serializable { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 3L; /**** * These are fields that we can supply from schedule data @@ -97,6 +97,8 @@ public final class TripStatusBean implements Serializable { private List situations; + private List timepointPredictions; + public TripBean getActiveTrip() { return activeTrip; } @@ -393,4 +395,12 @@ public List getSituations() { public void setSituations(List situations) { this.situations = situations; } -} \ No newline at end of file + + public List getTimepointPredictions() { + return timepointPredictions; + } + + public void setTimepointPredictions(List timepointPredictions) { + this.timepointPredictions = timepointPredictions; + } +} diff --git a/src/site/markdown/api/where/index.md b/src/site/markdown/api/where/index.md index aa7066e891..fe239f3fe9 100644 --- a/src/site/markdown/api/where/index.md +++ b/src/site/markdown/api/where/index.md @@ -111,6 +111,7 @@ The current list of supported API methods. Methods that are subject to changed * [agency](methods/agency.html) - get details for a specific agency * [arrival-and-departure-for-stop](methods/arrival-and-departure-for-stop.html) - details about a specific arrival/departure at a stop * [arrivals-and-departures-for-stop](methods/arrivals-and-departures-for-stop.html) - get current arrivals and departures for a stop +* [block](methods/block.html) - get block configuration for a specific block * [cancel-alarm](methods/cancel-alarm.html) - cancel a registered alarm * [current-time](methods/current-time.html) - retrieve the current system time * [plan-trip](methods/plan-trip.html) - plan a trip BETA diff --git a/src/site/markdown/api/where/methods/block.md b/src/site/markdown/api/where/methods/block.md new file mode 100644 index 0000000000..4d8daad5fa --- /dev/null +++ b/src/site/markdown/api/where/methods/block.md @@ -0,0 +1,38 @@ +[Back to API parent page](../index.html) + +# Method: block + +Get details of a specific block by id + +## Sample Request + +http://api.pugetsound.onebusaway.org/api/where/block/1_12540399.xml?key=TEST + +## Sample Response + + + 2 + 200 + OK + 1391465493476 + + + + MTA NYCT_GH_A4-Sunday_D_GH_21000_BX12-15 + + + + + + + + + +## Request Parameters + +* id - the id of the block, encoded directly in the url: + * `http://api.pugetsound.onebusaway.org/api/where/block/[ID GOES HERE].xml` + +## Response + +See details about the various properties of the [`` element](../elements/block-configuration.html).