package com.pennapps.labs.pennmobile; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.preference.PreferenceManager; import android.support.v7.widget.SearchView; import android.text.Editable; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import com.google.maps.DirectionsApi; import com.google.maps.DirectionsApiRequest; import com.google.maps.GeoApiContext; import com.google.maps.PendingResult; import com.google.maps.model.DirectionsResult; import com.google.maps.model.DirectionsRoute; import com.google.maps.model.EncodedPolyline; import com.google.maps.model.TravelMode; import com.pennapps.labs.pennmobile.adapters.RoutesAdapter; import com.pennapps.labs.pennmobile.adapters.SearchSuggestionAdapter; import com.pennapps.labs.pennmobile.api.Labs; import com.pennapps.labs.pennmobile.classes.Building; import com.pennapps.labs.pennmobile.classes.BusRoute; import com.pennapps.labs.pennmobile.classes.BusStop; import com.pennapps.labs.pennmobile.classes.MapCallbacks; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Semaphore; import butterknife.Bind; import butterknife.ButterKnife; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; public class MapFragment extends Fragment { private Labs mLabs; private MapView mapView; private GoogleMap googleMap; private SearchView searchView; private String query; private MainActivity activity; @Bind(R.id.map_initial_card) LinearLayout initCard; @Bind(R.id.map_search_card) LinearLayout searchCard; @Bind(R.id.map_search_name) TextView searchName; @Bind(R.id.map_search_location) TextView searchLoc; @Bind(R.id.map_suggestion) ListView suggestionList; @Bind(R.id.map_bus_card) TextView busStopName; private FloatingActionButton transitMode; private SearchSuggestionAdapter adapter; private Building currentBuilding; private Marker currentMarker; private Set<Circle> displayCircles; private Set<Building> searchedBuildings; private Set<Marker> loadedMarkers; private static MapCallbacks mapCallbacks; private Set<Circle> busStopCircles; private Set<BusStop> busStopAdded; private Circle currentBusStop; private BuildingWindowAdapter buildingAdpater; private TransitWindowAdapter transitAdapter; private LatLng startQueryLatLng; private Polyline startPolyline; private Set<Polyline> allStartPolylines; private LatLng endQueryLatLng; private Polyline endPolyline; private Set<Polyline> allEndPolylines; private Semaphore semaphore; private EditText from, to; private GeoApiContext geoapi; private boolean useBus, transit; private final static int CIRCLE_SIZE = 10; private final static double BOUND_EXPAND_CONSTANT = 0.004; private final static double SENSITIVITY_MULTIPLIER = 1.5; public static HashSet<BusRoute> selectedRoutes; private RoutesAdapter routesAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mLabs = MainActivity.getLabsInstance(); activity = (MainActivity) getActivity(); displayCircles = new HashSet<>(); loadedMarkers = new HashSet<>(); mapCallbacks = new MapCallbacks(); Context context = activity.getApplicationContext(); GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(context) .addConnectionCallbacks(mapCallbacks) .addOnConnectionFailedListener(mapCallbacks) .addApi(LocationServices.API) .build(); mapCallbacks.setGoogleApiClient(mGoogleApiClient); activity.closeKeyboard(); geoapi = new GeoApiContext().setApiKey(getString(R.string.google_api_key)); allStartPolylines = new HashSet<>(); allEndPolylines = new HashSet<>(); query = null; transit = false; useBus = true; selectedRoutes = new HashSet<>(); busStopCircles = new HashSet<>(); busStopAdded = new HashSet<>(); } @Override public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_map, container, false); mapView = (MapView) v.findViewById(R.id.mapView); mapView.onCreate(savedInstanceState); ButterKnife.bind(this, v); googleMap = mapView.getMap(); googleMap.getUiSettings().setMyLocationButtonEnabled(false); try { googleMap.setMyLocationEnabled(true); } catch (SecurityException e) { //No permission -> go back to main activity.showErrorToast(R.string.no_permission_map); FragmentManager fragmentManager = activity.getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.laundry_fragment, new MainFragment()) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .addToBackStack(null) .commit(); } googleMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { boolean changed = false; for (Circle c : displayCircles) { float[] results = new float[1]; Location.distanceBetween(latLng.latitude, latLng.longitude, c.getCenter().latitude, c.getCenter().longitude, results); if (results[0] <= SENSITIVITY_MULTIPLIER * CIRCLE_SIZE) { changed = true; displayCircles.remove(c); c.remove(); break; } } if (!changed) { checkBusStopClicked(latLng); return; } for (Building b : searchedBuildings) { float[] results = new float[1]; Location.distanceBetween(latLng.latitude, latLng.longitude, b.getLatLng().latitude, b.getLatLng().longitude, results); if (results[0] <= SENSITIVITY_MULTIPLIER * CIRCLE_SIZE) { currentBuilding = b; } } displayCircles.add(googleMap.addCircle(new CircleOptions() .center(currentMarker.getPosition()) .visible(true) .radius(CIRCLE_SIZE) .fillColor(Color.RED) .strokeWidth(0))); currentMarker.remove(); currentMarker = googleMap.addMarker(new MarkerOptions() .position(currentBuilding.getLatLng()) .title(currentBuilding.title) .snippet(currentBuilding.getImageURL())); } }); buildingAdpater = new BuildingWindowAdapter(inflater); transitAdapter = new TransitWindowAdapter(inflater); googleMap.setInfoWindowAdapter(buildingAdpater); try { MapsInitializer.initialize(activity); } catch (Exception e) { e.printStackTrace(); } googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mapCallbacks.getLatLng(), 14)); googleMap.setOnPolylineClickListener(new GoogleMap.OnPolylineClickListener() { @Override public void onPolylineClick(Polyline polyline) { if (polyline.equals(startPolyline) || polyline.equals(endPolyline)) { //this is ignored because the current polyline is already selected return; } if (allStartPolylines.contains(polyline)) { startPolyline.setColor(Color.GRAY); startPolyline = polyline; } else if (allEndPolylines.contains(polyline)) { endPolyline.setColor(Color.GRAY); endPolyline = polyline; } polyline.setColor(Color.BLUE); } }); RelativeLayout menuExtension = activity.getMenuMapExtension(); from = (EditText) menuExtension.findViewById(R.id.menu_map_from); to = (EditText) menuExtension.findViewById(R.id.menu_map_to); ImageButton swap = (ImageButton) menuExtension.findViewById(R.id.menu_map_swap); ImageButton useCurrent = (ImageButton) menuExtension.findViewById(R.id.menu_map_circle); transitMode = (FloatingActionButton) v.findViewById(R.id.map_direction); FloatingActionButton currentLocButton = (FloatingActionButton) v.findViewById(R.id.map_current_loc); transitMode.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FloatingActionButton button = (FloatingActionButton) v; if (!transit) { activity.openMapDirectionMenu(); to.setText(query); googleMap.setInfoWindowAdapter(transitAdapter); button.setImageResource(R.drawable.ic_directions_walk_white_24dp); useBus = false; transit = true; drawUserRoute("", query); } else if (useBus) { button.setImageResource(R.drawable.ic_directions_bus_white_24dp); drawUserRoute(from.getText().toString(), to.getText().toString()); } else { button.setImageResource(R.drawable.ic_directions_walk_white_24dp); drawUserRoute(from.getText().toString(), to.getText().toString()); } useBus = !useBus; } }); swap.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Editable fromText = from.getText(); from.setText(to.getText()); to.setText(fromText); } }); menuExtension.findViewById(android.R.id.home).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { activity.closeMapDirectionMenu(); suggestionList.setVisibility(View.GONE); transit = false; transitMode.setImageResource(R.drawable.ic_directions_white_24dp); googleMap.setInfoWindowAdapter(buildingAdpater); googleMap.clear(); Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.cardscalesmaller); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { searchName.setVisibility(View.GONE); searchLoc.setVisibility(View.GONE); initCard.setVisibility(View.VISIBLE); searchCard.setVisibility(View.GONE); busStopName.setVisibility(View.GONE); } @Override public void onAnimationEnd(Animation animation) { busStopName.setVisibility(View.VISIBLE); busStopName.setText(currentBuilding == null ? "" : currentBuilding.title); } @Override public void onAnimationRepeat(Animation animation) { } }); initCard.startAnimation(animation); } }); from.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (event != null) { to.requestFocus(); } return false; } }); to.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (event != null) { drawUserRoute(from.getText().toString(), to.getText().toString()); } return false; } }); useCurrent.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { from.setText(""); } }); currentLocButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); boundsBuilder.include(mapCallbacks.getLatLng()); changeZoomLevel(googleMap, boundsBuilder.build()); } }); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); } @Override public void onStart() { super.onStart(); mapCallbacks.getGoogleApiClient().connect(); } @Override public void onResume() { super.onResume(); mapView.onResume(); mapCallbacks.requestLocationUpdates(); activity.setTitle(R.string.map); activity.setNav(R.id.nav_map); } @Override public void onStop(){ super.onStop(); mapCallbacks.stopLocationUpdates(); } @Override public void onDestroy() { super.onDestroy(); mapView.onDestroy(); mapCallbacks.stopLocationUpdates(); activity.closeMapDirectionMenu(); } @Override public void onDestroyView() { super.onDestroyView(); ButterKnife.unbind(this); activity.closeMapDirectionMenu(); } @Override public void onLowMemory() { super.onLowMemory(); mapView.onLowMemory(); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle presses on the action bar items switch (item.getItemId()) { case R.id.building_search: return true; case R.id.transit_route: if (routesAdapter == null) { loadRouteAdapter(); } else { showRouteDialogBox(); } return true; default: return super.onOptionsItemSelected(item); } } @Override public void onPrepareOptionsMenu(Menu menu) { searchView = (SearchView) menu.findItem(R.id.building_search).getActionView(); searchView.setIconifiedByDefault(true); searchView.setIconified(true); } public static MapCallbacks getMapCallbacks(){ return mapCallbacks; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.building, menu); searchView = (SearchView) menu.findItem(R.id.building_search).getActionView(); final SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextChange(String arg0) { return true; } @Override public boolean onQueryTextSubmit(String arg0) { searchView.clearFocus(); query = arg0; SearchFavoriteFragment.addSearchQuery(R.string.map_search_count, R.array.previous_map_array, query, activity, true); searchBuildings(query); suggestionList.setVisibility(View.GONE); return true; } }; searchView.setOnQueryTextListener(queryListener); final View.OnFocusChangeListener focusListener = new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { showSuggestion(); } } }; searchView.setOnQueryTextFocusChangeListener(focusListener); final SearchView.OnCloseListener closeListener = new SearchView.OnCloseListener() { @Override public boolean onClose() { suggestionList.setVisibility(View.GONE); return false; } }; searchView.setOnCloseListener(closeListener); } private void showSuggestion() { final ArrayList<String> list = new ArrayList<>(SearchFavoriteFragment.MAX_SUGGESTION_SIZE); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity); int index = sharedPref.getInt(getString(R.string.map_search_count), -1); if (index != -1) { String[] previousKey = getResources().getStringArray(R.array.previous_map_array); for (int i = 0; i < SearchFavoriteFragment.MAX_SUGGESTION_SIZE; i++){ int id = (index + SearchFavoriteFragment.MAX_SUGGESTION_SIZE - i) % SearchFavoriteFragment.MAX_SUGGESTION_SIZE; String previous = sharedPref.getString(previousKey[id], ""); if (!previous.isEmpty()) { list.add(previous); } } } if (!list.isEmpty() && suggestionList != null) { suggestionList.setVisibility(View.VISIBLE); adapter = new SearchSuggestionAdapter(activity, list); suggestionList.setAdapter(adapter); suggestionList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { suggestionList.setVisibility(View.GONE); searchView.setQuery(adapter.getItem(position), true); } }); } } private void searchBuildings(String query) { mLabs.buildings(query) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<List<Building>>() { @Override public void call(List<Building> buildings) { searchedBuildings = new HashSet<>(buildings); drawBuildingResults(buildings); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { activity.showErrorToast(R.string.location_not_found); } }); } private void drawBuildingResults(List<Building> buildings) { googleMap.clear(); if (buildings.isEmpty()) { activity.showErrorToast(R.string.location_not_found); return; } LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); currentBuilding = buildings.remove(0); currentMarker = googleMap.addMarker(new MarkerOptions() .position(currentBuilding.getLatLng()) .title(currentBuilding.title) .snippet(currentBuilding.getImageURL())); for (Building building : buildings) { boundsBuilder.include(building.getLatLng()); displayCircles.add(googleMap.addCircle(new CircleOptions() .center(building.getLatLng()) .visible(true) .radius(CIRCLE_SIZE) .fillColor(Color.RED) .strokeWidth(0))); } changeZoomLevel(googleMap, boundsBuilder.build()); Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.cardscalebigger); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { busStopName.setVisibility(View.GONE); } @Override public void onAnimationEnd(Animation animation) { initCard.setVisibility(View.GONE); searchCard.setVisibility(View.VISIBLE); searchName.setText(currentBuilding.title); searchLoc.setText(currentBuilding.address); searchName.setVisibility(View.VISIBLE); searchLoc.setVisibility(View.VISIBLE); } @Override public void onAnimationRepeat(Animation animation) { } }); initCard.startAnimation(animation); suggestionList.setVisibility(View.GONE); } private void drawUserRoute(final String from, final String to) { activity.closeKeyboard(); startQueryLatLng = null; endQueryLatLng = null; semaphore = new Semaphore(1); query = to; if (to == null || to.isEmpty()) { query = getString(R.string.starting_location_hint); endQueryLatLng = mapCallbacks.getLatLng(); } else { mLabs.buildings(to).observeOn(AndroidSchedulers.mainThread()).subscribe( new Action1<List<Building>>() { @Override public void call(List<Building> buildings) { final LatLng destLatLng = getLocationLatLng(buildings, to); currentBuilding = buildings != null && buildings.size() >= 1 ? buildings.get(0) : currentBuilding; searchName.setText(currentBuilding.title); if (destLatLng == null) { activity.showErrorToast(R.string.no_destination_found); } else { endQueryLatLng = destLatLng; getRouteWithLoc(); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { activity.showErrorToast(R.string.no_destination_found); } }); } if (from.equals(getString(R.string.starting_location_hint)) || from.equals("")) { startQueryLatLng = mapCallbacks.getLatLng(); getRouteWithLoc(); } else { mLabs.buildings(from).observeOn(AndroidSchedulers.mainThread()).subscribe( new Action1<List<Building>>() { @Override public void call(List<Building> buildings) { LatLng startLatLng = getLocationLatLng(buildings, from); if (startLatLng == null) { activity.showErrorToast(R.string.no_origin_found); } else { startQueryLatLng = startLatLng; getRouteWithLoc(); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { activity.showErrorToast(R.string.no_origin_found); } } ); } } public void getRouteWithLoc() { try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } if (endQueryLatLng == null || startQueryLatLng == null) { semaphore.release(); return; } semaphore.release(); retrieveRoute(startQueryLatLng, endQueryLatLng); } private void retrieveRoute(final LatLng startLatLng, final LatLng destLatLng) { String latBegin = String.valueOf(startLatLng.latitude); String longBegin = Double.toString(startLatLng.longitude); String latEnd = String.valueOf(destLatLng.latitude); String longEnd = Double.toString(destLatLng.longitude); if (useBus) { mLabs.routing(latBegin, latEnd, longBegin, longEnd).observeOn(AndroidSchedulers.mainThread()) .subscribe( new Action1<BusRoute>() { @Override public void call(BusRoute route) { if (route == null || route.stops.size() == 0) { addWalkingPath(startLatLng, destLatLng, true, true); return; } clearMap(); PolylineOptions options = new PolylineOptions(); LatLngBounds.Builder builder = new LatLngBounds.Builder(); for (BusStop busStop : route.stops) { LatLng latLngBuff = busStop.getLatLng(); addMapMarker(route, busStop); options.add(latLngBuff); builder.include(latLngBuff); } builder.include(startLatLng); builder.include(destLatLng); options.width(15).color(getResources().getColor(R.color.color_primary)); googleMap.addPolyline(options); addWalkingPath(startLatLng, route.stops.get(0).getLatLng(), true, false); addWalkingPath(destLatLng, route.stops.get(route.stops.size() - 1).getLatLng(), false, false); googleMap.addMarker(new MarkerOptions() .position(destLatLng) .title(query)); changeZoomLevel(googleMap, builder.build()); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { findBusRoute(startLatLng, destLatLng); } }); } else { addWalkingPath(startLatLng, destLatLng, true, true); } } private void clearMap() { googleMap.clear(); displayCircles.clear(); if (searchedBuildings != null) { searchedBuildings.clear(); } loadedMarkers.clear(); searchLoc.setText(""); busStopCircles.clear(); busStopAdded.clear(); currentBusStop = null; } private LatLng getLocationLatLng(List<Building> buildings, String locationName) { if (buildings != null && !buildings.isEmpty()) { return buildings.get(0).getLatLng(); } Geocoder geocoder = new Geocoder(activity.getApplicationContext()); try { List<Address> locations = geocoder.getFromLocationName(locationName, 1); if (locations.size() != 0) { return new LatLng(locations.get(0).getLatitude(), locations.get(0).getLongitude()); } } catch (IOException e) { e.printStackTrace(); } activity.showErrorToast(R.string.no_path_found); return null; } private void addWalkingPath(final LatLng startWalking, final LatLng endWalking, final boolean start, final boolean clearOnSuccess) { DirectionsApiRequest req = DirectionsApi.getDirections(geoapi, startWalking.latitude + "," + startWalking.longitude, endWalking.latitude + "," + endWalking.longitude); req.mode(TravelMode.WALKING); req.setCallback(new PendingResult.Callback<DirectionsResult>() { @Override public void onResult(final DirectionsResult result) { activity.runOnUiThread(new Runnable() { @Override public void run() { if (clearOnSuccess) { clearMap(); } DirectionsRoute[] routes = result.routes; EncodedPolyline polyline = routes[0].overviewPolyline; PolylineOptions options = new PolylineOptions(); for (com.google.maps.model.LatLng latLng : polyline.decodePath()) { options.add(new LatLng(latLng.lat, latLng.lng)); } options.color(Color.BLUE).width(15); Polyline line = googleMap.addPolyline(options); if (start) { startPolyline = line; allStartPolylines.add(startPolyline); } else { endPolyline = line; allEndPolylines.add(endPolyline); } for (int i = 1; i < routes.length; i++) { PolylineOptions opt = new PolylineOptions(); for (com.google.maps.model.LatLng latLng : routes[i].overviewPolyline.decodePath()) { opt.add(new LatLng(latLng.lat, latLng.lng)); } opt.color(Color.GRAY).width(15); Polyline poly = googleMap.addPolyline(opt); if (start) { allStartPolylines.add(poly); } else { allEndPolylines.add(poly); } } addGoogleWarning(result); if (clearOnSuccess) { LatLngBounds.Builder bounds = new LatLngBounds.Builder(); bounds.include(startWalking); bounds.include(endWalking); changeZoomLevel(googleMap, bounds.build()); } } }); } @Override public void onFailure(Throwable e) { activity.showErrorToast(R.string.google_map_fail); } }); } private void addMapMarker(BusRoute route, BusStop busStop) { if (busStop.getName() != null) { if (route.stops.indexOf(busStop) != 0 && route.stops.indexOf(busStop) != route.stops.size() - 1) { drawBusStop(busStop); } else { googleMap.addMarker(new MarkerOptions() .position(busStop.getLatLng()) .title(busStop.getName())); } } } public static void changeZoomLevel(GoogleMap googleMap, LatLngBounds bounds){ Location NECorner = new Location(""); Location SWCorner = new Location(""); LatLng northeast = bounds.northeast; LatLng southwest = bounds.southwest; NECorner.setLatitude(northeast.latitude); NECorner.setLatitude(northeast.longitude); SWCorner.setLatitude(southwest.latitude); SWCorner.setLatitude(southwest.longitude); int padding = 100; if (SWCorner.distanceTo(NECorner) < 40) { bounds = bounds.including(new LatLng(northeast.latitude + BOUND_EXPAND_CONSTANT, northeast.longitude - BOUND_EXPAND_CONSTANT)); bounds = bounds.including(new LatLng(southwest.latitude - BOUND_EXPAND_CONSTANT, southwest.longitude + BOUND_EXPAND_CONSTANT)); } bounds = bounds.including(new LatLng(southwest.latitude - 0.0005, southwest.longitude)); googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, padding)); } private class BuildingWindowAdapter implements GoogleMap.InfoWindowAdapter { private View view; public BuildingWindowAdapter(LayoutInflater inflater) { view = inflater.inflate(R.layout.info_window, null); } @Override public View getInfoWindow(Marker arg0) { return null; } @Override public View getInfoContents(final Marker marker) { currentMarker = marker; ImageView imageView = (ImageView) view.findViewById(R.id.building_image); final ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.mapProgress); TextView name = (TextView) view.findViewById(R.id.building_name); name.setText(marker.getTitle()); if (marker.getSnippet().isEmpty()) { imageView.setVisibility(View.GONE); } else if (loadedMarkers.contains(currentMarker)) { Picasso.with(activity).load(marker.getSnippet()).fit().centerInside().into(imageView); } else { loadedMarkers.add(currentMarker); progressBar.setVisibility(View.VISIBLE); Picasso.with(activity).load(marker.getSnippet()).fit().centerInside().into(imageView, new Callback() { @Override public void onSuccess() { progressBar.setVisibility(View.GONE); currentMarker.hideInfoWindow(); currentMarker.showInfoWindow(); } @Override public void onError() {} }); } return view; } } private static class TransitWindowAdapter implements GoogleMap.InfoWindowAdapter { private View view; public TransitWindowAdapter(LayoutInflater inflater) { view = inflater.inflate(R.layout.busstop_info_window, null); } @Override public View getInfoWindow(Marker arg0) { return null; } @Override public View getInfoContents(final Marker arg0) { TextView name = (TextView) view.findViewById(R.id.bus_stop_name); name.setText(arg0.getTitle()); return view; } } private void findBusRoute (final LatLng startLatLng, final LatLng destLatLng) { mLabs.bus_stops().observeOn(AndroidSchedulers.mainThread()).subscribe( new Action1<List<BusStop>>() { @Override public void call(List<BusStop> busStops) { HashMap<String, TreeSet<BusStop>> map = new HashMap<>(); String west = getString(R.string.bus_west); String east = getString(R.string.bus_east); map.put(east, new TreeSet<>(new BusStopComparator(east))); map.put(west, new TreeSet<>(new BusStopComparator(west))); for (BusStop bs : busStops) { generateBusRouteMap(bs); for (Map.Entry<String, Integer> e : bs.routesMap.entrySet()) { if (map.containsKey(e.getKey())) { map.get(e.getKey()).add(bs); } } } float[] res = new float[1]; BusStop eastStart = null, eastEnd = null, westStart = null, westEnd = null; double minStartBuffer = Double.MAX_VALUE, minEndBuffer = Double.MAX_VALUE, total; for (BusStop bs : map.get(east)) { Location.distanceBetween(startLatLng.latitude, startLatLng.longitude, bs.getLatLng().latitude, bs.getLatLng().longitude, res); if (res[0] < minStartBuffer) { minStartBuffer = res[0]; eastStart = bs; } Location.distanceBetween(destLatLng.latitude, destLatLng.longitude, bs.getLatLng().latitude, bs.getLatLng().longitude, res); if (res[0] < minEndBuffer) { minEndBuffer = res[0]; eastEnd = bs; } } total = minStartBuffer + minEndBuffer; minStartBuffer = Double.MAX_VALUE; minEndBuffer = Double.MAX_VALUE; for (BusStop bs : map.get(west)) { Location.distanceBetween(startLatLng.latitude, startLatLng.longitude, bs.getLatLng().latitude, bs.getLatLng().longitude, res); if (res[0] < minStartBuffer) { minStartBuffer = res[0]; westStart = bs; } Location.distanceBetween(destLatLng.latitude, destLatLng.longitude, bs.getLatLng().latitude, bs.getLatLng().longitude, res); if (res[0] < minEndBuffer) { minEndBuffer = res[0]; westEnd = bs; } } DirectionsApiRequest req; final LinkedList<BusStop> interStops = new LinkedList<>(); if (total > minStartBuffer + minEndBuffer) { //west if (westStart == null || westEnd == null) { activity.showErrorToast(R.string.no_bus_route); addWalkingPath(startLatLng, destLatLng, true, true); return; } req = getGoogleBusRoute(map, west, westStart, westEnd, interStops); } else { //east if (eastStart == null || eastEnd == null) { activity.showErrorToast(R.string.no_bus_route); addWalkingPath(startLatLng, destLatLng, true, true); return; } req = getGoogleBusRoute(map, east, eastStart, eastEnd, interStops); } if (eastStart == null || westStart == null || (interStops.isEmpty() && eastStart.equals(eastEnd) && westStart.equals(westEnd))) { activity.showErrorToast(R.string.no_bus_route); addWalkingPath(startLatLng, destLatLng, true, true); return; } final BusStop startStop = total > minStartBuffer + minEndBuffer ? westStart : eastStart; final BusStop endStop = total > minStartBuffer + minEndBuffer ? westEnd : eastEnd; req.mode(TravelMode.DRIVING).optimizeWaypoints(false); req.setCallback(getBusRouteCallback(interStops, startStop, endStop, startLatLng, destLatLng)); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { addWalkingPath(startLatLng, destLatLng, true, true); } }); } private PendingResult.Callback<DirectionsResult> getBusRouteCallback(final List<BusStop> interStops, final BusStop startStop, final BusStop endStop , final LatLng startLatLng, final LatLng destLatLng) { return new PendingResult.Callback<DirectionsResult>() { @Override public void onResult(final DirectionsResult result) { activity.runOnUiThread(new Runnable() { @Override public void run() { clearMap(); DirectionsRoute[] routes = result.routes; EncodedPolyline polyline = routes[0].overviewPolyline; PolylineOptions options = new PolylineOptions(); for (com.google.maps.model.LatLng latLng : polyline.decodePath()) { options.add(new LatLng(latLng.lat, latLng.lng)); } options.color(getResources().getColor(R.color.color_primary)).width(15); googleMap.addPolyline(options); addGoogleWarning(result); LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); for (BusStop bs : interStops) { drawBusStop(bs); boundsBuilder.include(bs.getLatLng()); } googleMap.addMarker(new MarkerOptions() .position(startStop.getLatLng()) .title(startStop.getName())); googleMap.addMarker(new MarkerOptions() .position(endStop.getLatLng()) .title(endStop.getName())); boundsBuilder.include(startStop.getLatLng()); boundsBuilder.include(endStop.getLatLng()); boundsBuilder.include(startLatLng); boundsBuilder.include(destLatLng); changeZoomLevel(googleMap, boundsBuilder.build()); addWalkingPath(startLatLng, startStop.getLatLng(), true, false); addWalkingPath(destLatLng, endStop.getLatLng(), false, false); } }); } @Override public void onFailure(Throwable e) { activity.showErrorToast(R.string.no_bus_route); addWalkingPath(startLatLng, destLatLng, true, true); } }; } private void addGoogleWarning(DirectionsResult result) { StringBuilder builder = new StringBuilder(searchLoc.getText()); for (DirectionsRoute r : result.routes) { if (!builder.toString().contains(r.copyrights)) { if (builder.length() > 0) { builder.append(" "); } builder.append(r.copyrights); } } boolean added = false; for (DirectionsRoute r : result.routes) { for (String s : r.warnings) { if (!builder.toString().contains(s)) { if (!added) { added = true; builder.append("\n"); } builder.append(s); } } } searchLoc.setText(builder.toString()); } private DirectionsApiRequest getGoogleBusRoute(HashMap<String, TreeSet<BusStop>> map, String s, BusStop start, BusStop end, LinkedList<BusStop> stops) { DirectionsApiRequest req; LinkedList<String> waypoints = new LinkedList<>(); int startID = start.routesMap.get(s), endID = end.routesMap.get(s); if (startID < endID) { //start before end, so just return stuff in the middle for (BusStop bs : map.get(s)) { if (bs.routesMap.get(s) > startID && endID > bs.routesMap.get(s)) { waypoints.add(bs.getLatLng().latitude + "," + bs.getLatLng().longitude); stops.add(bs); } } } else { //start bigger, so wrap around for (BusStop bs : map.get(s)) { if (bs.routesMap.get(s) > startID || endID > bs.routesMap.get(s)) { waypoints.add(bs.getLatLng().latitude + "," + bs.getLatLng().longitude); stops.add(bs); } } } req = DirectionsApi.getDirections(geoapi, start.getLatLng().latitude + "," + start.getLatLng().longitude, end.getLatLng().latitude + "," + end.getLatLng().longitude); if (!waypoints.isEmpty()) { req.waypoints(waypoints.toArray(new String[waypoints.size()])); } return req; } private void generateBusRouteMap(BusStop bs) { if (bs.routesMap == null) { bs.routesMap = new HashMap<>(); Map<String, Double> bufferMap = (Map<String, Double>) bs.routes; if (bufferMap != null) { for (Map.Entry<String, Double> e : bufferMap.entrySet()) { bs.routesMap.put(e.getKey(), ((int) (double) e.getValue())); } } } } private class BusStopComparator implements Comparator<BusStop>, Serializable { private String name; public BusStopComparator (String name) { this.name = name; } public int compare(BusStop b1, BusStop b2) { if (b1.routesMap == null) { generateBusRouteMap(b1); } if (b2.routesMap == null) { generateBusRouteMap(b1); } if (!b1.routesMap.containsKey(name) || !b2.routesMap.containsKey(name)) { return (int) (b1.getLatLng().latitude - b2.getLatLng().latitude); } return b1.routesMap.get(name) - b2.routesMap.get(name); } } public static void toggleRouteSelection(BusRoute busRoute) { if (selectedRoutes.contains(busRoute)) { selectedRoutes.remove(busRoute); } else { selectedRoutes.add(busRoute); } } private void loadRouteAdapter() { mLabs.routes().observeOn(AndroidSchedulers.mainThread()).subscribe( new Action1<List<BusRoute>>() { @Override public void call(List<BusRoute> routes) { routesAdapter = new RoutesAdapter(activity.getApplicationContext(), routes); showRouteDialogBox(); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { activity.showErrorToast(R.string.no_path_found); } }); } private void showRouteDialogBox() { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.routes_list).setAdapter(routesAdapter, null) .setPositiveButton(R.string.routes_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); clearMap(); LatLngBounds.Builder builder = new LatLngBounds.Builder(); if (selectedRoutes.isEmpty()) { googleMap.setInfoWindowAdapter(buildingAdpater); } else { googleMap.setInfoWindowAdapter(transitAdapter); } for (BusRoute busRoute : selectedRoutes) { drawOfficialRoute(builder, busRoute); } if (!selectedRoutes.isEmpty()) { MapFragment.changeZoomLevel(googleMap, builder.build()); } } }).show(); } private void drawOfficialRoute(LatLngBounds.Builder builder, BusRoute busRoute) { PolylineOptions options = busRoute.getPolylineOptions(); for (BusStop bs : busRoute.stops) { drawBusStop(bs); } for (BusStop busStop : busRoute.stops) { for (BusStop bs : busStop.path_to) { builder.include(bs.getLatLng()); } builder.include(busStop.getLatLng()); } for (BusStop bs : busRoute.stops.get(0).path_to) { builder.include(bs.getLatLng()); } googleMap.addPolyline(options); } private void drawBusStop(BusStop busStop) { busStopCircles.add(googleMap.addCircle(new CircleOptions() .center(busStop.getLatLng()) .visible(true) .radius(CIRCLE_SIZE) .fillColor(getResources().getColor(R.color.white)) .strokeWidth(3) .strokeColor(getResources().getColor(R.color.secondary_text)) .zIndex(1))); busStopAdded.add(busStop); } private void checkBusStopClicked(LatLng pressed) { BusStop current = null; for (BusStop bs : busStopAdded) { float[] results = new float[1]; Location.distanceBetween(pressed.latitude, pressed.longitude, bs.getLatLng().latitude, bs.getLatLng().longitude, results); if (results[0] <= SENSITIVITY_MULTIPLIER * CIRCLE_SIZE) { current = bs; busStopName.setText(current.getName()); searchName.setText(current.getName()); searchLoc.setText(""); break; } } if (current != null) { for (Circle c : busStopCircles) { if (c.getCenter().equals(current.getLatLng())) { if (c.equals(currentBusStop)) { c.setFillColor(getResources().getColor(R.color.white)); currentBusStop = null; searchName.setText(currentBuilding == null ? "" : currentBuilding.title); searchLoc.setText(currentBuilding == null ? "" : currentBuilding.address); busStopName.setText(""); } else { c.setFillColor(getResources().getColor(R.color.red)); if (currentBusStop != null) { currentBusStop.setFillColor(getResources().getColor(R.color.white)); } currentBusStop = c; } return; } } } } }