package org.droidplanner.services.android.impl.core.gcs.follow; import com.o3dr.services.android.lib.coordinate.LatLongAlt; import com.o3dr.services.android.lib.coordinate.LatLong; import org.droidplanner.services.android.impl.core.helpers.geoTools.GeoTools; import org.droidplanner.services.android.impl.core.gcs.location.Location; import timber.log.Timber; /** * Created by kellys on 2/24/16. */ public class LocationRelay { static final String TAG = LocationRelay.class.getSimpleName(); private static final float LOCATION_ACCURACY_THRESHOLD = 10.0f; private static final float JUMP_FACTOR = 4.0f; private static boolean VERBOSE = false; public static String getLatLongFromLocation(final android.location.Location location) { return android.location.Location.convert(location.getLatitude(), android.location.Location.FORMAT_DEGREES) + " " + android.location.Location.convert(location.getLongitude(), android.location.Location.FORMAT_DEGREES); } private android.location.Location mLastLocation; private float mTotalSpeed = 0; private int mSpeedReadings = 0; public void onFollowStart() { mTotalSpeed = 0; mSpeedReadings = 0; mLastLocation = null; } /** * Convert the specified Android location to a local Location, and track speed/accuracy */ public Location toGcsLocation(android.location.Location androidLocation) { if(androidLocation == null) return null; Location gcsLocation = null; if(VERBOSE) Timber.d("toGcsLocation(): followLoc=" + androidLocation); // If location has no bearing, set one based on its heading from the // previous location (or 0 if no previous location). if(!androidLocation.hasBearing()) { if(mLastLocation != null) { LatLong last = new LatLong(mLastLocation.getLatitude(), mLastLocation.getLongitude()); LatLong newLoc = new LatLong(androidLocation.getLatitude(), androidLocation.getLongitude()); androidLocation.setBearing((float)GeoTools.getHeadingFromCoordinates(last, newLoc)); } else { androidLocation.setBearing(0); } } boolean ok = (androidLocation.hasAccuracy() && androidLocation.hasBearing() && androidLocation.getTime() > 0); if(!ok) { Timber.w("toGcsLocation(): Location needs accuracy, bearing, and time."); } else { float distanceToLast = -1.0f; long timeSinceLast = -1L; final long androidLocationTime = androidLocation.getTime(); if (mLastLocation != null) { distanceToLast = androidLocation.distanceTo(mLastLocation); timeSinceLast = (androidLocationTime - mLastLocation.getTime()); } // mm/ms (does a better job calculating for locations that arrive at < 1-second intervals) final float currentSpeed = (distanceToLast > 0f && timeSinceLast > 0) ? ((distanceToLast * 1000) / timeSinceLast) : 0f; final boolean isAccurate = isLocationAccurate(androidLocation.getAccuracy(), currentSpeed); if(VERBOSE) { Timber.d( "toLocation(): distancetoLast=%.2f timeToLast=%d currSpeed=%.2f accurate=%s", distanceToLast, timeSinceLast, currentSpeed, isAccurate); } // Make a new location gcsLocation = new Location( new LatLongAlt( androidLocation.getLatitude(), androidLocation.getLongitude(), androidLocation.getAltitude() ), androidLocation.getBearing(), androidLocation.getSpeed(), isAccurate, androidLocation.getTime() ); mLastLocation = androidLocation; if(VERBOSE) Timber.d("External location lat/lng=" + getLatLongFromLocation(androidLocation)); } return gcsLocation; } private boolean isLocationAccurate(float accuracy, float currentSpeed) { if (accuracy >= LOCATION_ACCURACY_THRESHOLD) { Timber.w("isLocationAccurate() -- High/bad accuracy: " + accuracy); return false; } mTotalSpeed += currentSpeed; float avg = (mTotalSpeed / ++mSpeedReadings); // If moving: if (currentSpeed > 0) { // if average indicates some movement if (avg >= 1.0) { // Reject unreasonable updates. if (currentSpeed >= (avg * JUMP_FACTOR)) { Timber.w("isLocationAccurate() -- High current speed: " + currentSpeed); return false; } } } return true; } }