[TASK] Initial commit with basic product setup
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
namespace Mapbox.Unity.Location
|
||||
{
|
||||
|
||||
|
||||
using Mapbox.Utils;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Base class for implementing different smoothing strategies
|
||||
/// </summary>
|
||||
public abstract class AngleSmoothingAbstractBase : MonoBehaviour, IAngleSmoothing
|
||||
{
|
||||
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Number of measurements used for smoothing. Keep that number as low as feasible as collection of measurements depends on update time of location provider (minimum 500ms). eg 6 smoothes over the last 3 seconds.")]
|
||||
[Range(5, 20)]
|
||||
public int _measurements = 5;
|
||||
|
||||
|
||||
public AngleSmoothingAbstractBase()
|
||||
{
|
||||
_angles = new CircularBuffer<double>(_measurements);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal storage for latest 'n' values. Latest value at [0], <see cref="Mapbox.Utils.CircularBuffer{T}"/>
|
||||
/// </summary>
|
||||
protected CircularBuffer<double> _angles;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// For conversions from degrees to radians needed for Math functions.
|
||||
/// </summary>
|
||||
protected readonly double DEG2RAD = Math.PI / 180.0d;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// For conversions from radians to degrees.
|
||||
/// </summary>
|
||||
protected readonly double RAD2DEG = 180.0d / Math.PI;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add angle to list of angles used for calculation.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public void Add(double angle)
|
||||
{
|
||||
// safe measures to stay within [0..<360]
|
||||
angle = angle < 0 ? angle + 360 : angle >= 360 ? angle - 360 : angle;
|
||||
_angles.Add(angle);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculate smoothed angle from previously added angles.
|
||||
/// </summary>
|
||||
/// <returns>Smoothed angle</returns>
|
||||
public abstract double Calculate();
|
||||
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
protected void debugLogAngle(double raw, double smoothed)
|
||||
{
|
||||
double debugAngle = Math.Atan2(Math.Sin(smoothed * DEG2RAD), Math.Cos(smoothed * DEG2RAD)) * RAD2DEG;
|
||||
debugAngle = debugAngle < 0 ? debugAngle + 360 : debugAngle >= 360 ? debugAngle - 360 : debugAngle;
|
||||
Debug.Log(string.Format("{0:0.000} => {1:0.000}", raw, smoothed));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96b2e341a57b7f44a965174f6d88c67a
|
||||
timeCreated: 1527175159
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace Mapbox.Unity.Location
|
||||
{
|
||||
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Simple averaging latest 'n' values.
|
||||
/// </summary>
|
||||
public class AngleSmoothingAverage : AngleSmoothingAbstractBase
|
||||
{
|
||||
|
||||
|
||||
public override double Calculate()
|
||||
{
|
||||
|
||||
// calc mean heading taking into account that eg 355° and 5° should result in 0° and not 180°
|
||||
// refs:
|
||||
// https://en.wikipedia.org/wiki/Mean_of_circular_quantities
|
||||
// https://rosettacode.org/wiki/Averages/Mean_angle
|
||||
// https://rosettacode.org/wiki/Averages/Mean_angle#C.23
|
||||
double cos = _angles.Sum(a => Math.Cos(a * DEG2RAD)) / _angles.Count;
|
||||
double sin = _angles.Sum(a => Math.Sin(a * DEG2RAD)) / _angles.Count;
|
||||
|
||||
// round as we don't need super high precision
|
||||
double finalAngle = Math.Round(Math.Atan2(sin, cos) * RAD2DEG, 2);
|
||||
debugLogAngle(finalAngle, finalAngle);
|
||||
// stay within [0..<360]
|
||||
finalAngle = finalAngle < 0 ? finalAngle + 360 : finalAngle >= 360 ? finalAngle - 360 : finalAngle;
|
||||
debugLogAngle(finalAngle, finalAngle);
|
||||
|
||||
return finalAngle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6843ccbf70c208645a89b4c8c37a4ecf
|
||||
timeCreated: 1527174926
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
namespace Mapbox.Unity.Location
|
||||
{
|
||||
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>Smooths angles via a exponential moving average (EMA).</para>
|
||||
/// <para>https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average</para>
|
||||
/// <para>https://stackoverflow.com/questions/8450020/calculate-exponential-moving-average-on-a-queue-in-c-sharp</para>
|
||||
/// </summary>
|
||||
public class AngleSmoothingEMA : AngleSmoothingAbstractBase
|
||||
{
|
||||
|
||||
|
||||
public AngleSmoothingEMA() : base()
|
||||
{
|
||||
_alpha = 2.0d / (double)(_measurements + 1);
|
||||
}
|
||||
|
||||
|
||||
private double _alpha;
|
||||
|
||||
public override double Calculate()
|
||||
{
|
||||
// reverse order, _angles[0] is latest
|
||||
double[] angles = _angles.Reverse().ToArray();
|
||||
|
||||
// since we cannot work directly on the angles (eg think about 355 and 5)
|
||||
// we convert to cartesian coordinates and apply filtering there
|
||||
// aproximation should be good enough for the use case of compass filtering
|
||||
// differences occur only at the 2nd or 3rd digit after the decimal point
|
||||
|
||||
double sin = Math.Sin(angles[0] * DEG2RAD);
|
||||
double cos = Math.Cos(angles[0] * DEG2RAD);
|
||||
debugLogAngle(angles[0], Math.Atan2(sin, cos) * RAD2DEG);
|
||||
|
||||
for (int i = 1; i < angles.Length; i++)
|
||||
{
|
||||
sin = (Math.Sin(angles[i] * DEG2RAD) - sin) * _alpha + sin;
|
||||
cos = (Math.Cos(angles[i] * DEG2RAD) - cos) * _alpha + cos;
|
||||
debugLogAngle(angles[i], Math.Atan2(sin, cos) * RAD2DEG);
|
||||
}
|
||||
|
||||
// round, don't need crazy precision
|
||||
double finalAngle = Math.Round(Math.Atan2(sin, cos) * RAD2DEG, 2);
|
||||
debugLogAngle(finalAngle, finalAngle);
|
||||
// stay within [0..<360]
|
||||
finalAngle = finalAngle < 0 ? finalAngle + 360 : finalAngle >= 360 ? finalAngle - 360 : finalAngle;
|
||||
debugLogAngle(finalAngle, finalAngle);
|
||||
|
||||
return finalAngle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26ab886ba5422c248b693e60311088c7
|
||||
timeCreated: 1527174401
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
namespace Mapbox.Unity.Location
|
||||
{
|
||||
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Smoothing via low pass filter
|
||||
/// </summary>
|
||||
public class AngleSmoothingLowPass : AngleSmoothingAbstractBase
|
||||
{
|
||||
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Factor to change smoothing. The lower the factor the slower the angle changes. '1' would be no smoothing")]
|
||||
[Range(0.01f, 0.9f)]
|
||||
private double _smoothingFactor = 0.5;
|
||||
|
||||
|
||||
public AngleSmoothingLowPass() : base() { }
|
||||
|
||||
|
||||
public AngleSmoothingLowPass(double smoothingFactor) : base()
|
||||
{
|
||||
_smoothingFactor = smoothingFactor;
|
||||
}
|
||||
|
||||
|
||||
public override double Calculate()
|
||||
{
|
||||
// reverse order, latest in _angles is at [0]
|
||||
double[] angles = _angles.Reverse().ToArray();
|
||||
|
||||
// since we cannot work directly on the angles (eg think about 355 and 5)
|
||||
// we convert to cartesian coordinates and apply filtering there
|
||||
// aproximation should be good enough for the use case of compass filtering
|
||||
// differences occur only at the 2nd or 3rd digit after the decimal point
|
||||
|
||||
double lastSin = Math.Sin(angles[0] * DEG2RAD);
|
||||
double lastCos = Math.Cos(angles[0] * DEG2RAD);
|
||||
|
||||
debugLogAngle(angles[0], Math.Atan2(lastSin, lastCos) * RAD2DEG);
|
||||
|
||||
for (int i = 1; i < angles.Length; i++)
|
||||
{
|
||||
double angle = angles[i];
|
||||
lastSin = _smoothingFactor * Math.Sin(angle * DEG2RAD) + (1 - _smoothingFactor) * lastSin;
|
||||
lastCos = _smoothingFactor * Math.Cos(angle * DEG2RAD) + (1 - _smoothingFactor) * lastCos;
|
||||
debugLogAngle(angles[i], Math.Atan2(lastSin, lastCos) * RAD2DEG);
|
||||
}
|
||||
|
||||
// round, don't need crazy precision
|
||||
double finalAngle = Math.Round(Math.Atan2(lastSin, lastCos) * RAD2DEG, 2);
|
||||
debugLogAngle(finalAngle, finalAngle);
|
||||
// stay within [0..<360]
|
||||
finalAngle = finalAngle < 0 ? finalAngle + 360 : finalAngle >= 360 ? finalAngle - 360 : finalAngle;
|
||||
debugLogAngle(finalAngle, finalAngle);
|
||||
|
||||
return finalAngle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bc20e719eeb82d448d4698d29d526c0
|
||||
timeCreated: 1527174376
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Mapbox.Unity.Location
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Doesn't do any calculations. Just passes latest value through.
|
||||
/// </summary>
|
||||
public class AngleSmoothingNoOp : AngleSmoothingAbstractBase
|
||||
{
|
||||
|
||||
|
||||
public override double Calculate() { return _angles[0]; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ea49ff9cb53e9b44b5fa981d0009588
|
||||
timeCreated: 1527179489
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Mapbox.Unity.Location
|
||||
{
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public interface IAngleSmoothing
|
||||
{
|
||||
|
||||
void Add(double angle);
|
||||
double Calculate();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e44baa04db660e4a953a1c006235c94
|
||||
timeCreated: 1527174166
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user