package com.miguelbcr.rx_gpsservice.app; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; 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.miguelbcr.io.rx_gps_service.lib.entities.LatLong; import com.miguelbcr.io.rx_gps_service.lib.entities.LatLongDetailed; import com.miguelbcr.io.rx_gps_service.lib.entities.RouteStats; import com.miguelbcr.rx_gpsservice.R; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; public class PlaceMapFragment extends SupportMapFragment { private static final float DEFAULT_ZOOM = 16f; private static final LatLng DEFAULT_LATLNG = new LatLng(39.62261494, 2.98965454); BitmapHelper bitmapHelper; private Place place; private List<? extends Place> places; private GoogleMap googleMap; private Map<Marker, Object> markersMap = new HashMap<>(); private Listener listener; private BitmapDescriptor iconRoute, iconPoi, iconUser; private Polyline polylineRoute; private Polyline polylineUser; private Polyline polylineUserLastPath; private Marker markerUser; public interface Listener { void setOnInfoWindowClickListener(Place place); } public static PlaceMapFragment getInstance() { PlaceMapFragment fragment = new PlaceMapFragment(); return fragment; } public static PlaceMapFragment getInstance(Place place, Listener listener) { PlaceMapFragment fragment = new PlaceMapFragment(); fragment.place = place; fragment.listener = listener; return fragment; } public static PlaceMapFragment getInstance(List<Place> places, Listener listener) { PlaceMapFragment fragment = new PlaceMapFragment(); fragment.places = places; fragment.listener = listener; return fragment; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); bitmapHelper = new BitmapHelper(); initViews(); } private void initViews() { getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap gMap) { googleMap = gMap; googleMap.getUiSettings().setAllGesturesEnabled(true); googleMap.clear(); googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(DEFAULT_LATLNG, DEFAULT_ZOOM)); setCustomInfoWindow(); PlaceMapFragment.this.setMapListeners(); if (place != null) PlaceMapFragment.this.showPlace(place, false, 0, listener); if (places != null && !places.isEmpty()) PlaceMapFragment.this.showPlaces(places, false, places.get(0).getId(), listener); } }); } @Override public void onDestroy() { iconRoute = null; iconPoi = null; iconUser = null; polylineRoute = removePath(polylineRoute); polylineUser = removePath(polylineUser); polylineUserLastPath = removePath(polylineUserLastPath); if (markersMap != null) { markersMap.clear(); markersMap = null; } if (googleMap != null) { // Avoid memory leak: https://code.google.com/p/gmaps-api-issues/issues/detail?id=8111 // googleMap.setMyLocationEnabled(false); googleMap.clear(); googleMap = null; } super.onDestroy(); } private Polyline removePath(Polyline polyline) { if (polyline != null) { polyline.remove(); } return null; } public void applyZoom(float zooom) { if (googleMap == null) { return; } googleMap.moveCamera(CameraUpdateFactory.zoomTo(zooom)); } public void showPlace(Place place, boolean clearMap, int idPlaceToGo, Listener listener) { if (googleMap == null) { return; } if (clearMap) { googleMap.clear(); } this.place = place; this.listener = listener; if (place instanceof Route) { Route route = (Route) place; if (route.getId() == 0) return; // Creating new route. Route is empty LatLng latLng = new LatLng(route.getLatLong().latitude(), route.getLatLong().longitude()); markersMap.put(addMark(latLng, route.getName(), getIconRoute(), route.getId() == idPlaceToGo), route); latLng = new LatLng(route.getLatLongEnd().latitude(), route.getLatLongEnd().longitude()); markersMap.put(addMark(latLng, route.getName(), getIconRoute(), false), route); polylineRoute = removePath(polylineRoute); polylineRoute = drawPath(route.getPath(), ContextCompat.getColor(getContext(), R.color.blue), 1f, polylineRoute); } else { if (TextUtils.isEmpty(place.getUrlImage())) { markersMap.put(addMarkerPoi(place, getIconPoi(), idPlaceToGo), place); } else { Picasso.with(getContext()).load(place.getUrlImage()).into(getTargetForPois(place, idPlaceToGo)); } } } public void showPlaces(List<? extends Place> places, boolean clearMap, int idPlaceToGo, Listener listener) { if (googleMap == null) { return; } if (clearMap) { googleMap.clear(); } this.places = places; this.listener = listener; for (Place place : places) { showPlace(place, false, idPlaceToGo, listener); if (place.getId() == idPlaceToGo) { showMarkInfoWindow(idPlaceToGo); } } } public void disableNavigationControls() { if (googleMap == null) return; googleMap.getUiSettings().setMapToolbarEnabled(false); } public void navigateTo(double latitude, double longitude) { Uri gmmIntentUri = Uri.parse("google.navigation:q=" + latitude + ", " + longitude); Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); mapIntent.setPackage("com.google.android.apps.maps"); startActivity(mapIntent); } public void drawPathUser(List<LatLong> waypoints) { if (googleMap == null) return; if (polylineUser != null && polylineUser.getPoints().size() == waypoints.size()) return; polylineUser = removePath(polylineUser); polylineUserLastPath = removePath(polylineUserLastPath); polylineUser = drawPath(waypoints, ContextCompat.getColor(getContext(), R.color.blue), 2f, polylineUser); if (waypoints != null && !waypoints.isEmpty()) { showCheckpoints(waypoints); LatLong lastLatLong = waypoints.get(waypoints.size() - 1); LatLng latLng = new LatLng(lastLatLong.latitude(), lastLatLong.longitude()); drawSegmentPathUser(latLng); } } public void updateUserLocation(RouteStats routeStats, boolean goToUserLocation) { LatLong currentLocation = routeStats.getLastLatLong(); if (googleMap == null || isEmptyLatLong(currentLocation)) return; LatLng latLng = new LatLng(currentLocation.latitude(), currentLocation.longitude()); drawSegmentPathUser(latLng); if (markerUser != null) markerUser.remove(); markerUser = addMark(latLng, "", getIconUser(), false); markersMap.put(markerUser, routeStats); markerUser.showInfoWindow(); if (goToUserLocation) googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); } public void resetUserPosition(LatLong latLong) { if (googleMap == null || isEmptyLatLong(latLong)) return; LatLng latLng = new LatLng(latLong.latitude(), latLong.longitude()); if (markerUser != null) markerUser.remove(); markerUser = addMark(latLng, "", getIconUser(), false); markerUser.hideInfoWindow(); markersMap.put(markerUser, RouteStats.create(0, 0, 0f, 0f, 0f, 0f, LatLongDetailed.create(new Location("empty")), new ArrayList<LatLong>(), new ArrayList<LatLongDetailed>())); googleMap.animateCamera(CameraUpdateFactory.newLatLng(latLng)); } private boolean isEmptyLatLong(LatLong latLong) { return latLong == null || (latLong.latitude() == 0 && latLong.longitude() == 0); } private void showCheckpoints(List<LatLong> latLongs) { List<Place> places = new ArrayList<>(); for (LatLong latLong : latLongs) { if (latLong.isCheckPoint()) { Place checkPoint = new Place(); checkPoint.setId(latLongs.size()); checkPoint.setLatLong(latLong); places.add(checkPoint); } } showPlaces(places, false, 0, null); } private BitmapDescriptor getIconRoute() { if (iconRoute == null) { Bitmap bitmap = bitmapHelper.getBitmap(getContext(), R.drawable.ic_assistant_photo); bitmap = bitmapHelper.getTintedBitmap(bitmap, ContextCompat.getColor(getContext(), R.color.red)); iconRoute = BitmapDescriptorFactory.fromBitmap(bitmap); } return iconRoute; } private BitmapDescriptor getIconUser() { if (iconUser == null) { Bitmap bitmap = bitmapHelper.getBitmap(getContext(), R.drawable.ic_accessibility); bitmap = bitmapHelper.getTintedBitmap(bitmap, ContextCompat.getColor(getContext(), R.color.green)); iconUser = BitmapDescriptorFactory.fromBitmap(bitmap); } return iconUser; } private BitmapDescriptor getIconPoi() { if (iconPoi == null) { Bitmap bitmap = bitmapHelper.getBitmap(getContext(), R.drawable.ic_place); bitmap = bitmapHelper.getTintedBitmap(bitmap, ContextCompat.getColor(getContext(), R.color.orange)); iconPoi = BitmapDescriptorFactory.fromBitmap(bitmap); } return iconPoi; } private Marker addMark(LatLng latLng, String title, BitmapDescriptor icon, boolean goToMark) { Marker marker = googleMap.addMarker(new MarkerOptions().position(latLng).title(title).icon(icon)); if (goToMark) googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM)); return marker; } private Marker addMarkerPoi(final Place place, final BitmapDescriptor bitmapDescriptor, final int idPlaceToGo) { LatLng latLng = new LatLng(place.getLatLong().latitude(), place.getLatLong().longitude()); return addMark(latLng, place.getName(), bitmapDescriptor, place.getId() == idPlaceToGo); } private Target getTargetForPois(final Place place, final int idPlaceToGo) { return new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { bitmap = bitmapHelper.getTintedBitmap(bitmap, ContextCompat.getColor(getContext(), R.color.orange)); bitmap = bitmapHelper.getScaledBitmap(bitmap, (int) getResources().getDimension(R.dimen._30dp)); markersMap.put(addMarkerPoi(place, BitmapDescriptorFactory.fromBitmap(bitmap), idPlaceToGo), place); } @Override public void onBitmapFailed(Drawable errorDrawable) { markersMap.put(addMarkerPoi(place, getIconPoi(), idPlaceToGo), place); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } }; } private void showMarkInfoWindow(int objectId) { if (objectId > 0) { for (Map.Entry<Marker, Object> entry : markersMap.entrySet()) { Object object = entry.getValue(); if (object instanceof Place) { Marker marker = entry.getKey(); Place place = (Place) object; if (place.getId() == objectId) marker.showInfoWindow(); } } } } private void setMapListeners() { googleMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { @Override public void onInfoWindowClick(Marker marker) { Object object = markersMap.get(marker); if (object instanceof Place) { if (listener != null) { listener.setOnInfoWindowClickListener((Place) object); } } } }); } private Polyline drawPath(List<LatLong> latLongs, int color, float zIndex, Polyline polyline) { if (googleMap != null && latLongs != null && !latLongs.isEmpty()) { PolylineOptions polyLineOptions = new PolylineOptions(); polyLineOptions.width(getResources().getDimension(R.dimen._2dp)); polyLineOptions.color(color); polyLineOptions.zIndex(zIndex); for (LatLong latLong : latLongs) { polyLineOptions.add(new LatLng(latLong.latitude(), latLong.longitude())); } if (polyline != null) polyline.remove(); return googleMap.addPolyline(polyLineOptions); } return null; } private void drawSegmentPathUser(LatLng latLng) { PolylineOptions polyLineOptions = new PolylineOptions(); polyLineOptions.width(getResources().getDimension(R.dimen._2dp)); polyLineOptions.color(ContextCompat.getColor(getContext(), R.color.blue)); polyLineOptions.zIndex(2f); if (polylineUserLastPath != null) { for (LatLng latLngOld : polylineUserLastPath.getPoints()) polyLineOptions.add(latLngOld); } polyLineOptions.add(latLng); polylineUserLastPath = removePath(polylineUserLastPath); polylineUserLastPath = googleMap.addPolyline(polyLineOptions); } private void setCustomInfoWindow() { googleMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() { // Use default InfoWindow frame @Override public View getInfoWindow(Marker marker) { return null; } // Defines the custom contents of the InfoWindow @Override public View getInfoContents(final Marker marker) { View view = LayoutInflater.from(getContext()).inflate(R.layout.user_info_window, null); final Object object = markersMap.get(marker); if (object instanceof RouteStats) { view.setLayoutParams(new RelativeLayout.LayoutParams((int) getResources().getDimension(R.dimen._100dp), ViewGroup.LayoutParams.WRAP_CONTENT)); RouteStats routeStats = (RouteStats) object; String text = getInfoWindowText(routeStats); ((TextView) view.findViewById(R.id.tv_data)).setText(text); return view; } else if (object instanceof Place) { view.setLayoutParams(new RelativeLayout.LayoutParams((int) getResources().getDimension(R.dimen._70dp), ViewGroup.LayoutParams.WRAP_CONTENT)); Place place = (Place) object; String text = getInfoWindowText(place); ((TextView) view.findViewById(R.id.tv_data)).setText(text); return view; } return view; } }); } private String getInfoWindowText(RouteStats routeStats) { return getTimeFormatted(routeStats.time()) + "\n" + getDistanceFormatted(routeStats.distance()) + "\n" + getSpeedFormatted(routeStats.speed(), true) + "\n" + routeStats.latLongs().size() + " waypoints"; } private String getInfoWindowText(Place place) { return "Alt: " + getDistanceFormatted((long) place.getLatLong().altitude()); } private String getDistanceFormatted(long distance) { if (distance < 1000) { return String.valueOf(distance) + " m"; } return String.format(Locale.ENGLISH, "%.2f", distance / 1000f).replace(".", ",") + " km"; } private String getSpeedFormatted(float speed, boolean isMetersPerSecond) { int speedConverted = Math.round(isMetersPerSecond ? speed * 3.6f : speed); return String.format("%d", speedConverted) + " km/h"; } private String getTimeFormatted(long seconds) { int hours = (int) (seconds / 3600); int minutes = (int) (seconds - hours * 3600) / 60; int secs = (int) (seconds - hours * 3600 - minutes * 60); // return Observable.just(String.format("%01dh %02dm %02ds", hours, minutes, secs)); return String.format("%01d:%02d:%02d", hours, minutes, secs); } }