package com.schneeloch.bostonbusmap_library.parser; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.transit.realtime.GtfsRealtime; import com.google.transit.realtime.GtfsRealtime.EntitySelector; import com.google.transit.realtime.GtfsRealtime.FeedEntity; import com.google.transit.realtime.GtfsRealtime.FeedMessage; import com.google.transit.realtime.GtfsRealtime.TranslatedString.Translation; import com.schneeloch.bostonbusmap_library.data.Alert; import com.schneeloch.bostonbusmap_library.data.Alerts; import com.schneeloch.bostonbusmap_library.data.IAlerts; import com.schneeloch.bostonbusmap_library.data.RouteTitles; import com.schneeloch.bostonbusmap_library.data.StopLocation; import com.schneeloch.bostonbusmap_library.database.Schema; import com.schneeloch.bostonbusmap_library.provider.IDatabaseAgent; import com.schneeloch.bostonbusmap_library.transit.ITransitSystem; import com.schneeloch.bostonbusmap_library.transit.TransitSource; import com.schneeloch.bostonbusmap_library.transit.TransitSystem; import com.schneeloch.bostonbusmap_library.util.DownloadHelper; public class MbtaAlertsParser implements IAlertsParser { private final ITransitSystem transitSystem; private final RouteTitles routeTitles; /** * Mapping of gtfs route id to a Nextbus route id. If key doesn't exist, * there is no difference between gtfs route id and Nextbus route id */ private static final ImmutableMap<String, String> gtfsRoutes; static { ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); builder.put("01", "1"); builder.put("04", "4"); builder.put("05", "5"); builder.put("07", "7"); builder.put("08", "8"); builder.put("09", "9"); builder.put("931_", "Red"); builder.put("933_", "Red"); builder.put("946_", "Blue"); builder.put("9462", "Blue"); builder.put("948_", "Blue"); builder.put("9482", "Blue"); builder.put("903_", "Orange"); builder.put("913_", "Orange"); gtfsRoutes = builder.build(); } public MbtaAlertsParser(TransitSystem transitSystem) { this.transitSystem = transitSystem; this.routeTitles = transitSystem.getRouteKeysToTitles(); } @Override public IAlerts obtainAlerts(IDatabaseAgent databaseAgent) throws IOException { Alerts.Builder builder = Alerts.builder(); Date now = new Date(); Set<Integer> validRouteTypes = Sets.newHashSet(); for (Schema.Routes.SourceId value : Schema.Routes.SourceId.values()) { validRouteTypes.add(value.getValue()); } String alertsUrl = TransitSystem.ALERTS_URL; DownloadHelper downloadHelper = new DownloadHelper(alertsUrl); try { InputStream data = downloadHelper.getResponseData(); FeedMessage message = FeedMessage.parseFrom(data); for (FeedEntity entity : message.getEntityList()) { GtfsRealtime.Alert alert = entity.getAlert(); //TODO: handle active_period, cause, effect //TODO: we don't handle trip-specific alerts yet //TODO: currently it doesn't discriminate alerts for // a stop on one route vs the same stop on another ImmutableList.Builder<String> stopsBuilder = ImmutableList.builder(); List<String> routes = Lists.newArrayList(); List<Schema.Routes.SourceId> sources = Lists.newArrayList(); List<String> commuterRailTripIds = Lists.newArrayList(); boolean isSystemWide = false; for (EntitySelector selector : alert.getInformedEntityList()) { // this should be a logical AND inside an EntitySelector // and logical OR between EntitySelectors. This is a little // looser than that, but shouldn't cause any harm if (selector.hasTrip() && selector.getTrip().hasTripId()) { // this is a hack since it relies on the commuter rail // GTFS trip id having a similar id as the train number // which isn't true for subway or bus String tripId = selector.getTrip().getTripId(); if (tripId.startsWith("CR-")) { String[] pieces = tripId.split("-"); commuterRailTripIds.add(pieces[pieces.length - 1]); } } if (selector.hasStopId()) { String stopId = selector.getStopId(); stopsBuilder.add(stopId); } else if (selector.hasRouteId()) { String gtfsRouteId = selector.getRouteId(); String routeId = translateGtfsRoute(gtfsRouteId); routes.add(routeId); } else if (selector.hasRouteType()) { int routeTypeVal = selector.getRouteType(); if (validRouteTypes.contains(routeTypeVal)) { sources.add(Schema.Routes.SourceId.fromValue(routeTypeVal)); } } else { isSystemWide = true; } } ImmutableList<String> stops = stopsBuilder.build(); String description = ""; if (alert.hasHeaderText() && alert.getHeaderText().getTranslationCount() > 0) { Translation translation = alert.getHeaderText().getTranslation(0); description += translation.getText(); } if (alert.hasDescriptionText() && alert.getDescriptionText().getTranslationCount() > 0) { Translation translation = alert.getDescriptionText().getTranslation(0); description += "\n\n" + translation.getText(); } // now construct alert and add for each stop, route, and systemwide if (isSystemWide) { Alert systemWideAlert = new Alert(now, "Systemwide", description); builder.addSystemWideAlert(systemWideAlert); } for (String commuterRailTripId : commuterRailTripIds) { Alert commuterRailAlert = new Alert(now, "Commuter Rail Trip " + commuterRailTripId, description); builder.addAlertForCommuterRailTrip(commuterRailTripId, commuterRailAlert); } for (Schema.Routes.SourceId routeType : sources) { TransitSource source = transitSystem.getTransitSourceByRouteType(routeType); if (source != null) { String sourceDescription = source.getDescription(); Alert routeTypeAlert = new Alert(now, "All " + sourceDescription, description); builder.addAlertForRouteType(routeType, routeTypeAlert); } } for (String route : routes) { String routeTitle = routeTitles.getTitle(route); Alert routeAlert = new Alert(now, "Route " + routeTitle, description); builder.addAlertForRoute(route, routeAlert); } ConcurrentMap<String, StopLocation> stopMapping = Maps.newConcurrentMap(); databaseAgent.getStops(stops, transitSystem, stopMapping); for (String stop : stops) { String stopTitle = stop; StopLocation stopLocation = stopMapping.get(stop); if (stopLocation != null) { stopTitle = stopLocation.getTitle(); } Alert stopAlert = new Alert(now, "Stop " + stopTitle, description); builder.addAlertForStop(stop, stopAlert); } } return builder.build(); } finally { downloadHelper.disconnect(); } } /** * GTFS Routes are slightly different from NextBus routes for the MBTA * @param routeId * @return */ private String translateGtfsRoute(String routeId) { String newRoute = gtfsRoutes.get(routeId); if (newRoute != null) { return newRoute; } else { return routeId; } } }