Java tutorial
package com.eatthepath.gtfs; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.eatthepath.gtfs.realtime.Vehicle; import com.eatthepath.jeospatial.util.SimpleGeospatialPoint; import com.eatthepath.jeospatial.vptree.VPTree; import com.google.transit.realtime.GtfsRealtime.Alert; import com.google.transit.realtime.GtfsRealtime.FeedEntity; import com.google.transit.realtime.GtfsRealtime.FeedMessage; import com.google.transit.realtime.GtfsRealtime.TripUpdate; import com.google.transit.realtime.GtfsRealtime.VehiclePosition; public class TransitSystem { private final String id; private final List<URL> realtimeUrls; private final FeedInfo feedInfo; private final Map<String, Agency> agenciesById; private final Map<String, Stop> stopsById; private final Map<String, Route> routesById; private final Map<String, ServiceSchedule> serviceSchedulesById; private final HashMap<Stop, List<Route>> routesByStop; private final HashMap<String, Trip> tripsById; private final VPTree<Stop> stopTree; private final transient ReadWriteLock realtimeDataLock; private final transient HashMap<URL, Long> realtimeFeedTimestamps; private final transient HashMap<String, Vehicle> vehiclesById; private final transient HashMap<Route, List<Vehicle>> vehiclesByRoute; private final transient HashMap<Trip, List<Vehicle>> vehiclesByTrip; private final Logger log = LoggerFactory.getLogger(TransitSystem.class); protected TransitSystem(final String id, final FeedInfo feedInfo, final Map<String, Agency> agenciesById, final Map<String, Stop> stopsById, final Map<String, Route> routesById, final Map<String, ServiceSchedule> serviceSchedulesById, final List<URL> realtimeUrls) { this.id = id; this.feedInfo = feedInfo; this.agenciesById = agenciesById; this.stopsById = stopsById; this.routesById = routesById; this.serviceSchedulesById = serviceSchedulesById; this.realtimeUrls = realtimeUrls; final ArrayList<Stop> freestandingStops = new ArrayList<Stop>(); for (final Stop stop : this.stopsById.values()) { if (stop.getParentStation() == null) { freestandingStops.add(stop); } } this.routesByStop = new HashMap<Stop, List<Route>>(); this.tripsById = new HashMap<String, Trip>(); for (final Route route : this.routesById.values()) { for (final Stop stop : route.getStopsServed()) { if (!this.routesByStop.containsKey(stop)) { this.routesByStop.put(stop, new ArrayList<Route>()); } this.routesByStop.get(stop).add(route); for (final Trip trip : route.getTrips()) { this.tripsById.put(trip.getId(), trip); } } } this.stopTree = new VPTree<Stop>(freestandingStops); this.realtimeDataLock = new ReentrantReadWriteLock(); this.realtimeFeedTimestamps = new HashMap<URL, Long>(); this.vehiclesById = new HashMap<String, Vehicle>(); this.vehiclesByRoute = new HashMap<Route, List<Vehicle>>(); this.vehiclesByTrip = new HashMap<Trip, List<Vehicle>>(); } public String getId() { return this.id; } public FeedInfo getFeedInfo() { return this.feedInfo; } public Agency getAgencyById(final String id) { return this.agenciesById.get(id); } public Stop getStopById(final String id) { return this.stopsById.get(id); } public Set<Stop> getStopsServedByRoute(final Route route) { return route.getStopsServed(); } public Trip getTripById(final String id) { return this.tripsById.get(id); } public Route getRouteById(final String id) { return this.routesById.get(id); } public Set<Route> getRoutesForStop(final Stop stop) { final HashSet<Route> routesServingStop = new HashSet<Route>(); if (stop.isStation()) { for (final Stop childStop : stop.getChildStops()) { routesServingStop.addAll(this.getRoutesForStop(childStop)); } } if (this.routesByStop.containsKey(stop)) { routesServingStop.addAll(this.routesByStop.get(stop)); } return routesServingStop; } public List<Stop> getStopsInBoundingBox(double west, double east, double north, double south) { return this.stopTree.getAllPointsInBoundingBox(west, east, north, south); } public Set<Route> getRoutesForNearbyStops(double latitude, double longitude, int maxStops) { HashSet<Route> routes = new HashSet<Route>(); List<Stop> stops = this.stopTree.getNearestNeighbors(new SimpleGeospatialPoint(latitude, longitude), maxStops); for (Stop stop : stops) { for (Route route : this.getRoutesForStop(stop)) { routes.add(route); } } return routes; } public Set<Route> getRoutesForStopsInRange(double latitude, double longitude, double radius) { HashSet<Route> routes = new HashSet<Route>(); List<Stop> stops = this.stopTree .getAllNeighborsWithinDistance(new SimpleGeospatialPoint(latitude, longitude), radius); for (Stop stop : stops) { for (Route route : this.getRoutesForStop(stop)) { routes.add(route); } } return routes; } public Vehicle getVehicleById(final String id) { this.realtimeDataLock.readLock().lock(); try { return this.vehiclesById.get(id); } finally { this.realtimeDataLock.readLock().unlock(); } } public List<Vehicle> getVehiclesByRoute(final Route route) { this.realtimeDataLock.readLock().lock(); try { if (this.vehiclesByRoute.containsKey(route)) { return java.util.Collections.unmodifiableList(this.vehiclesByRoute.get(route)); } else { return new ArrayList<Vehicle>(); } } finally { this.realtimeDataLock.readLock().unlock(); } } public void fetchAndProcessRealtimeEvents() throws IOException { for (final URL url : this.realtimeUrls) { this.processEventsFromUrl(url); } } private void processEventsFromUrl(final URL url) throws IOException { final InputStream eventInputStream = url.openStream(); try { final FeedMessage feedMessage = FeedMessage.parseFrom(eventInputStream); final boolean feedMayContainNewData; if (feedMessage.getHeader().hasTimestamp()) { if (this.realtimeFeedTimestamps.containsKey(url)) { feedMayContainNewData = feedMessage.getHeader().getTimestamp() > this.realtimeFeedTimestamps .get(url); } else { feedMayContainNewData = true; } this.realtimeFeedTimestamps.put(url, feedMessage.getHeader().getTimestamp()); } else { // We don't know when the feed was last updated by the provider, so // assume that it may contain new data. feedMayContainNewData = true; } if (feedMayContainNewData) { final boolean isIncremental; if (feedMessage.getHeader().hasIncrementality()) { switch (feedMessage.getHeader().getIncrementality()) { case FULL_DATASET: { isIncremental = false; break; } case DIFFERENTIAL: { isIncremental = true; break; } default: { log.warn(String.format( "Unexpected incrementality \"%s\" in feed from \"%s\" for transit system \"%s\"", feedMessage.getHeader().getIncrementality().toString(), url, this.getId())); isIncremental = false; } } } else { isIncremental = false; } this.realtimeDataLock.writeLock().lock(); try { if (!isIncremental) { this.flushRealtimeData(); } for (final FeedEntity entity : feedMessage.getEntityList()) { if (entity.hasTripUpdate()) { this.processTripUpdate(entity.getTripUpdate()); } if (entity.hasVehicle()) { this.processVehiclePosition(entity.getVehicle()); } if (entity.hasAlert()) { this.processAlert(entity.getAlert()); } } } finally { this.realtimeDataLock.writeLock().unlock(); } } } finally { try { eventInputStream.close(); } catch (IOException e) { log.error("Failed to close event input stream.", e); } } } private void flushRealtimeData() { this.realtimeDataLock.writeLock().lock(); try { this.vehiclesById.clear(); this.vehiclesByRoute.clear(); this.vehiclesByTrip.clear(); } finally { this.realtimeDataLock.writeLock().unlock(); } } private void processTripUpdate(final TripUpdate tripUpdate) { // TODO } private void processVehiclePosition(final VehiclePosition vehiclePosition) { this.realtimeDataLock.writeLock().lock(); try { final Vehicle vehicle; if (vehiclePosition.getVehicle().hasId()) { Vehicle previouslyKnownVehicle = this.getVehicleById(vehiclePosition.getVehicle().getId()); if (previouslyKnownVehicle != null) { vehicle = previouslyKnownVehicle; } else { vehicle = new Vehicle(StringUtils.trimToNull(vehiclePosition.getVehicle().getId())); } } else { vehicle = new Vehicle(null); } vehicle.setLabel(vehiclePosition.getVehicle().getLabel()); vehicle.setLicensePlate(vehiclePosition.getVehicle().getLicensePlate()); if (vehiclePosition.hasPosition()) { vehicle.setPosition(new SimpleGeospatialPoint(vehiclePosition.getPosition().getLatitude(), vehiclePosition.getPosition().getLongitude())); vehicle.setBearing( vehiclePosition.getPosition().hasBearing() ? vehiclePosition.getPosition().getBearing() : null); vehicle.setOdometer( vehiclePosition.getPosition().hasOdometer() ? vehiclePosition.getPosition().getOdometer() : null); vehicle.setSpeed( vehiclePosition.getPosition().hasSpeed() ? vehiclePosition.getPosition().getSpeed() : null); } if (vehiclePosition.hasTrip()) { // TODO Deal with frequency-expanded trips if (vehiclePosition.getTrip().hasTripId()) { vehicle.setTrip(this.getTripById(vehiclePosition.getTrip().getTripId())); } else if (vehiclePosition.getTrip().hasRouteId()) { vehicle.setRoute(this.getRouteById(vehiclePosition.getTrip().getRouteId())); } else { vehicle.setTrip(null); vehicle.setRoute(null); } if (vehicle.getTrip() != null) { final Trip trip = vehicle.getTrip(); if (!this.vehiclesByTrip.containsKey(trip)) { this.vehiclesByTrip.put(trip, new ArrayList<Vehicle>()); } this.vehiclesByTrip.get(trip).add(vehicle); } if (vehicle.getRoute() != null) { final Route route = vehicle.getRoute(); if (!this.vehiclesByRoute.containsKey(route)) { this.vehiclesByRoute.put(route, new ArrayList<Vehicle>()); } this.vehiclesByRoute.get(route).add(vehicle); } } if (vehiclePosition.hasStopId()) { vehicle.setCurrentStop(this.getStopById(vehiclePosition.getStopId())); } else if (vehiclePosition.hasCurrentStopSequence()) { if (vehicle.getTrip() != null) { final Stop stop = vehicle.getTrip() .getStopByOriginalSequenceNumber(vehiclePosition.getCurrentStopSequence()); vehicle.setCurrentStop(stop); if (stop == null) { log.warn(String.format( "Vehicle \"%d\" is on trip \"%s\", but trip has no stop with original sequence number %d.", vehicle.getId(), vehicle.getTrip().getId(), vehiclePosition.getCurrentStopSequence())); } } else { log.warn(String.format( "Vehicle \"%s\" makes reference to a stop sequence number, but is not associated with a trip.", vehicle.getId())); } } vehicle.setStopStatus(vehiclePosition.getCurrentStatus()); vehicle.setCongestionLevel(vehiclePosition.getCongestionLevel()); } finally { this.realtimeDataLock.writeLock().unlock(); } } private void processAlert(final Alert alert) { // TODO } }