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; } } }