6. Consuming Service Alerts
The previous chapter introduced you to Protocol Buffers and showed you how load a remote GTFS-realtime feed into your Java project. This chapter will show you how to read the data from each of the three entity types (service alerts, vehicle positions and trip updates).
The previous chapter also showed you how to loop over all entities in a
feed using getEntityList()
. Each entity contains either a service
alert, a vehicle position or a trip update.
Once you have verified that a FeedEntity
element contains an alert,
you can retrieve the corresponding Alert
object using
getAlert()
.
for (FeedEntity entity : fm.getEntityList()) {
if (entity.hasAlert()) {
Alert alert = entity.getAlert();
processAlert(alert);
}
}
You can then access the specific properties of a service alert using the returned object.
Cause & Effect
For example, to retrieve the cause value for the alert, you would first
check for its presence with hasCause()
then retrieve the value using
getCause()
.
public void processAlert(Alert alert) {
if (alert.hasCause()) {
Cause cause = alert.getCause();
// ...
}
// ...
}
The Cause
object is an enumerator, meaning it has a finite number of
possible values. To determine which value the object corresponds to,
call getNumber()
to compare it to the possible values.
switch (cause.getNumber()) {
case Cause.ACCIDENT_VALUE:
// ...
case Cause.MEDICAL_EMERGENCY_VALUE:
// ...
}
Note: There are other possible cause values to include in the switch statement; these have been omitted here as they are all covered in the specification earlier in this book.
The Effect
field works in the same way. The difference is that the
possible list of values to compare against is different.
if (alert.hasEffect()) {
Effect effect = alert.getEffect();
switch (effect.getNumber()) {
case Effect.DETOUR_VALUE:
// ...
case Effect.SIGNIFICANT_DELAYS_VALUE:
// ...
}
}
Title, Description and URL
Each of these fields are of type TranslatedString
. A
TranslatedString
may contain multiple Translation
objects, so
when processing these fields you must loop over the available
translations.
For example, to loop over the available translations for the header text you iterate over getTranslationList().
if (alert.hasHeaderText()) {
TranslatedString header = alert.getHeaderText();
for (Translation translation : header.getTranslationList()) {
// Process the translation here
}
}
Note: To access the description you would use hasDescription()
and
getDescription()
, while to access the URL you would use hasUrl()
and
getUrl()
.
Alternatively, you can use getTranslationCount()
and
getTranslation()
to retrieve each of the available translations.
for (int i = 0; i < header.getTranslationCount(); i++) {
Translation translation = header.getTranslation(i);
// Process the translation here
}
A Translation
object is made up of text and optionally, its
associated language. When dealing with the URL field, the text retrieved
from getText()
contains a full URL.
if (translation.hasLanguage()) {
String language = translation.getLanguage();
if (language.equals("fr")) {
// Do something for French language
}
else {
// All other languages
}
}
if (translation.hasText()) {
String text = translation.getText();
// Do something with the text
}
Note: Most GTFS-realtime feeds only specify text in a single language, and therefore do not include the language value.
Active Period
A service alert may contain zero or more time ranges, each of which specify the dates and times the alert is active for. If none are specified then the alert is active as long as it exists within the feed.
You can access each of the TimeRange
objects using either of the
following methods:
for (TimeRange timeRange : alert.getActivePeriodList()) {
// ...
}
for (int i = 0; i < alert.getActivePeriodCount(); i++) {
TimeRange timeRange = alert.getActivePeriod(i);
// ...
}
A TimeRange
can have either a start or finish date, or it may
contain both. In Java, you can turn each of these dates into a
java.util.Date
object as shown below.
if (timeRange.hasStart()) {
Date start = new Date(timeRange.getStart() * 1000);
// ...
}
if (timeRange.hasEnd()) {
Date end = new Date(timeRange.getEnd() * 1000);
// ...
}
Note: The date value is multiplied by 1,000 because the date in the
GTFS-realtime is represented by the number of seconds since January 1,
1970, while java.util.Date
is instantiated using the number of
milliseconds since the same date.
Affected Entities
A service alert may contain zero or more affected entities, each of which describes a route, stop, agency, trip or route type. You can access these entities using either of the following methods:
for (EntitySelector entity : alert.getInformedEntityList()) {
}
for (int i = 0; i < alert.getInformedEntityCount(); i++) {
EntitySelector entity = alert.getInformedEntity(i);
}
There are a number of properties available in the EntitySelector
object, each of which can be used to match the entity to the
corresponding GTFS feed.
For example, if the EntitySelector
object has a route ID value, then
you should be able to locate the route in the corresponding GTFS feed's
routes.txt
file.
The properties can be accessed as follows:
if (entity.hasAgencyId()) {
String agencyId = entity.getAgencyId();
}
if (entity.hasRouteId()) {
String routeId = entity.getRouteId();
}
if (entity.hasRouteType()) {
int routeType = entity.getRouteType();
}
if (entity.hasStopId()) {
String stopId = entity.getStopId();
}
The route type value is an Integer and if present must correspond either to the standard GTFS route type values, or to the extended route type values.
The other entity information that can be contained in EntitySelector
is trip information. You can access the trip properties as follows:
if (entity.hasTrip()) {
TripDescriptor trip = entity.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();
}
}
You can test the ScheduleRelationship
value by comparing the
getNumber()
value to one of the available constants, as follows.
if (entity.hasTrip()) {
// ...
if (trip.hasScheduleRelationship()) {
ScheduleRelationship sr = trip.getScheduleRelationship();
switch (sr.getNumber()) {
case ScheduleRelationship.ADDED_VALUE:
// ...
break;
case ScheduleRelationship.CANCELED_VALUE:
// ...
break;
case ScheduleRelationship.SCHEDULED_VALUE:
// ...
break;
case ScheduleRelationship.UNSCHEDULED_VALUE:
// ...
break;
}
}
}