diff --git a/app/src/main/java/com/wytehat/btlogger/LiveRangeFinderActivity.java b/app/src/main/java/com/wytehat/btlogger/LiveRangeFinderActivity.java index 057aa89..989d3d6 100644 --- a/app/src/main/java/com/wytehat/btlogger/LiveRangeFinderActivity.java +++ b/app/src/main/java/com/wytehat/btlogger/LiveRangeFinderActivity.java @@ -59,6 +59,11 @@ public class LiveRangeFinderActivity extends Activity implements LocationListene private boolean searching, calibrating, mapReady; private long lastSignalTime, lastLoggedTime; private float heading; + /** Magnetic-to-true north correction for the current position. */ + private float declination; + /** Smoothed compass azimuth, kept in sin/cos form to survive the 0/360 wrap. */ + private float headingSin, headingCos; + private boolean headingPrimed; private long lastHeadingMapUpdate; private String headingSource = "Compass"; private double lastRangeDistance; @@ -429,8 +434,12 @@ public class LiveRangeFinderActivity extends Activity implements LocationListene public void onLocationChanged(Location location) { currentLocation = location; + declination = new android.hardware.GeomagneticField( + (float) location.getLatitude(), (float) location.getLongitude(), + (float) location.getAltitude(), System.currentTimeMillis()).getDeclination(); if (location.hasBearing() && location.hasSpeed() && location.getSpeed() > 1.0f) { - heading = location.getBearing(); headingSource = "GPS movement"; + heading = location.getBearing(); headingSource = "GPS course"; + headingPrimed = false; } if (searching) updateMap(location, heading, lastRangeState, lastRangeDistance, false); } @@ -444,18 +453,41 @@ public class LiveRangeFinderActivity extends Activity implements LocationListene float[] orientation = new float[3]; SensorManager.getRotationMatrixFromVector(rotation, event.values); SensorManager.getOrientation(rotation, orientation); - float compass = (float) Math.toDegrees(orientation[0]); - if (compass < 0) compass += 360; + float compass = (float) Math.toDegrees(orientation[0]) + declination; + while (compass < 0) compass += 360; + while (compass >= 360) compass -= 360; if (currentLocation == null || !currentLocation.hasSpeed() || currentLocation.getSpeed() <= 1) { - heading = compass; headingSource = "compass / rotation sensor"; + heading = smoothHeading(compass); + headingSource = "compass"; long now = System.currentTimeMillis(); - if (searching && mapReady && now - lastHeadingMapUpdate >= 100L) { + /* + * The arrow must follow the phone whether or not a search is + * running - it is the user's own facing, not a search result. + */ + if (mapReady && now - lastHeadingMapUpdate >= 100L) { lastHeadingMapUpdate = now; js(String.format(Locale.US, "updateHeading(%.1f)", heading)); headingText.setText(String.format(Locale.US, "Heading: %.0f° (%s)", heading, headingSource)); } } } + + /** + * Averages the azimuth as a unit vector so the 359 -> 0 wrap does not + * swing the arrow the long way round. + */ + private float smoothHeading(float degrees) { + double radians = Math.toRadians(degrees); + float sin = (float) Math.sin(radians), cos = (float) Math.cos(radians); + if (!headingPrimed) { headingSin = sin; headingCos = cos; headingPrimed = true; } + else { + headingSin += (sin - headingSin) * 0.25f; + headingCos += (cos - headingCos) * 0.25f; + } + float smoothed = (float) Math.toDegrees(Math.atan2(headingSin, headingCos)); + return smoothed < 0 ? smoothed + 360 : smoothed; + } + public void onAccuracyChanged(Sensor sensor, int accuracy) { } private void loadMap() { @@ -506,14 +538,20 @@ public class LiveRangeFinderActivity extends Activity implements LocationListene } } + /** + * With no solved estimate the only honest target is the item's last + * known GPS fix. Projecting a pin along the phone's own heading - which + * is what this used to do - just parks the pin wherever the user points, + * so the arrow always appears to aim at it and it is useless for walking. + */ private void showFallbackPin(Location phone, float direction, double distance) { - double projected = Math.max(1.5, Math.min(20.0, distance)); - double radians = Math.toRadians(direction); - double latitude = phone.getLatitude() + Math.cos(radians) * projected / 111319.49; - double cosine = Math.max(0.2, Math.cos(Math.toRadians(phone.getLatitude()))); - double longitude = phone.getLongitude() + Math.sin(radians) * projected / (111319.49 * cosine); - js(String.format(Locale.US, "updateEstimate(%.7f,%.7f,%.1f,%.1f,true)", - latitude, longitude, Math.max(1.5, distance), direction)); + if (target != null && target.hasLocation) { + js(String.format(Locale.US, "updateEstimate(%.7f,%.7f,%.1f,%.1f,true)", + target.latitude, target.longitude, + Math.max(1.5, target.accuracy), direction)); + } else { + js("clearTarget()"); + } } private String sector(float bearing) { @@ -544,13 +582,14 @@ public class LiveRangeFinderActivity extends Activity implements LocationListene "var userIcon=L.divIcon({className:'',html:'