package rectangledbmi.com.pittsburghrealtimetracker.world; import com.google.android.gms.maps.model.Marker; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * This is supposed to be class that maps the stop markers to the route numbers. In this way, we're * making sure that the bus stop markers sync with the selection at constant time. * <p/> * Created by epicstar on 9/5/14. */ public class TransitStopCollection { /** * Routes by string followed by a reference to the stop container */ private ConcurrentHashMap<String, LinkedList<TransitStopContainer>> routeStops; /** * Stops by integer followed by their stop container */ private ConcurrentHashMap<Integer, TransitStopContainer> stops; public TransitStopCollection() { routeStops = new ConcurrentHashMap<>(10); stops = new ConcurrentHashMap<>(300); } /** * Adds a route to an already present stop. Zoom level is necessary to specify whether or not they * should be visible. * * @param stopId stop id * @param route route by string * @param zoom zoom level * @param visibleZoomLevel zoom level threshold * @return whether or not the stop was added to the route */ public boolean addRouteToMarker(Integer stopId, String route, float zoom, float visibleZoomLevel) { TransitStopContainer stop = getStop(stopId); if (stop != null) { if (stop.addMarkerToRoute(route, zoom, visibleZoomLevel)) { LinkedList<TransitStopContainer> stops = routeStops.get(route); if (stops == null) { stops = new LinkedList<>(); } stops.add(stop); routeStops.put(route, stops); return true; } } return false; } /** * Adds a marker to the stop container if one isn't already present by its stop id. This will then the route to the marker * * @param marker The marker of the stop from google maps * @param stopId stop id * @param route route by string * @param zoom zoom level * @param visibleZoomLevel zoom level threshold * @return whether or not the route was added to the stop */ public boolean addMarkerToRoute(Marker marker, Integer stopId, String route, float zoom, float visibleZoomLevel) { TransitStopContainer container = stops.get(stopId); if (container == null) { container = new TransitStopContainer(); } if (!container.hasMarker()) { container.addMarker(marker, route, zoom, visibleZoomLevel); stops.put(stopId, container); } return addRouteToMarker(stopId, route, zoom, visibleZoomLevel); } /** * Used when we select a route that was originally called on but is currently unselected. * * @param route route by string * @param zoom zoom level * @param zoomVisibility zoom level threshold * @return whether or not this this call was successful */ public boolean updateAddRoutes(String route, float zoom, float zoomVisibility) { LinkedList<TransitStopContainer> stoplist = routeStops.get(route); if (stoplist != null) { for (TransitStopContainer stop : stoplist) { stop.addMarkerToRoute(route, zoom, zoomVisibility); } return true; } return false; } /** * Removes stops that previously had a stop assigned to them but not anymore. * * @param route the route that was previously selected and is now selected * @return whether or not this call was successful */ public boolean removeRoute(String route) { LinkedList<TransitStopContainer> stoplist = routeStops.get(route); if (stoplist != null) { for (TransitStopContainer stop : stoplist) { stop.removeMarker(route); } return true; } return false; } public void clearRoutes() { for (String route : routeStops.keySet()) { for (TransitStopContainer stop : stops.values()) stop.removeMarker(route); } } public void destroyStops() { stops.clear(); routeStops.clear(); } /** * Gets the container by stop id * * @param stopId the stop id * @return the container that contains the stop info */ private TransitStopContainer getStop(int stopId) { return stops.get(stopId); } /** * Checks whether or not to show the stops depending on the zoom visibility. * * @param zoom zoom level * @param visibleZoomLevel zoom level threshold */ public void checkAllVisibility(float zoom, float visibleZoomLevel) { // System.out.println(stops); if (stops != null) { for (TransitStopContainer stop : stops.values()) { stop.setVisibility(zoom, visibleZoomLevel); } } } /** * Container class that contains the bus stop's markers and routes assigned to them */ public class TransitStopContainer { /** * The marker from google maps */ private Marker marker; /** * All routes assigned to the stop that are currently selected */ private HashSet<String> selectedBuses; /** * Initial constructor */ public TransitStopContainer() { marker = null; selectedBuses = new HashSet<>(10); } /** * Adds a route to an already present stop if it has a marker * * @param route route by string * @param zoom zoom level * @param visibleZoomLevel zoom level threshold * @return whether or not this call was successful */ public boolean addMarkerToRoute(String route, float zoom, float visibleZoomLevel) { if (marker != null) { if (selectedBuses.add(route)) { setVisibleIfPossible(zoom, visibleZoomLevel); } return true; } return false; } /** * Adds a marker if there is none to the container then adds the route * * @param marker the marker that could be added * @param route route by string * @param zoom zoom level * @param visibleZoomLevel zoom level threshold * @return whether or not this call was successful */ public boolean addMarker(Marker marker, String route, float zoom, float visibleZoomLevel) { if (!addMarkerToRoute(route, zoom, visibleZoomLevel)) { this.marker = marker; return addMarkerToRoute(route, zoom, visibleZoomLevel); } return false; } /** * Checks whether or not the stop should be invisible after the route is unselected. * If other routes are present with this stop, then it stays visible * * @param route the route that is considered to be removed. * @return whether or not this call was successful */ public boolean removeMarker(String route) { if (selectedBuses.remove(route)) { if (selectedBuses.isEmpty()) setInvisible(); return true; } return false; } /** * This sets the stop as invisible unconditionally */ private void setInvisible() { if (hasMarker()) { marker.setVisible(false); } } /** * The switch to say whether or not the stop is visible or invisible * * @param zoom the current zoom level * @param visibleZoomLevel the zoom level threshold */ public void setVisibility(float zoom, float visibleZoomLevel) { if (!setVisibleIfPossible(zoom, visibleZoomLevel)) { setInvisible(); } } /** * This sets the marker as visible assuming that the zoom level is higher than the threshold * and if any route is selected for the stop * * @param zoom the current zoom level * @param visibleZoomLevel the zoom level threshold * @return whether or not the marker should be visible */ private boolean setVisibleIfPossible(float zoom, float visibleZoomLevel) { if (hasMarker()) { if ((!selectedBuses.isEmpty()) && zoom >= visibleZoomLevel) { marker.setVisible(true); return true; } } return false; } /** * @return whether or not the container has a marker */ public boolean hasMarker() { return marker != null; } @Override public int hashCode() { return marker != null ? marker.hashCode() : 0; } } }