私はGPSデータを使用して、毎秒値を取得し、地図上の現在位置を表示しています。問題は、時々(特に精度が低い場合)値が大きく変動し、現在の位置がマップ内の離れたポイント間で「ジャンプ」することです。
これを回避するのに十分な簡単な方法について疑問に思っていました。最初のアイデアとして、特定のしきい値を超える精度で値を破棄することを考えましたが、他にももっと良い方法があると思います。プログラムがこれを実行する通常の方法は何ですか?
探しているものは カルマンフィルター と呼ばれます。 スムーズなナビゲーションデータ によく使用されます。それは必ずしも些細なことではなく、多くのチューニングを行うことができますが、非常に標準的なアプローチであり、うまく機能します。 C++実装である KFilterライブラリ が利用可能です。
私の次のフォールバックは 最小二乗適合 です。カルマンフィルターは速度を考慮してデータを平滑化しますが、最小二乗法では位置情報のみを使用します。それでも、実装と理解は間違いなく簡単です。 GNU Scientific Libraryには これの実装 があるようです。
まさにこの状況に使用できる簡単なカルマンフィルターを次に示します。それはAndroidデバイスで行ったいくつかの仕事から来ました。
一般的なカルマンフィルター理論は、共分散行列で表される推定の精度を備えたベクトルの推定に関するものです。ただし、Androidデバイスの位置を推定する場合、一般的な理論は非常に単純なケースに帰着します。 Androidロケーションプロバイダーは、メートル単位で測定される単一の数値として指定される精度とともに、緯度と経度としてロケーションを提供します。これは、カルマンフィルターの位置が2つの数値で測定される場合でも、共分散行列の代わりに、カルマンフィルターの精度を単一の数値で測定できることを意味します。また、スケーリング係数をカルマンフィルターに入れてそれらをすべて同じ単位に変換すると、結果を変換するときにそれらのスケーリング係数がキャンセルされるため、緯度、経度、メートルが事実上すべて異なる単位であるという事実は無視できます元のユニットに戻ります。
現在の位置の最適な推定値が最後に認識された位置であると想定しているため、コードを改善できます。誰かが移動している場合は、Androidのセンサーを使用してより良い推定値を生成できるはずです。コードには、1秒あたりのメートルで表される単一の自由パラメーターQがあります。これは、新しい位置推定がない場合に精度がどれほど速く低下するかを示します。 Qパラメータが高いほど、精度が速く低下することを意味します。カルマンフィルターは一般に、精度が予想よりも少し速く低下する場合に良好に機能するため、Android電話で歩き回る場合、Q = 3メートル/秒がうまく機能することがわかります。それ。しかし、高速の車で旅行する場合は、明らかにもっと大きな数を使用する必要があります。
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 KalmanLatLong(float Q_metres_per_second) { this.Q_metres_per_second = Q_metres_per_second; variance = -1; }
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;
}
/// <summary>
/// Kalman filter processing for lattitude and longitude
/// </summary>
/// <param name="lat_measurement_degrees">new measurement of lattidude</param>
/// <param name="lng_measurement">new measurement of longitude</param>
/// <param name="accuracy">measurement of 1 standard deviation error in metres</param>
/// <param name="TimeStamp_milliseconds">time of measurement</param>
/// <returns>new state</returns>
public void Process(double lat_measurement, double lng_measurement, float accuracy, long TimeStamp_milliseconds) {
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;
}
}
}
これは少し遅れる可能性があります...
私はこれを書いた KalmanLocationManager Android用、2つの最も一般的なロケーションプロバイダー、ネットワークとGPSをラップし、データをkalmanフィルターし、LocationListener
(2つの「本物」のようにプロバイダー)。
私は主に、測定値間の「補間」に使用します-たとえば、100ミリ秒ごとに更新(位置予測)を受信します(1秒の最大gpsレートの代わりに)。これにより、位置をアニメーション化するときのフレームレートが向上します。
実際には、緯度、経度、高度の各次元で3つのカルマンフィルターを使用します。とにかく、彼らは独立しています。
これにより、行列計算がはるかに簡単になります。1つの6x6状態遷移行列を使用する代わりに、3つの異なる2x2行列を使用します。実際、コードでは、マトリックスをまったく使用していません。すべての方程式を解き、すべての値はプリミティブ(double)です。
ソースコードは機能しており、デモアクティビティがあります。いくつかの場所にjavadocがないのでごめんなさい、追いつきます。
時間ごとの位置の変化から速度を計算しないでください。 GPSの位置は不正確かもしれませんが、正確な速度(5km/h以上)があります。そのため、GPSロケーションスタンプからの速度を使用します。さらに、ほとんどの場合は機能しますが、もちろんこれを行うべきではありません。
出荷時のGPS位置はすでにカルマンフィルターされているため、おそらく改善できません。後処理では、通常、GPSチップのような同じ情報はありません。
スムーズにできますが、これによりエラーが発生します。
デバイスが静止しているときに位置を削除してください。これにより、ジャンプ位置が削除され、一部のデバイス/構成では削除されません。
私は通常、加速度計を使用します。短期間での突然の位置変化は、高い加速を意味します。これが加速度計のテレメトリに反映されない場合、位置の計算に使用される「ベスト3」衛星の変更が原因であることがほぼ確実です(これをGPSテレポートと呼びます)。
GPSのテレポートによりアセットが停止してホッピングしている場合、重心を段階的に計算すると、より大きなシェルセットと効果的に交差し、精度が向上します。
アセットが静止していないときにこれを行うには、速度、船首方位、直線加速度および回転加速度(ジャイロがある場合)の加速度データに基づいて、可能性のある次の位置と方向を推定する必要があります。これは、多かれ少なかれ有名なKフィルターの機能です。 GPSモジュール以外のすべてを含むAHRSと、1つを接続するためのジャックを備えたAHRSで、約150ドルでハードウェア全体を入手できます。独自のCPUとカルマンフィルターが搭載されています。結果は安定しており、非常に良好です。慣性誘導はジッターに対して非常に耐性がありますが、時間とともにドリフトします。 GPSはジッターを起こしやすいが、時間とともにドリフトすることはなく、実際にはお互いを補正するために作られた。
より少ない数学/理論を使用する1つの方法は、一度に2、5、7、または10個のデータポイントをサンプリングし、外れ値であるデータポイントを決定することです。カルマンフィルターよりも外れ値の精度が低いのは、次の algorithm を使用して、ポイント間のペアごとの距離をすべて取得し、他から最も遠い距離を除外することです。通常、これらの値は、置き換える範囲外の値に最も近い値に置き換えられます
例えば
5つのサンプルポイントA、B、C、D、Eでの平滑化
ATOTAL =距離の合計AB AC AD AE
BTOTAL =距離の合計AB BC BD BE
CTOTAL =距離の合計AC BC CD CE
DTOTAL =距離の合計DA DB DC DE
ETOTAL =距離の合計EA EB EC DE
BTOTALが最大の場合、BD = min {AB、BC、BD、BE}であればポイントBをDに置き換えます
この平滑化は外れ値を決定し、ポイントDの代わりにBDの中点を使用して位置線を平滑化することで増強できます。走行距離は異なる場合があり、数学的に厳密なソリューションが存在します。
最小二乗法については、他にもいくつか実験があります。
最小二乗近似であるからといって、線形である必要があるというわけではありません。二次曲線をデータに最小二乗適合させると、ユーザーが加速しているシナリオに適合します。 (最小二乗法では、従属変数として座標を使用し、独立変数として時間を使用することに注意してください。)
報告された精度に基づいて、データポイントの重み付けを試みることもできます。精度が低い場合、これらのデータポイントは低くなります。
精度が低い場合は、単一のポイントを表示するのではなく、報告する精度に基づいてユーザーが範囲を示すことができる円または何かを表示することもできます。 (これは、iPhoneに組み込まれているGoogleマップアプリケーションの機能です。)
カルマンフィルターに戻ると、GPSデータ用のカルマンフィルターのC実装が見つかりました。 http://github.com/lacker/ikalman まだ試していませんが、しかし、有望なようです。
スプラインを使用することもできます。持っている値を入力し、既知のポイント間のポイントを補間します。これを最小二乗近似、移動平均、またはカルマンフィルター(他の回答で述べた)とリンクすると、「既知の」ポイント間のポイントを計算することができます。
既知の値の間で値を補間できると、Niceのスムーズな移行と、忠実度が高い場合に存在するデータの/ reasonable /近似が得られます。 http://en.wikipedia.org/wiki/Spline_interpolation
スプラインごとに特性が異なります。私が最も一般的に使用しているのは、AkimaとCubicスプラインです。
考慮すべきもう1つのアルゴリズムは、Ramer-Douglas-Peucker行単純化アルゴリズムです。これは、GPSデータの単純化に非常に一般的に使用されています。 ( http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm )
誰かが興味を持っているならCoffeeScriptにマッピングされます。 **編集->バックボーンも使用して申し訳ありませんが、アイデアは得られます。
Attribsのビーコンを受け入れるようにわずかに変更
{緯度:item.lat、経度:item.lng、日付:新しい日付(item.effective_at)、精度:item.gps_accuracy}
MIN_ACCURACY = 1
# mapped from http://stackoverflow.com/questions/1134579/smooth-gps-data
class v.Map.BeaconFilter
constructor: ->
_.extend(this, Backbone.Events)
process: (decay,beacon) ->
accuracy = Math.max beacon.accuracy, MIN_ACCURACY
unless @variance?
# if variance nil, inititalise some values
@variance = accuracy * accuracy
@timestamp_ms = beacon.date.getTime();
@lat = beacon.latitude
@lng = beacon.longitude
else
@timestamp_ms = beacon.date.getTime() - @timestamp_ms
if @timestamp_ms > 0
# time has moved on, so the uncertainty in the current position increases
@variance += @timestamp_ms * decay * decay / 1000;
@timestamp_ms = beacon.date.getTime();
# 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
_k = @variance / (@variance + accuracy * accuracy)
@lat = _k * (beacon.latitude - @lat)
@lng = _k * (beacon.longitude - @lng)
@variance = (1 - _k) * @variance
[@lat,@lng]
Javaコードを@StochasticallyからKotlinに変換しました
class KalmanLatLong
{
private val MinAccuracy: Float = 1f
private var Q_metres_per_second: Float = 0f
private var TimeStamp_milliseconds: Long = 0
private var lat: Double = 0.toDouble()
private var lng: Double = 0.toDouble()
private var variance: Float =
0.toFloat() // P matrix. Negative means object uninitialised. NB: units irrelevant, as long as same units used throughout
fun KalmanLatLong(Q_metres_per_second: Float)
{
this.Q_metres_per_second = Q_metres_per_second
variance = -1f
}
fun get_TimeStamp(): Long { return TimeStamp_milliseconds }
fun get_lat(): Double { return lat }
fun get_lng(): Double { return lng }
fun get_accuracy(): Float { return Math.sqrt(variance.toDouble()).toFloat() }
fun SetState(lat: Double, lng: Double, accuracy: Float, TimeStamp_milliseconds: Long)
{
this.lat = lat
this.lng = lng
variance = accuracy * accuracy
this.TimeStamp_milliseconds = TimeStamp_milliseconds
}
/// <summary>
/// Kalman filter processing for lattitude and longitude
/// https://stackoverflow.com/questions/1134579/smooth-gps-data/15657798#15657798
/// </summary>
/// <param name="lat_measurement_degrees">new measurement of lattidude</param>
/// <param name="lng_measurement">new measurement of longitude</param>
/// <param name="accuracy">measurement of 1 standard deviation error in metres</param>
/// <param name="TimeStamp_milliseconds">time of measurement</param>
/// <returns>new state</returns>
fun Process(lat_measurement: Double, lng_measurement: Double, accuracy: Float, TimeStamp_milliseconds: Long)
{
var accuracy = accuracy
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
val 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.toFloat() * 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
val 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
}
}
}