package org.droidplanner.android.fragments;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.o3dr.android.client.Drone;
import com.o3dr.services.android.lib.coordinate.LatLong;
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.CameraProxy;
import com.o3dr.services.android.lib.drone.property.Gps;
import org.droidplanner.android.R;
import org.droidplanner.android.fragments.helpers.ApiListenerFragment;
import org.droidplanner.android.graphic.map.GraphicDrone;
import org.droidplanner.android.graphic.map.GraphicGuided;
import org.droidplanner.android.graphic.map.GraphicHome;
import org.droidplanner.android.maps.DPMap;
import org.droidplanner.android.maps.MarkerInfo;
import org.droidplanner.android.maps.providers.DPMapProvider;
import org.droidplanner.android.proxy.mission.MissionProxy;
import org.droidplanner.android.utils.Utils;
import org.droidplanner.android.utils.prefs.AutoPanMode;
import org.droidplanner.android.utils.prefs.DroidPlannerPrefs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
public abstract class DroneMap extends ApiListenerFragment {
private final static String TAG = DroneMap.class.getSimpleName();
public static final String ACTION_UPDATE_MAP = Utils.PACKAGE_NAME + ".action.UPDATE_MAP";
private static final IntentFilter eventFilter = new IntentFilter();
static {
eventFilter.addAction(MissionProxy.ACTION_MISSION_PROXY_UPDATE);
eventFilter.addAction(AttributeEvent.GPS_POSITION);
eventFilter.addAction(AttributeEvent.GUIDED_POINT_UPDATED);
eventFilter.addAction(AttributeEvent.HEARTBEAT_FIRST);
eventFilter.addAction(AttributeEvent.HEARTBEAT_RESTORED);
eventFilter.addAction(AttributeEvent.HEARTBEAT_TIMEOUT);
eventFilter.addAction(AttributeEvent.STATE_DISCONNECTED);
eventFilter.addAction(AttributeEvent.CAMERA_FOOTPRINTS_UPDATED);
eventFilter.addAction(AttributeEvent.ATTITUDE_UPDATED);
eventFilter.addAction(ACTION_UPDATE_MAP);
}
private static final List<MarkerInfo> NO_EXTERNAL_MARKERS = Collections.emptyList();
private final BroadcastReceiver eventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!isResumed())
return;
final String action = intent.getAction();
switch (action) {
case ACTION_UPDATE_MAP:
case MissionProxy.ACTION_MISSION_PROXY_UPDATE:
postUpdate();
break;
case AttributeEvent.GPS_POSITION: {
mMapFragment.updateMarker(graphicDrone);
mMapFragment.updateDroneLeashPath(guided);
final Gps droneGps = drone.getAttribute(AttributeType.GPS);
if (droneGps != null && droneGps.isValid()) {
mMapFragment.addFlightPathPoint(droneGps.getPosition());
}
break;
}
case AttributeEvent.GUIDED_POINT_UPDATED:
mMapFragment.updateMarker(guided);
mMapFragment.updateDroneLeashPath(guided);
break;
case AttributeEvent.HEARTBEAT_FIRST:
case AttributeEvent.HEARTBEAT_RESTORED:
mMapFragment.updateMarker(graphicDrone);
break;
case AttributeEvent.STATE_DISCONNECTED:
case AttributeEvent.HEARTBEAT_TIMEOUT:
mMapFragment.updateMarker(graphicDrone);
break;
case AttributeEvent.CAMERA_FOOTPRINTS_UPDATED: {
CameraProxy camera = drone.getAttribute(AttributeType.CAMERA);
if (camera != null && camera.getLastFootPrint() != null)
mMapFragment.addCameraFootprint(camera.getLastFootPrint());
break;
}
case AttributeEvent.ATTITUDE_UPDATED: {
if (mAppPrefs.isRealtimeFootprintsEnabled()) {
final Gps droneGps = drone.getAttribute(AttributeType.GPS);
if (droneGps.isValid()) {
CameraProxy camera = drone.getAttribute(AttributeType.CAMERA);
if (camera != null && camera.getCurrentFieldOfView() != null)
mMapFragment.updateRealTimeFootprint(camera.getCurrentFieldOfView());
}
}
else{
mMapFragment.updateRealTimeFootprint(null);
}
break;
}
}
}
};
private final Handler mHandler = new Handler();
private final Runnable mUpdateMap = new Runnable() {
@Override
public void run() {
if (getActivity() == null && mMapFragment == null)
return;
final List<MarkerInfo> missionMarkerInfos = missionProxy.getMarkersInfos();
final List<MarkerInfo> externalMarkers = collectMarkersFromProviders();
final boolean isThereMissionMarkers = !missionMarkerInfos.isEmpty();
final boolean isThereExternalMarkers = !externalMarkers.isEmpty();
final boolean isHomeValid = home.isValid();
final boolean isGuidedVisible = guided.isVisible();
// Get the list of markers currently on the map.
final Set<MarkerInfo> markersOnTheMap = mMapFragment.getMarkerInfoList();
if (!markersOnTheMap.isEmpty()) {
if (isHomeValid) {
markersOnTheMap.remove(home);
}
if (isGuidedVisible) {
markersOnTheMap.remove(guided);
}
if (isThereMissionMarkers) {
markersOnTheMap.removeAll(missionMarkerInfos);
}
if(isThereExternalMarkers)
markersOnTheMap.removeAll(externalMarkers);
mMapFragment.removeMarkers(markersOnTheMap);
}
if (isHomeValid) {
mMapFragment.updateMarker(home);
}
if (isGuidedVisible) {
mMapFragment.updateMarker(guided);
}
if (isThereMissionMarkers) {
mMapFragment.updateMarkers(missionMarkerInfos, isMissionDraggable());
}
if(isThereExternalMarkers)
mMapFragment.updateMarkers(externalMarkers, false);
mMapFragment.updateMissionPath(missionProxy);
mMapFragment.updatePolygonsPaths(missionProxy.getPolygonsPath());
mHandler.removeCallbacks(this);
}
};
private final ConcurrentLinkedQueue<MapMarkerProvider> markerProviders = new ConcurrentLinkedQueue<>();
protected DPMap mMapFragment;
protected DroidPlannerPrefs mAppPrefs;
private GraphicHome home;
public GraphicDrone graphicDrone;
public GraphicGuided guided;
protected MissionProxy missionProxy;
public Drone drone;
protected Context context;
protected abstract boolean isMissionDraggable();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
final View view = inflater.inflate(R.layout.fragment_drone_map, viewGroup, false);
mAppPrefs = new DroidPlannerPrefs(context);
updateMapFragment();
return view;
}
@Override
public void onDetach() {
super.onDetach();
mHandler.removeCallbacksAndMessages(null);
}
@Override
public void onApiConnected() {
if (mMapFragment != null)
mMapFragment.clearMarkers();
getBroadcastManager().registerReceiver(eventReceiver, eventFilter);
drone = getDrone();
missionProxy = getMissionProxy();
home = new GraphicHome(drone);
graphicDrone = new GraphicDrone(drone);
guided = new GraphicGuided(drone);
postUpdate();
}
@Override
public void onApiDisconnected() {
getBroadcastManager().unregisterReceiver(eventReceiver);
}
private void updateMapFragment() {
// Add the map fragment instance (based on user preference)
final DPMapProvider mapProvider = Utils.getMapProvider(context);
final FragmentManager fm = getChildFragmentManager();
mMapFragment = (DPMap) fm.findFragmentById(R.id.map_fragment_container);
if (mMapFragment == null || mMapFragment.getProvider() != mapProvider) {
final Bundle mapArgs = new Bundle();
mapArgs.putInt(DPMap.EXTRA_MAX_FLIGHT_PATH_SIZE, getMaxFlightPathSize());
mMapFragment = mapProvider.getMapFragment();
((Fragment) mMapFragment).setArguments(mapArgs);
fm.beginTransaction().replace(R.id.map_fragment_container, (Fragment) mMapFragment)
.commit();
}
}
@Override
public void onPause() {
super.onPause();
mMapFragment.saveCameraPosition();
}
@Override
public void onResume() {
super.onResume();
mMapFragment.loadCameraPosition();
}
@Override
public void onStart() {
super.onStart();
updateMapFragment();
}
@Override
public void onStop() {
super.onStop();
mHandler.removeCallbacksAndMessages(null);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
context = activity.getApplicationContext();
}
public final void postUpdate() {
mHandler.post(mUpdateMap);
}
protected int getMaxFlightPathSize() {
return 0;
}
/**
* Adds padding around the edges of the map.
*
* @param left
* the number of pixels of padding to be added on the left of the
* map.
* @param top
* the number of pixels of padding to be added on the top of the
* map.
* @param right
* the number of pixels of padding to be added on the right of
* the map.
* @param bottom
* the number of pixels of padding to be added on the bottom of
* the map.
*/
public void setMapPadding(int left, int top, int right, int bottom) {
mMapFragment.setMapPadding(left, top, right, bottom);
}
public void saveCameraPosition() {
mMapFragment.saveCameraPosition();
}
public List<LatLong> projectPathIntoMap(List<LatLong> path) {
return mMapFragment.projectPathIntoMap(path);
}
/**
* Set map panning mode on the specified target.
*
* @param target
*/
public abstract boolean setAutoPanMode(AutoPanMode target);
/**
* Move the map to the user location.
*/
public void goToMyLocation() {
mMapFragment.goToMyLocation();
}
/**
* Move the map to the drone location.
*/
public void goToDroneLocation() {
mMapFragment.goToDroneLocation();
}
/**
* Update the map rotation.
*
* @param bearing
*/
public void updateMapBearing(float bearing) {
mMapFragment.updateCameraBearing(bearing);
}
/**
* Ignore marker clicks on the map and instead report the event as a
* mapClick
*
* @param skip
* if it should skip further events
*/
public void skipMarkerClickEvents(boolean skip) {
mMapFragment.skipMarkerClickEvents(skip);
}
public void addMapMarkerProvider(MapMarkerProvider provider){
if(provider != null) {
markerProviders.add(provider);
postUpdate();
}
}
public void removeMapMarkerProvider(MapMarkerProvider provider){
if(provider != null) {
markerProviders.remove(provider);
postUpdate();
}
}
public interface MapMarkerProvider {
MarkerInfo[] getMapMarkers();
}
private List<MarkerInfo> collectMarkersFromProviders(){
if(markerProviders.isEmpty())
return NO_EXTERNAL_MARKERS;
List<MarkerInfo> markers = new ArrayList<>();
for(MapMarkerProvider provider : markerProviders){
MarkerInfo[] externalMarkers = provider.getMapMarkers();
Collections.addAll(markers, externalMarkers);
}
if(markers.isEmpty())
return NO_EXTERNAL_MARKERS;
return markers;
}
}