From 1bf679c75abcfc474f4a2f33b81c6506333975cd Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Sun, 24 Mar 2013 20:05:48 -0400 Subject: [PATCH 01/14] Add debug page to TDS for displaying bounding box and WKT for the current bundle's agency coverage. --- .../controllers/BoundingBoxController.java | 69 +++++++++++++++++++ .../main/webapp/WEB-INF/jsp/bounding-box.jspx | 40 +++++++++++ 2 files changed, 109 insertions(+) create mode 100644 onebusaway-transit-data-federation-webapp/src/main/java/org/onebusaway/transit_data_federation_webapp/controllers/BoundingBoxController.java create mode 100644 onebusaway-transit-data-federation-webapp/src/main/webapp/WEB-INF/jsp/bounding-box.jspx 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/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 From 5beab47dac94431a8ba803346336fa741f85256a Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Mon, 20 May 2013 22:31:33 -0400 Subject: [PATCH 02/14] Preserve stop sequence values from GTFS so they can be matched to updates in a GTFS-realtime feed. Fixes #50. --- .../transit_graph/StopTimeEntriesFactory.java | 2 ++ .../gtfs_realtime/GtfsRealtimeTripLibrary.java | 14 +++++++++++--- .../impl/transit_graph/StopTimeEntryImpl.java | 13 ++++++++++++- .../services/transit_graph/StopTimeEntry.java | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) 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 db1bcfb196..d45b1e6c12 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.setOriginalSequence(stopTime.getStopSequence()); stopTimeEntry.setDropOffType(stopTime.getDropOffType()); stopTimeEntry.setPickupType(stopTime.getPickupType()); stopTimeEntry.setStop(stopEntry); 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 f9f9bd96d4..fd7be7cac2 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,4 +1,5 @@ /** + * Copyright (C) 2013 Kurt Raschke * Copyright (C) 2011 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -389,8 +390,15 @@ private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( if (stopTimeUpdate.hasStopSequence()) { int stopSequence = stopTimeUpdate.getStopSequence(); - if (0 <= stopSequence && stopSequence < stopTimes.size()) { - BlockStopTimeEntry blockStopTime = stopTimes.get(stopSequence); + + Map sequenceToStopTime = new HashMap(); + + for (BlockStopTimeEntry e: stopTimes) { + sequenceToStopTime.put(e.getStopTime().getOriginalSequence(), e); + } + + if (sequenceToStopTime.containsKey(stopSequence)) { + BlockStopTimeEntry blockStopTime = sequenceToStopTime.get(stopSequence); if (!stopTimeUpdate.hasStopId()) { return blockStopTime; } @@ -402,7 +410,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/transit_graph/StopTimeEntryImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/transit_graph/StopTimeEntryImpl.java index 4996b80482..eafe23fe45 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 = 6L; private int _stopTimeId; private int _arrivalTime; private int _departureTime; private int _sequence; + private int _originalSequence; private int _dropOffType; private int _pickupType; private int _shapePointIndex = -1; @@ -60,6 +62,10 @@ public void setSequence(int sequence) { _sequence = sequence; } + public void setOriginalSequence(int originalSequence) { + _originalSequence = originalSequence; + } + public void setDropOffType(int dropOffType) { _dropOffType = dropOffType; } @@ -117,6 +123,11 @@ public int getSequence() { return _sequence; } + @Override + public int getOriginalSequence() { + return _originalSequence; + } + @Override public int getDropOffType() { return _dropOffType; 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..f945cecf69 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"); @@ -23,6 +24,8 @@ public interface StopTimeEntry { public int getSequence(); + public int getOriginalSequence(); + public StopEntry getStop(); /** From 889b40385c0fbcb6984cfb2be8ce0f771f64077f Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Tue, 21 May 2013 00:08:12 -0400 Subject: [PATCH 03/14] Use MappingLibrary.mapToValue rather than a for loop. --- .../realtime/gtfs_realtime/GtfsRealtimeTripLibrary.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 fd7be7cac2..08a8c88c76 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 @@ -391,11 +391,7 @@ private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( if (stopTimeUpdate.hasStopSequence()) { int stopSequence = stopTimeUpdate.getStopSequence(); - Map sequenceToStopTime = new HashMap(); - - for (BlockStopTimeEntry e: stopTimes) { - sequenceToStopTime.put(e.getStopTime().getOriginalSequence(), e); - } + Map sequenceToStopTime = MappingLibrary.mapToValue(stopTimes, "stopTime.originalSequence"); if (sequenceToStopTime.containsKey(stopSequence)) { BlockStopTimeEntry blockStopTime = sequenceToStopTime.get(stopSequence); From a1bf5e23cfe63bf292ed9ef9f4c8319a5fdc7eca Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Tue, 21 May 2013 22:03:15 -0400 Subject: [PATCH 04/14] Per feedback from @barbeau, rename originalSequence to gtfsSequence for clarity. --- .../tasks/transit_graph/StopTimeEntriesFactory.java | 2 +- .../gtfs_realtime/GtfsRealtimeTripLibrary.java | 2 +- .../impl/transit_graph/StopTimeEntryImpl.java | 12 ++++++------ .../services/transit_graph/StopTimeEntry.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) 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 d45b1e6c12..fcd726688c 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 @@ -140,7 +140,7 @@ private List createInitialStopTimeEntries( stopTimeEntry.setId(stopTime.getId()); stopTimeEntry.setSequence(sequence); - stopTimeEntry.setOriginalSequence(stopTime.getStopSequence()); + stopTimeEntry.setGtfsSequence(stopTime.getStopSequence()); stopTimeEntry.setDropOffType(stopTime.getDropOffType()); stopTimeEntry.setPickupType(stopTime.getPickupType()); stopTimeEntry.setStop(stopEntry); 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 08a8c88c76..debc3041ff 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 @@ -391,7 +391,7 @@ private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( if (stopTimeUpdate.hasStopSequence()) { int stopSequence = stopTimeUpdate.getStopSequence(); - Map sequenceToStopTime = MappingLibrary.mapToValue(stopTimes, "stopTime.originalSequence"); + Map sequenceToStopTime = MappingLibrary.mapToValue(stopTimes, "stopTime.gtfsSequence"); if (sequenceToStopTime.containsKey(stopSequence)) { BlockStopTimeEntry blockStopTime = sequenceToStopTime.get(stopSequence); 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 eafe23fe45..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 @@ -23,13 +23,13 @@ public class StopTimeEntryImpl implements StopTimeEntry, Serializable { - private static final long serialVersionUID = 6L; + private static final long serialVersionUID = 7L; private int _stopTimeId; private int _arrivalTime; private int _departureTime; private int _sequence; - private int _originalSequence; + private int _gtfsSequence; private int _dropOffType; private int _pickupType; private int _shapePointIndex = -1; @@ -62,8 +62,8 @@ public void setSequence(int sequence) { _sequence = sequence; } - public void setOriginalSequence(int originalSequence) { - _originalSequence = originalSequence; + public void setGtfsSequence(int gtfsSequence) { + _gtfsSequence = gtfsSequence; } public void setDropOffType(int dropOffType) { @@ -124,8 +124,8 @@ public int getSequence() { } @Override - public int getOriginalSequence() { - return _originalSequence; + public int getGtfsSequence() { + return _gtfsSequence; } @Override 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 f945cecf69..e6f63a10e4 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 @@ -24,7 +24,7 @@ public interface StopTimeEntry { public int getSequence(); - public int getOriginalSequence(); + public int getGtfsSequence(); public StopEntry getStop(); From 64da3320af580bde98b02fdb6f5e0e2cc31c8b59 Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Tue, 21 May 2013 22:07:43 -0400 Subject: [PATCH 05/14] Document distinction between sequence and gtfsSequence in StopTimeEntry. --- .../services/transit_graph/StopTimeEntry.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 e6f63a10e4..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 @@ -22,8 +22,14 @@ 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(); From 417deb7f46f2d53d49c758e3fe7468e8a8eb6e5d Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Thu, 23 May 2013 20:42:03 -0400 Subject: [PATCH 06/14] Preserve per-stoptime predictions from GTFS-realtime updates. --- .../TripUpdatesForAgencyAction.java | 28 ++++++--- .../gtfs_realtime/GtfsRealtimeController.java | 28 ++++++--- .../impl/beans/TripStatusBeanServiceImpl.java | 13 +++++ .../realtime/BlockLocationServiceImpl.java | 1 + .../GtfsRealtimeServiceImpl.java | 54 ++++++++++++------ .../GtfsRealtimeTripLibrary.java | 11 ++++ .../services/realtime/BlockLocation.java | 12 ++++ .../model/trips/TimepointPredictionBean.java | 57 +++++++++++++++++++ .../model/trips/TripStatusBean.java | 12 +++- 9 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TimepointPredictionBean.java 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 4764ac739c..4cc406974c 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 @@ -27,6 +27,7 @@ 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 { @@ -58,15 +59,26 @@ 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(); + arrival.setTime(timepointPrediction.getTimepointPredictedTime()); + } + + 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.setTimestamp(vehicle.getLastUpdateTime() / 1000); } - - tripUpdate.setTimestamp(vehicle.getLastUpdateTime() / 1000); } } } 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/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 ef4b207628..cd3d95998c 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 @@ -22,11 +22,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; @@ -313,6 +315,17 @@ 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.setTimepointPredictedTime(tpr.getTimepointPredictedTime()); + timepointPredictions.add(tpb); + } + bean.setTimepointPredictions(timepointPredictions); + } + return bean; } 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 874f4d3875..8e74e4b3d7 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 @@ -550,6 +550,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(); 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..1dc9114da5 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 @@ -19,6 +19,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 +102,44 @@ 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); + + StopTimeEvent.Builder stopTimeEvent = StopTimeEvent.newBuilder(); + stopTimeEvent.setTime(tpr.getTimepointPredictedTime()); + stopTimeUpdate.setArrival(stopTimeEvent); + + 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 f9f9bd96d4..29694c0412 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 @@ -48,6 +48,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 { @@ -328,10 +329,13 @@ private void applyTripUpdatesToRecord(BlockDescriptor blockDescriptor, 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) { @@ -364,6 +368,12 @@ private void applyTripUpdatesToRecord(BlockDescriptor blockDescriptor, if (currentArrivalTime >= 0) { updateBestScheduleDeviation(currentTime, stopTime.getArrivalTime(), currentArrivalTime, best); + + long timepointPredictedTime = instance.getServiceDate() + (currentArrivalTime * 1000L); + TimepointPredictionRecord tpr = new TimepointPredictionRecord(); + tpr.setTimepointId(stopTime.getStop().getId()); + tpr.setTimepointPredictedTime(timepointPredictedTime); + timepointPredictions.add(tpr); } int currentDepartureTime = computeDepartureTime(stopTime, stopTimeUpdate, instance.getServiceDate()); @@ -381,6 +391,7 @@ private void applyTripUpdatesToRecord(BlockDescriptor blockDescriptor, if (best.timestamp != 0) { record.setTimeOfRecord(best.timestamp); } + record.setTimepointPredictions(timepointPredictions); } private BlockStopTimeEntry getBlockStopTimeForStopTimeUpdate( 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 66c2c5f9ef..e706625eb5 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; @@ -97,6 +99,8 @@ public class BlockLocation { private ScheduleDeviationSamples scheduleDeviations = null; private AgencyAndId vehicleId; + + private List timepointPredictions; public BlockLocation() { @@ -438,6 +442,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(); 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..4ba7c71163 --- /dev/null +++ b/onebusaway-transit-data/src/main/java/org/onebusaway/transit_data/model/trips/TimepointPredictionBean.java @@ -0,0 +1,57 @@ +/** + * 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.model.trips; + +import java.io.Serializable; + +public class TimepointPredictionBean implements Serializable { + + private static final long serialVersionUID = 1L; + + private String timepointId; + + private long timepointScheduledTime; + + private long timepointPredictedTime; + + 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 long getTimepointPredictedTime() { + return timepointPredictedTime; + } + + public void setTimepointPredictedTime(long timepointPredictedTime) { + this.timepointPredictedTime = timepointPredictedTime; + } +} 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 d1ca34df90..52f89f088b 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 @@ -90,6 +90,8 @@ public final class TripStatusBean implements Serializable { private List situations; + private List timepointPredictions; + public TripBean getActiveTrip() { return activeTrip; } @@ -361,4 +363,12 @@ public List getSituations() { public void setSituations(List situations) { this.situations = situations; } + + public List getTimepointPredictions() { + return timepointPredictions; + } + + public void setTimepointPredictions(List timepointPredictions) { + this.timepointPredictions = timepointPredictions; + } } From 83a3276eafd70c0531823e9e1d39319b31011d49 Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Mon, 3 Feb 2014 17:33:38 -0500 Subject: [PATCH 07/14] Document /where/block API method. --- src/site/markdown/api/where/index.md | 1 + src/site/markdown/api/where/methods/block.md | 38 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/site/markdown/api/where/methods/block.md diff --git a/src/site/markdown/api/where/index.md b/src/site/markdown/api/where/index.md index 6ad100cc54..119247e938 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..017f4a8bc6 --- /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/blockConfiguration.html). From 369599e3e9f800a533a2d0c2f8baca13474fc81d Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Mon, 3 Feb 2014 17:41:39 -0500 Subject: [PATCH 08/14] Fix typo in link to block-configuration.html --- src/site/markdown/api/where/methods/block.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/markdown/api/where/methods/block.md b/src/site/markdown/api/where/methods/block.md index 017f4a8bc6..4d8daad5fa 100644 --- a/src/site/markdown/api/where/methods/block.md +++ b/src/site/markdown/api/where/methods/block.md @@ -35,4 +35,4 @@ http://api.pugetsound.onebusaway.org/api/where/block/1_12540399.xml?key=TEST ## Response -See details about the various properties of the [`` element](../elements/blockConfiguration.html). +See details about the various properties of the [`` element](../elements/block-configuration.html). From 8985fc179aaff1488a0e2a48f797100154aa3812 Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Fri, 7 Feb 2014 17:24:27 -0500 Subject: [PATCH 09/14] Check search bounds against bundle coverage in getStops(), getTripsForBounds(), and getRoutes(). Fixes onebusaway/onebusaway-application-modules#59. --- .../federated/TransitDataServiceImpl.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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(); + } } From 0b744f0c3cde0522caf103296b327bce3190c6d1 Mon Sep 17 00:00:00 2001 From: Kurt Raschke Date: Mon, 10 Feb 2014 17:04:31 -0500 Subject: [PATCH 10/14] Prefer time field in a GTFS-realtime StopTimeEvent. The GTFS-realtime spec dictates that the time field should be preferred over delay if both are present, but OneBusAway's handling of the fields was inverted from the spec. --- .../gtfs_realtime/GtfsRealtimeTripLibrary.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 7158e1eee0..88c49d6683 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 @@ -449,19 +449,21 @@ private int getTimeForStopTimeUpdate(StopTimeUpdate stopTimeUpdate, long t = currentTime(); if (stopTimeUpdate.hasArrival()) { StopTimeEvent arrival = stopTimeUpdate.getArrival(); + if (arrival.hasTime()) { + return (int) (arrival.getTime() - serviceDate / 1000); + } if (arrival.hasDelay()) { return (int) ((t - serviceDate) / 1000 - arrival.getDelay()); } - if (arrival.hasTime()) - return (int) (arrival.getTime() - serviceDate / 1000); } if (stopTimeUpdate.hasDeparture()) { StopTimeEvent departure = stopTimeUpdate.getDeparture(); + if (departure.hasTime()) { + return (int) (departure.getTime() - serviceDate / 1000); + } if (departure.hasDelay()) { return (int) ((t - serviceDate) / 1000 - departure.getDelay()); } - if (departure.hasTime()) - return (int) (departure.getTime() - serviceDate / 1000); } throw new IllegalStateException( "expected at least an arrival or departure time or delay for update: " From 83bc29ff9515a265a7f68ae0fc5dc669ea7c528b Mon Sep 17 00:00:00 2001 From: cagryinside Date: Wed, 22 Jul 2015 11:53:00 -0400 Subject: [PATCH 11/14] Fix #139 - Implement in range interpolation strategy --- .../onebusaway/utility/EInRangeStrategy.java | 36 +++++++++++++++++++ .../utility/InterpolationLibrary.java | 21 +++++++++-- .../impl/ArrivalAndDepartureServiceImpl.java | 13 ++++--- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java 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..98bb954ee1 --- /dev/null +++ b/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java @@ -0,0 +1,36 @@ +/** + * 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 outside 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, + + /** + * Takes the closest value in the key-value map. + */ + 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..e44f13bd37 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. @@ -91,7 +92,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 +136,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-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..e3372bb96a 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. @@ -57,8 +58,10 @@ 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -656,7 +659,7 @@ private int getBestScheduleDeviation(ArrivalAndDepartureInstance instance, return (int) InterpolationLibrary.interpolate( scheduleDeviations.getScheduleTimes(), scheduleDeviations.getScheduleDeviationMus(), arrivalTime, - EOutOfRangeStrategy.LAST_VALUE); + EOutOfRangeStrategy.LAST_VALUE, EInRangeStrategy.PREVIOUS_VALUE); } else if (blockLocation.isScheduleDeviationSet()) { return (int) blockLocation.getScheduleDeviation(); } else { @@ -874,10 +877,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 +907,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); From c940477b340f4e758d6dd35b90ed12e1331dfa05 Mon Sep 17 00:00:00 2001 From: cagryinside Date: Wed, 22 Jul 2015 15:39:44 -0400 Subject: [PATCH 12/14] Fix #138 - #127 - Adding departure times to time point predictions, Downstream vehicle deviations are propagated to upstream stops --- .../TripUpdatesForAgencyAction.java | 12 ++++- .../api/TimepointPredictionRecord.java | 49 ++++++++++++++++--- .../impl/beans/TripStatusBeanServiceImpl.java | 6 ++- .../impl/realtime/BlockLocationRecord.java | 31 +++++++++--- .../realtime/BlockLocationServiceImpl.java | 24 +++++++-- .../GtfsRealtimeServiceImpl.java | 15 ++++-- .../GtfsRealtimeTripLibrary.java | 33 ++++++++++--- .../mybus/TimepointPredictionServiceImpl.java | 3 +- .../model/trips/TimepointPredictionBean.java | 44 ++++++++++++++--- 9 files changed, 176 insertions(+), 41 deletions(-) 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 4cc406974c..7506adb600 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. @@ -31,7 +32,7 @@ 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, @@ -64,7 +65,14 @@ protected void fillFeedMessage(FeedMessage.Builder feed, String agencyId, TripUpdate.StopTimeUpdate.Builder stopTimeUpdate = tripUpdate.addStopTimeUpdateBuilder(); stopTimeUpdate.setStopId(normalizeId(timepointPrediction.getTimepointId())); TripUpdate.StopTimeEvent.Builder arrival = stopTimeUpdate.getArrivalBuilder(); - arrival.setTime(timepointPrediction.getTimepointPredictedTime()); + 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); 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/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 cd3d95998c..b529584f8b 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. @@ -320,7 +321,10 @@ public TripStatusBean getBlockLocationAsStatusBean( for (TimepointPredictionRecord tpr: blockLocation.getTimepointPredictions()) { TimepointPredictionBean tpb = new TimepointPredictionBean(); tpb.setTimepointId(tpr.getTimepointId().toString()); - tpb.setTimepointPredictedTime(tpr.getTimepointPredictedTime()); + tpb.setTripId(tpr.getTripId().toString()); + tpb.setStopSequence(tpr.getStopSequence()); + tpb.setTimepointPredictedArrivalTime(tpr.getTimepointPredictedArrivalTime()); + tpb.setTimepointPredictedDepartureTime(tpr.getTimepointPredictedDepartureTime()); timepointPredictions.add(tpb); } bean.setTimepointPredictions(timepointPredictions); 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 8e74e4b3d7..c555ef5f1d 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. @@ -559,16 +560,28 @@ 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); + // 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 arrivalTime; + if (tpr.getTimepointPredictedDepartureTime() != -1) { + arrivalTime = stopTime.getDepartureTime(); + } else { + arrivalTime = stopTime.getArrivalTime(); + } + int deviation = (int) ((predictedTime - blockInstance.getServiceDate()) / 1000 - arrivalTime); scheduleDeviations.put(arrivalTime, (double) deviation); } } @@ -880,7 +893,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/gtfs_realtime/GtfsRealtimeServiceImpl.java b/onebusaway-transit-data-federation/src/main/java/org/onebusaway/transit_data_federation/impl/realtime/gtfs_realtime/GtfsRealtimeServiceImpl.java index 1dc9114da5..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. @@ -116,9 +117,17 @@ public FeedMessage getTripUpdates() { stopTimeUpdate.setStopId(AgencyAndId.convertToString(tpr.getTimepointId())); stopTimeUpdate.setScheduleRelationship(com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SCHEDULED); - StopTimeEvent.Builder stopTimeEvent = StopTimeEvent.newBuilder(); - stopTimeEvent.setTime(tpr.getTimepointPredictedTime()); - stopTimeUpdate.setArrival(stopTimeEvent); + 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); } 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 88c49d6683..1b7a0a7e76 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,6 +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. @@ -363,24 +364,40 @@ private void applyTripUpdatesToRecord(BlockDescriptor blockDescriptor, 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); - + long timepointPredictedTime = instance.getServiceDate() + (currentArrivalTime * 1000L); - TimepointPredictionRecord tpr = new TimepointPredictionRecord(); - tpr.setTimepointId(stopTime.getStop().getId()); - tpr.setTimepointPredictedTime(timepointPredictedTime); - timepointPredictions.add(tpr); - } - int currentDepartureTime = computeDepartureTime(stopTime, - stopTimeUpdate, instance.getServiceDate()); + 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); } } } 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/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 index 4ba7c71163..6e256c5033 100644 --- 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 @@ -1,5 +1,6 @@ /** * 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. @@ -19,13 +20,19 @@ public class TimepointPredictionBean implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; private String timepointId; + private String tripId; + + private int stopSequence = -1; + private long timepointScheduledTime; - private long timepointPredictedTime; + private long timepointPredictedArrivalTime = -1; + + private long timepointPredictedDepartureTime = -1; public TimepointPredictionBean() { @@ -47,11 +54,36 @@ public void setTimepointScheduledTime(long timepointScheduledTime) { this.timepointScheduledTime = timepointScheduledTime; } - public long getTimepointPredictedTime() { - return timepointPredictedTime; + 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 setTimepointPredictedTime(long timepointPredictedTime) { - this.timepointPredictedTime = timepointPredictedTime; + public void setTimepointPredictedDepartureTime( + long timepointPredictedDepartureTime) { + this.timepointPredictedDepartureTime = timepointPredictedDepartureTime; } } From 1a74a49f42cfde389179f90a97c70cdcc4cdcbda Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Tue, 25 Aug 2015 16:04:01 -0400 Subject: [PATCH 13/14] Add unit tests that cover per-stop predictions (#127, #138, and #139) * Current implementation of per-stop predictions keeps the current OBA internal data model of supporting one deviation per stop. As a result, there are two situations (marked by TODO statements in ArrivalAndDepartureServiceImplTest) where OBA can't entirely represent both arrival and departure information correctly. We include two commented-out unit tests for these situations, which can be uncommented if future works overhauls the OBA internal model to fully support arrival and departure deviations for each stop * Change to using TransitInterpolationLibrary, which correctly implements GTFS-rt behavior of only returning real-time information if a prediction is upstream of the stop in question. InterpolationLibrary, which is a generalized interpolation model outside of transit, didn't accurately represent this situation. * Add/fix/clarify documentation throughout code affected by #127, #138, and #139. --- .../onebusaway/utility/EInRangeStrategy.java | 5 +- .../utility/InterpolationLibrary.java | 3 + .../utility/TransitInterpolationLibrary.java | 129 +++ .../impl/ArrivalAndDepartureServiceImpl.java | 53 +- .../ScheduledBlockLocationServiceImpl.java | 2 +- .../realtime/BlockLocationServiceImpl.java | 17 +- .../VehicleLocationRecordCacheImpl.java | 2 +- .../ArrivalAndDepartureServiceImplTest.java | 760 ++++++++++++++++++ 8 files changed, 944 insertions(+), 27 deletions(-) create mode 100644 onebusaway-core/src/main/java/org/onebusaway/utility/TransitInterpolationLibrary.java create mode 100644 onebusaway-transit-data-federation/src/test/java/org/onebusaway/transit_data_federation/impl/ArrivalAndDepartureServiceImplTest.java diff --git a/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java b/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java index 98bb954ee1..46d5c40d49 100644 --- a/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java +++ b/onebusaway-core/src/main/java/org/onebusaway/utility/EInRangeStrategy.java @@ -23,14 +23,15 @@ 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 outside the key range of the + * 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, /** - * Takes the closest value in the key-value map. + * 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 e44f13bd37..846f0ffd10 100644 --- a/onebusaway-core/src/main/java/org/onebusaway/utility/InterpolationLibrary.java +++ b/onebusaway-core/src/main/java/org/onebusaway/utility/InterpolationLibrary.java @@ -24,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 { 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-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 e3372bb96a..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 @@ -16,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; @@ -61,10 +52,20 @@ 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 { @@ -643,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, EInRangeStrategy.PREVIOUS_VALUE); } else if (blockLocation.isScheduleDeviationSet()) { - return (int) blockLocation.getScheduleDeviation(); + return blockLocation.getScheduleDeviation(); } else { - return 0; + return 0.0; } } 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 93fe4dd506..c832baa531 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; 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 c555ef5f1d..7381e457b0 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 @@ -65,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; @@ -572,17 +573,21 @@ private BlockLocation getBlockLocation(BlockInstance blockInstance, for (BlockStopTimeEntry blockStopTime : blockConfig.getStopTimes()) { StopTimeEntry stopTime = blockStopTime.getStopTime(); StopEntry stop = stopTime.getStop(); - // StopSequence equals to -1 when there is no stop sequence in the gtfs-rt + // 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 arrivalTime; + 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) { - arrivalTime = stopTime.getDepartureTime(); + // Prefer departure time, because if both exist departure deviations should be the ones propagated downstream + arrivalOrDepartureTime = stopTime.getDepartureTime(); } else { - arrivalTime = stopTime.getArrivalTime(); + arrivalOrDepartureTime = stopTime.getArrivalTime(); } - int deviation = (int) ((predictedTime - blockInstance.getServiceDate()) / 1000 - arrivalTime); - scheduleDeviations.put(arrivalTime, (double) deviation); + int deviation = (int) ((predictedTime - blockInstance.getServiceDate()) / 1000 - arrivalOrDepartureTime); + scheduleDeviations.put(index, (double) deviation); } } } 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/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 From 604864e506ba2b2e32ce3718c71d82c8f538923a Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 18 Dec 2014 10:45:55 -0500 Subject: [PATCH 14/14] Fix #109 - Add Travis CI builds for pull requests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..f5c99a7f66 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: java \ No newline at end of file