aboutsummaryrefslogtreecommitdiff
path: root/.idea/gtfs-realtime-book/ch-07-consuming-vehicle-positions.md
diff options
context:
space:
mode:
Diffstat (limited to '.idea/gtfs-realtime-book/ch-07-consuming-vehicle-positions.md')
-rw-r--r--.idea/gtfs-realtime-book/ch-07-consuming-vehicle-positions.md509
1 files changed, 509 insertions, 0 deletions
diff --git a/.idea/gtfs-realtime-book/ch-07-consuming-vehicle-positions.md b/.idea/gtfs-realtime-book/ch-07-consuming-vehicle-positions.md
new file mode 100644
index 0000000..1b745d0
--- /dev/null
+++ b/.idea/gtfs-realtime-book/ch-07-consuming-vehicle-positions.md
@@ -0,0 +1,509 @@
+## 7. Consuming Vehicle Positions
+
+Just like when consuming service alerts, you can loop over the
+`FeedEntity` objects returned from `getEntityList()` to process
+vehicle positions. If an entity contains a vehicle position, you can
+retrieve it using the `getVehicle()` method.
+
+```java
+for (FeedEntity entity : fm.getEntityList()) {
+ if (entity.hasAlert()) {
+ VehiclePosition vp = entity.getVehicle();
+ processVehiclePosition(vp);
+ }
+}
+```
+
+You can then process the returned `VehiclePosition` object to extract
+the details of the vehicle position.
+
+### Timestamp
+
+One of the provided values is a timestamp reading of when the vehicle
+position reading was taken.
+
+```java
+if (vp.hasTimestamp()) {
+ Date timestamp = new Date(vp.getTimestamp() * 1000);
+
+}
+```
+
+***Note:** The value is multiplied by 1,000 because the java.util.Date
+class accepts milliseconds, whereas GTFS-realtime uses whole seconds.*
+
+This value is useful because the age of a reading can dictate how the
+data is interpreted. For example, if your latest reading was only thirty
+seconds earlier, your users would realize it is very recent and
+therefore is probably quite accurate. On the other hand, if the latest
+reading was ten minutes earlier, they would see it had not updated
+recently and may therefore not be completely accurate.
+
+The other way this value is useful is for determining whether to store
+this new vehicle position. If your previous reading for the same vehicle
+has the same timestamp, you can ignore this update, as nothing has
+changed.
+
+### Geographic Location
+
+A `VehiclePosition` object contains a `Position` object, which
+contains a vehicle's latitude and longitude, and may also include other
+useful information such as its bearing and speed.
+
+```java
+public static void processVehiclePosition(VehiclePosition vp) {
+ if (vp.hasPosition()) {
+ Position position = vp.getPosition();
+
+ if (position.hasLatitude() && position.hasLongitude()) {
+ float latitude = position.getLatitude();
+ float longitude = position.getLongitude();
+
+ // ...
+ }
+
+ // ...
+ }
+}
+```
+
+Even though the `Position` element of a `VehiclePosition` is
+required (according to the specification), checking for it explicitly
+means you can handle its omission gracefully. Remember: when consuming
+GTFS-realtime data you are likely to be relying on a third-party data
+provider who may or may not follow the specification correctly.
+
+Likewise, the latitude and longitude are also required, but it is still
+prudent to ensure they are included. These values are treated as any
+floating-point numbers, so technically they may not be valid geographic
+coordinates.
+
+A basic check to ensure the coordinates are valid is to ensure the
+latitude is between -90 and 90 and the longitude is between -180 and
+180.
+
+```java
+float latitude = position.getLatitude();
+float longitude = position.getLongitude();
+
+if (Math.abs(latitude) <= 90 && Math.abs(longitude) <= 180) {
+ // Valid coordinate
+
+}
+else {
+ // Invalid coordinate
+
+}
+```
+
+A more advanced check would be to determine a bounding box of the data
+provider's entire public transportation network from the corresponding
+GTFS feed's `stops.txt`. You would then check that all received
+coordinates are within or near the bounding box.
+
+***Note:** The important lesson to take from this is that a GTFS-realtime
+feed may appear to adhere to the specification, but you should still
+perform your own sanity checks on received data.*
+
+In addition to the latitude and longitude, you can also retrieve a
+vehicle's speed, bearing and odometer reading.
+
+```java
+if (position.hasBearing()) {
+ float bearing = position.getBearing();
+
+ // Degrees from 0-359. 0 is North, 90 is East, 180 is South, 270 is West
+
+}
+
+if (position.hasOdometer()) {
+ double odometer = position.getOdometer();
+
+ // Meters
+}
+
+if (position.hasSpeed()) {
+ float speed = position.getSpeed();
+
+ // Meters per second
+}
+```
+
+### Trip Information
+
+In order to associate a vehicle position with a particular trip from the
+corresponding GTFS feed, vehicle positions may include a trip
+descriptor.
+
+***Note:** The trip descriptor is declared as optional in the
+GTFS-realtime specification. Realistically, it will be hard to provide
+value to end-users without knowing which trip the position corresponds
+to. At the very least, you would need to know the route (which can be
+specified via the trip descriptor).*
+
+Just like with service alerts (covered in the previous chapter), there
+are a number of values you can retrieve from a trip descriptor to
+determine which trip a vehicle position belongs to. The following
+listing demonstrates how to access this data:
+
+```java
+if (vp.hasTrip()) {
+ TripDescriptor trip = vp.getTrip();
+
+ if (trip.hasTripId()) {
+ String tripId = trip.getTripId();
+
+ }
+
+ if (trip.hasRouteId()) {
+ String routeId = trip.getRouteId();
+
+ }
+
+ if (trip.hasStartDate()) {
+ String startDate = trip.getStartDate();
+
+ }
+
+ if (trip.hasStartTime()) {
+ String startTime = trip.getStartTime();
+
+ }
+
+ if (trip.hasScheduleRelationship()) {
+ ScheduleRelationship sr = trip.getScheduleRelationship();
+
+ }
+}
+```
+
+### Vehicle Identifiers
+
+There are a number of values available in a vehicle position entity by
+which to identify a vehicle. You can access an internal identifier (not
+for public display), a label (such as a vehicle number painted on to a
+vehicle), or a license plate, as shown in the following listing:
+
+```java
+if (vp.hasVehicle()) {
+ VehicleDescriptor vehicle = vp.getVehicle();
+
+ if (vehicle.hasId()) {
+ String id = vehicle.getId();
+
+ }
+
+ if (vehicle.hasLabel()) {
+ String label = vehicle.getLabel();
+
+ }
+
+ if (vehicle.hasLicensePlate()) {
+ String licensePlate = vehicle.getLicensePlate();
+
+ }
+}
+```
+
+The vehicle descriptor and the values contained within are all optional.
+In the case where this information is not available, you can use the
+trip descriptor provided with each vehicle position to match up vehicle
+positions across multiple updates.
+
+Being able to match up the trip and/or vehicle reliably over subsequent
+updates allows you reliably track the ongoing position changes for a
+particular vehicle. For instance, if you wanted to animate the vehicle
+moving on a map as new positions were received, you would need to know
+that each update corresponds to a particular vehicle.
+
+### Current Stop
+
+Each vehicle position record can be associated with a single stop. If
+specified, this stop must appear in the corresponding GTFS feed.
+
+The stop can be identified either by the `stop_id` value, or by using
+the `current_stop_sequence` value. If you use the stop sequence, the
+stop can be determined by finding the corresponding record in the GTFS
+feed's `stop_times.txt` file.
+
+```java
+if (vp.hasStopId()) {
+ String stopId = vp.getStopId();
+
+}
+
+if (vp.hasCurrentStopSequence()) {
+ int sequence = vp.getCurrentStopSequence();
+
+}
+```
+
+***Note:** It is possible for the same stop to be visited multiple times
+in a single trip (consider a loop service, although there are other
+instances when this may also happen). The stop sequence value can be
+useful to disambiguate this case.*
+
+On its own, knowing the stop has no meaning without context. The
+`current_status` field provides this context, indicating that the
+vehicle is either:
+
+* In transit to the stop (it is the next stop but the vehicle is not yet nearby)
+* About to arrive at the stop
+* Currently stopped at the stop.
+
+According to the GTFS-realtime specification, you can only make use of
+`current_status` if the stop sequence is specified.
+
+The following code shows how you can retrieve and check the value of the
+stop status.
+
+```java
+if (vp.hasCurrentStopSequence()) {
+ int sequence = vp.getCurrentStopSequence();
+
+ if (vp.hasCurrentStatus()) {
+ VehicleStopStatus status = vp.getCurrentStatus();
+
+ switch (status.getNumber()) {
+ case VehicleStopStatus.IN_TRANSIT_TO_VALUE:
+ // ...
+
+ case VehicleStopStatus.INCOMING_AT_VALUE:
+ // ...
+
+ case VehicleStopStatus.STOPPED_AT_VALUE:
+ // ...
+
+ }
+ }
+}
+```
+
+### Congestion Levels
+
+The other two values that may be included with a vehicle position relate
+to the congestion inside and outside of the vehicle.
+
+The `congestion_level` value indicates the flow of traffic. This value does
+not indicate whether or not the vehicle is running to schedule, since congestion
+levels are typically accounted for in scheduling.
+
+The following code shows how you can check the congestion level value:
+
+```java
+if (vp.hasCongestionLevel()) {
+ CongestionLevel congestion = vp.getCongestionLevel();
+
+ switch (congestion.getNumber()) {
+ case CongestionLevel.UNKNOWN_CONGESTION_LEVEL_VALUE:
+ // ...
+
+ case CongestionLevel.RUNNING_SMOOTHLY_VALUE:
+ // ...
+
+ case CongestionLevel.STOP_AND_GO_VALUE:
+ // ...
+
+ case CongestionLevel.SEVERE_CONGESTION_VALUE:
+ // ...
+
+ case CongestionLevel.CONGESTION_VALUE:
+ // ...
+
+ }
+}
+```
+
+The `occupancy_status` value indicates how full the vehicle currently
+is. This can be useful to present to your users so they know what to
+expect before the vehicle arrives. For instance:
+
+* A person with a broken leg may not want to travel on a standing
+ room-only bus
+* Someone traveling late at night might prefer a taxi over an empty
+ train for safety reasons
+* If a bus is full and not accepting passengers, someone may stay at
+ home for longer until a bus with seats is coming by.
+
+You can check the occupancy status of a vehicle as follows:
+
+```java
+if (vp.hasOccupancyStatus()) {
+ OccupancyStatus status = vp.getOccupancyStatus();
+
+ switch (status.getNumber()) {
+ case OccupancyStatus.EMPTY_VALUE:
+ // ...
+
+ case OccupancyStatus.MANY_SEATS_AVAILABLE_VALUE:
+ // ...
+
+ case OccupancyStatus.FEW_SEATS_AVAILABLE_VALUE:
+ // ...
+
+ case OccupancyStatus.STANDING_ROOM_ONLY_VALUE:
+ // ...
+
+ case OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY_VALUE:
+ // ...
+
+ case OccupancyStatus.FULL_VALUE:
+ // ...
+
+ case OccupancyStatus.NOT_ACCEPTING_PASSENGERS_VALUE:
+ // ...
+
+ }
+}
+```
+
+### Determining a Vehicle's Bearing
+
+One of the values that can be specified in a GTFS-realtime vehicle
+position update is the bearing of the vehicle being reported. This value
+indicates either the direction the vehicle is facing, or the direction
+towards the next stop.
+
+Ideally, the bearing contains the actual direction that the vehicle is
+facing, not the direction to the next stop, since it is possible for
+this value to be inaccurate. For example, if a Northbound vehicle is
+stopped at a stop (that is, directly beside it), then the calculated
+bearing would indicate the vehicle was facing East, not North.
+
+***Note:** This example assumes that the vehicle is in a country that
+drives on the right-hand side of the road.*
+
+There are several ways to calculate a vehicle's bearing if it is not
+specified in a GTFS-realtime feed:
+
+* Determine the direction towards the next stop
+* Determine the direction using a previous vehicle position reading
+* A combination of the above.
+
+***Note:** The GTFS-realtime specification states that feed providers
+should not include the bearing if it is calculated using previous
+positions. This is because consumers of the feed can calculate this, as
+shown in the remainder of this chapter.*
+
+### Bearing to Next Stop
+
+In order to determine the bearing from the current location to the next
+stop, there are two values you need:
+
+* **Vehicle position.** This is provided in the `latitude` and
+ `longitude` fields of the vehicle position.
+* **Position of the next stop.** This is provided by the `stop_id`
+ or `current_stop_sequence` fields of the vehicle position.
+
+***Note:** Many GTFS-realtime vehicle position feeds do not include
+information about the next stop, and consequently this technique will
+not work in those instances. If this is the case, using the previous
+reading to determine the bearing would be used, as shown later in
+*Bearing From Previous Position*.*
+
+The following formula is used to determine the bearing between the
+starting location and the next stop:
+
+```
+θ = atan2( sin Δλ ⋅ cos φ2 , cos φ1 ⋅ sin φ2 − sin φ1 ⋅ cos φ2 ⋅ cos Δλ )
+```
+
+In this equation, the starting point is indicated by *1*, while the next
+stop is represented by *2*. Latitude is represented by *φ*, while
+longitude is represented by *λ*. For example, *φ2* means the latitude of
+the next stop.
+
+The resultant value *θ* is the bearing between the two points in
+*radians*, and must then be converted to degrees (0-360).
+
+This equation can be represented in Java as follows. It accepts that
+latitude and longitude of two points in degrees, and returns the bearing
+in degrees.
+
+```java
+public static double calculateBearing(double lat1Deg, double lon1Deg, double lat2Deg, double lon2Deg) {
+
+ // Convert all degrees to radians
+ double lat1 = Math.toRadians(lat1Deg);
+ double lon1 = Math.toRadians(lon2Deg);
+ double lat2 = Math.toRadians(lat2Deg);
+ double lon2 = Math.toRadians(lon2Deg);
+
+ // sin Δλ ⋅ cos φ2
+ double y = Math.sin(lon2 - lon1) * Math.cos(lat2);
+
+ // cos φ1 ⋅ sin φ2 − sin φ1 ⋅ cos φ2 ⋅ cos Δλ
+ double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
+
+ // Calculate the bearing in radians
+ double bearingRad = Math.atan2(y, x);
+
+ // Convert radians to degrees
+ double bearingDeg = Math.toDegrees(bearingRad);
+
+ // Ensure x is positive, in the range of 0 <= x < 360
+ if (bearingDeg < 0) {
+ bearingDeg += 360;
+ }
+
+ return bearingDeg;
+}
+```
+
+One thing to be aware of when using the next stop to determine bearing
+is the `current_status` value of the vehicle position (if specified).
+If the value is `STOPPED_AT`, then the calculated angle might be
+significantly wrong, since the vehicle is likely directly next to the
+stop. In this instance, you should either use the next stop, or
+determine the bearing from the previous position reading.
+
+### Bearing From Previous Position
+
+Similar to calculating a vehicle's bearing using the direction to the
+next stop, you can also use a previous reading to calculate the
+vehicle's direction.
+
+The following diagram shows two readings for the same vehicle, as well
+as the calculated bearing from the first point to the second.
+
+![Bearing From Previous Position](images/bearing-from-position.png)
+
+To calculate the bearing in this way, you need the following data:
+
+* **Current vehicle position.** This is provided in the `latitude`
+ and `longitude` fields of the vehicle position.
+* **Previous vehicle position.** This should be the most recent
+ vehicle position recorded prior to receiving the current vehicle
+ position. Additionally, this position must represent a different
+ location to the current position. If a vehicle is stationary for
+ several minutes, you may receive several locations for the vehicle
+ with the same coordinates.
+
+***Note:** In the case of multiple readings at the same location, you
+should use a minimum distance threshold to decide whether or not the
+location is the same. For instance, you may decide that the two previous
+locations must be more than, say, 10 meters to use it for comparison.*
+
+It is also necessary to check the age of the previous reading. If the
+previous reading is more than a minute or two old, it is likely that the
+vehicle has travelled far enough to render the calculated bearing
+meaningless.
+
+### Combination
+
+These two strategies are useful to approximate a vehicle's bearing if
+it is not specified in a vehicle position message, but they are still
+reliant on certain data being available.
+
+The first technique needs to know the next stop, while the second needs
+a previous vehicle position reading.
+
+Your algorithm to determine a vehicle's position could be as follows:
+
+* Use the provided bearing value in the vehicle position if this
+ available.
+* Otherwise, if you have a recent previous reading, calculate the
+ direction using that and the current reading.
+* Otherwise, if the next stop is known, show the bearing of the
+ vehicle towards the stop.
+