package com.schneeloch.bostonbusmap_library.data; import com.google.common.base.Optional; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.schneeloch.bostonbusmap_library.database.Schema; import com.schneeloch.bostonbusmap_library.math.Geometry; import com.schneeloch.bostonbusmap_library.transit.ITransitSystem; import java.util.Collection; /** * This class stores information about the bus. This information is mostly taken * from the feed */ public class BusLocation implements Location { /** * Current latitude of bus, in radians */ public final float latitude; /** * Current longitude of bus, in radians */ public final float longitude; /** * Current latitude of bus, in degrees */ public final float latitudeAsDegrees; /** * Current longitude of bus, in degrees */ public final float longitudeAsDegrees; /** * Unique id for a vehicle. Never null */ public final String busId; protected final String routeName; /** * When the feed says the information was last updated. */ protected final long lastFeedUpdateInMillis; protected final String headsign; /** * What is the heading mentioned for the bus? */ private Optional<Integer> heading; private SimplePredictionView predictionView = SimplePredictionView.empty(); private ImmutableCollection<Alert> snippetAlerts = ImmutableList.of(); private static final int LOCATIONTYPE = 1; public BusLocation(float latitude, float longitude, String id, long lastFeedUpdateInMillis, Optional<Integer> heading, String routeName, String headsign) { this.latitude = (float) (latitude * Geometry.degreesToRadians); this.longitude = (float) (longitude * Geometry.degreesToRadians); this.latitudeAsDegrees = latitude; this.longitudeAsDegrees = longitude; this.busId = id; this.lastFeedUpdateInMillis = lastFeedUpdateInMillis; if (heading == null) { throw new RuntimeException("heading must not be null"); } this.heading = heading; this.routeName = routeName; this.headsign = headsign; } public boolean hasHeading() { return heading.isPresent(); } public int getHeading() { if (heading.isPresent()) { return heading.get(); } else { return -1; } } @Override public float distanceFrom(double lat2, double lon2) { return Geometry.computeCompareDistance(latitude, longitude, lat2, lon2); } @Override public double distanceFromInMiles(double centerLatAsRadians, double centerLonAsRadians) { return Geometry.computeDistanceInMiles(latitude, longitude, centerLatAsRadians, centerLonAsRadians); } @Override public void addToSnippetAndTitle(RouteConfig routeConfig, Location location, RouteTitles routeKeysToTitles, Locations locations) { BusLocation busLocation = (BusLocation) location; PredictionView oldPredictionView = predictionView; String snippet = oldPredictionView.getSnippet() + "<br /><br />" + busLocation.makeSnippet(); String snippetTitle; snippetTitle = oldPredictionView.getSnippetTitle() + ", " + headsign; //TODO: support alerts on multiple routes at once predictionView = new SimplePredictionView(snippet, snippetTitle, snippetAlerts); } @Override public void makeSnippetAndTitle(RouteConfig routeConfig, RouteTitles routeKeysToTitles, Locations locations) { String snippet = makeSnippet(); String snippetTitle = makeTitle(routeKeysToTitles); ITransitSystem transitSystem = locations.getTransitSystem(); IAlerts alerts = transitSystem.getAlerts(); snippetAlerts = getAlerts(alerts); predictionView = new SimplePredictionView(snippet, snippetTitle, snippetAlerts); } protected String getBetaWarning() { return ""; } private String makeSnippet() { String snippet = getBetaWarning(); snippet += getBusNumberMessage(); int secondsAgo = (int) (System.currentTimeMillis() - lastFeedUpdateInMillis) / 1000; snippet += "Last update: " + secondsAgo + " seconds ago"; if (heading.isPresent()) { snippet += "<br />Heading: " + heading.get() + " deg (" + convertHeadingToCardinal(heading.get()) + ")"; } return snippet; } protected String getBusNumberMessage() { return "Bus number: " + busId + "<br />"; } private String makeTitle(RouteTitles routeTitles) { String title = ""; title += "Route "; String routeTitle = routeTitles.getTitle(routeName); if (routeTitle == null) { title += "not mentioned"; } else { title += routeTitle; } title += "<br/>" + headsign; return title; } /** * Converts a heading to a cardinal direction string * * @param degree * heading in degrees, where 0 is north and 90 is east * @return a direction (for example "N" for north) */ private String convertHeadingToCardinal(double degree) { // shift degree so all directions line up nicely degree += 360.0 / 16; // 22.5 if (degree >= 360) { degree -= 360; } // get an index into the directions array int index = (int) (degree / (360.0 / 8.0)); if (index < 0 || index >= 8) { return "calculation error"; } String[] directions = new String[] { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; return directions[index]; } @Override public GroupKey makeGroupKey() { return new VehicleLocations.Key(getTransitSourceType(), routeName, busId); } public String getBusNumber() { return busId; } @Override public float getLatitudeAsDegrees() { // TODO Auto-generated method stub return latitudeAsDegrees; } @Override public float getLongitudeAsDegrees() { // TODO Auto-generated method stub return longitudeAsDegrees; } @Override public Favorite isFavorite() { return Favorite.IsNotFavorite; } /** * The route name * * @return */ public String getRouteId() { return routeName; } @Override public PredictionView getPredictionView() { return predictionView; } @Override public boolean hasMoreInfo() { return false; } @Override public boolean hasFavorite() { return false; } @Override public boolean hasReportProblem() { return true; } @Override public boolean isIntersection() { return false; } @Override public Schema.Routes.SourceId getTransitSourceType() { return Schema.Routes.SourceId.Bus; } @Override public boolean isUpdated() { // this is only relevant for stops return false; } @Override public boolean needsUpdating() { return true; } protected ImmutableCollection<Alert> getAlerts(IAlerts alerts) { return alerts.getAlertsByRoute(routeName, getTransitSourceType()); } @Override public LocationType getLocationType() { return LocationType.Vehicle; } @Override public Collection<String> getRoutes() { return ImmutableList.of(routeName); } @Override public Optional<String> getParent() { return Optional.absent(); } }