package com.schneeloch.bostonbusmap_library.transit;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.schneeloch.bostonbusmap_library.data.BusLocation;
import com.schneeloch.bostonbusmap_library.data.CommuterRailStopLocation;
import com.schneeloch.bostonbusmap_library.data.CommuterTrainLocation;
import com.schneeloch.bostonbusmap_library.data.Directions;
import com.schneeloch.bostonbusmap_library.data.IAlerts;
import com.schneeloch.bostonbusmap_library.data.ITransitDrawables;
import com.schneeloch.bostonbusmap_library.data.Locations;
import com.schneeloch.bostonbusmap_library.data.RouteConfig;
import com.schneeloch.bostonbusmap_library.data.RoutePool;
import com.schneeloch.bostonbusmap_library.data.Selection;
import com.schneeloch.bostonbusmap_library.data.StopLocation;
import com.schneeloch.bostonbusmap_library.data.SubwayStopLocation;
import com.schneeloch.bostonbusmap_library.data.SubwayTrainLocation;
import com.schneeloch.bostonbusmap_library.data.TransitSourceCache;
import com.schneeloch.bostonbusmap_library.data.TransitSourceTitles;
import com.schneeloch.bostonbusmap_library.data.VehicleLocations;
import com.schneeloch.bostonbusmap_library.database.Schema;
import com.schneeloch.bostonbusmap_library.parser.GtfsRealtimeVehicleParser;
import com.schneeloch.bostonbusmap_library.parser.MbtaRealtimePredictionsParser;
import com.schneeloch.bostonbusmap_library.parser.MbtaRealtimeVehicleParser;
import com.schneeloch.bostonbusmap_library.util.DownloadHelper;
import com.schneeloch.bostonbusmap_library.util.SearchHelper;
public class MbtaRealtimeTransitSource implements TransitSource {
private static final String dataUrlPrefix = "http://realtime.mbta.com/developer/api/v2/";
private static final String apiKey = "gmozilm-CkSCh8CE53wvsw";
private static final String vehicleGtfsRealtimeUrl = "http://developer.mbta.com/lib/GTRTFS/Alerts/VehiclePositions.pb";
private final ITransitDrawables drawables;
private final TransitSourceTitles routeTitles;
private final ITransitSystem transitSystem;
public static final ImmutableMap<String, String> gtfsNameToRouteName;
public static final ImmutableMultimap<String, String> routeNameToGtfsName;
public static final ImmutableMap<String, Schema.Routes.SourceId> routeNameToTransitSource;
private final TransitSourceCache cache;
private static final Schema.Routes.SourceId[] transitSourceIds = new Schema.Routes.SourceId[] {
Schema.Routes.SourceId.Bus,
Schema.Routes.SourceId.Subway,
Schema.Routes.SourceId.CommuterRail
};
public MbtaRealtimeTransitSource(ITransitDrawables drawables,
TransitSourceTitles routeTitles,
TransitSystem transitSystem) {
this.drawables = drawables;
this.routeTitles = routeTitles;
this.transitSystem = transitSystem;
cache = new TransitSourceCache();
}
static {
String greenRoute = "Green";
String blueRoute = "Blue";
String orangeRoute = "Orange";
String redRoute = "Red";
// workaround for the quick and dirty way things are done in this app
// TODO: fix local names to match field names
ImmutableMap.Builder<String, Schema.Routes.SourceId> routeToTransitSourceIdBuilder =
ImmutableMap.builder();
ImmutableMap.Builder<String, String> gtfsNameToRouteNameBuilder =
ImmutableMap.builder();
gtfsNameToRouteNameBuilder.put("Green-B", greenRoute);
gtfsNameToRouteNameBuilder.put("Green-C", greenRoute);
gtfsNameToRouteNameBuilder.put("Green-D", greenRoute);
gtfsNameToRouteNameBuilder.put("Green-E", greenRoute);
gtfsNameToRouteNameBuilder.put("Red", redRoute);
gtfsNameToRouteNameBuilder.put("Orange", orangeRoute);
gtfsNameToRouteNameBuilder.put("Blue", blueRoute);
String[] commuterRailRoutes = new String[] {
"CR-Greenbush",
"CR-Kingston",
"CR-Middleborough",
"CR-Fairmount",
"CR-Providence",
"CR-Franklin",
"CR-Needham",
"CR-Worcester",
"CR-Fitchburg",
"CR-Lowell",
"CR-Haverhill",
"CR-Newburyport",
"CapeFlyer",
};
routeToTransitSourceIdBuilder.put(greenRoute, Schema.Routes.SourceId.Subway);
routeToTransitSourceIdBuilder.put(redRoute, Schema.Routes.SourceId.Subway);
routeToTransitSourceIdBuilder.put(orangeRoute, Schema.Routes.SourceId.Subway);
routeToTransitSourceIdBuilder.put(blueRoute, Schema.Routes.SourceId.Subway);
for (String commuterRailRoute : commuterRailRoutes) {
gtfsNameToRouteNameBuilder.put(commuterRailRoute, commuterRailRoute);
routeToTransitSourceIdBuilder.put(commuterRailRoute, Schema.Routes.SourceId.CommuterRail);
}
gtfsNameToRouteName = gtfsNameToRouteNameBuilder.build();
ImmutableMultimap.Builder<String, String> routeNameToGtfsNameBuilder =
ImmutableMultimap.builder();
for (String routeName : gtfsNameToRouteName.keySet()) {
String gtfsName = gtfsNameToRouteName.get(routeName);
routeNameToGtfsNameBuilder.put(gtfsName, routeName);
}
routeNameToGtfsName = routeNameToGtfsNameBuilder.build();
routeNameToTransitSource = routeToTransitSourceIdBuilder.build();
}
@Override
public void refreshData(RouteConfig routeConfig, Selection selection,
int maxStops, double centerLatitude, double centerLongitude,
VehicleLocations busMapping,
RoutePool routePool, Directions directions, Locations locationsObj)
throws IOException, ParserConfigurationException, SAXException {
Selection.Mode mode = selection.getMode();
List<String> routesInUrl = Lists.newArrayList();
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
ImmutableSet<String> routeNames;
switch (mode) {
case BUS_PREDICTIONS_ONE:
case VEHICLE_LOCATIONS_ONE: {
String routeName = routeConfig.getRouteName();
if (routeNameToTransitSource.containsKey(routeName)) {
if (cache.canUpdateVehiclesForRoute(routeName) && cache.canUpdatePredictionForRoute(routeName)) {
builder.add(routeName);
}
}
}
break;
case VEHICLE_LOCATIONS_ALL:
case BUS_PREDICTIONS_ALL:
case BUS_PREDICTIONS_STAR: {
for (String routeName : routeNameToTransitSource.keySet()) {
if (cache.canUpdateVehiclesForRoute(routeName) && cache.canUpdatePredictionForRoute(routeName)) {
builder.add(routeName);
}
}
break;
}
}
routeNames = builder.build();
if (routeNames.size() == 0) {
return;
}
for (String routeName : routeNames) {
for (String gtfsRoute : routeNameToGtfsName.get(routeName)) {
routesInUrl.add(gtfsRoute);
}
}
String routesString = Joiner.on(",").join(routesInUrl);
String vehiclesUrl = dataUrlPrefix + "vehiclesbyroutes?api_key=" + apiKey + "&format=json&routes=" + routesString;
String predictionsUrl = dataUrlPrefix + "predictionsbyroutes?api_key=" + apiKey + "&format=json&include_service_alerts=false&routes=" + routesString;
DownloadHelper vehiclesDownloadHelper = new DownloadHelper(vehiclesUrl);
try {
InputStream vehicleStream = vehiclesDownloadHelper.getResponseData();
InputStreamReader reader = new InputStreamReader(vehicleStream);
MbtaRealtimeVehicleParser vehicleParser = new MbtaRealtimeVehicleParser(routeTitles, busMapping, directions, routeNames);
vehicleParser.runParse(reader, this);
reader.close();
}
finally {
vehiclesDownloadHelper.disconnect();
DownloadHelper predictionsDownloadHelper = new DownloadHelper(predictionsUrl);
try {
InputStream predictionsStream = predictionsDownloadHelper.getResponseData();
InputStreamReader predictionsData = new InputStreamReader(predictionsStream);
MbtaRealtimePredictionsParser parser = new MbtaRealtimePredictionsParser(routeNames, routePool, routeTitles);
parser.runParse(predictionsData);
predictionsData.close();
} finally {
predictionsDownloadHelper.disconnect();
}
for (String route : routeNames) {
cache.updatePredictionForRoute(route);
cache.updateVehiclesForRoute(route);
}
}
}
@Override
public boolean hasPaths() {
return true;
}
@Override
public String searchForRoute(String indexingQuery, String lowercaseQuery) {
return SearchHelper.naiveSearch(indexingQuery, lowercaseQuery, routeTitles);
}
@Override
public ITransitDrawables getDrawables() {
return drawables;
}
@Override
public StopLocation createStop(float latitude, float longitude,
String stopTag, String stopTitle, String route, Optional<String> parent) {
Schema.Routes.SourceId transitSourceId = routeNameToTransitSource.get(route);
StopLocation stop;
if (transitSourceId == Schema.Routes.SourceId.Subway) {
stop = new SubwayStopLocation.SubwayBuilder(latitude,
longitude, stopTag, stopTitle, parent).build();
}
else if (transitSourceId == Schema.Routes.SourceId.CommuterRail) {
stop = new CommuterRailStopLocation.CommuterRailBuilder(latitude, longitude, stopTag, stopTitle, parent).build();
}
else {
throw new RuntimeException("Unexpected transit source: " + transitSourceId + ", stop " + stopTag + ", title " + stopTitle + ", route " + route);
}
stop.addRoute(route);
return stop;
}
@Override
public BusLocation createVehicleLocation(float latitude, float longitude, String id, long lastFeedUpdateInMillis, Optional<Integer> heading, String routeName, String headsign) {
Schema.Routes.SourceId sourceId = routeNameToTransitSource.get(routeName);
if (sourceId == Schema.Routes.SourceId.CommuterRail) {
return new CommuterTrainLocation(latitude, longitude, id, lastFeedUpdateInMillis, heading, routeName, headsign);
}
else if (sourceId == Schema.Routes.SourceId.Subway) {
return new SubwayTrainLocation(latitude, longitude, id, lastFeedUpdateInMillis, heading, routeName, headsign);
}
else {
throw new RuntimeException("Unknown source");
}
}
@Override
public TransitSourceTitles getRouteTitles() {
return routeTitles;
}
@Override
public int getLoadOrder() {
return 2;
}
@Override
public Schema.Routes.SourceId[] getTransitSourceIds() {
return transitSourceIds;
}
@Override
public boolean requiresSubwayTable() {
return false;
}
@Override
public IAlerts getAlerts() {
return transitSystem.getAlerts();
}
@Override
public String getDescription() {
return "Subway";
}
}