package org.droidplanner.android.maps;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
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.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.location.LocationAvailability;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.Projection;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
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.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.VisibleRegion;
import com.o3dr.android.client.Drone;
import com.o3dr.services.android.lib.coordinate.LatLong;
import com.o3dr.services.android.lib.coordinate.LatLongAlt;
import com.o3dr.services.android.lib.drone.attribute.AttributeEvent;
import com.o3dr.services.android.lib.drone.attribute.AttributeType;
import com.o3dr.services.android.lib.drone.property.FootPrint;
import com.o3dr.services.android.lib.drone.property.Gps;
import com.o3dr.services.android.lib.util.googleApi.GoogleApiClientManager;
import com.o3dr.services.android.lib.util.googleApi.GoogleApiClientManager.GoogleApiClientTask;
import org.droidplanner.android.DroidPlannerApp;
import org.droidplanner.android.R;
import org.droidplanner.android.fragments.SettingsFragment;
import org.droidplanner.android.graphic.map.GraphicHome;
import org.droidplanner.android.maps.providers.DPMapProvider;
import org.droidplanner.android.maps.providers.google_map.DownloadMapboxMapActivity;
import org.droidplanner.android.maps.providers.google_map.GoogleMapPrefConstants;
import org.droidplanner.android.maps.providers.google_map.GoogleMapPrefFragment;
import org.droidplanner.android.maps.providers.google_map.tiles.TileProviderManager;
import org.droidplanner.android.maps.providers.google_map.tiles.arcgis.ArcGISTileProviderManager;
import org.droidplanner.android.maps.providers.google_map.tiles.mapbox.MapboxTileProviderManager;
import org.droidplanner.android.maps.providers.google_map.tiles.mapbox.MapboxUtils;
import org.droidplanner.android.maps.providers.google_map.tiles.mapbox.offline.MapDownloader;
import org.droidplanner.android.utils.MapUtils;
import org.droidplanner.android.utils.prefs.AutoPanMode;
import org.droidplanner.android.utils.prefs.DroidPlannerPrefs;
import org.jetbrains.annotations.NotNull;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import timber.log.Timber;
public class GoogleMapFragment extends SupportMapFragment implements DPMap,
GoogleApiClientManager.ManagerListener {
private static final long USER_LOCATION_UPDATE_INTERVAL = 30000; // ms
private static final long USER_LOCATION_UPDATE_FASTEST_INTERVAL = 5000; // ms
private static final float USER_LOCATION_UPDATE_MIN_DISPLACEMENT = 0; // m
private static final float GO_TO_MY_LOCATION_ZOOM = 17f;
private static final int ONLINE_TILE_PROVIDER_Z_INDEX = -1;
private static final int OFFLINE_TILE_PROVIDER_Z_INDEX = -2;
private static final int GET_DRAGGABLE_FROM_MARKER_INFO = -1;
private static final int IS_DRAGGABLE = 0;
private static final int IS_NOT_DRAGGABLE = 1;
private static final IntentFilter eventFilter = new IntentFilter();
static {
eventFilter.addAction(AttributeEvent.GPS_POSITION);
eventFilter.addAction(SettingsFragment.ACTION_MAP_ROTATION_PREFERENCE_UPDATED);
}
private final static Api<? extends Api.ApiOptions.NotRequiredOptions>[] apisList = new Api[]{LocationServices.API};
private final BroadcastReceiver eventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (action) {
case AttributeEvent.GPS_POSITION:
if (mPanMode.get() == AutoPanMode.DRONE) {
final Drone drone = getDroneApi();
if (!drone.isConnected())
return;
final Gps droneGps = drone.getAttribute(AttributeType.GPS);
if (droneGps != null && droneGps.isValid()) {
final LatLong droneLocation = droneGps.getPosition();
updateCamera(droneLocation);
}
}
break;
case SettingsFragment.ACTION_MAP_ROTATION_PREFERENCE_UPDATED:
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
setupMapUI(googleMap);
}
});
break;
}
}
};
private final Map<Marker, MarkerInfo> markersMap = new HashMap<>();
private final Map<Polyline, PolylineInfo> polylinesMap = new HashMap<>();
private DroidPlannerPrefs mAppPrefs;
private final AtomicReference<AutoPanMode> mPanMode = new AtomicReference<AutoPanMode>(
AutoPanMode.DISABLED);
private final Handler handler = new Handler();
private final LocationCallback locationCb = new LocationCallback() {
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
super.onLocationAvailability(locationAvailability);
}
@Override
public void onLocationResult(LocationResult result) {
super.onLocationResult(result);
final Location location = result.getLastLocation();
if (location == null)
return;
//Update the user location icon.
if (userMarker == null) {
final MarkerOptions options = new MarkerOptions()
.position(new LatLng(location.getLatitude(), location.getLongitude()))
.draggable(false)
.flat(true)
.visible(true)
.anchor(0.5f, 0.5f)
.icon(BitmapDescriptorFactory.fromResource(R.drawable.user_location));
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
userMarker = googleMap.addMarker(options);
}
});
} else {
userMarker.setPosition(new LatLng(location.getLatitude(), location.getLongitude()));
}
if (mPanMode.get() == AutoPanMode.USER) {
Timber.d("User location changed.");
updateCamera(MapUtils.locationToCoord(location), (int) getMap().getCameraPosition().zoom);
}
if (mLocationListener != null) {
mLocationListener.onLocationChanged(location);
}
}
};
private final GoogleApiClientTask mGoToMyLocationTask = new GoogleApiClientTask() {
@Override
public void doRun() {
final Location myLocation = LocationServices.FusedLocationApi.getLastLocation(getGoogleApiClient());
if (myLocation != null) {
updateCamera(MapUtils.locationToCoord(myLocation), GO_TO_MY_LOCATION_ZOOM);
if (mLocationListener != null)
mLocationListener.onLocationChanged(myLocation);
}
}
};
private final GoogleApiClientTask mRemoveLocationUpdateTask = new GoogleApiClientTask() {
@Override
public void doRun() {
LocationServices.FusedLocationApi.removeLocationUpdates(getGoogleApiClient(), locationCb);
}
};
private final GoogleApiClientTask mRequestLocationUpdateTask = new GoogleApiClientTask() {
@Override
public void doRun() {
final LocationRequest locationReq = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setFastestInterval(USER_LOCATION_UPDATE_FASTEST_INTERVAL)
.setInterval(USER_LOCATION_UPDATE_INTERVAL)
.setSmallestDisplacement(USER_LOCATION_UPDATE_MIN_DISPLACEMENT);
LocationServices.FusedLocationApi.requestLocationUpdates(getGoogleApiClient(), locationReq,
locationCb, handler.getLooper());
}
};
private final GoogleApiClientTask requestLastLocationTask = new GoogleApiClientTask() {
@Override
protected void doRun() {
final Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(getGoogleApiClient());
if (lastLocation != null && mLocationListener != null) {
mLocationListener.onLocationChanged(lastLocation);
}
}
};
private GoogleApiClientManager mGApiClientMgr;
private Marker userMarker;
private Polyline flightPath;
private Polyline missionPath;
private Polyline mDroneLeashPath;
private boolean showFlightPath;
/*
* DP Map listeners
*/
private DPMap.OnMapClickListener mMapClickListener;
private DPMap.OnMapLongClickListener mMapLongClickListener;
private DPMap.OnMarkerClickListener mMarkerClickListener;
private DPMap.OnMarkerDragListener mMarkerDragListener;
private android.location.LocationListener mLocationListener;
private List<Polygon> polygonsPaths = new ArrayList<>();
protected DroidPlannerApp dpApp;
private Polygon footprintPoly;
/*
Tile overlay
*/
private TileOverlay onlineTileOverlay;
private TileOverlay offlineTileOverlay;
private TileProviderManager tileProviderManager;
private final OnMapReadyCallback loadCameraPositionTask = new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
final SharedPreferences settings = mAppPrefs.prefs;
final CameraPosition.Builder camera = new CameraPosition.Builder();
camera.bearing(settings.getFloat(PREF_BEA, DEFAULT_BEARING));
camera.tilt(settings.getFloat(PREF_TILT, DEFAULT_TILT));
camera.zoom(settings.getFloat(PREF_ZOOM, DEFAULT_ZOOM_LEVEL));
camera.target(new LatLng(settings.getFloat(PREF_LAT, DEFAULT_LATITUDE),
settings.getFloat(PREF_LNG, DEFAULT_LONGITUDE)));
googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(camera.build()));
}
};
private final OnMapReadyCallback setupMapTask = new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
setupMapUI(googleMap);
setupMapOverlay(googleMap);
setupMapListeners(googleMap);
}
};
private LocalBroadcastManager lbm;
private GoogleMap map;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
dpApp = (DroidPlannerApp) activity.getApplication();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
setHasOptionsMenu(true);
final FragmentActivity activity = getActivity();
final Context context = activity.getApplicationContext();
lbm = LocalBroadcastManager.getInstance(context);
final View view = super.onCreateView(inflater, viewGroup, bundle);
mGApiClientMgr = new GoogleApiClientManager(context, new Handler(), apisList);
mGApiClientMgr.setManagerListener(this);
mAppPrefs = DroidPlannerPrefs.getInstance(context);
final Bundle args = getArguments();
if (args != null) {
showFlightPath = args.getBoolean(EXTRA_SHOW_FLIGHT_PATH);
}
// Load the map
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
map = googleMap;
}
});
return view;
}
private GoogleMap getMap() {
return map;
}
@Override
public void onStart() {
super.onStart();
mGApiClientMgr.start();
mGApiClientMgr.addTask(mRequestLocationUpdateTask);
lbm.registerReceiver(eventReceiver, eventFilter);
setupMap();
}
@Override
public void onStop() {
super.onStop();
mGApiClientMgr.addTask(mRemoveLocationUpdateTask);
lbm.unregisterReceiver(eventReceiver);
mGApiClientMgr.stopSafely();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_google_map, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
switch(item.getItemId()){
case R.id.menu_download_mapbox_map:
startActivity(new Intent(getContext(), DownloadMapboxMapActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onPrepareOptionsMenu(Menu menu){
final MenuItem item = menu.findItem(R.id.menu_download_mapbox_map);
if(item != null) {
final boolean isEnabled = shouldShowDownloadMapMenuOption();
item.setEnabled(isEnabled);
item.setVisible(isEnabled);
}
}
private boolean shouldShowDownloadMapMenuOption(){
final Context context = getContext();
final @GoogleMapPrefConstants.TileProvider String tileProvider = GoogleMapPrefFragment.PrefManager
.getMapTileProvider(context);
return (GoogleMapPrefConstants.MAPBOX_TILE_PROVIDER.equals(tileProvider)
|| GoogleMapPrefConstants.ARC_GIS_TILE_PROVIDER.equals(tileProvider))
&& GoogleMapPrefFragment.PrefManager.addDownloadMenuOption(context);
}
@Override
public void clearFlightPath() {
if (flightPath != null) {
flightPath.remove();
flightPath = null;
}
}
@Override
public void downloadMapTiles(MapDownloader mapDownloader, VisibleMapArea mapRegion, int minimumZ, int maximumZ) {
if(tileProviderManager == null)
return;
tileProviderManager.downloadMapTiles(mapDownloader, mapRegion, minimumZ, maximumZ);
}
@Override
public LatLong getMapCenter() {
return MapUtils.latLngToCoord(getMap().getCameraPosition().target);
}
@Override
public float getMapZoomLevel() {
return getMap().getCameraPosition().zoom;
}
@Override
public float getMaxZoomLevel() {
return getMap().getMaxZoomLevel();
}
@Override
public float getMinZoomLevel() {
return getMap().getMinZoomLevel();
}
@Override
public void selectAutoPanMode(AutoPanMode target) {
final AutoPanMode currentMode = mPanMode.get();
if (currentMode == target)
return;
mPanMode.compareAndSet(currentMode, target);
}
private Drone getDroneApi() {
return dpApp.getDrone();
}
@Override
public DPMapProvider getProvider() {
return DPMapProvider.GOOGLE_MAP;
}
@Override
public void addFlightPathPoint(final LatLongAlt coord) {
final LatLng position = MapUtils.coordToLatLng(coord);
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
if (showFlightPath) {
if (flightPath == null) {
PolylineOptions flightPathOptions = new PolylineOptions();
flightPathOptions.color(FLIGHT_PATH_DEFAULT_COLOR)
.width(FLIGHT_PATH_DEFAULT_WIDTH).zIndex(1);
flightPath = googleMap.addPolyline(flightPathOptions);
}
List<LatLng> oldFlightPath = flightPath.getPoints();
oldFlightPath.add(position);
flightPath.setPoints(oldFlightPath);
}
}
});
}
@Override
public void clearAll(){
clearUserMarker();
clearMarkers();
clearPolylines();
clearFlightPath();
clearMissionPath();
clearFootPrints();
clearPolygonPaths();
clearDroneLeashPath();
GoogleMap googleMap = getMap();
if(googleMap != null){
googleMap.clear();
}
}
private void clearUserMarker(){
if(userMarker != null){
userMarker.remove();
userMarker = null;
}
}
private void clearFootPrints(){
if(footprintPoly != null){
footprintPoly.remove();
footprintPoly = null;
}
}
private void clearPolygonPaths(){
for(Polygon polygon: polygonsPaths){
polygon.remove();
}
polygonsPaths.clear();
}
@Override
public void clearMarkers() {
for(MarkerInfo markerInfo : markersMap.values()){
markerInfo.removeProxyMarker();
}
markersMap.clear();
}
private void clearDroneLeashPath(){
if(mDroneLeashPath != null){
mDroneLeashPath.remove();
mDroneLeashPath = null;
}
}
private void clearMissionPath(){
if(missionPath != null){
missionPath.remove();
missionPath = null;
}
}
@Override
public void clearPolylines() {
for(PolylineInfo info: polylinesMap.values()){
info.removeProxy();
}
polylinesMap.clear();
}
private PolylineOptions fromPolylineInfo(PolylineInfo info){
return new PolylineOptions()
.addAll(MapUtils.coordToLatLng(info.getPoints()))
.clickable(info.isClickable())
.color(info.getColor())
.geodesic(info.isGeodesic())
.visible(info.isVisible())
.width(info.getWidth())
.zIndex(info.getZIndex());
}
private MarkerOptions fromMarkerInfo(MarkerInfo markerInfo, boolean isDraggable){
final LatLong coord = markerInfo.getPosition();
if (coord == null) {
return null;
}
final MarkerOptions markerOptions = new MarkerOptions()
.position(MapUtils.coordToLatLng(coord))
.draggable(isDraggable)
.alpha(markerInfo.getAlpha())
.anchor(markerInfo.getAnchorU(), markerInfo.getAnchorV())
.infoWindowAnchor(markerInfo.getInfoWindowAnchorU(), markerInfo.getInfoWindowAnchorV())
.rotation(markerInfo.getRotation())
.snippet(markerInfo.getSnippet())
.title(markerInfo.getTitle())
.flat(markerInfo.isFlat())
.visible(markerInfo.isVisible());
final Bitmap markerIcon = markerInfo.getIcon(getResources());
if (markerIcon != null) {
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(markerIcon));
}
return markerOptions;
}
private MarkerOptions fromMarkerInfo(MarkerInfo markerInfo){
return fromMarkerInfo(markerInfo, markerInfo.isDraggable());
}
@Override
public void addMarker(final MarkerInfo markerInfo) {
if (markerInfo == null || markerInfo.isOnMap())
return;
final MarkerOptions options = fromMarkerInfo(markerInfo);
if (options == null)
return;
GoogleMap googleMap = getMap();
if (googleMap != null) {
Marker marker = googleMap.addMarker(options);
markerInfo.setProxyMarker(new ProxyMapMarker(marker));
markersMap.put(marker, markerInfo);
}
}
@Override
public void addMarkers(final List<MarkerInfo> markerInfoList){
addMarkers(markerInfoList, GET_DRAGGABLE_FROM_MARKER_INFO);
}
@Override
public void addMarkers(final List<MarkerInfo> markerInfoList, boolean isDraggable){
addMarkers(markerInfoList, isDraggable ? IS_DRAGGABLE : IS_NOT_DRAGGABLE);
}
@Override
public void addPolyline(final PolylineInfo polylineInfo) {
if(polylineInfo == null || polylineInfo.isOnMap())
return;
final PolylineOptions options = fromPolylineInfo(polylineInfo);
GoogleMap googleMap = getMap();
if (googleMap != null) {
Polyline polyline = googleMap.addPolyline(options);
polylineInfo.setProxyPolyline(new ProxyMapPolyline(polyline));
polylinesMap.put(polyline, polylineInfo);
}
}
private void addMarkers(final List<MarkerInfo> markerInfoList, int draggableType) {
if (markerInfoList == null || markerInfoList.isEmpty())
return;
final int infoCount = markerInfoList.size();
final MarkerOptions[] optionsSet = new MarkerOptions[infoCount];
for (int i = 0; i < infoCount; i++) {
MarkerInfo markerInfo = markerInfoList.get(i);
boolean isDraggable = draggableType == GET_DRAGGABLE_FROM_MARKER_INFO
? markerInfo.isDraggable()
: draggableType == IS_DRAGGABLE;
optionsSet[i] = markerInfo.isOnMap() ? null : fromMarkerInfo(markerInfo, isDraggable);
}
GoogleMap googleMap = getMap();
if (googleMap != null) {
for (int i = 0; i < infoCount; i++) {
MarkerOptions options = optionsSet[i];
if (options == null)
continue;
Marker marker = googleMap.addMarker(options);
MarkerInfo markerInfo = markerInfoList.get(i);
markerInfo.setProxyMarker(new ProxyMapMarker(marker));
markersMap.put(marker, markerInfo);
}
}
}
@Override
public List<LatLong> projectPathIntoMap(List<LatLong> path) {
List<LatLong> coords = new ArrayList<LatLong>();
Projection projection = getMap().getProjection();
for (LatLong point : path) {
LatLng coord = projection.fromScreenLocation(new Point((int) point
.getLatitude(), (int) point.getLongitude()));
coords.add(MapUtils.latLngToCoord(coord));
}
return coords;
}
@Override
public void removeMarker(MarkerInfo markerInfo){
if(markerInfo == null || !markerInfo.isOnMap())
return;
ProxyMapMarker proxyMarker = (ProxyMapMarker) markerInfo.getProxyMarker();
markerInfo.removeProxyMarker();
markersMap.remove(proxyMarker.marker);
}
@Override
public void removeMarkers(Collection<MarkerInfo> markerInfoList) {
if (markerInfoList == null || markerInfoList.isEmpty()) {
return;
}
for (MarkerInfo markerInfo : markerInfoList) {
removeMarker(markerInfo);
}
}
@Override
public void removePolyline(PolylineInfo polylineInfo) {
if(polylineInfo == null || !polylineInfo.isOnMap())
return;
ProxyMapPolyline proxy = (ProxyMapPolyline) polylineInfo.getProxyPolyline();
polylineInfo.removeProxy();
polylinesMap.remove(proxy.polyline);
}
@Override
public void setMapPadding(int left, int top, int right, int bottom) {
getMap().setPadding(left, top, right, bottom);
}
@Override
public void setOnMapClickListener(OnMapClickListener listener) {
mMapClickListener = listener;
}
@Override
public void setOnMapLongClickListener(OnMapLongClickListener listener) {
mMapLongClickListener = listener;
}
@Override
public void setOnMarkerDragListener(OnMarkerDragListener listener) {
mMarkerDragListener = listener;
}
@Override
public void setOnMarkerClickListener(OnMarkerClickListener listener) {
mMarkerClickListener = listener;
}
@Override
public void setLocationListener(android.location.LocationListener receiver) {
mLocationListener = receiver;
//Update the listener with the last received location
if (mLocationListener != null) {
mGApiClientMgr.addTask(requestLastLocationTask);
}
}
private void updateCamera(final LatLong coord) {
if (coord != null) {
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
final float zoomLevel = googleMap.getCameraPosition().zoom;
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(MapUtils.coordToLatLng(coord),
zoomLevel));
}
});
}
}
@Override
public void updateCamera(final LatLong coord, final float zoomLevel) {
if (coord != null) {
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
MapUtils.coordToLatLng(coord), zoomLevel));
}
});
}
}
@Override
public void updateCameraBearing(float bearing) {
final CameraPosition cameraPosition = new CameraPosition(MapUtils.coordToLatLng
(getMapCenter()), getMapZoomLevel(), 0, bearing);
getMap().animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
@Override
public void updateDroneLeashPath(PathSource pathSource) {
List<LatLong> pathCoords = pathSource.getPathPoints();
final List<LatLng> pathPoints = new ArrayList<LatLng>(pathCoords.size());
for (LatLong coord : pathCoords) {
pathPoints.add(MapUtils.coordToLatLng(coord));
}
if (mDroneLeashPath == null) {
final PolylineOptions flightPath = new PolylineOptions();
flightPath.color(DRONE_LEASH_DEFAULT_COLOR).width(
MapUtils.scaleDpToPixels(DRONE_LEASH_DEFAULT_WIDTH, getResources()));
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
mDroneLeashPath = getMap().addPolyline(flightPath);
mDroneLeashPath.setPoints(pathPoints);
}
});
}
else {
mDroneLeashPath.setPoints(pathPoints);
}
}
@Override
public void updateMissionPath(PathSource pathSource) {
List<LatLong> pathCoords = pathSource.getPathPoints();
final List<LatLng> pathPoints = new ArrayList<>(pathCoords.size());
for (LatLong coord : pathCoords) {
pathPoints.add(MapUtils.coordToLatLng(coord));
}
if (missionPath == null) {
final PolylineOptions pathOptions = new PolylineOptions();
pathOptions.color(MISSION_PATH_DEFAULT_COLOR).width(MISSION_PATH_DEFAULT_WIDTH);
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
missionPath = getMap().addPolyline(pathOptions);
missionPath.setPoints(pathPoints);
}
});
}
else {
missionPath.setPoints(pathPoints);
}
}
@Override
public void updatePolygonsPaths(List<List<LatLong>> paths) {
GoogleMap map = getMap();
if (map == null) {
return;
}
for (Polygon poly : polygonsPaths) {
poly.remove();
}
for (List<LatLong> contour : paths) {
PolygonOptions pathOptions = new PolygonOptions();
pathOptions.strokeColor(POLYGONS_PATH_DEFAULT_COLOR).strokeWidth(
POLYGONS_PATH_DEFAULT_WIDTH);
final List<LatLng> pathPoints = new ArrayList<LatLng>(contour.size());
for (LatLong coord : contour) {
pathPoints.add(MapUtils.coordToLatLng(coord));
}
pathOptions.addAll(pathPoints);
polygonsPaths.add(map.addPolygon(pathOptions));
}
}
@Override
public void addCameraFootprint(FootPrint footprintToBeDraw) {
PolygonOptions pathOptions = new PolygonOptions();
pathOptions.strokeColor(FOOTPRINT_DEFAULT_COLOR).strokeWidth(FOOTPRINT_DEFAULT_WIDTH);
pathOptions.fillColor(FOOTPRINT_FILL_COLOR);
for (LatLong vertex : footprintToBeDraw.getVertexInGlobalFrame()) {
pathOptions.add(MapUtils.coordToLatLng(vertex));
}
getMap().addPolygon(pathOptions);
}
/**
* Save the map camera state on a preference file
* http://stackoverflow.com/questions
* /16697891/google-maps-android-api-v2-restoring
* -map-state/16698624#16698624
*/
@Override
public void saveCameraPosition() {
final GoogleMap googleMap = getMap();
if(googleMap == null)
return;
CameraPosition camera = googleMap.getCameraPosition();
mAppPrefs.prefs.edit()
.putFloat(PREF_LAT, (float) camera.target.latitude)
.putFloat(PREF_LNG, (float) camera.target.longitude)
.putFloat(PREF_BEA, camera.bearing)
.putFloat(PREF_TILT, camera.tilt)
.putFloat(PREF_ZOOM, camera.zoom).apply();
}
@Override
public void loadCameraPosition() {
getMapAsync(loadCameraPositionTask);
}
private void setupMap() {
// Make sure the map is initialized
MapsInitializer.initialize(getActivity().getApplicationContext());
getMapAsync(setupMapTask);
}
@Override
public void zoomToFit(List<LatLong> coords) {
if (!coords.isEmpty()) {
final List<LatLng> points = new ArrayList<LatLng>();
for (LatLong coord : coords)
points.add(MapUtils.coordToLatLng(coord));
final LatLngBounds bounds = getBounds(points);
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
final Activity activity = getActivity();
if (activity == null)
return;
final View rootView = ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
if (rootView == null)
return;
final int height = rootView.getHeight();
final int width = rootView.getWidth();
Timber.d("Screen W %d, H %d", width, height);
if (height > 0 && width > 0) {
CameraUpdate animation = CameraUpdateFactory.newLatLngBounds(bounds, width, height, 100);
googleMap.animateCamera(animation);
}
}
});
}
}
@Override
public void zoomToFitMyLocation(final List<LatLong> coords) {
mGApiClientMgr.addTask(new GoogleApiClientTask() {
@Override
protected void doRun() {
final Location myLocation = LocationServices.FusedLocationApi.getLastLocation(getGoogleApiClient());
if (myLocation != null) {
final List<LatLong> updatedCoords = new ArrayList<LatLong>(coords);
updatedCoords.add(MapUtils.locationToCoord(myLocation));
zoomToFit(updatedCoords);
} else {
zoomToFit(coords);
}
}
});
}
@Override
public void goToMyLocation() {
if (!mGApiClientMgr.addTask(mGoToMyLocationTask)) {
Timber.e("Unable to add google api client task.");
}
}
@Override
public void goToDroneLocation() {
Drone dpApi = getDroneApi();
if (!dpApi.isConnected())
return;
Gps gps = dpApi.getAttribute(AttributeType.GPS);
if (!gps.isValid()) {
Toast.makeText(getActivity().getApplicationContext(), R.string.drone_no_location, Toast.LENGTH_SHORT).show();
return;
}
final float currentZoomLevel = getMap().getCameraPosition().zoom;
final LatLong droneLocation = gps.getPosition();
updateCamera(droneLocation, (int) currentZoomLevel);
}
private void setupMapListeners(GoogleMap googleMap) {
final GoogleMap.OnMapClickListener onMapClickListener = new GoogleMap.OnMapClickListener() {
@Override
public void onMapClick(LatLng latLng) {
if (mMapClickListener != null) {
mMapClickListener.onMapClick(MapUtils.latLngToCoord(latLng));
}
}
};
googleMap.setOnMapClickListener(onMapClickListener);
googleMap.setOnMapLongClickListener(new GoogleMap.OnMapLongClickListener() {
@Override
public void onMapLongClick(LatLng latLng) {
if (mMapLongClickListener != null) {
mMapLongClickListener.onMapLongClick(MapUtils.latLngToCoord(latLng));
}
}
});
googleMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() {
@Override
public void onMarkerDragStart(Marker marker) {
if (mMarkerDragListener != null) {
final MarkerInfo markerInfo = markersMap.get(marker);
if(!(markerInfo instanceof GraphicHome)) {
markerInfo.setPosition(MapUtils.latLngToCoord(marker.getPosition()));
mMarkerDragListener.onMarkerDragStart(markerInfo);
}
}
}
@Override
public void onMarkerDrag(Marker marker) {
if (mMarkerDragListener != null) {
final MarkerInfo markerInfo = markersMap.get(marker);
if(!(markerInfo instanceof GraphicHome)) {
markerInfo.setPosition(MapUtils.latLngToCoord(marker.getPosition()));
mMarkerDragListener.onMarkerDrag(markerInfo);
}
}
}
@Override
public void onMarkerDragEnd(Marker marker) {
if (mMarkerDragListener != null) {
final MarkerInfo markerInfo = markersMap.get(marker);
markerInfo.setPosition(MapUtils.latLngToCoord(marker.getPosition()));
mMarkerDragListener.onMarkerDragEnd(markerInfo);
}
}
});
googleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
if (mMarkerClickListener != null) {
final MarkerInfo markerInfo = markersMap.get(marker);
if (markerInfo != null)
return mMarkerClickListener.onMarkerClick(markerInfo);
}
return false;
}
});
}
private void setupMapUI(GoogleMap map) {
map.setMyLocationEnabled(false);
UiSettings mUiSettings = map.getUiSettings();
mUiSettings.setMyLocationButtonEnabled(false);
mUiSettings.setMapToolbarEnabled(false);
mUiSettings.setCompassEnabled(false);
mUiSettings.setTiltGesturesEnabled(false);
mUiSettings.setZoomControlsEnabled(false);
mUiSettings.setRotateGesturesEnabled(mAppPrefs.isMapRotationEnabled());
}
private void setupMapOverlay(GoogleMap map) {
final Context context = getContext();
if(context == null)
return;
final @GoogleMapPrefConstants.TileProvider String tileProvider = GoogleMapPrefFragment.PrefManager.getMapTileProvider(context);
switch(tileProvider){
case GoogleMapPrefConstants.GOOGLE_TILE_PROVIDER:
setupGoogleTileProvider(context, map);
break;
case GoogleMapPrefConstants.MAPBOX_TILE_PROVIDER:
setupMapboxTileProvider(context, map);
break;
case GoogleMapPrefConstants.ARC_GIS_TILE_PROVIDER:
setupArcGISTileProvider(context, map);
break;
}
}
private void setupGoogleTileProvider(Context context, GoogleMap map){
//Reset the tile provider manager
tileProviderManager = null;
//Remove the mapbox tile providers
if(offlineTileOverlay != null){
offlineTileOverlay.remove();
offlineTileOverlay = null;
}
if(onlineTileOverlay != null){
onlineTileOverlay.remove();
onlineTileOverlay = null;
}
map.setMapType(GoogleMapPrefFragment.PrefManager.getMapType(context));
}
private void setupArcGISTileProvider(Context context, GoogleMap map){
Timber.i("Enabling ArcGIS tile provider.");
//Remove the default google map layer
map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
final GoogleMapPrefFragment.PrefManager prefManager = GoogleMapPrefFragment.PrefManager;
String selectedMap = prefManager.getArcGISMapType(context);
if(!(tileProviderManager instanceof ArcGISTileProviderManager)
|| !selectedMap.equals(((ArcGISTileProviderManager) tileProviderManager).getSelectedMap())){
//Setup the online tile overlay
if(onlineTileOverlay != null){
onlineTileOverlay.remove();
onlineTileOverlay = null;
}
tileProviderManager = new ArcGISTileProviderManager(context, selectedMap);
TileOverlayOptions options = new TileOverlayOptions()
.tileProvider(tileProviderManager.getOnlineTileProvider())
.zIndex(ONLINE_TILE_PROVIDER_Z_INDEX);
onlineTileOverlay = map.addTileOverlay(options);
//Setup the offline tile overlay
if(offlineTileOverlay != null){
offlineTileOverlay.remove();
offlineTileOverlay = null;
}
if(prefManager.isOfflineMapLayerEnabled(context)){
options = new TileOverlayOptions()
.tileProvider(tileProviderManager.getOfflineTileProvider())
.zIndex(OFFLINE_TILE_PROVIDER_Z_INDEX);
offlineTileOverlay = map.addTileOverlay(options);
}
}
}
private void setupMapboxTileProvider(Context context, GoogleMap map){
Timber.d("Enabling mapbox tile provider.");
//Remove the default google map layer.
map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
final GoogleMapPrefFragment.PrefManager prefManager = GoogleMapPrefFragment.PrefManager;
final String mapboxId = prefManager.getMapboxId(context);
final String mapboxAccessToken = prefManager.getMapboxAccessToken(context);
final int maxZoomLevel = (int) map.getMaxZoomLevel();
if (!(tileProviderManager instanceof MapboxTileProviderManager)
|| !mapboxId.equals(((MapboxTileProviderManager) tileProviderManager).getMapboxId())
|| !mapboxAccessToken.equals(((MapboxTileProviderManager) tileProviderManager).getMapboxAccessToken())) {
//Setup the online tile overlay
if (onlineTileOverlay != null) {
onlineTileOverlay.remove();
onlineTileOverlay = null;
}
tileProviderManager = new MapboxTileProviderManager(context, mapboxId, mapboxAccessToken, maxZoomLevel);
TileOverlayOptions options = new TileOverlayOptions()
.tileProvider(tileProviderManager.getOnlineTileProvider())
.zIndex(ONLINE_TILE_PROVIDER_Z_INDEX);
onlineTileOverlay = map.addTileOverlay(options);
//Setup the offline tile overlay
if(offlineTileOverlay != null){
offlineTileOverlay.remove();
offlineTileOverlay = null;
}
if(prefManager.isOfflineMapLayerEnabled(context)){
options = new TileOverlayOptions()
.tileProvider(tileProviderManager.getOfflineTileProvider())
.zIndex(OFFLINE_TILE_PROVIDER_Z_INDEX);
offlineTileOverlay = map.addTileOverlay(options);
}
}
//Check if the mapbox credentials are valid.
new AsyncTask<Void, Void, Integer>(){
@Override
protected Integer doInBackground(Void... params) {
final Context context = getContext();
return MapboxUtils.fetchReferenceTileUrl(context, mapboxId, mapboxAccessToken);
}
@Override
protected void onPostExecute(Integer result){
if(result != null){
switch(result){
case HttpURLConnection.HTTP_UNAUTHORIZED:
case HttpURLConnection.HTTP_NOT_FOUND:
//Invalid mapbox credentials
Context context = getContext();
if (context != null) {
Toast.makeText(context, R.string.alert_invalid_mapbox_credentials, Toast.LENGTH_LONG).show();
}
break;
}
}
}
}.execute();
}
protected void clearMap() {
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
googleMap.clear();
setupMapOverlay(googleMap);
}
});
}
private LatLngBounds getBounds(List<LatLng> pointsList) {
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (LatLng point : pointsList) {
builder.include(point);
}
return builder.build();
}
public double getMapRotation() {
GoogleMap map = getMap();
if (map != null) {
return map.getCameraPosition().bearing;
} else {
return 0;
}
}
public VisibleMapArea getVisibleMapArea(){
final GoogleMap map = getMap();
if(map == null)
return null;
final VisibleRegion mapRegion = map.getProjection().getVisibleRegion();
return new VisibleMapArea(MapUtils.latLngToCoord(mapRegion.farLeft),
MapUtils.latLngToCoord(mapRegion.nearLeft),
MapUtils.latLngToCoord(mapRegion.nearRight),
MapUtils.latLngToCoord(mapRegion.farRight));
}
@Override
public void updateRealTimeFootprint(FootPrint footprint) {
List<LatLong> pathPoints = footprint == null
? Collections.<LatLong>emptyList()
: footprint.getVertexInGlobalFrame();
if (pathPoints.isEmpty()) {
if (footprintPoly != null) {
footprintPoly.remove();
footprintPoly = null;
}
} else {
if (footprintPoly == null) {
PolygonOptions pathOptions = new PolygonOptions()
.strokeColor(FOOTPRINT_DEFAULT_COLOR)
.strokeWidth(FOOTPRINT_DEFAULT_WIDTH)
.fillColor(FOOTPRINT_FILL_COLOR);
for (LatLong vertex : pathPoints) {
pathOptions.add(MapUtils.coordToLatLng(vertex));
}
footprintPoly = getMap().addPolygon(pathOptions);
} else {
List<LatLng> list = new ArrayList<LatLng>();
for (LatLong vertex : pathPoints) {
list.add(MapUtils.coordToLatLng(vertex));
}
footprintPoly.setPoints(list);
}
}
}
@Override
public void onGoogleApiConnectionError(ConnectionResult connectionResult) {
final Activity activity = getActivity();
if (activity == null)
return;
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult(activity, 0);
} catch (IntentSender.SendIntentException e) {
//There was an error with the resolution intent. Try again.
if (mGApiClientMgr != null)
mGApiClientMgr.start();
}
} else {
onUnavailableGooglePlayServices(connectionResult.getErrorCode());
}
}
@Override
public void onUnavailableGooglePlayServices(int i) {
final Activity activity = getActivity();
if (activity != null) {
GooglePlayServicesUtil.showErrorDialogFragment(i, getActivity(), 0, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
activity.finish();
}
});
}
}
@Override
public void onManagerStarted() {
}
@Override
public void onManagerStopped() {
}
private static class ProxyMapPolyline implements PolylineInfo.ProxyPolyline {
private final Polyline polyline;
private ProxyMapPolyline(Polyline polyline) {
this.polyline = polyline;
}
@Override
public void setPoints(@NotNull List<? extends LatLong> points) {
polyline.setPoints(MapUtils.coordToLatLng(points));
}
@Override
public void clickable(boolean clickable) {
polyline.setClickable(clickable);
}
@Override
public void color(int color) {
polyline.setColor(color);
}
@Override
public void geodesic(boolean geodesic) {
polyline.setGeodesic(geodesic);
}
@Override
public void visible(boolean visible) {
polyline.setVisible(visible);
}
@Override
public void width(float width) {
polyline.setWidth(width);
}
@Override
public void zIndex(float zIndex) {
polyline.setZIndex(zIndex);
}
@Override
public void remove() {
polyline.remove();
}
}
/**
* GoogleMap implementation of the ProxyMarker interface.
*/
private static class ProxyMapMarker implements MarkerInfo.ProxyMarker {
private final Marker marker;
ProxyMapMarker(Marker marker){
this.marker = marker;
}
@Override
public void setAlpha(float alpha) {
marker.setAlpha(alpha);
}
@Override
public void setAnchor(float anchorU, float anchorV) {
marker.setAnchor(anchorU, anchorV);
}
@Override
public void setDraggable(boolean draggable) {
marker.setDraggable(draggable);
}
@Override
public void setFlat(boolean flat) {
marker.setFlat(flat);
}
@Override
public void setIcon(Bitmap icon) {
if(icon != null) {
marker.setIcon(BitmapDescriptorFactory.fromBitmap(icon));
}
}
@Override
public void setInfoWindowAnchor(float anchorU, float anchorV) {
marker.setInfoWindowAnchor(anchorU, anchorV);
}
@Override
public void setPosition(LatLong coord) {
if(coord != null) {
marker.setPosition(MapUtils.coordToLatLng(coord));
}
}
@Override
public void setRotation(float rotation) {
marker.setRotation(rotation);
}
@Override
public void setSnippet(String snippet) {
marker.setSnippet(snippet);
}
@Override
public void setTitle(String title) {
marker.setTitle(title);
}
@Override
public void setVisible(boolean visible) {
marker.setVisible(visible);
}
@Override
public void removeMarker(){
marker.remove();
}
@Override
public boolean equals(Object other){
if(this == other)
return true;
if(!(other instanceof ProxyMapMarker))
return false;
return this.marker.equals(((ProxyMapMarker) other).marker);
}
@Override
public int hashCode(){
return this.marker.hashCode();
}
}
}