using System.Collections;
using UnityEngine;
#if PLATFORM_ANDROID
using UnityEngine.Android;
#endif
namespace ARLocation
{
///
/// Abstract location provider. All concrete location provider implementations
/// should derive from this.
///
public abstract class AbstractLocationProvider : ILocationProvider
{
protected double LowPassFilterFactor;
///
/// The name of the location provider.
///
/// The name.
public abstract string Name { get; }
///
/// The options of the location provider.
///
/// The options.
public LocationProviderOptions Options { get; set; }
///
/// Gets or sets the current location.
///
/// The current location.
public LocationReading CurrentLocation { get; protected set; }
///
/// Gets or sets the previous location.
///
/// The last location.
public LocationReading LastLocation { get; protected set; }
public LocationReading LastLocationRaw { get; protected set; }
///
/// Gets or sets the previous raw location reading.
///
/// The raw location last.
public LocationReading CurrentLocationRaw { get; protected set; }
///
/// The current heading reading.
///
/// The current heading.
public HeadingReading CurrentHeading { get; protected set; }
///
/// The previous heading reading.
///
/// The last heading.
public HeadingReading LastHeading { get; protected set; }
///
/// The start point, i.e., the first measured location.
///
/// The start point.
public LocationReading FirstLocation { get; protected set; }
///
/// Gets or sets the current status of the location provider.
///
/// The status.
public LocationProviderStatus Status { get; protected set; }
///
/// If true, the location provider is enablied and getting regular location
/// updated from the device.
///
/// true if is enabled; otherwise, false.
public bool IsEnabled { get; protected set; }
///
/// If true, the first reading has not occured yet.
///
/// true if first reading; otherwise, false.
public bool FirstReading { get; protected set; }
///
/// If true, the provider has a functioning magnetic compass sensor.
///
/// true if is compass enabled; otherwise, false.
public abstract bool IsCompassEnabled { get; }
///
/// The start time of the location provider.
///
/// The start time.
public float StartTime { get; protected set; }
///
/// If true, location updates are paused.
///
public bool Paused { get; protected set; }
public int LocationUpdateCount { get; protected set; }
public bool HasStarted => Status == LocationProviderStatus.Started;
public bool ApplyCompassTiltCompensationOnAndroid { get; set; } = true;
public double DistanceFromStartPoint
{
get { return LocationReading.HorizontalDistance(FirstLocation, CurrentLocation); }
}
///
/// Event for when a new location data is received.
///
public event LocationUpdatedDelegate LocationUpdated;
///
/// Event for when a new compass data is received.
///
public event CompassUpdateDelegate CompassUpdated;
public event LocationEnabledDelegate LocationEnabled;
public event LocationFailedDelegate LocationFailed;
public event LocationUpdatedDelegate LocationUpdatedRaw;
///
/// Reads the location from the device; should be implemented by each
/// provider.
///
/// The location.
protected abstract LocationReading? ReadLocation();
///
/// Reads the heading from the device; should be implemented by each
/// provider.
///
/// The heading.
protected abstract HeadingReading? ReadHeading();
///
/// Requests the location and compass updates from the device; should be implemented by each
/// provider.
///
protected abstract void RequestLocationAndCompassUpdates();
///
/// Updates the location service status from the device; should be implemented by each
/// provider.
///
protected abstract void UpdateLocationRequestStatus();
protected AbstractLocationProvider()
{
IsEnabled = false;
FirstReading = true;
Paused = false;
Status = LocationProviderStatus.Idle;
}
public virtual IEnumerator Start(uint maxWaitTime = 10000, uint delay = 0)
{
// Debug.Log("[AbstractLocationProvider]: Starting...");
#if PLATFORM_ANDROID
if (!Permission.HasUserAuthorizedPermission(Permission.FineLocation))
{
Permission.RequestUserPermission(Permission.FineLocation);
}
yield return new WaitForSeconds(1);
#endif
if (delay > 0)
{
yield return new WaitForSeconds(delay);
}
RequestLocationAndCompassUpdates();
uint maxWait = maxWaitTime;
UpdateLocationRequestStatus();
while (Status == LocationProviderStatus.Initializing && maxWait > 0)
{
// Debug.Log("[AbstractLocationProvider]: Wait... " + maxWait);
yield return new WaitForSeconds(1);
maxWait--;
UpdateLocationRequestStatus();
}
if (maxWait < 1)
{
// Debug.LogError("[AbstractLocationProvider]: Timed out.");
LocationFailed?.Invoke("Timed out");
yield break;
}
if (Status == LocationProviderStatus.Failed)
{
// Debug.LogError("[AbstractLocationProvider]: Falied to initialize location updates.");
LocationFailed?.Invoke("Falied to initialize location updates.");
yield break;
}
if (Status != LocationProviderStatus.Started)
{
// Debug.LogError("[AbstractLocationProvider]: Unknown error initializing location updates. " + Status);
LocationFailed?.Invoke("Unknown error initializing location updates.");
yield break;
}
// Debug.Log("[AbstractLocationProvider]: Started!");
FirstReading = true;
StartTime = Time.time;
}
public void ForceLocationUpdate()
{
LocationUpdated?.Invoke(CurrentLocation, LastLocation);
LocationUpdatedRaw?.Invoke(CurrentLocationRaw, LastLocationRaw);
}
protected virtual void InnerOnEnabled()
{
}
protected void EmitLocationUpdated()
{
LocationUpdated?.Invoke(CurrentLocation, LastLocation);
}
protected void EmitLocationUpdatedRaw()
{
LocationUpdatedRaw?.Invoke(CurrentLocationRaw, LastLocationRaw);
}
protected void EmitCompassUpdated()
{
CompassUpdated?.Invoke(CurrentHeading, LastHeading);
}
protected void UpdateLocation(LocationReading newLocation)
{
if (newLocation.timestamp == CurrentLocationRaw.timestamp)
{
return;
}
LastLocationRaw = CurrentLocationRaw;
CurrentLocationRaw = newLocation;
EmitLocationUpdatedRaw();
if (!ShouldUpdateLocation(newLocation))
{
return;
}
LastLocation = CurrentLocation;
CurrentLocation = newLocation;
LocationUpdateCount++;
EmitLocationUpdated();
}
protected void UpdateHeading(HeadingReading newHeading)
{
if (!ShouldUpdateHeading(newHeading))
{
return;
}
LastHeading = CurrentHeading;
CurrentHeading = newHeading;
EmitCompassUpdated();
}
protected bool ShouldUpdateHeading(HeadingReading newHeading)
{
if (newHeading.timestamp == CurrentHeading.timestamp)
{
return false;
}
return true;
}
protected bool ShouldUpdateLocation(LocationReading newLocation)
{
if (Paused)
{
return false;
}
if (newLocation.timestamp - CurrentLocation.timestamp < ((long) (Options.TimeBetweenUpdates * 1000)))
{
return false;
}
if (LocationReading.HorizontalDistance(newLocation, CurrentLocation) < Options.MinDistanceBetweenUpdates)
{
return false;
}
if ((newLocation.accuracy > Options.AccuracyRadius) && (Options.AccuracyRadius > 0))
{
return false;
}
return true;
}
public virtual void Update()
{
if (!HasStarted)
{
return;
}
var location = ReadLocation();
var heading = ReadHeading();
if (location == null || heading == null)
{
// Debug.Log("[AbstractLocationProvider]: Null reading");
return;
}
if (FirstReading)
{
FirstLocation = location.Value;
CurrentLocation = FirstLocation;
CurrentLocationRaw = FirstLocation;
CurrentHeading = heading.Value;
IsEnabled = true;
FirstReading = false;
LocationEnabled?.Invoke();
InnerOnEnabled();
EmitCompassUpdated();
EmitLocationUpdated();
EmitLocationUpdatedRaw();
return;
}
UpdateLocation(location.Value);
UpdateHeading(heading.Value);
}
public void Restart()
{
LocationUpdateCount = 0;
FirstReading = true;
}
public void ResetStartPoint()
{
FirstLocation = CurrentLocation;
}
public void SetCompassLowPassFactor(double factor)
{
LowPassFilterFactor = factor;
}
public string GetStatusString()
{
switch (Status)
{
case LocationProviderStatus.Idle:
return "Idle";
case LocationProviderStatus.Failed:
return "Failed";
case LocationProviderStatus.Initializing:
return "Initializing";
case LocationProviderStatus.Started:
return "Started";
}
return "UnknownStatus";
}
public string GetInfoString()
{
return Name +
"{ \n" +
CurrentLocation + "\n" +
CurrentHeading + "\n" +
"Status = " + GetStatusString() + "\n" +
"DistanceFromStartPoint = " + DistanceFromStartPoint + "\n" +
"TimeSinceStart = " + (Time.time - StartTime) + "\n" +
"}";
}
public void OnEnabled(LocationEnabledDelegate del)
{
LocationEnabled += del;
if (IsEnabled)
{
del();
}
}
public void OnFail(LocationFailedDelegate del)
{
LocationFailed += del;
}
///
/// Pauses location updates
///
public void Pause()
{
Paused = true;
}
///
/// Resumes location updates
///
public void Resume()
{
Paused = false;
}
}
}