C、C++、Dなどのシステムレベルのプログラミング言語では、緯度と経度を格納するための最適なタイプ/エンコーディングは何ですか?
私が見るオプションは次のとおりです。
deg = (360/2^32)*val
簡単な解決策(FP)には、解像度が非常に不均一であるという大きな欠点があります(イギリスのどこかでミクロン単位で測定できますが、日本では測定できません)。また、これにはFP比較とその他)のすべての問題があります。他のオプションは、データのライフサイクルのさまざまな部分(生成、表示、計算など)で余分な労力が必要です。
興味深いオプションの1つは、Latitudeが増加するとビット数が増え、Longitudeが減少する(極に近づくにつれて)浮動精度型です。
これを十分にカバーしていない関連質問:
BTW:32ビットでは、赤道でのE/W解像度が約0.3インチになります。これは、高品質のGPSセットアップが機能するスケールに近いものです(IIRCは、一部のモードで約0.5インチに達することがあります)。
OTOH 32ビットが地球の表面に均一に分布している場合、一辺が約344mの正方形をインデックス化できます。5バイトで21m、6B-> 1.3m、8B-> 5mmになります。
現時点では特定の用途を念頭に置いているわけではありませんが、以前はこのようなことをしていたことがあり、ある時点で再び使用することを期待しています。
最も簡単な方法は、度単位でfloat/doubleとして保存することです。 NとEが正、SとWが負。分と秒が60から外れていることを思い出してください(31 45'Nは31.75)。値を見ればわかりやすくなり、必要に応じてラジアンに変換するのは簡単です。
大圏 2つの座標間の距離などの緯度と経度の計算は、通常は倍精度を使用する三角関数に大きく依存しています。他の形式は、少なくともサイン、コサイン、atan2、平方根の別の実装に依存します。任意の精度の数値(JavaのBigDecimalなど)はこれには機能しません。 2 ^ 32が均一に広がるintのようなものには、同様の問題があります。
均一性のポイントはいくつかのコメントで出てきました。これについては、経度に関して地球は均一ではないことに単純に注意します。北極圏の1アーク秒の経度は、赤道よりも短い距離です。倍精度の浮動小数点数は、地球上のどこでもミリメートル以下の精度を提供します。これでは不十分ですか?そうでない場合は、なぜですか?
また、必要な計算の種類が使用するストレージ形式に影響を与えるため、その情報で何をしたいのかに注意する価値があります。
経度と緯度は、32ビットの浮動小数点数よりも高い精度で一般に知られていません。したがって、ストレージスペースが心配な場合は、フロートを使用できます。ただし、一般的には、数値を倍精度として扱う方が便利です。
ラジアンは、理論計算に便利です。 (たとえば、ラジアンを使用する場合にのみ、サインの微分はコサインになります。)しかし、一般に度数はよりわかりやすく解釈しやすいので、度数に固執することをお勧めします。
Decimal Degrees に関するこのウィキペディアの記事によると、精度が8の10進表現は十分すぎるはずです。
0 decimal places, 1.0 = 111 km
...
7 decimal places, 0.0000001 = 1.11 cm
8 decimal places, 0.00000001 = 1.11 mm
浮動小数点値で言及した問題が問題になる可能性がありますか?答えが「いいえ」の場合、ラジアン値を倍精度で使用することをお勧めします。とにかく三角関数計算を行う場合は必要になります。
Doubleの使用時に精度が低下する問題がある場合、または三角法を使用しない場合は、整数範囲へのマッピングのソリューションをお勧めします-これにより、最高の解像度が得られ、どんな表示形式にも簡単に変換できますあなたが使用しているロケールは、適切な0子午線を選択した後、高精度の浮動小数点値に変換するために使用できます。
PS:地心球座標を使用する人がいないのはなぜだろうといつも思っていました-地理座標にかなり近いはずであり、回転楕円体のこのようなすべての派手な計算は必要ありません計算;楽しみのために、Gauss-Krüger-Koordinaten(ドイツのKatasteramtで使用されている)をGPS座標に変換したかったのです。マッピング自体は非常にクレイジーです...
どのエンコーディングが「最良」であるかは、実際に目標/要件によって異なります。
算術、浮動小数点の緯度、経度を実行している場合、非常に便利です。その他の場合、デカルト座標(x、y、z)がより便利です。たとえば、地球の表面上の点だけを気にする場合、 n-vector を使用できます。
長期保存に関しては、IEEE浮動小数点は、気にする必要のない範囲(緯度/経度)またはデカルト座標の場合は気にしない精度のためにビットを浪費します(Originで非常に良い精度が必要な場合を除きます)どんな理由であれ)。もちろん、いずれかのタイプの座標を好みのサイズのintにマッピングして、そのintの範囲全体が、関心のある解像度で関心のある範囲をカバーするようにすることができます。
もちろん、エンコーディングのビットを無駄にしないだけでなく、他に考えるべきことがあります。たとえば、(Geohashes)[https://en.wikipedia.org/wiki/Geohash]には、同じエリア内の他のジオハッシュを簡単に見つけることができるNiceプロパティがあります。 (ほとんどが同じ接頭辞を持ち、他の接頭辞は計算できます。)残念ながら、赤道付近の経度の精度は極付近と同じ精度を維持します。現在、ストレージに64ビットのジオハッシュを使用しています。これにより、赤道で約3 mの解像度が得られます。
Maidenhead Locator System にはいくつかの類似した特性がありますが、コンピューターに保存するのではなく、人間間の場所の通信に最適化されているようです。 (MLS文字列を格納すると、かなり些細なエラー検出のために多くのビットが無駄になります。)
極を異なる方法で処理することがわかった1つのシステムは、 軍事グリッド参照システム ですが、それも人間のコミュニケーション指向です。 (そして、lat/lonに変換したり、lat/lonに変換したりするのは苦痛のようです。
正確に必要なものに応じて、 に似たものを使用することができます(== --- ==)極近くのユニバーサル極座標座標系 と [〜#〜] utm [〜#〜] 世界の他の地域では、最大1ビットを使用して、2つのシステムのどちらを使用しているかを示します。気にするポイントのほとんどが極に近いとは考えにくいからです。たとえば、11は極座標系の使用を示し、00、01、および10は他のシステムの使用を示し、表現の一部であると言うことで、「半分のビット」を使用できます。
申し訳ありませんが、これは少し長いですが、最近学んだことを保存したかったのです。悲しいことに、地球上の点を均一な精度で表現するための標準的で正気で効率的な方法は見つかりませんでした。
編集:極に近い経度に必要な低い精度をより直接的に活用するため、あなたが望むものにもっと似ている別のアプローチを見つけました。法線ベクトルの保存に関する多くの研究があることがわかります。 最適化された球面座標を使用した法線ベクトルのエンコード は、最小レベルの精度を維持しながら法線ベクトルをエンコードするこのようなシステムについて説明しますが、地理座標。
A Java lat/long値をFloat/Doubleにキャストすることによるメートル単位の最大丸め誤差を計算するプログラム:
import Java.util.*;
import Java.lang.*;
import com.javadocmd.simplelatlng.*;
import com.javadocmd.simplelatlng.util.*;
public class MaxError {
public static void main(String[] args) {
Float flng = 180f;
Float flat = 0f;
LatLng fpos = new LatLng(flat, flng);
double flatprime = Float.intBitsToFloat(Float.floatToIntBits(flat) ^ 1);
double flngprime = Float.intBitsToFloat(Float.floatToIntBits(flng) ^ 1);
LatLng fposprime = new LatLng(flatprime, flngprime);
double fdistanceM = LatLngTool.distance(fpos, fposprime, LengthUnit.METER);
System.out.println("Float max error (meters): " + fdistanceM);
Double dlng = 180d;
Double dlat = 0d;
LatLng dpos = new LatLng(dlat, dlng);
double dlatprime = Double.longBitsToDouble(Double.doubleToLongBits(dlat) ^ 1);
double dlngprime = Double.longBitsToDouble(Double.doubleToLongBits(dlng) ^ 1);
LatLng dposprime = new LatLng(dlatprime, dlngprime);
double ddistanceM = LatLngTool.distance(dpos, dposprime, LengthUnit.METER);
System.out.println("Double max error (meters): " + ddistanceM);
}
}
出力:
Float max error (meters): 1.7791213425235692
Double max error (meters): 0.11119508289500799
0.3インチの解像度は、数年にわたる地震が違いを生むレベルまで低下しています。世界中でこのような精緻な解決策が必要だと思う理由を再考することもできます。
太平洋のいくつかの拡散センターは、 15 cm /年 程度変化します。
http://www.esri.com/news/arcuser/0400/wdside.html
赤道では、経度の1秒は緯度の1秒にほぼ等しく、これは海里の60分の1(または101.27フィートまたは30.87メートル)です。
32ビットの浮動小数点には、23ビットの明示的なデータが含まれます。
180 * 3600にはlog2(648000)= 19.305634287546711769425914064259ビットのデータが必要です。符号ビットは個別に保存されるため、180度の量だけで済むことに注意してください。
23からlog2(648000)のビットを減算した後、サブ秒データ用に余分な3.694365712453288230574085935741ビットが残っています。
それは2 ^ 3.694365712453288230574085935741 = 12.945382716049382716049382716053パーツ/秒です。
したがって、floatデータ型は、赤道で30.87/12.945382716049382716049382716053〜= 2.38メートルの精度を持つことができます。
いい質問です!
私はこの質問が今9歳であることを知っています、そしてあなたが求めていた答えの一部しか知りませんが、私はちょうど同じような質問を持ってここに来ました、そしてハードウェアや利用可能なGPSなど、その質問が尋ねられてから多くのものが変わりました。さまざまな種類のアプリケーションでさまざまな種類のGPSを扱うファームウェアでこのテーマを頻繁に使用し、作業したさまざまなアプリケーションの「最適な設計」の作成に費やした時間(および日数)を失いました。発展した。
いつものように、さまざまなソリューションがメリットとコストを提供し、最終的に、「最適な設計」は常にシステム要件に対するメリットとコストの「最適な」ものになります。同じ質問をするときに考慮しなければならないことがいくつかあります。
CPU時間コスト
CPUに浮動小数点コプロセッサーが組み込まれていない場合(多くのマイクロコントローラーの場合のように)、「float」、「double」、および「long double」の処理は非常にコストがかかります。たとえば、私が定期的に使用している1つの16ビットマイクロコントローラーでは、「double」値を使用した乗算は326 CPUクロックサイクルかかり、除算は1193クロックサイクルかかります。とても高い!
精度のトレードオフ
赤道で、「float」(IEEE-754 32ビット浮動小数点値)、符号付き次数値を表す必要があり、7つの「クリーンな」有効な10進数が表現可能であると仮定し、1つの最下位10進数の変更(179.9999から180.0000など)は、約11.12メートルの距離を表します。これは、ハードシステムの精度要件を満たす場合と満たさない場合があります。一方、「ダブル」(15の「クリーンな」有効な10進数字で表されるため、179.999999999999から180.000000000000への変更)は約0.00011 mmを表します。
入力精度の制限
GPSからの入力を処理する場合、実際の精度の桁数は何桁になりますか。また、何桁を保持する必要がありますか?
開発時間コスト
IEEE-754 64ビットの倍精度値(「double」)と32ビットの単精度値(「float」)は、両方の数学ライブラリーが事実上すべてのCコンパイラーに付属しているため、C言語での処理に非常に便利です。 、通常は非常に信頼性があります。 CPUにハードウェア浮動小数点プロセッサが搭載されている場合、これは簡単な選択です。
RAMとストレージのコスト
これらの値の多くをRAM(またはMYSQLなどのストレージ)に保持する必要がある場合、使用可能なRAM(およびストレージスペース)がソリューションの実行性に影響を与える可能性があります。
利用可能なデータと必要なデータ
この記事で扱っている1つの例(この質問にここに来た理由)は、バイナリGPS情報を提供できるu-blox M8 GPSを扱っていることです(翻訳のCPUオーバーヘッドを節約しますASCII NMEA文)。このバイナリ形式(「UBXプロトコル」と呼ばれる)では、緯度と経度は符号付き32ビット整数として表され、その表現は(赤道で)最小約1.11 cmの精度を表すことができます。たとえば、-105.0269805度の経度は-1050269805(32ビットすべてを使用)として表され、1 LSbの変化はどこでも緯度で約1.11 cmの変化を表し、赤道で1.11 cmの経度を表します(余弦に比例して高緯度ではそれ以下)緯度の)。このGPSが含まれるアプリケーションは、ナビゲーションタスクを実行します(既に存在し、十分にテストされたコード)。「double」データ型が必要です。残念ながら、この整数をIEEE-754 64ビット「ダブル」に変換することは、整数のベース2ビットを「ダブル」の内部表現ビットに移動するだけでは簡単に実行できません。 10進数の10進シフト。代わりに、基数2の10進シフトであった場合、整数の基数2ビットは、変換をほとんど必要とせずに「double」のビットフィールドに移動できます。しかし、悲しいかな、これは私が持っている符号付き整数の場合ではありません。そのため、ハードウェア浮動小数点プロセッサを搭載していないCPUでの乗算には326 CPUクロックサイクルかかります。
double ldLatitude;
int32_t li32LatFromGps;
ldLatitude = (double)li32LatFromGps * 0.0000001;
この乗算はこれよりも選択されていることに注意してください。
ldLatitude = (double)li32LatFromGps / 10000000.0;
私が扱っているCPUの「ダブル」乗算は「ダブル」除算よりも約3.6倍速いためです。これがマイクロコントローラの世界での生活です。 :-)
素晴らしい(そして週末に時間を割くことができれば将来的には)であろうことは、32ビットの符号付き整数でナビゲーションタスクを直接行うことができればです!その場合、変換は必要ありません。..しかし、そのような整数でナビゲーションタスクを実行するのに費用がかかりますか? CPUコスト、おそらくはるかに効率的。開発時間のコストは? IEEE-754 64ビット「ダブル」値を使用する、特に十分にテストされたシステムがすでに配置されている場合は特に、別の質問です。さらに、マップデータを提供する既存のソフトウェア(「度」値を使用)があります。このソフトウェアは、一晩のタスクではなく、符号付き整数を使用するために変換する必要があります。
非常に興味深いオプションの1つは、未加工の緯度/経度整数を使用して、「長方形」(実際には極で三角形になる台形)の近似間の交点を直接(変換なしで)表すことです。赤道では、これらの長方形の寸法は、東西約1.11 cm x南北1.11であり、一方、例えばロンドンの緯度では、東西約0.69 cm x南北1.11 cmです。それは、アプリケーションが必要とするものに応じて、対処するのが簡単な場合とそうでない場合があります。
とにかく、これらの考えや議論が、このトピックをシステムの「最高の設計」のために見ている他の人に役立つことを願っています。
よろしく、ヴィック
次のコードは、WGS84座標をロスレスで座標を符号なしlong(つまり8バイト)にパックします。
using System;
using System.Collections.Generic;
using System.Text;
namespace Utils
{
/// <summary>
/// Lossless conversion of OSM coordinates to a simple long.
/// </summary>
unsafe class CoordinateStore
{
private readonly double _lat, _lon;
private readonly long _encoded;
public CoordinateStore(double lon,double lat)
{
// Ensure valid lat/lon
if (lon < -180.0) lon = 180.0+(lon+180.0); else if (lon > 180.0) lon = -180.0 + (lon-180.0);
if (lat < -90.0) lat = 90.0 + (lat + 90.0); else if (lat > 90.0) lat = -90.0 + (lat - 90.0);
_lon = lon; _lat = lat;
// Move to 0..(180/90)
var dlon = (decimal)lon + 180m;
var dlat = (decimal)lat + 90m;
// Calculate grid
var grid = (((int)dlat) * 360) + ((int)dlon);
// Get local offset
var ilon = (uint)((dlon - (int)(dlon))*10000000m);
var ilat = (uint)((dlat - (int)(dlat))*10000000m);
var encoded = new byte[8];
fixed (byte* pEncoded = &encoded[0])
{
((ushort*)pEncoded)[0] = (ushort) grid;
((ushort*)pEncoded)[1] = (ushort)(ilon&0xFFFF);
((ushort*)pEncoded)[2] = (ushort)(ilat&0xFFFF);
pEncoded[6] = (byte)((ilon >> 16)&0xFF);
pEncoded[7] = (byte)((ilat >> 16)&0xFF);
_encoded = ((long*) pEncoded)[0];
}
}
public CoordinateStore(long source)
{
// Extract grid and local offset
int grid;
decimal ilon, ilat;
var encoded = new byte[8];
fixed(byte *pEncoded = &encoded[0])
{
((long*) pEncoded)[0] = source;
grid = ((ushort*) pEncoded)[0];
ilon = ((ushort*)pEncoded)[1] + (((uint)pEncoded[6]) << 16);
ilat = ((ushort*)pEncoded)[2] + (((uint)pEncoded[7]) << 16);
}
// Recalculate 0..(180/90) coordinates
var dlon = (uint)(grid % 360) + (ilon / 10000000m);
var dlat = (uint)(grid / 360) + (ilat / 10000000m);
// Returns to WGS84
_lon = (double)(dlon - 180m);
_lat = (double)(dlat - 90m);
}
public double Lon { get { return _lon; } }
public double Lat { get { return _lat; } }
public long Encoded { get { return _encoded; } }
public static long PackCoord(double lon,double lat)
{
return (new CoordinateStore(lon, lat)).Encoded;
}
public static KeyValuePair<double, double> UnPackCoord(long coord)
{
var tmp = new CoordinateStore(coord);
return new KeyValuePair<double, double>(tmp.Lat,tmp.Lon);
}
}
}
「保存」することで「記憶を保持する」ことを意味する場合、本当の問題は次のとおりです。あなたはそれらをどうするつもりですか?
これらの座標がおもしろいことをする前に、math.hの関数を介してラジアンとして流し込まれていると思います。ビットフィールドにパックされたDeg/Min/Secsで動作する非常に多くの超越関数を実装する予定がない限り。
それでは、物事をシンプルに保ち、要件の精度でIEEE-754度またはラジアンで保存してみませんか?
decimal
データ型を使用できます。
CREATE TABLE IF NOT EXISTS `map` (
`latitude` decimal(18,15) DEFAULT NULL,
`longitude` decimal(18,15) DEFAULT NULL
);