From 65bc1beaae1a12b5ea277ecc2587c10295a759e9 Mon Sep 17 00:00:00 2001 From: ne0fhyk Date: Tue, 27 Jan 2015 21:39:16 -0800 Subject: [PATCH] completed implementation of the Guided scan follow mode. --- Android/build.gradle | 4 +- Android/res/layout/fragment_mode_follow.xml | 2 +- Android/res/values/strings.xml | 3 + .../android/activities/FlightActivity.java | 9 + .../android/fragments/DroneMap.java | 56 +++- .../fragments/mode/FlightModePanel.java | 1 - .../fragments/mode/ModeFollowFragment.java | 278 +++++++++++------- .../graphic/map/GuidedScanROIMarkerInfo.java | 48 +++ 8 files changed, 294 insertions(+), 107 deletions(-) create mode 100644 Android/src/org/droidplanner/android/graphic/map/GuidedScanROIMarkerInfo.java diff --git a/Android/build.gradle b/Android/build.gradle index 60679e4a51..95877fc6fb 100644 --- a/Android/build.gradle +++ b/Android/build.gradle @@ -11,7 +11,7 @@ dependencies { compile 'com.android.support:cardview-v7:21.0.2' compile 'com.android.support:recyclerview-v7:21.0.2' - compile 'com.o3dr:3dr-services-lib:2.2.2' + compile 'com.o3dr:3dr-services-lib:2.2.6' compile files('libs/droneapi-java-0.3-SNAPSHOT.jar') compile files('libs/j2xx.jar') @@ -28,7 +28,7 @@ android { applicationId 'org.droidplanner.android' minSdkVersion 14 targetSdkVersion 21 - versionCode 30013 + versionCode 30014 versionName getGitVersion() } diff --git a/Android/res/layout/fragment_mode_follow.xml b/Android/res/layout/fragment_mode_follow.xml index 90d6d83647..0a3a6a8909 100644 --- a/Android/res/layout/fragment_mode_follow.xml +++ b/Android/res/layout/fragment_mode_follow.xml @@ -14,7 +14,7 @@ style="@style/ModeDetailText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/mode_guided"/> + android:text="@string/mode_follow"/> Sport allows a pilot to control rate of rotation directly with the addition of self-leveling. Stabilize levels the vehicle in flight. PosHold allows position hold (GPS) with direct response to pilot input. + The vehicle follows the user based on the device GPS location. Waypoint @@ -526,5 +527,7 @@ Clear selected waypoints from the map? Clear selected waypoints select all + Targeting selected location. + Vehicle follows the user while remaining locked on a point of interest. Long click on the map to select the point of interest. diff --git a/Android/src/org/droidplanner/android/activities/FlightActivity.java b/Android/src/org/droidplanner/android/activities/FlightActivity.java index 0096427745..df000755e0 100644 --- a/Android/src/org/droidplanner/android/activities/FlightActivity.java +++ b/Android/src/org/droidplanner/android/activities/FlightActivity.java @@ -27,6 +27,7 @@ import com.sothree.slidinguppanel.SlidingUpPanelLayout; import org.droidplanner.android.R; +import org.droidplanner.android.fragments.DroneMap; import org.droidplanner.android.fragments.FlightActionsFragment; import org.droidplanner.android.fragments.FlightMapFragment; import org.droidplanner.android.fragments.TelemetryFragment; @@ -415,6 +416,14 @@ public void setGuidedClickListener(FlightMapFragment.OnGuidedClickListener liste mapFragment.setGuidedClickListener(listener); } + public void addMapMarkerProvider(DroneMap.MapMarkerProvider provider){ + mapFragment.addMapMarkerProvider(provider); + } + + public void removeMapMarkerProvider(DroneMap.MapMarkerProvider provider){ + mapFragment.removeMapMarkerProvider(provider); + } + @Override public void onStart() { super.onStart(); diff --git a/Android/src/org/droidplanner/android/fragments/DroneMap.java b/Android/src/org/droidplanner/android/fragments/DroneMap.java index c5f884efce..95bece9f3e 100644 --- a/Android/src/org/droidplanner/android/fragments/DroneMap.java +++ b/Android/src/org/droidplanner/android/fragments/DroneMap.java @@ -33,13 +33,18 @@ 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); @@ -51,9 +56,12 @@ public abstract class DroneMap extends ApiListenerFragment { eventFilter.addAction(AttributeEvent.STATE_DISCONNECTED); eventFilter.addAction(AttributeEvent.CAMERA_FOOTPRINTS_UPDATED); eventFilter.addAction(AttributeEvent.ATTITUDE_UPDATED); + eventFilter.addAction(ACTION_UPDATE_MAP); } - private final BroadcastReceiver eventReceiver = new BroadcastReceiver() { + private static final List NO_EXTERNAL_MARKERS = Collections.emptyList(); + + private final BroadcastReceiver eventReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (!isResumed()) @@ -61,9 +69,11 @@ public void onReceive(Context context, Intent intent) { 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); @@ -124,8 +134,10 @@ public void run() { return; final List missionMarkerInfos = missionProxy.getMarkersInfos(); + final List externalMarkers = collectMarkersFromProviders(); final boolean isThereMissionMarkers = !missionMarkerInfos.isEmpty(); + final boolean isThereExternalMarkers = !externalMarkers.isEmpty(); final boolean isHomeValid = home.isValid(); final boolean isGuidedVisible = guided.isVisible(); @@ -145,6 +157,9 @@ public void run() { markersOnTheMap.removeAll(missionMarkerInfos); } + if(isThereExternalMarkers) + markersOnTheMap.removeAll(externalMarkers); + mMapFragment.removeMarkers(markersOnTheMap); } @@ -160,6 +175,9 @@ public void run() { mMapFragment.updateMarkers(missionMarkerInfos, isMissionDraggable()); } + if(isThereExternalMarkers) + mMapFragment.updateMarkers(externalMarkers, false); + mMapFragment.updateMissionPath(missionProxy); mMapFragment.updatePolygonsPaths(missionProxy.getPolygonsPath()); @@ -168,6 +186,8 @@ public void run() { } }; + private final ConcurrentLinkedQueue markerProviders = new ConcurrentLinkedQueue<>(); + protected DPMap mMapFragment; protected DroidPlannerPrefs mAppPrefs; @@ -342,4 +362,38 @@ public void updateMapBearing(float bearing) { 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 collectMarkersFromProviders(){ + if(markerProviders.isEmpty()) + return NO_EXTERNAL_MARKERS; + + List markers = new ArrayList<>(); + for(MapMarkerProvider provider : markerProviders){ + MarkerInfo[] externalMarkers = provider.getMapMarkers(); + Collections.addAll(markers, externalMarkers); + } + + if(markers.isEmpty()) + return NO_EXTERNAL_MARKERS; + + return markers; + } } diff --git a/Android/src/org/droidplanner/android/fragments/mode/FlightModePanel.java b/Android/src/org/droidplanner/android/fragments/mode/FlightModePanel.java index 0cd746dc8a..975fcf8ba2 100644 --- a/Android/src/org/droidplanner/android/fragments/mode/FlightModePanel.java +++ b/Android/src/org/droidplanner/android/fragments/mode/FlightModePanel.java @@ -61,7 +61,6 @@ public void onApiDisconnected() { private void onModeUpdate(Drone drone) { // Update the info panel fragment - drone = getDrone(); final State droneState = drone.getAttribute(AttributeType.STATE); Fragment infoPanel; if (droneState == null || !droneState.isConnected()) { diff --git a/Android/src/org/droidplanner/android/fragments/mode/ModeFollowFragment.java b/Android/src/org/droidplanner/android/fragments/mode/ModeFollowFragment.java index 4da9df90c1..db320ce038 100644 --- a/Android/src/org/droidplanner/android/fragments/mode/ModeFollowFragment.java +++ b/Android/src/org/droidplanner/android/fragments/mode/ModeFollowFragment.java @@ -13,8 +13,10 @@ import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; import com.o3dr.android.client.Drone; +import com.o3dr.android.client.apis.gcs.FollowApi; 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; @@ -23,119 +25,171 @@ import org.beyene.sius.unit.length.LengthUnit; import org.droidplanner.android.R; +import org.droidplanner.android.fragments.DroneMap; +import org.droidplanner.android.graphic.map.GuidedScanROIMarkerInfo; +import org.droidplanner.android.maps.MarkerInfo; import org.droidplanner.android.utils.unit.providers.length.LengthUnitProvider; import org.droidplanner.android.widgets.spinnerWheel.CardWheelHorizontalView; import org.droidplanner.android.widgets.spinnerWheel.adapters.LengthWheelAdapter; -import org.droidplanner.android.widgets.spinnerWheel.adapters.NumericWheelAdapter; -public class ModeFollowFragment extends ModeGuidedFragment implements OnItemSelectedListener { +public class ModeFollowFragment extends ModeGuidedFragment implements OnItemSelectedListener, DroneMap.MapMarkerProvider { - private static final IntentFilter eventFilter = new IntentFilter(AttributeEvent.FOLLOW_UPDATE); + private static final int ROI_TARGET_MARKER_INDEX = 0; - private final BroadcastReceiver eventReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (AttributeEvent.FOLLOW_UPDATE.equals(action)) { - final FollowState followState = getDrone().getAttribute(AttributeType.FOLLOW_STATE); - if (followState != null) { - spinner.setSelection(adapter.getPosition(followState.getMode())); - } - } - } - }; + private static final IntentFilter eventFilter = new IntentFilter(AttributeEvent.FOLLOW_UPDATE); - private Spinner spinner; - private ArrayAdapter adapter; + private final BroadcastReceiver eventReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (AttributeEvent.FOLLOW_UPDATE.equals(action)) { + final FollowState followState = getDrone().getAttribute(AttributeType.FOLLOW_STATE); + if (followState != null) { + spinner.setSelection(adapter.getPosition(followState.getMode())); + } + } + } + }; + + private final MarkerInfo[] markers = new MarkerInfo[1]; - private CardWheelHorizontalView mRadiusWheel; + { + markers[ROI_TARGET_MARKER_INDEX] = new GuidedScanROIMarkerInfo(); + } + + private TextView modeDescription; + private Spinner spinner; + private ArrayAdapter adapter; - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_mode_follow, container, false); - } + private CardWheelHorizontalView mRadiusWheel; - @Override - public void onViewCreated(View parentView, Bundle savedInstanceState) { - super.onViewCreated(parentView, savedInstanceState); + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_mode_follow, container, false); + } - final Context context = getContext(); + @Override + public void onViewCreated(View parentView, Bundle savedInstanceState) { + super.onViewCreated(parentView, savedInstanceState); + + final Context context = getContext(); final LengthUnitProvider lengthUP = getLengthUnitProvider(); - final LengthWheelAdapter radiusAdapter = new LengthWheelAdapter(context, R.layout.wheel_text_centered, + final LengthWheelAdapter radiusAdapter = new LengthWheelAdapter(context, R.layout.wheel_text_centered, lengthUP.boxBaseValueToTarget(2), lengthUP.boxBaseValueToTarget(200)); - mRadiusWheel = (CardWheelHorizontalView) parentView.findViewById(R.id.radius_spinner); - mRadiusWheel.setViewAdapter(radiusAdapter); - mRadiusWheel.addScrollListener(this); - - spinner = (Spinner) parentView.findViewById(R.id.follow_type_spinner); - adapter = new FollowTypesAdapter(context, getAppPrefs().isAdvancedMenuEnabled()); - spinner.setAdapter(adapter); - spinner.setOnItemSelectedListener(this); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - if (mRadiusWheel != null) { - mRadiusWheel.removeChangingListener(this); - } - } - - @Override - public void onApiConnected() { - super.onApiConnected(); - updateCurrentRadius(); - getBroadcastManager().registerReceiver(eventReceiver, eventFilter); - } - - @Override - public void onApiDisconnected() { - super.onApiDisconnected(); - getBroadcastManager().unregisterReceiver(eventReceiver); - } - - @Override - public void onScrollingEnded(CardWheelHorizontalView cardWheel, LengthUnit oldValue, LengthUnit newValue) { - switch (cardWheel.getId()) { - case R.id.radius_spinner: - final Drone drone = getDrone(); - if (drone.isConnected()) - drone.setFollowMeRadius(newValue.toBase().getValue()); - break; - - default: - super.onScrollingEnded(cardWheel, oldValue, newValue); - break; - } - } - - private void updateCurrentRadius() { - final Drone drone = getDrone(); - if (mRadiusWheel != null && drone.isConnected()) { - final FollowState followState = getDrone().getAttribute(AttributeType.FOLLOW_STATE); - mRadiusWheel.setCurrentValue((getLengthUnitProvider().boxBaseValueToTarget(followState.getRadius()))); - } - } + modeDescription = (TextView) parentView.findViewById(R.id.ModeDetail); - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - final FollowType type = adapter.getItem(position); + mRadiusWheel = (CardWheelHorizontalView) parentView.findViewById(R.id.radius_spinner); + mRadiusWheel.setViewAdapter(radiusAdapter); + mRadiusWheel.addScrollListener(this); + + spinner = (Spinner) parentView.findViewById(R.id.follow_type_spinner); + adapter = new FollowTypesAdapter(context, getAppPrefs().isAdvancedMenuEnabled()); + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + if (mRadiusWheel != null) { + mRadiusWheel.removeChangingListener(this); + } + } - final Drone drone = getDrone(); - if (drone.isConnected()) { - drone.enableFollowMe(type); - } + @Override + public void onApiConnected() { + super.onApiConnected(); + + final FollowState followState = getDrone().getAttribute(AttributeType.FOLLOW_STATE); + if(followState != null){ + final FollowType followType = followState.getMode(); + spinner.setSelection(adapter.getPosition(followType)); + onFollowTypeUpdate(followType, followState.getParams()); + } + + parentActivity.addMapMarkerProvider(this); + getBroadcastManager().registerReceiver(eventReceiver, eventFilter); + } - if(type.hasRadius()) { + private void onFollowTypeUpdate(FollowType followType, Bundle params){ + if (followType.hasParam(FollowType.EXTRA_FOLLOW_RADIUS)) { showRadiusPicker(); updateCurrentRadius(); - } - else{ + } else { hideRadiusPicker(); } - } + + if(!followType.hasParam(FollowType.EXTRA_FOLLOW_ROI_TARGET)) + markers[ROI_TARGET_MARKER_INDEX].setPosition(null); + else if(params != null){ + params.setClassLoader(LatLong.class.getClassLoader()); + LatLong roiTarget = params.getParcelable(FollowType.EXTRA_FOLLOW_ROI_TARGET); + if(roiTarget != null){ + updateROITargetMarker(roiTarget); + } + } + } + + private void updateModeDescription(FollowType followType){ + switch(followType){ + case GUIDED_SCAN: + modeDescription.setText(R.string.mode_follow_guided_scan); + break; + + default: + modeDescription.setText(R.string.mode_follow); + break; + } + } + + @Override + public void onApiDisconnected() { + super.onApiDisconnected(); + parentActivity.removeMapMarkerProvider(this); + getBroadcastManager().unregisterReceiver(eventReceiver); + } + + @Override + public void onScrollingEnded(CardWheelHorizontalView cardWheel, LengthUnit oldValue, LengthUnit newValue) { + switch (cardWheel.getId()) { + case R.id.radius_spinner: + final Drone drone = getDrone(); + if (drone.isConnected()) { + Bundle params = new Bundle(); + params.putDouble(FollowType.EXTRA_FOLLOW_RADIUS, newValue.toBase().getValue()); + FollowApi.updateFollowParams(drone, params); + } + break; + + default: + super.onScrollingEnded(cardWheel, oldValue, newValue); + break; + } + } + + private void updateCurrentRadius() { + final Drone drone = getDrone(); + if (mRadiusWheel != null && drone.isConnected()) { + final FollowState followState = getDrone().getAttribute(AttributeType.FOLLOW_STATE); + Bundle params = followState.getParams(); + double radius = params.getDouble(FollowType.EXTRA_FOLLOW_RADIUS, 2); + mRadiusWheel.setCurrentValue((getLengthUnitProvider().boxBaseValueToTarget(radius))); + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + final FollowType type = adapter.getItem(position); + + final Drone drone = getDrone(); + if (drone.isConnected()) { + drone.enableFollowMe(type); + } + + onFollowTypeUpdate(type, null); + } private void hideRadiusPicker() { mRadiusWheel.setVisibility(View.GONE); @@ -146,12 +200,33 @@ private void showRadiusPicker() { } @Override - public void onNothingSelected(AdapterView arg0) { - } + public void onNothingSelected(AdapterView arg0) { + } + + @Override + public void onGuidedClick(LatLong coord) { + final Drone drone = getDrone(); + final FollowState followState = drone.getAttribute(AttributeType.FOLLOW_STATE); + if (followState != null && followState.isEnabled() && followState.getMode().hasParam(FollowType.EXTRA_FOLLOW_ROI_TARGET)) { + Toast.makeText(getContext(), R.string.guided_scan_roi_set_message, Toast.LENGTH_LONG).show(); + + Bundle params = new Bundle(); + params.putParcelable(FollowType.EXTRA_FOLLOW_ROI_TARGET, coord); + FollowApi.updateFollowParams(drone, params); + updateROITargetMarker(coord); + } else { + super.onGuidedClick(coord); + } + } + + private void updateROITargetMarker(LatLong target){ + markers[ROI_TARGET_MARKER_INDEX].setPosition(target); + getBroadcastManager().sendBroadcast(new Intent(DroneMap.ACTION_UPDATE_MAP)); + } @Override - public void onGuidedClick(LatLong coord){ - super.onGuidedClick(coord); + public MarkerInfo[] getMapMarkers() { + return markers; } private static class FollowTypesAdapter extends ArrayAdapter { @@ -164,22 +239,21 @@ public FollowTypesAdapter(Context context, boolean isAdvancedMenuEnabled) { } @Override - public View getView(int position, View convertView, ViewGroup parent){ + public View getView(int position, View convertView, ViewGroup parent) { TextView view; - if(convertView == null){ + if (convertView == null) { view = (TextView) inflater.inflate(R.layout.list_item_follow_types, parent, false); - } - else{ + } else { view = (TextView) convertView; } final FollowType followType = getItem(position); view.setText(followType.getTypeLabel()); return view; - } + } @Override - public View getDropDownView(int position, View convertView, ViewGroup parent){ + public View getDropDownView(int position, View convertView, ViewGroup parent) { return getView(position, convertView, parent); } } diff --git a/Android/src/org/droidplanner/android/graphic/map/GuidedScanROIMarkerInfo.java b/Android/src/org/droidplanner/android/graphic/map/GuidedScanROIMarkerInfo.java new file mode 100644 index 0000000000..fd7ae9f866 --- /dev/null +++ b/Android/src/org/droidplanner/android/graphic/map/GuidedScanROIMarkerInfo.java @@ -0,0 +1,48 @@ +package org.droidplanner.android.graphic.map; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import com.o3dr.services.android.lib.coordinate.LatLong; + +import org.droidplanner.android.R; +import org.droidplanner.android.maps.MarkerInfo; + +/** + * Created by Fredia Huya-Kouadio on 1/27/15. + */ +public class GuidedScanROIMarkerInfo extends MarkerInfo.SimpleMarkerInfo { + + private LatLong roiCoord; + + @Override + public void setPosition(LatLong coord){ + this.roiCoord = coord; + } + + @Override + public LatLong getPosition(){ + return roiCoord; + } + + @Override + public Bitmap getIcon(Resources res){ + return BitmapFactory.decodeResource(res, R.drawable.ic_roi); + } + + @Override + public boolean isVisible(){ + return roiCoord != null; + } + + @Override + public float getAnchorU() { + return 0.5f; + } + + @Override + public float getAnchorV() { + return 0.5f; + } +}