M241(HOLUX社のGPSロガー)の記録データを,プロアトラスで表示できるように変換するプログラム。
M241は,低価格のGPSで,昔のカメラフィルムのような入れ物に入っていて,愛敬のある格好をしている。
約10万地点のデータを保存できるので,あちこち歩いて,パソコンに読み取り,歩いた経路を表示できる。
カシーミールなどで軌跡を表示するには,M241附属のソフトで,gpxファイルを作成すれば,容易に表示できる。
しかし,添付のソフトでは,プロアトラスのxmpsファイルに変換ができない。
以下のプログラムは,M241のtrlファイルを,プロアトラスのxmpsファイルに変換するものである。
カットアンドトライでバラック積み立てで作成し,コメントも少なく,著者本人ですら,今となっては理解不可能の部分もある。
xmpsファイルへの変換では,必要最小限のxmlファイルを生成しているので,通常のxmpsファイルのデータ構造より簡略化している。
開発言語はC#2008expressである。MicroSoftが無料で提供しているものだが,サンデープログラマが使うには十分である。
これがメインのForm画面である。
各ポイントのデータである。変換対象に入れるか省くか,設定できる。
できあがったxmpsファイルである。xmlファイルである。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;
namespace holux2proatlas
{
public partial class Form1 : Form
{
public XmlDocument xmps = new XmlDocument();
public XmlDocument gpx = new XmlDocument();
private makeXmps mX;
public XmlNamespaceManager nsmgr;
public const double a = 6377397.155;
public const double a84 = 6378137;
public const double f84 = 0.0033528106647478;
public const double f_tokyo = 0.00334277318;
public double lat,lon,height;
public double tklat, tklon, tkheight;
public double radlat, radlon;
public double X,Y,Z;
public int deg, min, sec;
public string br;
public int rdbtn;
public int dgvbtn;
public string clrbtn;
public int ver;
public int hozon;
public string fname;
public Form1()
{
InitializeComponent();
br = Environment.NewLine;
rdbtn = 1;
dgvbtn = 1;
ver = 1;
hozon = 1;
clrbtn = "0,0,255,172"; //デフォルトの線色は青
dataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
}
private void 名前を付けて保存AToolStripMenuItem_Click(object sender, EventArgs e)
{
hozonsuru();
}
private void hozonsuru()
{
Stream myStream;
saveFileDialog1.Filter = "xmps files (*.xmps)|*.xmps|All files (*.*)|*.*";
saveFileDialog1.FilterIndex = 1;
saveFileDialog1.RestoreDirectory = true;
saveFileDialog1.FileName = fname;
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = saveFileDialog1.OpenFile()) != null)
{
mX.saveXmps(myStream);
myStream.Close();
}
}
}
private void trl2xmps()
{
string mpsx, tmp1, tmp2, tmp4, tm;
char[] charsToTrim = { ',' };
Stream myStream;
try
{
openFileDialog1.Filter = "trl files (*.trl)|*.trl|All files (*.*)|*.*"; ;
openFileDialog1.FilterIndex = 1;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = openFileDialog1.OpenFile()) != null)
{
using (myStream)
{
fname =Path.GetFileNameWithoutExtension(openFileDialog1.FileName);
byte[] trl =File.ReadAllBytes(openFileDialog1.FileName);
int rec;
int nagasa;
byte[] tmp=new byte[20];
//M241のファームウェアのバージョンの違い
//V1.13は20バイト
// 日時(4),緯度(4),経度(4),高度(3),速度(4),checksum(1)
//V1.12は速度がなく16バイト
if (ver == 1)
{
rec = 20;
nagasa = trl.Length / 20;
}
else
{
rec = 16;
nagasa = trl.Length / 16;
}
ConvtClass1 myConvt = new ConvtClass1();
dataGridView1.Rows.Add(nagasa);
dataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithAutoHeaderText;
mpsx = "";
tm = "";
tmp4 = "";
mX = new makeXmps();
mX.linecolor = clrbtn;
for (int i = 0; i < nagasa; i++)
{
for (int j = 0; j < rec; j++)
{
tmp[j] = trl[i * rec + j];
}
myConvt.setBytes(tmp);
height = Convert.ToDouble(myConvt.Height);
//プロアトラス用にwgs84測地系から東京測地系に変換する
wgs2tokyo(myConvt.WGSLatitude, myConvt.WGSLongitude);
tmp1 = (Convert.ToInt32(rad2dms(tklat) * 3600000)).ToString();
tmp2 = (Convert.ToInt32(rad2dms(tklon) * 3600000)).ToString();
tmp4 = myConvt.getTimeAll();
mX.makeNavi("alt_" + i.ToString(), tkheight.ToString("0.000000"));
mX.makeNavi("time_" + i.ToString(), tmp4);
string sp = "-";
if (ver == 1)
{
sp = myConvt.Speed;
}
mX.makeNavi("vel_" + i.ToString(), sp);
switch (dgvbtn)
{
case 1:
writeDGV(i, tmp4, myConvt.WGSLatitude, myConvt.WGSLongitude, myConvt.Height, sp);
break;
case 2:
writeDGV(i, tmp4, rad2dms(tklat).ToString(), rad2dms(tklon).ToString(), tkheight.ToString(), sp);
break;
case 3:
writeDGV(i, tmp4, tmp1, tmp2, tkheight.ToString(), sp);
break;
default:
break;
}
mX.record2history(i, tmp1, tmp2);
mpsx = mpsx + tmp1 + "," + tmp2 + ",";
if (i == 1)
{
tm = tmp4 + "〜";
}
}
mX.history2root();
tm = tm + tmp4 + "のGPSナビゲーションログです";
//desc.InnerText = tm;
mpsx = mpsx.TrimEnd(charsToTrim);
mX.poly2root(mpsx, tm);
textBox2.Text = mX.getString();
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("エラーです。ファームウェアのバージョンを確認して下さい。" + br + ex.Message);
}
}
//4バイト16進文字列を浮動小数点として数値に変換する
private double getDouble(string b4)
{
uint num = uint.Parse(b4, System.Globalization.NumberStyles.AllowHexSpecifier);
byte[] floatVals = BitConverter.GetBytes(num);
float f = BitConverter.ToSingle(floatVals, 0);
return Convert.ToDouble(f);
}
private void txt2xmps()
{
try
{
Stream myStream;
string mpsx = "";
string tm = "";
char[] charsToTrim = { ',' };
int i = 0;
openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"; ;
openFileDialog1.FilterIndex = 1;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = openFileDialog1.OpenFile()) != null)
{
using (myStream)
{
fname = Path.GetFileNameWithoutExtension(openFileDialog1.FileName);
mX = new makeXmps();
mX.linecolor = clrbtn;
using (StreamReader sr = new StreamReader(myStream))
{
string line, tmp1, tmp2, tmp4;
tmp4 = "";
//M241のtrlをtxtに変換したものは,次のフォーマットになっている
//140.1097565,37.9097061,112.36,Sat Jan 09 10:40:52 2010
//
while ((line = sr.ReadLine()) != null)
{
string[] sp = line.Split(charsToTrim);
//tmp1 = (Convert.ToInt32(Convert.ToDouble(sp[1]) * 3600000 - 10650)).ToString();
//tmp2 = (Convert.ToInt32(Convert.ToDouble(sp[0]) * 3600000 + 12270)).ToString();
height = Convert.ToDouble(sp[2]);
//プロアトラス用にwgs84測地系から東京測地系に変換する
wgs2tokyo(sp[1], sp[0]);
tmp1 = (Convert.ToInt32(rad2dms(tklat) * 3600000)).ToString();
tmp2 = (Convert.ToInt32(rad2dms(tklon) * 3600000)).ToString();
tmp4 = sp[3];
mpsx = mpsx + tmp1 + "," + tmp2 + ",";
mX.makeNavi("alt_" + i.ToString(), tkheight.ToString("0.000000"));
mX.makeNavi("time_" + i.ToString(), tmp4);
//recordをひとつ作り,historyに追加
mX.record2history(i, tmp1, tmp2);
dataGridView1.Rows.Add(1);
switch (dgvbtn)
{
case 1:
writeDGV(i, tmp4, sp[1], sp[0], sp[2], "-");
break;
case 2:
writeDGV(i, tmp4, rad2dms(tklat).ToString(), rad2dms(tklon).ToString(), tkheight.ToString(), "-");
break;
case 3:
writeDGV(i, tmp4, tmp1, tmp2, tkheight.ToString(), "-");
break;
default:
break;
}
if (i == 0)
{
tm = tmp4 + "〜";
}
i++;
}
mX.history2root();
tm = tm + tmp4 + "のGPSナビゲーションログです";
mpsx = mpsx.TrimEnd(charsToTrim);
}
mX.poly2root(mpsx, tm);
textBox2.Text = mX.getString();
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("エラーです。ファームウェアのバージョンを確認して下さい。" + br + ex.Message);
}
}
private void gpx2xmps()
{
try
{
Stream myStream;
string mpsx = "";
string tm = "";
char[] charsToTrim = { ',' };
int i = 0;
openFileDialog1.Filter = "gpx files (*.gpx)|*.gpx|All files (*.*)|*.*"; ;
openFileDialog1.FilterIndex = 1;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = openFileDialog1.OpenFile()) != null)
{
using (myStream)
{
fname = Path.GetFileNameWithoutExtension(openFileDialog1.FileName);
//gpxファイルを読み込む
gpx.Load(myStream);
//wpt要素のコレクションを取得
XmlNodeList wpts;
if (ver == 1)
{
wpts = gpx.DocumentElement.GetElementsByTagName("wpt");
}
else
{
wpts = gpx.DocumentElement.GetElementsByTagName("trkpt");
}
int nagasa = wpts.Count;
dataGridView1.Rows.Add(nagasa);
//wptの子要素を取得するselectsinglenodeを使うため名前空間が必要
nsmgr = new XmlNamespaceManager(gpx.NameTable);
//gpxのデフォルト名前空間をgpとして登録する
nsmgr.AddNamespace("gp", "http://www.topografix.com/GPX/1/0");
mX = new makeXmps();
mX.linecolor = clrbtn;
string tmp1, tmp2, tmp4;
tmp4 = "";
//各ポイントの情報を取得する
foreach( XmlNode wpt in wpts)
{
XmlAttributeCollection wptatri = wpt.Attributes;
//高度を取得する
if (ver == 1)
{
height = Convert.ToDouble(wpt.SelectSingleNode("gp:ele", nsmgr).InnerText);
}
else
{
height = 0.0;
}
//プロアトラス用にwgs84測地系から東京測地系に変換する
wgs2tokyo(wptatri.GetNamedItem("lat").InnerText, wptatri.GetNamedItem("lon").InnerText);
tmp1 =(Convert.ToInt32(rad2dms(tklat) * 3600000)).ToString();
tmp2 =(Convert.ToInt32(rad2dms(tklon) * 3600000)).ToString();
//日時を取得,gpは先に定義したデフォルト名前空間である
tmp4 = wpt.SelectSingleNode("gp:time",nsmgr).InnerText;
string sp = "-";
if (ver == 1)
{
sp = wpt.SelectSingleNode("gp:speed", nsmgr).InnerText;
}
mX.makeNavi("alt_" + i.ToString(), tkheight.ToString("0.000000"));
mX.makeNavi("time_" + i.ToString(), tmp4);
mX.makeNavi("vel_" + i.ToString(), sp);
switch (dgvbtn)
{
case 1:
writeDGV(i, tmp4, wptatri.GetNamedItem("lat").InnerText, wptatri.GetNamedItem("lon").InnerText, height.ToString(), sp);
break;
case 2:
writeDGV(i, tmp4, rad2dms(tklat).ToString(), rad2dms(tklon).ToString(), tkheight.ToString(), sp);
break;
case 3:
writeDGV(i, tmp4, tmp1, tmp2, tkheight.ToString(), sp);
break;
default:
break;
}
mpsx = mpsx + tmp1 + "," + tmp2 + ",";
//recordをひとつ作り,historyに追加
mX.record2history(i, tmp1, tmp2);
if (i == 0)
{
tm = tmp4 + "〜";
}
i++;
}
//ルートに<history>を追加
mX.history2root();
tm = tm + tmp4 + "のGPSナビゲーションログです";
//最後のコンマを削除
mpsx = mpsx.TrimEnd(charsToTrim);
mX.poly2root(mpsx, tm);
textBox2.Text = mX.getString();
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("エラーです。ファームウェアのバージョンを確認して下さい。" + br + ex.Message);
}
}
private void writeDGV(int num,string dt, string lati, string longi, string taka, string speed)
{
dataGridView1[0, num].Value = true;
dataGridView1[1, num].Value = num.ToString();
dataGridView1[2, num].Value = dt;
dataGridView1[3, num].Value = lati;
dataGridView1[4, num].Value = longi;
dataGridView1[5, num].Value = taka;
dataGridView1[6, num].Value = speed;
}
//測地系の変換
private void wgs2tokyo(string tmplat, string tmplon)
{
radlat = make_radian(tmplat);
radlon = make_radian(tmplon);
dms2XYZ();
WGS84tokyo();
XYZ2dms();
}
//wgs84測地系の経緯度を,三次元直行座標系に変換する
//lat,lon,height→XYZ
private void dms2XYZ()
{
double e_square, N, f, adms;
adms = a84;
f = f84;
e_square = f * (2 - f);
N = adms / Math.Sqrt(1-e_square*Math.Sin(radlat)*Math.Sin(radlat));
X = (N + height) * Math.Cos(radlat) * Math.Cos(radlon);
Y = (N + height) * Math.Cos(radlat) * Math.Sin(radlon);
Z = (N * (1 - e_square) + height) * Math.Sin(radlat);
}
//三次元直行座標系で,wgs84から東京に変換する(シフト量を加える)
private void WGS84tokyo()
{
double xx, yy, zz;
xx = X;
yy = Y;
zz = Z;
X = xx + 147.54;
Y = yy - 507.26;
Z = zz - 680.47;
}
//東京測地系で,三次元直行座標系を経緯度に変換する(ラジアン)
private void XYZ2dms()
{
double b_dash, P, theta, E_square, E_square_dash, neu, f, adms;
adms = a;
f = f_tokyo;
b_dash = adms * (1 - f);
P = Math.Sqrt(X * X + Y * Y);
theta=Math.Atan((Z*adms)/(P*b_dash));
E_square = (adms * adms - b_dash * b_dash) / (adms * adms);
E_square_dash = (adms * adms - b_dash * b_dash) / (b_dash * b_dash);
tklat = Math.Atan((Z + E_square_dash * b_dash * Math.Sin(theta) * Math.Sin(theta) * Math.Sin(theta)) / (P - E_square * adms * Math.Cos(theta) * Math.Cos(theta) * Math.Cos(theta)));
tklon = Math.Atan(Y / X) + Math.PI;
neu = adms / Math.Sqrt(1 - E_square * Math.Sin(tklat) * Math.Sin(tklat));
tkheight = P / Math.Cos(tklat) - neu;
}
//度をラジアンに変換
private double make_radian(string deg)
{
return 3.141592654*Convert.ToDouble(deg)/180;
}
//ラジアンを度に変換
private double rad2dms(double rad)
{
double sd;
sd = rad * 180 / Math.PI;
return sd;
}
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
rdbtn = 1;
textBox1.Text = rdbtn.ToString();
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
rdbtn = 2;
textBox1.Text = rdbtn.ToString();
}
private void radioButton3_CheckedChanged(object sender, EventArgs e)
{
rdbtn = 3;
textBox1.Text = rdbtn.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
henkan();
}
private void henkan()
{
dataGridView1.Rows.Clear();
textBox2.Text = "";
textBox1.Text = "";
try
{
switch (rdbtn)
{
case 1:
trl2xmps();
break;
case 2:
gpx2xmps();
break;
case 3:
txt2xmps();
break;
default:
trl2xmps();
break;
}
textBox1.Text = "もしエラーがでなかったなら,変換が終了しています。";
if (hozon == 2)
{
if (MessageBox.Show("変換が終了しました。引き続いて保存します", "xmpsの保存", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
hozonsuru();
}
else
{
textBox1.Text = "保存しないで変換が終了しました。";
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void radioButton6_CheckedChanged(object sender, EventArgs e)
{
dgvbtn = 1;
}
private void radioButton7_CheckedChanged(object sender, EventArgs e)
{
dgvbtn = 2;
}
private void radioButton8_CheckedChanged(object sender, EventArgs e)
{
dgvbtn = 3;
}
private void radioButton9_CheckedChanged(object sender, EventArgs e)
{
ver = 1;
}
private void radioButton10_CheckedChanged(object sender, EventArgs e)
{
ver = 2;
}
private void 変換実行ToolStripMenuItem_Click(object sender, EventArgs e)
{
henkan();
}
private void radioButton4_CheckedChanged(object sender, EventArgs e)
{
hozon = 1;
}
private void radioButton5_CheckedChanged(object sender, EventArgs e)
{
hozon = 2;
}
private void コピーCToolStripMenuItem_Click(object sender, EventArgs e)
{
textBox2.Copy();
}
private void dGVコピーToolStripMenuItem_Click(object sender, EventArgs e)
{
Clipboard.SetDataObject(dataGridView1.GetClipboardContent());
}
private void toolStripMenuItem1_Click(object sender, EventArgs e)
{
Clipboard.SetDataObject(dataGridView1.GetClipboardContent());
}
private void 内容CToolStripMenuItem_Click(object sender, EventArgs e)
{
string hp;
hp = "ソフトの機能" + br;
hp = hp + "・M241GPSロガーの記録を,地図ソフトのプロアトラスでルート表示できるように変換する。" + br + br;
hp = hp + "・M241はutilityでダウンロードすると,trlファイルとして保存される。" + br;
hp = hp + " また,trlファイルをgpxファイル,txtファイルに変換できる。" + br + br;
hp = hp + "・本ソフトは,trl,gpx,txtのデータを,プロアトラスのxmpsファイルに変換できる。" + br + br;
hp = hp + "ファームウェアのバージョンがv1.12とv1.13では,速度データの有無でtrlファイルの1データが16バイトから20バイトになり,gpxファイルでもフォーマットがかなり違う。" + br;
hp = hp + "変換エラーが出た場合は,ファームウェアのバージョン設定が異なっている。" + br;
hp = hp + "txtは単純なtxtだけに対応し,NMEAのtxtには対応していない。" + br + br;
hp = hp + "・作成したxmpsファイルを,プロアトラスでファイル読み込みすると記録した軌跡が表示される。" + br;
textBox1.Text = hp;
}
private void radioButton11_CheckedChanged(object sender, EventArgs e)
{
clrbtn = "255,0,0,172";
}
private void radioButton12_CheckedChanged(object sender, EventArgs e)
{
clrbtn = "0,255,0,172";
}
private void radioButton13_CheckedChanged(object sender, EventArgs e)
{
clrbtn = "0,255,255,172";
}
private void radioButton14_CheckedChanged(object sender, EventArgs e)
{
clrbtn = "0,0,255,172";
}
private void radioButton15_CheckedChanged(object sender, EventArgs e)
{
clrbtn = "255,0,255,172";
}
private void radioButton16_CheckedChanged(object sender, EventArgs e)
{
clrbtn = "255,255,0,172";
}
private void button2_Click(object sender, EventArgs e)
{
textBox2.Text = "";
mX = new makeXmps();
mX.linecolor = clrbtn;
string mpsx,tmp1,tmp2,tmp4;
mpsx = "";
tmp1 = "";
tmp2 = "";
tmp4 = "";
int j = 0;
bool cb;
string tm="";
char[] charsToTrim = { ',' };
int count = dataGridView1.Rows.Count - 1;
for (int i = 0; i < count; i++)
{
//textBox1.AppendText(dataGridView1[0, i].Value.ToString() + br);
if (String.Compare(dataGridView1[0, i].Value.ToString(), "True") == 0)
{
cb = true;
}
else
{
cb = false;
}
if (cb == true)
{
//高度を取得する
height = Convert.ToDouble(dataGridView1[5, i].Value);
//プロアトラス用にwgs84測地系から東京測地系に変換する
wgs2tokyo(dataGridView1[3, i].Value.ToString(), dataGridView1[4, i].Value.ToString());
tmp1 = (Convert.ToInt32(rad2dms(tklat) * 3600000)).ToString();
tmp2 = (Convert.ToInt32(rad2dms(tklon) * 3600000)).ToString();
//textBox1.AppendText(tmp1 + ":" + tmp2 + br);
//日時を取得,gpは先に定義したデフォルト名前空間である
tmp4 = dataGridView1[2, i].Value.ToString();
string sp = dataGridView1[6, i].Value.ToString();
mX.makeNavi("alt_" + j.ToString(), tkheight.ToString("0.000000"));
mX.makeNavi("time_" + j.ToString(), tmp4);
mX.makeNavi("vel_" + j.ToString(), sp);
mpsx = mpsx + tmp1 + "," + tmp2 + ",";
//recordをひとつ作り,historyに追加
mX.record2history(j, tmp1, tmp2);
if (j == 0)
{
tm = tmp4 + "〜";
}
j++;
}
}
//ルートに<history>を追加
mX.history2root();
//rootele.AppendChild(hist);
tm = tm + tmp4 + "のGPSナビゲーションログです";
//desc.InnerText = tm;
//最後のコンマを削除
mpsx = mpsx.TrimEnd(charsToTrim);
mX.poly2root(mpsx, tm);
textBox2.Text = mX.getString();
hozonsuru();
}
}
}