package com.schneeloch.bostonbusmap_library.data; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.schneeloch.bostonbusmap_library.annotations.KeepSorted; import com.schneeloch.bostonbusmap_library.database.Schema; import com.schneeloch.bostonbusmap_library.math.Geometry; import com.schneeloch.bostonbusmap_library.transit.ITransitSystem; import com.schneeloch.bostonbusmap_library.util.Constants; public class StopLocation implements Location { private final float latitudeAsDegrees; private final float longitudeAsDegrees; private final String tag; private final String title; private Predictions predictions; private Favorite isFavorite = Favorite.IsNotFavorite; protected boolean recentlyUpdated; /** * If route is in here, the stop has been updated for that route */ protected ImmutableList<String> everUpdated = ImmutableList.of(); private final Optional<String> parent; /** * A set of routes the stop belongs to */ @KeepSorted @IsGuardedBy("this") private final RouteSet routes = new RouteSet(); private static final int LOCATIONTYPE = 3; protected StopLocation(Builder builder) { this.latitudeAsDegrees = builder.latitudeAsDegrees; this.longitudeAsDegrees = builder.longitudeAsDegrees; this.tag = builder.tag; this.title = builder.title; this.parent = builder.parent; } public Updated wasEverUpdated(RouteConfig routeConfig) { if (routeConfig != null) { return everUpdated.contains(routeConfig.getRouteName()) ? Updated.All : Updated.None; } else { int updatedCount = 0; for (String route : routes.getRoutes()) { if (everUpdated.contains(route)) { updatedCount++; } } if (updatedCount == routes.getRoutes().size()) { return Updated.All; } else if (updatedCount == 0) { return Updated.None; } else { return Updated.Some; } } } public static class Builder { private final float latitudeAsDegrees; private final float longitudeAsDegrees; private final String tag; private final String title; private final Optional<String> parent; public Builder(float latitudeAsDegrees, float longitudeAsDegrees, String tag, String title, Optional<String> parent) { this.latitudeAsDegrees = latitudeAsDegrees; this.longitudeAsDegrees = longitudeAsDegrees; this.tag = tag; this.title = title; this.parent = parent; } public StopLocation build() { return new StopLocation(this); } } @Override public float distanceFrom(double centerLatitude, double centerLongitude) { float latitude = (float) (latitudeAsDegrees * Geometry.degreesToRadians); float longitude = (float) (longitudeAsDegrees * Geometry.degreesToRadians); return Geometry.computeCompareDistance(latitude, longitude, centerLatitude, centerLongitude); } public double distanceFromInMiles(double latitudeAsRads, double longitudeAsRads) { float latitude = (float) (latitudeAsDegrees * Geometry.degreesToRadians); float longitude = (float) (longitudeAsDegrees * Geometry.degreesToRadians); return Geometry.computeDistanceInMiles(latitude, longitude, latitudeAsRads, longitudeAsRads); } public void clearRecentlyUpdated() { recentlyUpdated = false; } @Override public int getHeading() { return 0; } @Override public GroupKey makeGroupKey() { return new StationaryGroupKey(this); } @Override public float getLatitudeAsDegrees() { return latitudeAsDegrees; } @Override public float getLongitudeAsDegrees() { return longitudeAsDegrees; } @Override public boolean hasHeading() { return false; } public Predictions getPredictions() { return predictions; } @Override public void makeSnippetAndTitle(RouteConfig routeConfig, RouteTitles routeKeysToTitles, Locations locations) { if (predictions == null) { predictions = new Predictions(); } ITransitSystem transitSystem = locations.getTransitSystem(); IAlerts alertsObj = transitSystem.getAlerts(); ImmutableCollection<Alert> alerts = alertsObj.getAlertsByRouteSetAndStop( routes.getRoutes(), tag, getTransitSourceType()); predictions.makeSnippetAndTitle(routeConfig, routeKeysToTitles, routes, this, alerts, locations); } @Override public void addToSnippetAndTitle(RouteConfig routeConfig, Location location, RouteTitles routeKeysToTitles, Locations locations) { if (predictions == null) { predictions = new Predictions(); } StopLocation stopLocation = (StopLocation)location; ITransitSystem transitSystem = locations.getTransitSystem(); IAlerts alertsObj = transitSystem.getAlerts(); ImmutableCollection<Alert> newAlerts = alertsObj.getAlertsByRouteSetAndStop( stopLocation.getRoutes(), stopLocation.getStopTag(), stopLocation.getTransitSourceType()); predictions.addToSnippetAndTitle(routeConfig, stopLocation, routeKeysToTitles, stopLocation.routes, newAlerts, locations); } public String getStopTag() { return tag; } public void clearPredictions(RouteConfig routeConfig) { if (predictions != null) { predictions.clearPredictions(routeConfig != null ? routeConfig.getRouteName() : null); } recentlyUpdated = true; if (routeConfig != null) { if (!everUpdated.contains(routeConfig.getRouteName())) { List<String> everUpdatedCopy = Lists.newArrayList(everUpdated); // Defensive check since we aren't using synchronize here if (!everUpdatedCopy.contains(routeConfig.getRouteName())) { everUpdatedCopy.add(routeConfig.getRouteName()); } everUpdated = ImmutableList.copyOf(everUpdatedCopy); } } else { everUpdated = routes.getRoutes(); } } public void addPrediction(IPrediction prediction) { if (predictions == null) { predictions = new Predictions(); } predictions.addPredictionIfNotExists(prediction); } public String getTitle() { return title; } public void setFavorite(Favorite b) { isFavorite = b; } @Override public Favorite isFavorite() { return isFavorite; } /** * The list of routes that owns the StopLocation. NOTE: this is not in any particular order * @return */ @Override public Collection<String> getRoutes() { return routes.getRoutes(); } public String getFirstRoute() { return routes.getFirstRoute(); } @Override public String toString() { return MoreObjects.toStringHelper(this).add("id", tag).toString(); } /** * Are these predictions experimental? * @return */ public boolean isBeta() { return false; } @Override public PredictionView getPredictionView() { if (predictions != null) { return predictions.getPredictionView(); } else { return StopPredictionViewImpl.empty(); } } public void addRoute(String route) { routes.addRoute(route); } @Override public boolean hasMoreInfo() { return true; } @Override public boolean hasFavorite() { return true; } @Override public boolean hasReportProblem() { return true; } @Override public boolean isIntersection() { return false; } public boolean hasRoute(String route) { return routes.hasRoute(route); } @Override public Schema.Routes.SourceId getTransitSourceType() { return Schema.Routes.SourceId.Bus; } public boolean supportsBusPredictionsAllMode() { return true; } @Override public LocationType getLocationType() { return LocationType.Stop; } @Override public boolean isUpdated() { return recentlyUpdated; } @Override public boolean needsUpdating() { return false; } @Override public boolean equals(Object o) { if (!(o instanceof StopLocation)) { return false; } StopLocation other = (StopLocation)o; return tag.equals(other.tag) && title.equals(other.title) && (int)(latitudeAsDegrees * Constants.E6) == (int)(other.latitudeAsDegrees * Constants.E6) && (int)(longitudeAsDegrees * Constants.E6) == (int)(other.longitudeAsDegrees * Constants.E6) && getTransitSourceType() == other.getTransitSourceType() && parent.equals(other.parent); } @Override public Optional<String> getParent() { return parent; } }