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.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.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.Altitude; 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.PolylineInfo; import org.droidplanner.android.maps.providers.DPMapProvider; import org.droidplanner.android.maps.providers.google_map.tiles.mapbox.offline.MapDownloader; import org.droidplanner.android.proxy.mission.MissionProxy; import org.droidplanner.android.proxy.mission.item.MissionItemProxy; import org.droidplanner.android.proxy.mission.item.markers.MissionItemMarkerInfo; import org.droidplanner.android.utils.Utils; import org.droidplanner.android.utils.prefs.AutoPanMode; import org.droidplanner.android.utils.prefs.DroidPlannerPrefs; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; public abstract class DroneMap extends ApiListenerFragment { private static final String EXTRA_DRONE_FLIGHT_PATH = "extra_drone_flight_path"; 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_CONNECTED); eventFilter.addAction(AttributeEvent.STATE_DISCONNECTED); eventFilter.addAction(AttributeEvent.CAMERA_FOOTPRINTS_UPDATED); eventFilter.addAction(AttributeEvent.ATTITUDE_UPDATED); eventFilter.addAction(AttributeEvent.HOME_UPDATED); eventFilter.addAction(ACTION_UPDATE_MAP); } 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: guided.updateMarker(DroneMap.this); break; case AttributeEvent.HOME_UPDATED: home.updateMarker(DroneMap.this); break; case MissionProxy.ACTION_MISSION_PROXY_UPDATE: home.updateMarker(DroneMap.this); onMissionUpdate(); break; case AttributeEvent.GPS_POSITION: { graphicDrone.updateMarker(DroneMap.this); mMapFragment.updateDroneLeashPath(guided); updateFlightPath(); break; } case AttributeEvent.GUIDED_POINT_UPDATED: guided.updateMarker(DroneMap.this); mMapFragment.updateDroneLeashPath(guided); break; case AttributeEvent.HEARTBEAT_FIRST: case AttributeEvent.HEARTBEAT_RESTORED: case AttributeEvent.STATE_CONNECTED: graphicDrone.updateMarker(DroneMap.this); break; case AttributeEvent.STATE_DISCONNECTED: case AttributeEvent.HEARTBEAT_TIMEOUT: graphicDrone.updateMarker(DroneMap.this); break; case AttributeEvent.CAMERA_FOOTPRINTS_UPDATED: { if(mAppPrefs.isRealtimeFootprintsEnabled()) { 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; } } } }; protected final LinkedList<LatLongAlt> flightPathPoints = new LinkedList<>(); private final Map<MissionItemProxy, List<MarkerInfo>> missionMarkers = new HashMap<>(); private final LinkedList<MarkerInfo> externalMarkersToAdd = new LinkedList<>(); private final LinkedList<PolylineInfo> externalPolylinesToAdd = new LinkedList<>(); protected DPMap mMapFragment; protected DroidPlannerPrefs mAppPrefs; private GraphicHome home; private GraphicDrone graphicDrone; private GraphicGuided guided; protected MissionProxy missionProxy; protected Drone drone; protected Context context; protected abstract boolean isMissionDraggable(); public DPMap getMapFragment(){ return mMapFragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { final View view = inflater.inflate(R.layout.fragment_drone_map, viewGroup, false); updateMapFragment(); if(bundle != null){ flightPathPoints.clear(); List<LatLongAlt> flightPoints = (List<LatLongAlt>) bundle.getSerializable(EXTRA_DRONE_FLIGHT_PATH); if(flightPoints != null && !flightPoints.isEmpty()){ flightPathPoints.addAll(flightPoints); } } return view; } @Override public void onApiConnected() { if (mMapFragment != null) mMapFragment.clearAll(); drone = getDrone(); missionProxy = getMissionProxy(); home = new GraphicHome(drone, getContext()); mMapFragment.addMarker(home); graphicDrone = new GraphicDrone(drone); mMapFragment.addMarker(graphicDrone); guided = new GraphicGuided(drone); mMapFragment.addMarker(guided); for(LatLongAlt point : flightPathPoints) { mMapFragment.addFlightPathPoint(point); } onMissionUpdate(); getBroadcastManager().registerReceiver(eventReceiver, eventFilter); } private void updateFlightPath(){ if(showFlightPath()) { LatLongAlt currentFlightPoint = getCurrentFlightPoint(); if(currentFlightPoint != null){ mMapFragment.addFlightPathPoint(currentFlightPoint); flightPathPoints.add(currentFlightPoint); } } } protected LatLongAlt getCurrentFlightPoint(){ final Gps droneGps = drone.getAttribute(AttributeType.GPS); if (droneGps != null && droneGps.isValid()) { Altitude droneAltitude = drone.getAttribute(AttributeType.ALTITUDE); LatLongAlt point = new LatLongAlt(droneGps.getPosition(), droneAltitude.getAltitude()); return point; } return null; } protected final void onMissionUpdate(){ if (!shouldUpdateMission()) { return; } mMapFragment.updateMissionPath(missionProxy); mMapFragment.updatePolygonsPaths(missionProxy.getPolygonsPath()); //TODO: improve mission markers rendering performance List<MissionItemProxy> proxyMissionItems = missionProxy.getItems(); // Clear the previous proxy mission item markers. Map<MissionItemProxy, List<MarkerInfo>> newMissionMarkers = new HashMap<>(proxyMissionItems.size()); for(MissionItemProxy proxyItem : proxyMissionItems){ List<MarkerInfo> proxyMarkers = missionMarkers.remove(proxyItem); if(proxyMarkers == null){ proxyMarkers = MissionItemMarkerInfo.newInstance(proxyItem); if(!proxyMarkers.isEmpty()){ // Add the new markers to the map. mMapFragment.addMarkers(proxyMarkers, isMissionDraggable()); } } else { //Refresh the proxy markers for(MarkerInfo marker: proxyMarkers){ if (marker.isOnMap()) { marker.updateMarker(DroneMap.this); } else { mMapFragment.addMarker(marker); } } } newMissionMarkers.put(proxyItem, proxyMarkers); } // Remove the now invalid mission items for(List<MarkerInfo> invalidMarkers : missionMarkers.values()) { mMapFragment.removeMarkers(invalidMarkers); } missionMarkers.clear(); missionMarkers.putAll(newMissionMarkers); } protected boolean shouldUpdateMission() { return true; } public void downloadMapTiles(MapDownloader mapDownloader, int minimumZ, int maximumZ){ if(mMapFragment == null) return; mMapFragment.downloadMapTiles(mapDownloader, getVisibleMapArea(), minimumZ, maximumZ); } @Override public void onApiDisconnected() { getBroadcastManager().unregisterReceiver(eventReceiver); } private void updateMapFragment() { // Add the map fragment instance (based on user preference) final DPMapProvider mapProvider = mAppPrefs.getMapProvider(); 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.putBoolean(DPMap.EXTRA_SHOW_FLIGHT_PATH, showFlightPath()); mMapFragment = mapProvider.getMapFragment(); ((Fragment) mMapFragment).setArguments(mapArgs); fm.beginTransaction().replace(R.id.map_fragment_container, (Fragment) mMapFragment) .commit(); } if(!externalMarkersToAdd.isEmpty()){ for(MarkerInfo markerInfo = externalMarkersToAdd.poll(); markerInfo != null && !externalMarkersToAdd.isEmpty(); markerInfo = externalMarkersToAdd.poll()){ mMapFragment.addMarker(markerInfo); } } if (!externalPolylinesToAdd.isEmpty()) { for(PolylineInfo polylineInfo = externalPolylinesToAdd.poll(); polylineInfo != null && !externalPolylinesToAdd.isEmpty(); polylineInfo = externalPolylinesToAdd.poll()){ mMapFragment.addPolyline(polylineInfo); } } } @Override public void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); if(mMapFragment != null) { if(!flightPathPoints.isEmpty()){ outState.putSerializable(EXTRA_DRONE_FLIGHT_PATH, flightPathPoints); } } } @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 onAttach(Activity activity) { super.onAttach(activity); context = activity.getApplicationContext(); mAppPrefs = DroidPlannerPrefs.getInstance(context); } protected boolean showFlightPath(){ return false; } protected void clearFlightPath(){ if (mMapFragment != null) { mMapFragment.clearFlightPath(); } flightPathPoints.clear(); } /** * 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); } public void addMarker(MarkerInfo markerInfo){ if(markerInfo == null) return; if(mMapFragment != null) { mMapFragment.addMarker(markerInfo); } else{ externalMarkersToAdd.add(markerInfo); } } public void addPolyline(PolylineInfo polylineInfo) { if (polylineInfo == null) return; if(mMapFragment != null) { mMapFragment.addPolyline(polylineInfo); } else { externalPolylinesToAdd.add(polylineInfo); } } public void removeMarker(MarkerInfo markerInfo){ if(markerInfo == null) return; if(mMapFragment != null){ mMapFragment.removeMarker(markerInfo); } else{ externalMarkersToAdd.remove(markerInfo); } } public void removePolyline(PolylineInfo polylineInfo) { if(polylineInfo == null) return; if(mMapFragment != null){ mMapFragment.removePolyline(polylineInfo); } else{ externalPolylinesToAdd.remove(polylineInfo); } } public DPMap.VisibleMapArea getVisibleMapArea(){ return mMapFragment == null ? null : mMapFragment.getVisibleMapArea(); } }