適当に乱数で作った8地点と、UTM/MGRS的にヤバそうな地点のフチの部分。数が少ないのであんまりテストデータには向かないかも。
データは以下の構造体に入ってる。
typedef struct Position_data
{
struct
{
double latitude;
double longitude;
double height;
} LLH;
struct
{
double x;
double y;
double z;
} ECEF;
const char *const MGRS;
// if MGRS is null, UTM is invalid
struct
{
uint8_t zone_lon;
bool is_north_side;
double east;
double north;
} UTM;
} Position_data;
LLHはLatLonが度単位、heightがメートル単位、ECEFはすべてメートル、MGRSはdigit10(5:5)でゾーンはゼロ埋め、UTMは1メートル分解能。
MGRSは文字リテラルへのポインタだが、UTM/MGRSに変換できない地域(北緯84度以上の北極周辺と南緯80度以上の南極周辺)の場合はMGRSにヌルポインタがセットされる。
LLHとECEFの変換には以下のサイトを使った。
Latitude/Longitude/Height <--> ECEF via J-Script BLH
LLHとUTM/MGRSの変換には以下のサイトを使った。
Convert between Latitude/Longitude & UTM coordinates
とりあえずLLHとECEFは「GPSのための実用プログラミング」という本のソースコードを参考にしたプログラムでチェックしている。LLHとUTMはwikipediaの式を参考にしたプログラムでチェックしている。UTMとMGRSは上記リンクのJavaScriptコードを参考にしたプログラムでチェックしている。
この本のプログラムでは、ECEF→LLHで、sqrt(x*x+y*y)<1の条件の際にheightの値が不正になる不具合がある(正確には、sqrtが数十cmを下回ると急速にheightのエラーが大きくなる)。某F氏が作者の承諾を得てC++に移植したコードでもそのまま残ってる気がする(未確認)。
上記LLH-ECEF変換のサイトのJavaScriptコードを見てみると、sqrt(x*x+y*y)が一定以下の場合は特殊な処理を行っているらしい。とりあえずsqrt(x*x+y*y)<1e0の場合はheight = fabs(z) - a * (1 - f)で対応した。
データセットでは(0オリジンで)2番めがx=1程度になるようにしている。3番目と4番目は地軸直上のそれぞれ北極側、南極側にしている。データセット2をPASSして3でFAILするならこの部分の問題がある。
UTM/MGRSではノルウェー南西付近とノルウェー領スヴァールバル諸島の付近に特殊な処理をしている。データセットの内10セットはこの地域の経度方向の区切り付近のデータになっている。
日本で位置情報を扱う国土地理院としては、災害時の地図情報としてMGRSを使いたいらしい。ただし、なぜかMGRSとは言わず、UTMと言っている。「昔はUTMを使っていたから、MGRSもUTMと言い張る」みたいな理論らしい。ミリタリーと名の付くものを嫌ったのか? とにかく、諸外国のUTMと地理院のUTMは別物なので要注意。
地理院が防災・災害時にMGRSを使いたがっている割には、MGRSの計算方法とかの情報が極めて少ない。今回一番参考になったのはwikipediaの計算式と先のリンクのJavaScriptコードだった。大丈夫か国土地理院(地理院のWebサイトにあるPDFにUTM変換のJavaScriptコードが有るんだけど、非常にわかりずらい)。
/***
最近、何もやる気でないけど、何もしないには暇すぎる、という状況。ということで暇つぶしに位置座標関係のソースコードを書いたりしてる。
JSF++のコーディングルールを意識したコードを書いて、CppUnitでテストしている。TDDという程じゃないが。
CppUnitはかなり使いづらい。基本的にCPPUNIT_ASSERT_EQUALを使って、多重定義で各種の型に対応しているようだが、例えばuint8_tを渡すと文字として処理される。結果、FAILすると期待値や結果がASCIIで表示される。期待値や結果が2とか8だったりすると、文字としては何も表示されない。わけがわからない。
他にも、const char*と別のconst char*を渡すと、常にFAILする。これは「ポインタが渡されたんだな!よし、ポインタを比較するぞ!。別のアドレスを指しているな?FAILだ!!」みたいなことらしい。文字リテラルと文字リテラル同士の比較なら文字列として比較されるが、そんなもんはテストする意味ない。結局、trueと0 == strcmp(expected, actual)の比較を行うしか無い。ただし、boolの比較の場合、trueとfalseの表示ではなく、0と1で表示される。わけがわからないよ。
テストと並行して開発していくと、コーディングよりもテストケースを作るほうがはるかに時間がかかる。コーディングのほうが時間がかかる場合は、そもそもコード規模が大きすぎる(あるいはテストが少なすぎる)ので、プログラムとして不適切。
結果、コードの品質を保証するにはテストをするべきだが、テストケースを作る行為に非常にコストがかかる。テストをしたからと言って、テストした点以外の場所にでかい虫が隠れてる可能性もあるし。
そりゃF-35も納期伸びてコスト上がるわけだ、みたいな感想。
F-35はEO-DASで敵機や見方機の追跡、EOTSで地上目標の探知・識別、AESAで空中/地上目標の探知・識別ができるらしい。ということは、様々なデータを処理するためのソースコードが有るわけで、それについてもちゃんとテストが行われている。はず。テストデータだけでもいったいどれだけ有るのか。
F-35のソフトウェアは開発中だから、「値下げしろ」と言われれば、ソースコード開発のどこかを切るしか無い(ハードウェアの設計は大体終わってるはずで、そこでコストカットは厳しいはず)。となると、「値下げしろ」と言われて以降のソースコードは、結構ヤバイのかも知れない。
JSF++を読んでると、コード例で<LM_string.h>というヘッダがインクルードされてる。おそらくロッキード・マーティンが作成した文字列処理に関するライブラリなのだろう。こういうレベルから作っていくんだから、防衛装備品は大変だなぁ、と思う次第。
例えばJSF++ではstdio.hは使用が禁止されている。他にも時間処理系のライブラリも使用禁止。そういうのを全部作らなきゃいけないんだから、大変だよなぁ。
***/
データセットは500行くらい有るので以下。
// X0, Y0, Z0
{
.LLH = {
.latitude = 0,
.longitude = 0,
.height = -6378137,
},
.ECEF = {
.x = 0,
.y = 0,
.z = 0,
},
.MGRS = "31NAA6602100000",
.UTM = {
.zone_lon = 31,
.is_north_side = true,
.east = 166021,
.north = 0,
},
},
// LAT0, LNG0, HGT0
{
.LLH = {
.latitude = 0,
.longitude = 0,
.height = 0,
},
.ECEF = {
.x = 6378137,
.y = 0,
.z = 0,
},
.MGRS = "31NAA6602100000",
.UTM = {
.zone_lon = 31,
.is_north_side = true,
.east = 166021,
.north = 0,
},
},
// LAT89.99999, LNG0, HGT0
{
.LLH = {
.latitude = 89.99999,
.longitude = 0,
.height = 0,
},
.ECEF = {
.x = 1,
.y = 0,
.z = 6356752,
},
.MGRS = 0,
.UTM = {0},
},
// LAT90, LNG0, HGT1000
{
.LLH = {
.latitude = 90,
.longitude = 0,
.height = 1000,
},
.ECEF = {
.x = 0,
.y = 0,
.z = 6357752,
},
.MGRS = 0,
.UTM = {0},
},
// LAT-90, LNG0, HGT1000
{
.LLH = {
.latitude = -90,
.longitude = 0,
.height = 1000,
},
.ECEF = {
.x = 0,
.y = 0,
.z = -6357752,
},
.MGRS = 0,
.UTM = {0},
},
// north limit
{
.LLH = {
.latitude = 84.1,
.longitude = 0,
.height = 0,
},
.ECEF = {
.x = 657807,
.y = 0,
.z = 6322854,
},
.MGRS = 0,
.UTM = {0},
},
{
.LLH = {
.latitude = 83.9,
.longitude = 0,
.height = 0,
},
.ECEF = {
.x = 680021,
.y = 0,
.z = 6320520,
},
.MGRS = "31XDP6442417856",
.UTM = {
.zone_lon = 31,
.is_north_side = true,
.east = 464424,
.north = 9317856,
},
},
// south limit
{
.LLH = {
.latitude = -80.1,
.longitude = 0,
.height = 0,
},
.ECEF = {
.x = 1100167,
.y = 0,
.z = -6261472,
},
.MGRS = 0,
.UTM = {0},
},
{
.LLH = {
.latitude = -79.9,
.longitude = 0,
.height = 0,
},
.ECEF = {
.x = 1122159,
.y = 0,
.z = -6257594,
},
.MGRS = "31CDM4129228062",
.UTM = {
.zone_lon = 31,
.is_north_side = false,
.east = 441293,
.north = 1128062,
},
},
// north, east GND+
{
.LLH = {
.latitude = 3.139898224,
.longitude = 163.4391952,
.height = 2263151.322,
},
.ECEF = {
.x = -8270455,
.y = 2459371,
.z = 470984,
},
.MGRS = "58NCJ2655947186",
.UTM = {
.zone_lon = 58,
.is_north_side = true,
.east = 326559,
.north = 347186,
},
},
// north, west, GND+
{
.LLH = {
.latitude = 46.60867605,
.longitude = -17.42848449,
.height = 313308.3116,
},
.ECEF = {
.x = 4393246,
.y = -1379159,
.z = 4839662,
},
.MGRS = "28TCS1403264543",
.UTM = {
.zone_lon = 28,
.is_north_side = true,
.east = 314032,
.north = 5164544,
},
},
// south, east, GND+
{
.LLH = {
.latitude = -64.50753857,
.longitude = 156.3549503,
.height = 1485395.364,
},
.ECEF = {
.x = -3107161,
.y = 1360396,
.z = -7075073,
},
.MGRS = "57DUJ7300543780",
.UTM = {
.zone_lon = 57,
.is_north_side = false,
.east = 373005,
.north = 2843781,
},
},
// south, west, GND+
{
.LLH = {
.latitude = -26.10929069,
.longitude = -63.90064215,
.height = 703424.3562,
},
.ECEF = {
.x = 2799114,
.y = -5713865,
.z = -3099506,
},
.MGRS = "20JMS0994711901",
.UTM = {
.zone_lon = 20,
.is_north_side = false,
.east = 409947,
.north = 7111901,
},
},
// north, east, GND-
{
.LLH = {
.latitude = 1.062974536,
.longitude = 124.5334989,
.height = -2089274.706,
},
.ECEF = {
.x = -2430890,
.y = 3532541,
.z = 78772,
},
.MGRS = "51NXB7063117533",
.UTM = {
.zone_lon = 51,
.is_north_side = true,
.east = 670631,
.north = 117533,
},
},
// north, west, GND-
{
.LLH = {
.latitude = 32.1116965,
.longitude = -158.1357492,
.height = -567397.3101,
},
.ECEF = {
.x = -4572494,
.y = -1834818,
.z = 3069317,
},
.MGRS = "04SEA8153553143",
.UTM = {
.zone_lon = 4,
.is_north_side = true,
.east = 581535,
.north = 3553143,
},
},
// south, east, GND-
{
.LLH = {
.latitude = -77.29623938,
.longitude = 179.460186,
.height = -623829.425,
},
.ECEF = {
.x = -1269864,
.y = 11964,
.z = -5591571,
},
.MGRS = "60CWV6037718903",
.UTM = {
.zone_lon = 60,
.is_north_side = false,
.east = 560378,
.north = 1418904,
},
},
// south, west, GND-
{
.LLH = {
.latitude = -36.49895715,
.longitude = -84.00224313,
.height = -558814.733,
},
.ECEF = {
.x = 489434,
.y = -4658407,
.z = -3440454,
},
.MGRS = "16HGE6850456527",
.UTM = {
.zone_lon = 16,
.is_north_side = false,
.east = 768505,
.north = 5956527,
},
},
// UTM 31V and 32V area
{
.LLH = {
.latitude = 60,
.longitude = 2.9,
.height = 0,
},
.ECEF = {
.x = 3193010,
.y = 161751,
.z = 5500477,
},
.MGRS = "31VDG9442251415",
.UTM = {
.zone_lon = 31,
.is_north_side = true,
.east = 494422,
.north = 6651415,
},
},
{
.LLH = {
.latitude = 60,
.longitude = 3.1,
.height = 0,
},
.ECEF = {
.x = 3192426,
.y = 172896,
.z = 5500477,
},
.MGRS = "32VJM7120366091",
.UTM = {
.zone_lon = 32,
.is_north_side = true,
.east = 171203,
.north = 6666091,
},
},
// UTM 31X, 33X, 35X and 37X area
{
.LLH = {
.latitude = 80,
.longitude = 8.9,
.height = 0,
},
.ECEF = {
.x = 1097786,
.y = 171909,
.z = 6259543,
},
.MGRS = "31XFJ1418587381",
.UTM = {
.zone_lon = 31,
.is_north_side = true,
.east = 614186,
.north = 8887381,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 9.1,
.height = 0,
},
.ECEF = {
.x = 1097180,
.y = 175740,
.z = 6259543,
},
.MGRS = "33XUJ8581487381",
.UTM = {
.zone_lon = 33,
.is_north_side = true,
.east = 385814,
.north = 8887381,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 20.9,
.height = 0,
},
.ECEF = {
.x = 1038055,
.y = 396395,
.z = 6259543,
},
.MGRS = "33XXJ1418587381",
.UTM = {
.zone_lon = 33,
.is_north_side = true,
.east = 614186,
.north = 8887381,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 21.1,
.height = 0,
},
.ECEF = {
.x = 1036665,
.y = 400016,
.z = 6259543,
},
.MGRS = "35XLJ8581487381",
.UTM = {
.zone_lon = 35,
.is_north_side = true,
.east = 385814,
.north = 8887381,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 32.9,
.height = 0,
},
.ECEF = {
.x = 932956,
.y = 603556,
.z = 6259543,
},
.MGRS = "35XPJ1418587381",
.UTM = {
.zone_lon = 35,
.is_north_side = true,
.east = 614186,
.north = 8887381,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 33.1,
.height = 0,
},
.ECEF = {
.x = 930844,
.y = 606809,
.z = 6259543,
},
.MGRS = "37XCJ8581487381",
.UTM = {
.zone_lon = 37,
.is_north_side = true,
.east = 385814,
.north = 8887381,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 41.9,
.height = 0,
},
.ECEF = {
.x = 827053,
.y = 742072,
.z = 6259543,
},
.MGRS = "37XEJ5619682986",
.UTM = {
.zone_lon = 37,
.is_north_side = true,
.east = 556196,
.north = 8882987,
},
},
{
.LLH = {
.latitude = 80,
.longitude = 42.1,
.height = 0,
},
.ECEF = {
.x = 824457,
.y = 744955,
.z = 6259543,
},
.MGRS = "38XMP4380382986",
.UTM = {
.zone_lon = 38,
.is_north_side = true,
.east = 443804,
.north = 8882987,
},
},
0 件のコメント:
コメントを投稿