diff --git a/README.md b/README.md index a7e53a0..49a70c0 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ allprojects { ``` #### Step 2. -Add the ARCore-Location dependency. Replace `1.0.5` with the latest release from the [releases tab on Github](https://github.com/appoly/ARCore-Location/releases) +Add the ARCore-Location dependency. Replace `1.0.6` with the latest release from the [releases tab on Github](https://github.com/appoly/ARCore-Location/releases) ``` dependencies { - compile 'com.github.appoly:ARCore-Location:1.0.5' + compile 'com.github.appoly:ARCore-Location:1.0.6' } ``` diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java index 6aa6a1e..735d2bf 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationMarker.java @@ -25,8 +25,10 @@ public class LocationMarker { private LocationNodeRender renderEvent; private float scaleModifier = 1F; private float height = 0F; - private boolean scaleAtDistance = true; private int onlyRenderWhenWithin = Integer.MAX_VALUE; + private ScalingMode scalingMode = ScalingMode.FIXED_SIZE_ON_SCREEN; + private float gradualScalingMinScale = 0.8F; + private float gradualScalingMaxScale = 1.4F; public LocationMarker(double longitude, double latitude, Node node) { this.longitude = longitude; @@ -34,6 +36,22 @@ public LocationMarker(double longitude, double latitude, Node node) { this.node = node; } + public float getGradualScalingMinScale() { + return gradualScalingMinScale; + } + + public void setGradualScalingMinScale(float gradualScalingMinScale) { + this.gradualScalingMinScale = gradualScalingMinScale; + } + + public float getGradualScalingMaxScale() { + return gradualScalingMaxScale; + } + + public void setGradualScalingMaxScale(float gradualScalingMaxScale) { + this.gradualScalingMaxScale = gradualScalingMaxScale; + } + /** * Only render this marker when within [onlyRenderWhenWithin] metres * @@ -71,21 +89,21 @@ public void setHeight(float height) { } /** - * Whether the marker should stay at the same size on screen, regardless of distance. + * How the markers should scale * - * @return - true/false + * @return - ScalingMode */ - public boolean shouldScaleAtDistance() { - return scaleAtDistance; + public ScalingMode getScalingMode() { + return scalingMode; } /** - * Whether the marker should stay at the same size on screen, regardless of distance. + * Whether the marker should scale, regardless of distance. * - * @param scaleAtDistance - true/false + * @param scalingMode - ScalingMode.X */ - public void setScaleAtDistance(boolean scaleAtDistance) { - this.scaleAtDistance = scaleAtDistance; + public void setScalingMode(ScalingMode scalingMode) { + this.scalingMode = scalingMode; } /** @@ -106,7 +124,6 @@ public void setScaleModifier(float scaleModifier) { this.scaleModifier = scaleModifier; } - /** * Called on each frame * @@ -123,4 +140,10 @@ public void setRenderEvent(LocationNodeRender renderEvent) { this.renderEvent = renderEvent; } + public enum ScalingMode { + FIXED_SIZE_ON_SCREEN, + NO_SCALING, + GRADUAL_TO_MAX_RENDER_DISTANCE + } + } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java index 7172de2..3f184e6 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/LocationScene.java @@ -14,7 +14,9 @@ import java.util.ArrayList; import uk.co.appoly.arcorelocation.rendering.LocationNode; +import uk.co.appoly.arcorelocation.rendering.LocationNodeRender; import uk.co.appoly.arcorelocation.sensor.DeviceLocation; +import uk.co.appoly.arcorelocation.sensor.DeviceLocationChanged; import uk.co.appoly.arcorelocation.sensor.DeviceOrientation; import uk.co.appoly.arcorelocation.utils.LocationUtils; @@ -41,6 +43,8 @@ public class LocationScene { private int bearingAdjustment = 0; private String TAG = "LocationScene"; private boolean anchorsNeedRefresh = true; + private boolean minimalRefreshing = false; + private boolean refreshAnchorsAsLocationChanges = false; private Handler mHandler = new Handler(); Runnable anchorRefreshTask = new Runnable() { @Override @@ -50,7 +54,7 @@ public void run() { } }; private Session mSession; - + private DeviceLocationChanged locationChangedEvent; public LocationScene(Context mContext, Activity mActivity, ArSceneView mArSceneView) { Log.i(TAG, "Location Scene initiated."); this.mContext = mContext; @@ -65,6 +69,46 @@ public LocationScene(Context mContext, Activity mActivity, ArSceneView mArSceneV deviceOrientation.resume(); } + public boolean minimalRefreshing() { + return minimalRefreshing; + } + + public void setMinimalRefreshing(boolean minimalRefreshing) { + this.minimalRefreshing = minimalRefreshing; + } + + public boolean refreshAnchorsAsLocationChanges() { + return refreshAnchorsAsLocationChanges; + } + + public void setRefreshAnchorsAsLocationChanges(boolean refreshAnchorsAsLocationChanges) { + if (refreshAnchorsAsLocationChanges) { + stopCalculationTask(); + } else { + startCalculationTask(); + } + refreshAnchors(); + this.refreshAnchorsAsLocationChanges = refreshAnchorsAsLocationChanges; + } + + /** + * Get additional event to run as device location changes. + * Save creating extra sensor classes + * + * @return + */ + public DeviceLocationChanged getLocationChangedEvent() { + return locationChangedEvent; + } + + /** + * Set additional event to run as device location changes. + * Save creating extra sensor classes + */ + public void setLocationChangedEvent(DeviceLocationChanged locationChangedEvent) { + this.locationChangedEvent = locationChangedEvent; + } + public int getAnchorRefreshInterval() { return anchorRefreshInterval; } @@ -80,6 +124,18 @@ public void setAnchorRefreshInterval(int anchorRefreshInterval) { startCalculationTask(); } + public void clearMarkers() { + for(LocationMarker lm : mLocationMarkers) { + if(lm.anchorNode != null) { + lm.anchorNode.getAnchor().detach(); + lm.anchorNode.setEnabled(false); + lm.anchorNode = null; + } + + } + mLocationMarkers = new ArrayList<>(); + } + /** * The distance cap for distant markers. * ARCore doesn't like markers that are 2000km away :/ @@ -126,9 +182,10 @@ public void refreshAnchors() { private void refreshAnchorsIfRequired(Frame frame) { if (anchorsNeedRefresh) { + Log.i(TAG, "Refreshing anchors..."); anchorsNeedRefresh = false; - if(deviceLocation == null || deviceLocation.currentBestLocation == null) { + if (deviceLocation == null || deviceLocation.currentBestLocation == null) { Log.i(TAG, "Location not yet established."); return; } @@ -198,9 +255,12 @@ private void refreshAnchorsIfRequired(Frame frame) { // Current camera height float y = frame.getCamera().getDisplayOrientedPose().ty(); - if(mLocationMarkers.get(i).anchorNode != null && + if (mLocationMarkers.get(i).anchorNode != null && mLocationMarkers.get(i).anchorNode.getAnchor() != null) { mLocationMarkers.get(i).anchorNode.getAnchor().detach(); + mLocationMarkers.get(i).anchorNode.setAnchor(null); + mLocationMarkers.get(i).anchorNode.setEnabled(false); + mLocationMarkers.get(i).anchorNode = null; } // Don't immediately assign newly created anchor in-case of exceptions @@ -218,14 +278,20 @@ private void refreshAnchorsIfRequired(Frame frame) { } mLocationMarkers.get(i).anchorNode.setScaleModifier(mLocationMarkers.get(i).getScaleModifier()); - mLocationMarkers.get(i).anchorNode.setScaleAtDistance(mLocationMarkers.get(i).shouldScaleAtDistance()); + mLocationMarkers.get(i).anchorNode.setScalingMode(mLocationMarkers.get(i).getScalingMode()); + mLocationMarkers.get(i).anchorNode.setGradualScalingMaxScale(mLocationMarkers.get(i).getGradualScalingMaxScale()); + mLocationMarkers.get(i).anchorNode.setGradualScalingMinScale(mLocationMarkers.get(i).getGradualScalingMinScale()); mLocationMarkers.get(i).anchorNode.setHeight(mLocationMarkers.get(i).getHeight()); + if(minimalRefreshing) + mLocationMarkers.get(i).anchorNode.scaleAndRotate(); } catch (Exception e) { e.printStackTrace(); } } + + System.gc(); } } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java index 7c5e4d2..9790ae9 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/rendering/LocationNode.java @@ -20,9 +20,13 @@ public class LocationNode extends AnchorNode { private LocationMarker locationMarker; private LocationNodeRender renderEvent; private int distance; + private double distanceInAR; private float scaleModifier = 1F; private float height = 0F; - private boolean scaleAtDistance = true; + private float gradualScalingMinScale = 0.8F; + private float gradualScalingMaxScale = 1.4F; + + private LocationMarker.ScalingMode scalingMode = LocationMarker.ScalingMode.FIXED_SIZE_ON_SCREEN; private LocationScene locationScene; public LocationNode(Anchor anchor, LocationMarker locationMarker, LocationScene locationScene) { @@ -59,16 +63,24 @@ public int getDistance() { return distance; } + public double getDistanceInAR() { + return distanceInAR; + } + public void setDistance(int distance) { this.distance = distance; } - public boolean shouldScaleAtDistance() { - return scaleAtDistance; + public void setDistanceInAR(double distanceInAR) { + this.distanceInAR = distanceInAR; } - public void setScaleAtDistance(boolean scaleAtDistance) { - this.scaleAtDistance = scaleAtDistance; + public LocationMarker.ScalingMode getScalingMode() { + return scalingMode; + } + + public void setScalingMode(LocationMarker.ScalingMode scalingMode) { + this.scalingMode = scalingMode; } @Override @@ -85,6 +97,39 @@ public void onUpdate(FrameTime frameTime) { return; } + Vector3 cameraPosition = getScene().getCamera().getWorldPosition(); + Vector3 nodePosition = n.getWorldPosition(); + + // Compute the difference vector between the camera and anchor + float dx = cameraPosition.x - nodePosition.x; + float dy = cameraPosition.y - nodePosition.y; + float dz = cameraPosition.z - nodePosition.z; + + // Compute the straight-line distance. + setDistanceInAR(Math.sqrt(dx * dx + dy * dy + dz * dz)); + + + if (locationScene.shouldOffsetOverlapping()) { + if (locationScene.mArSceneView.getScene().overlapTestAll(n).size() > 0) { + setHeight(getHeight() + 1.2F); + } + } + } + + if(!locationScene.minimalRefreshing()) + scaleAndRotate(); + + + if (renderEvent != null) { + if(this.isTracking() && this.isActive() && this.isEnabled()) + renderEvent.render(this); + } + + } + + public void scaleAndRotate() { + + for (Node n : getChildren()) { int markerDistance = (int) Math.ceil( LocationUtils.distance( locationMarker.latitude, @@ -105,13 +150,24 @@ public void onUpdate(FrameTime frameTime) { float scale = 1F; - // Make sure marker stays the same size on screen, no matter the distance - if (shouldScaleAtDistance()) - scale = 0.5F * (float) renderDistance; + switch (scalingMode) { + + // Make sure marker stays the same size on screen, no matter the distance + case FIXED_SIZE_ON_SCREEN: + scale = 0.5F * (float) renderDistance; + + // Distant markers a little smaller + if (markerDistance > 3000) + scale *= 0.75F; + + break; + + case GRADUAL_TO_MAX_RENDER_DISTANCE: + float scaleDifference = gradualScalingMaxScale - gradualScalingMinScale; + scale = (gradualScalingMinScale + ((locationScene.getDistanceLimit() - markerDistance) * (scaleDifference / locationScene.getDistanceLimit()))) * renderDistance; + break; + } - // Distant markers a little smaller - if (markerDistance > 3000) - scale *= 0.75F; scale *= scaleModifier; @@ -125,17 +181,24 @@ public void onUpdate(FrameTime frameTime) { //locationMarker.node.setWorldScale(new Vector3(scale, scale, scale)); n.setWorldScale(new Vector3(scale, scale, scale)); - if (locationScene.shouldOffsetOverlapping()) { - if (locationScene.mArSceneView.getScene().overlapTestAll(n).size() > 0) { - setHeight(getHeight() + 1.2F); - } - } - if (renderEvent != null) { - renderEvent.render(this); - } } + } + + public float getGradualScalingMinScale() { + return gradualScalingMinScale; + } + + public void setGradualScalingMinScale(float gradualScalingMinScale) { + this.gradualScalingMinScale = gradualScalingMinScale; + } + + public float getGradualScalingMaxScale() { + return gradualScalingMaxScale; + } + public void setGradualScalingMaxScale(float gradualScalingMaxScale) { + this.gradualScalingMaxScale = gradualScalingMaxScale; } } diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java index 3449a28..06d2b22 100644 --- a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocation.java @@ -1,13 +1,20 @@ package uk.co.appoly.arcorelocation.sensor; import android.content.Context; +import android.location.Criteria; +import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; +import android.os.SystemClock; +import android.util.Log; import android.widget.Toast; +import java.util.ArrayList; + import uk.co.appoly.arcorelocation.LocationScene; +import uk.co.appoly.arcorelocation.utils.KalmanLatLong; /** * Created by John on 02/03/2018. @@ -15,112 +22,59 @@ public class DeviceLocation implements LocationListener { + private static final String TAG = DeviceLocation.class.getSimpleName(); private static final int TWO_MINUTES = 1000 * 60 * 2; public Location currentBestLocation; + private boolean isLocationManagerUpdatingLocation; + private ArrayList locationList; + private ArrayList oldLocationList; + private ArrayList noAccuracyLocationList; + private ArrayList inaccurateLocationList; + private ArrayList kalmanNGLocationList; + private float currentSpeed = 0.0f; // meters/second + private KalmanLatLong kalmanFilter; + private int gpsCount = 0; + private long runStartTimeInMillis; private LocationManager locationManager; private LocationScene locationScene; + private int minimumAccuracy = 25; public DeviceLocation(LocationScene locationScene) { this.locationScene = locationScene; - requestLocationUpdates(); - } - - private void requestLocationUpdates() { - try { - // Getting LocationManager object - locationManager = (LocationManager) locationScene.mContext.getSystemService(Context.LOCATION_SERVICE); - - boolean isGPSEnable = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); - boolean isNetworkEnable = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - Location location = null; - - if (isNetworkEnable) { - location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, this); - } else if (isGPSEnable) { - location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this); - } else { - Toast.makeText(locationScene.mContext, "All location providers are disabled.", Toast.LENGTH_SHORT).show(); - return; - } - if (location != null) - onLocationChanged(location); + isLocationManagerUpdatingLocation = false; + locationList = new ArrayList<>(); + noAccuracyLocationList = new ArrayList<>(); + oldLocationList = new ArrayList<>(); + inaccurateLocationList = new ArrayList<>(); + kalmanNGLocationList = new ArrayList<>(); + kalmanFilter = new KalmanLatLong(3); - } catch (SecurityException e) { - Toast.makeText(locationScene.mContext, "Enable location permissions from settings", Toast.LENGTH_SHORT).show(); - } + startUpdatingLocation(); } - /** - * Only replaces current location if this reading is - * more likely to be accurate - * - * @param location - * @return - */ - protected boolean isBetterLocation(Location location) { - if (currentBestLocation == null) { - // A new location is always better than no location - return true; - } - - // Check whether the new location fix is newer or older - long timeDelta = location.getTime() - currentBestLocation.getTime(); - boolean isSignificantlyNewer = timeDelta > TWO_MINUTES; - boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; - boolean isNewer = timeDelta > 0; - - // If it's been more than two minutes since the current location, use the new location - // because the user has likely moved - if (isSignificantlyNewer) { - return true; - // If the new location is more than two minutes older, it must be worse - } else if (isSignificantlyOlder) { - return false; - } - - // Check whether the new location fix is more or less accurate - int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); - boolean isLessAccurate = accuracyDelta > 0; - boolean isMoreAccurate = accuracyDelta < 0; - boolean isSignificantlyLessAccurate = accuracyDelta > 200; - - // Check if the old and new location are from the same provider - boolean isFromSameProvider = isSameProvider(location.getProvider(), - currentBestLocation.getProvider()); - - // Determine location quality using a combination of timeliness and accuracy - if (isMoreAccurate) { - return true; - } else if (isNewer && !isLessAccurate) { - return true; - } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { - return true; - } - return false; + public int getMinimumAccuracy() { + return minimumAccuracy; } - private boolean isSameProvider(String provider1, String provider2) { - if (provider1 == null) { - return provider2 == null; - } - return provider1.equals(provider2); + public void setMinimumAccuracy(int minimumAccuracy) { + this.minimumAccuracy = minimumAccuracy; } + @Override - public void onLocationChanged(Location location) { - if (isBetterLocation(location)) { - currentBestLocation = location; - requestLocationUpdates(); - } + public void onLocationChanged(final Location newLocation) { + Log.d(TAG, "(" + newLocation.getLatitude() + "," + newLocation.getLongitude() + ")"); + + gpsCount++; + + filterAndAddLocation(newLocation); } @Override public void onProviderDisabled(String provider) { - requestLocationUpdates(); + startUpdatingLocation(); } @Override @@ -137,4 +91,162 @@ public void onStatusChanged(String provider, int status, Bundle extras) { } + public void startUpdatingLocation() { + if (this.isLocationManagerUpdatingLocation == false) { + isLocationManagerUpdatingLocation = true; + runStartTimeInMillis = (long) (SystemClock.elapsedRealtimeNanos() / 1000000); + + + locationList.clear(); + + oldLocationList.clear(); + noAccuracyLocationList.clear(); + inaccurateLocationList.clear(); + kalmanNGLocationList.clear(); + + LocationManager locationManager = (LocationManager) locationScene.mContext.getSystemService(locationScene.mContext.LOCATION_SERVICE); + + //Exception thrown when GPS or Network provider were not available on the user's device. + try { + Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.ACCURACY_FINE); //setAccuracyは内部では、https://stackoverflow.com/a/17874592/1709287の用にHorizontalAccuracyの設定に変換されている。 + criteria.setPowerRequirement(Criteria.POWER_HIGH); + criteria.setAltitudeRequired(false); + criteria.setSpeedRequired(true); + criteria.setCostAllowed(true); + criteria.setBearingRequired(false); + + //API level 9 and up + criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH); + criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH); + //criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH); + //criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH); + + Integer gpsFreqInMillis = 5000; + Integer gpsFreqInDistance = 1; // in meters + + //locationManager.addGpsStatusListener(this); + + locationManager.requestLocationUpdates(gpsFreqInMillis, gpsFreqInDistance, criteria, this, null); + + /* Battery Consumption Measurement */ + gpsCount = 0; + + } catch (IllegalArgumentException e) { + Log.e(TAG, e.getLocalizedMessage()); + } catch (SecurityException e) { + Log.e(TAG, e.getLocalizedMessage()); + } catch (RuntimeException e) { + Log.e(TAG, e.getLocalizedMessage()); + } + } + } + + + private long getLocationAge(Location newLocation) { + long locationAge; + if (android.os.Build.VERSION.SDK_INT >= 17) { + long currentTimeInMilli = (long) (SystemClock.elapsedRealtimeNanos() / 1000000); + long locationTimeInMilli = (long) (newLocation.getElapsedRealtimeNanos() / 1000000); + locationAge = currentTimeInMilli - locationTimeInMilli; + } else { + locationAge = System.currentTimeMillis() - newLocation.getTime(); + } + return locationAge; + } + + + private boolean filterAndAddLocation(Location location) { + + if (currentBestLocation == null) { + currentBestLocation = location; + + locationEvents(); + } + + long age = getLocationAge(location); + + if (age > 5 * 1000) { //more than 5 seconds + Log.d(TAG, "Location is old"); + oldLocationList.add(location); + Toast.makeText(locationScene.mContext, "Rejected: old", Toast.LENGTH_SHORT).show(); + return false; + } + + if (location.getAccuracy() <= 0) { + Log.d(TAG, "Latitidue and longitude values are invalid."); + Toast.makeText(locationScene.mContext, "Rejected: invalid", Toast.LENGTH_SHORT).show(); + noAccuracyLocationList.add(location); + return false; + } + + //setAccuracy(newLocation.getAccuracy()); + float horizontalAccuracy = location.getAccuracy(); + if (horizontalAccuracy > getMinimumAccuracy()) { //10meter filter + Log.d(TAG, "Accuracy is too low."); + inaccurateLocationList.add(location); + Toast.makeText(locationScene.mContext, "Rejected: innacurate", Toast.LENGTH_SHORT).show(); + return false; + } + + + /* Kalman Filter */ + float Qvalue; + + long locationTimeInMillis = (long) (location.getElapsedRealtimeNanos() / 1000000); + long elapsedTimeInMillis = locationTimeInMillis - runStartTimeInMillis; + + if (currentSpeed == 0.0f) { + Qvalue = 3.0f; //3 meters per second + } else { + Qvalue = currentSpeed; // meters per second + } + + kalmanFilter.Process(location.getLatitude(), location.getLongitude(), location.getAccuracy(), elapsedTimeInMillis, Qvalue); + double predictedLat = kalmanFilter.get_lat(); + double predictedLng = kalmanFilter.get_lng(); + + Location predictedLocation = new Location("");//provider name is unecessary + predictedLocation.setLatitude(predictedLat);//your coords of course + predictedLocation.setLongitude(predictedLng); + float predictedDeltaInMeters = predictedLocation.distanceTo(location); + + if (predictedDeltaInMeters > 60) { + Log.d(TAG, "Kalman Filter detects mal GPS, we should probably remove this from track"); + kalmanFilter.consecutiveRejectCount += 1; + + if (kalmanFilter.consecutiveRejectCount > 3) { + kalmanFilter = new KalmanLatLong(3); //reset Kalman Filter if it rejects more than 3 times in raw. + } + + kalmanNGLocationList.add(location); + Toast.makeText(locationScene.mContext, "Rejected: kalman filter", Toast.LENGTH_SHORT).show(); + return false; + } else { + kalmanFilter.consecutiveRejectCount = 0; + } + + + Log.d(TAG, "Location quality is good enough."); + currentBestLocation = predictedLocation; + currentSpeed = location.getSpeed(); + locationList.add(location); + + locationEvents(); + + + return true; + } + + public void locationEvents() { + if (locationScene.getLocationChangedEvent() != null) { + locationScene.getLocationChangedEvent().onChange(currentBestLocation); + } + + if (locationScene.refreshAnchorsAsLocationChanges()) { + locationScene.refreshAnchors(); + } + } + + } \ No newline at end of file diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocationChanged.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocationChanged.java new file mode 100644 index 0000000..d4b905f --- /dev/null +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/sensor/DeviceLocationChanged.java @@ -0,0 +1,12 @@ +package uk.co.appoly.arcorelocation.sensor; + +import android.location.Location; + +/** + * Created by johnwedgbury on 01/06/2018. + */ + + +public interface DeviceLocationChanged { + void onChange(Location location); +} diff --git a/arcore-location/src/main/java/uk/co/appoly/arcorelocation/utils/KalmanLatLong.java b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/utils/KalmanLatLong.java new file mode 100644 index 0000000..d518b37 --- /dev/null +++ b/arcore-location/src/main/java/uk/co/appoly/arcorelocation/utils/KalmanLatLong.java @@ -0,0 +1,109 @@ +package uk.co.appoly.arcorelocation.utils; + +/** + * Created by johnwedgbury on 01/06/2018. + */ + +public class KalmanLatLong { + private final float MinAccuracy = 1; + + private float Q_metres_per_second; + private long TimeStamp_milliseconds; + private double lat; + private double lng; + private float variance; // P matrix. Negative means object uninitialised. + // NB: units irrelevant, as long as same units used + // throughout + public int consecutiveRejectCount; + + public KalmanLatLong(float Q_metres_per_second) { + this.Q_metres_per_second = Q_metres_per_second; + variance = -1; + consecutiveRejectCount = 0; + } + + public long get_TimeStamp() { + return TimeStamp_milliseconds; + } + + public double get_lat() { + return lat; + } + + public double get_lng() { + return lng; + } + + public float get_accuracy() { + return (float) Math.sqrt(variance); + } + + public void SetState(double lat, double lng, float accuracy, + long TimeStamp_milliseconds) { + this.lat = lat; + this.lng = lng; + variance = accuracy * accuracy; + this.TimeStamp_milliseconds = TimeStamp_milliseconds; + } + + // / + // / Kalman filter processing for lattitude and longitude + // / + // / new measurement of + // lattidude + // / new measurement of longitude + // / measurement of 1 standard deviation error in + // metres + // / time of measurement + // / new state + public void Process(double lat_measurement, double lng_measurement, + float accuracy, long TimeStamp_milliseconds, float Q_metres_per_second) { + this.Q_metres_per_second = Q_metres_per_second; + + if (accuracy < MinAccuracy) + accuracy = MinAccuracy; + if (variance < 0) { + // if variance < 0, object is unitialised, so initialise with + // current values + this.TimeStamp_milliseconds = TimeStamp_milliseconds; + lat = lat_measurement; + lng = lng_measurement; + variance = accuracy * accuracy; + } else { + // else apply Kalman filter methodology + + long TimeInc_milliseconds = TimeStamp_milliseconds + - this.TimeStamp_milliseconds; + if (TimeInc_milliseconds > 0) { + // time has moved on, so the uncertainty in the current position + // increases + variance += TimeInc_milliseconds * Q_metres_per_second + * Q_metres_per_second / 1000; + this.TimeStamp_milliseconds = TimeStamp_milliseconds; + // TO DO: USE VELOCITY INFORMATION HERE TO GET A BETTER ESTIMATE + // OF CURRENT POSITION + } + + // Kalman gain matrix K = Covarariance * Inverse(Covariance + + // MeasurementVariance) + // NB: because K is dimensionless, it doesn't matter that variance + // has different units to lat and lng + float K = variance / (variance + accuracy * accuracy); + // apply K + lat += K * (lat_measurement - lat); + lng += K * (lng_measurement - lng); + // new Covarariance matrix is (IdentityMatrix - K) * Covarariance + variance = (1 - K) * variance; + } + } + + public int getConsecutiveRejectCount() { + return consecutiveRejectCount; + } + + public void setConsecutiveRejectCount(int consecutiveRejectCount) { + this.consecutiveRejectCount = consecutiveRejectCount; + } + + +} \ No newline at end of file